From bfcfa4f6276935ed28b93642d9789e04294a78cf Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Tue, 18 Mar 2025 19:58:05 +0100 Subject: [PATCH] Avoid locking the mutex and blocking the main/render thread on destroy at the same time, which could cause deadlocks under some circumstances. --- FSR3UnityPlugin.cpp | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/FSR3UnityPlugin.cpp b/FSR3UnityPlugin.cpp index 77c5c83..17a44cd 100644 --- a/FSR3UnityPlugin.cpp +++ b/FSR3UnityPlugin.cpp @@ -292,18 +292,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.upscalingContext == nullptr && !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.upscalingContext == nullptr && !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); } } @@ -311,13 +320,16 @@ 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]; if (feature.upscalingContext != nullptr) { s_ffxFunctions.DestroyContext(&feature.upscalingContext, nullptr); @@ -332,13 +344,19 @@ static void DestroyFeature(uint32_t 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_ffxModule != nullptr) { FreeLibrary(s_ffxModule); @@ -442,8 +460,6 @@ static FfxApiResource GetVulkanTextureResource(UnityVulkanInstance& instance, FS // 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_DX11Device == nullptr && s_DX12BackendDesc.device == nullptr && s_VulkanBackendDesc.vkDevice == nullptr) return; @@ -461,6 +477,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) } case BaseEventId + FSR3PluginEvent::eExecute: { + std::lock_guard lock(s_FeatureMutex); + auto* params = (FSR3CommandExecutionData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return; @@ -598,6 +616,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) } case BaseEventId + FSR3PluginEvent::eInit: { + std::lock_guard lock(s_FeatureMutex); + auto* params = (FSR3CommandInitializationData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return;