From 49fa7d69a2c2a144bc2239687b72c20906b0ae4c Mon Sep 17 00:00:00 2001 From: Nico de Poel Date: Sat, 15 Mar 2025 16:51:15 +0100 Subject: [PATCH] Await end of frame on the CPU before destroying a context, if the GPU has a dispatch queued on the current frame. Fixes remaining crashes in D3D12 when enabling/disabling the plugin too rapidly. --- FSR3UnityPlugin.cpp | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/FSR3UnityPlugin.cpp b/FSR3UnityPlugin.cpp index 0188ecc..12ad251 100644 --- a/FSR3UnityPlugin.cpp +++ b/FSR3UnityPlugin.cpp @@ -50,6 +50,8 @@ struct FSR3Feature uint32_t upscaleSizeHeight; uint32_t flags; + uint64_t dispatchFrameValue; + FSR3TextureTable textureTable; }; @@ -65,6 +67,8 @@ static ffx::CreateBackendVKDesc s_VulkanBackendDesc{}; static FfxDevice s_DX11Device = nullptr; static FfxFsr2Interface s_Fsr2Interface; +static HANDLE s_FrameFenceEventHandle = nullptr; + // Unity plugin load event extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { @@ -221,6 +225,7 @@ extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_InitApi() return false; s_DX12BackendDesc.device = device; + s_FrameFenceEventHandle = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); return LoadFidelityFXLibrary(TEXT("amd_fidelityfx_dx12.dll")); } @@ -243,6 +248,19 @@ extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_InitApi() static void DestroyFeature(uint32_t featureSlot) { auto& feature = s_Features[featureSlot]; + + 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()) + { + frameFence->SetEventOnCompletion(feature.dispatchFrameValue, s_FrameFenceEventHandle); + WaitForSingleObjectEx(s_FrameFenceEventHandle, INFINITE, false); + } + } + if (feature.upscalingContext != nullptr) { s_ffxFunctions.DestroyContext(&feature.upscalingContext, nullptr); @@ -275,6 +293,12 @@ extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_ShutdownApi() free(s_Fsr2Interface.scratchBuffer); s_Fsr2Interface.scratchBuffer = nullptr; } + + if (s_FrameFenceEventHandle != nullptr) + { + CloseHandle(s_FrameFenceEventHandle); + s_FrameFenceEventHandle = nullptr; + } s_VulkanBackendDesc.vkDevice = nullptr; s_VulkanBackendDesc.vkPhysicalDevice = nullptr; @@ -442,6 +466,9 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) s_GraphicsD3D12->CommandRecordingState(&state); dispatchUpscale.commandList = state.commandList; + // Keep track of which frame this dispatch is happening on, so we can verify when it's finished + feature.dispatchFrameValue = s_GraphicsD3D12->GetNextFrameFenceValue(); + dispatchUpscale.color = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.colorInput.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.depth = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.depth.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.motionVectors = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.motionVectors.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); @@ -456,6 +483,9 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) s_GraphicsVulkan->CommandRecordingState(&state, kUnityVulkanGraphicsQueueAccess_DontCare); dispatchUpscale.commandList = state.commandBuffer; + // TODO: probably need to make use of state.currentFrameNumber and state.safeFrameNumber to keep track of when it's safe to destroy the upscaling context + feature.dispatchFrameValue = state.currentFrameNumber; + UnityVulkanInstance instance = s_GraphicsVulkan->Instance(); dispatchUpscale.color = GetVulkanTextureResource(instance, feature.textureTable.colorInput); dispatchUpscale.depth = GetVulkanTextureResource(instance, feature.textureTable.depth);