From e40f6b07059079befbfa6bdda0fc8ce545ea4054 Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Tue, 18 Mar 2025 20:25:41 +0100 Subject: [PATCH] Configure render event for DX12 and Vulkan, to ensure the upscaler gets called without any synchronization errors. Avoid locking the mutex and blocking the main/render thread on destroy at the same time, which could cause deadlocks under some circumstances. --- FSR2UnityPlugin.cpp | 56 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 11 deletions(-) diff --git a/FSR2UnityPlugin.cpp b/FSR2UnityPlugin.cpp index 8e02a68..08fb772 100644 --- a/FSR2UnityPlugin.cpp +++ b/FSR2UnityPlugin.cpp @@ -23,7 +23,7 @@ #include "FSR2UnityTypes.h" -static const int32_t BaseEventId = 0; +static const int32_t BaseEventId = 313; static IUnityInterfaces* s_UnityInterfaces = nullptr; static IUnityLog* s_Log = nullptr; @@ -106,6 +106,12 @@ static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType ev UNITY_LOG_ERROR(s_Log, "Could not obtain D3D12 Graphics interface!"); return; } + + UnityD3D12PluginEventConfig eventConfig; + eventConfig.graphicsQueueAccess = kUnityD3D12GraphicsQueueAccess_DontCare; + eventConfig.flags = kUnityD3D12EventConfigFlag_EnsurePreviousFrameSubmission | kUnityD3D12EventConfigFlag_FlushCommandBuffers | kUnityD3D12EventConfigFlag_SyncWorkerThreads | kUnityD3D12EventConfigFlag_ModifiesCommandBuffersState; + eventConfig.ensureActiveRenderTextureIsBound = false; + s_GraphicsD3D12->ConfigureEvent(BaseEventId + FSR2PluginEvent::eExecute, &eventConfig); break; } case kUnityGfxRendererVulkan: @@ -116,6 +122,12 @@ static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType ev UNITY_LOG_ERROR(s_Log, "Could not obtain Vulkan Graphics interface!"); return; } + + UnityVulkanPluginEventConfig eventConfig; + eventConfig.graphicsQueueAccess = kUnityVulkanGraphicsQueueAccess_DontCare; + eventConfig.flags = kUnityVulkanEventConfigFlag_EnsurePreviousFrameSubmission | kUnityVulkanEventConfigFlag_FlushCommandBuffers | kUnityVulkanEventConfigFlag_SyncWorkerThreads | kUnityVulkanEventConfigFlag_ModifiesCommandBuffersState; + eventConfig.renderPassPrecondition = kUnityVulkanRenderPass_EnsureInside; + s_GraphicsVulkan->ConfigureEvent(BaseEventId + FSR2PluginEvent::eExecute, &eventConfig); break; } }; @@ -225,18 +237,27 @@ extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_InitApi() static void DestroyFeature(uint32_t featureSlot) { - auto& feature = s_Features[featureSlot]; - if (!feature.isValid) - return; + uint64_t dispatchFrameValue = 0; + { + // We need to lock the features list while we're accessing it, but we also can't hold the lock while blocking the main/render thread at the same time. + // Otherwise we're very likely to create a deadlock. So instead we only lock for as long as is necessary to grab the data we need from the feature. + std::lock_guard lock(s_FeatureMutex); + + auto& feature = s_Features[featureSlot]; + if (!feature.isValid) + return; + + dispatchFrameValue = feature.dispatchFrameValue; + } if (s_GraphicsD3D12 != nullptr) { // If there's still an upscale dispatch executing on the current frame, wait until rendering is finished before destroying its context. // Thanks to https://alextardif.com/D3D11To12P1.html for explaining how fences work and how to make the CPU wait for a command queue to complete. ID3D12Fence* frameFence = s_GraphicsD3D12->GetFrameFence(); - if (feature.dispatchFrameValue > frameFence->GetCompletedValue()) + if (dispatchFrameValue > frameFence->GetCompletedValue()) { - frameFence->SetEventOnCompletion(feature.dispatchFrameValue, s_FrameFenceEventHandle); + frameFence->SetEventOnCompletion(dispatchFrameValue, s_FrameFenceEventHandle); WaitForSingleObjectEx(s_FrameFenceEventHandle, INFINITE, false); } } @@ -244,26 +265,35 @@ static void DestroyFeature(uint32_t featureSlot) { UnityVulkanRecordingState state; s_GraphicsVulkan->CommandRecordingState(&state, kUnityVulkanGraphicsQueueAccess_DontCare); - if (feature.dispatchFrameValue > state.safeFrameNumber) + if (dispatchFrameValue > state.safeFrameNumber) { UnityVulkanInstance instance = s_GraphicsVulkan->Instance(); vkQueueWaitIdle(instance.graphicsQueue); } } + std::lock_guard lock(s_FeatureMutex); + + auto& feature = s_Features[featureSlot]; ffxFsr2ContextDestroy(&feature.upscalingContext); FreeFeatureSlot(featureSlot); } extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_ShutdownApi() { - std::lock_guard lock(s_FeatureMutex); + size_t numFeatures = 0; + { + std::lock_guard lock(s_FeatureMutex); + numFeatures = s_Features.size(); + } - for (uint32_t slot = 0; slot < s_Features.size(); ++slot) + for (uint32_t slot = 0; slot < numFeatures; ++slot) { DestroyFeature(slot); } + std::lock_guard lock(s_FeatureMutex); + if (s_Fsr2Interface.scratchBuffer != nullptr) { free(s_Fsr2Interface.scratchBuffer); @@ -367,8 +397,6 @@ static void DestroyVulkanImageView(UnityVulkanInstance& instance, FSR2TextureDes // Plugin function to handle a specific rendering event static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) { - std::lock_guard lock(s_FeatureMutex); - if (s_Device == nullptr) return; @@ -386,6 +414,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) } case BaseEventId + FSR2PluginEvent::eExecute: { + std::lock_guard lock(s_FeatureMutex); + auto* params = (FSR2CommandExecutionData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return; @@ -484,6 +514,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) } case BaseEventId + FSR2PluginEvent::ePostExecute: { + std::lock_guard lock(s_FeatureMutex); + auto* params = (FSR2CommandExecutionData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return; @@ -506,6 +538,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) } case BaseEventId + FSR2PluginEvent::eInit: { + std::lock_guard lock(s_FeatureMutex); + auto* params = (FSR2CommandInitializationData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return;