Browse Source

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.
fsr2
Nico de Poel 11 months ago
parent
commit
e40f6b0705
  1. 48
      FSR2UnityPlugin.cpp

48
FSR2UnityPlugin.cpp

@ -23,7 +23,7 @@
#include "FSR2UnityTypes.h" #include "FSR2UnityTypes.h"
static const int32_t BaseEventId = 0;
static const int32_t BaseEventId = 313;
static IUnityInterfaces* s_UnityInterfaces = nullptr; static IUnityInterfaces* s_UnityInterfaces = nullptr;
static IUnityLog* s_Log = 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!"); UNITY_LOG_ERROR(s_Log, "Could not obtain D3D12 Graphics interface!");
return; 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; break;
} }
case kUnityGfxRendererVulkan: 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!"); UNITY_LOG_ERROR(s_Log, "Could not obtain Vulkan Graphics interface!");
return; 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; break;
} }
}; };
@ -225,18 +237,27 @@ extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_InitApi()
static void DestroyFeature(uint32_t featureSlot) static void DestroyFeature(uint32_t featureSlot)
{ {
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<std::mutex> lock(s_FeatureMutex);
auto& feature = s_Features[featureSlot]; auto& feature = s_Features[featureSlot];
if (!feature.isValid) if (!feature.isValid)
return; return;
dispatchFrameValue = feature.dispatchFrameValue;
}
if (s_GraphicsD3D12 != nullptr) 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. // 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. // 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(); 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); WaitForSingleObjectEx(s_FrameFenceEventHandle, INFINITE, false);
} }
} }
@ -244,26 +265,35 @@ static void DestroyFeature(uint32_t featureSlot)
{ {
UnityVulkanRecordingState state; UnityVulkanRecordingState state;
s_GraphicsVulkan->CommandRecordingState(&state, kUnityVulkanGraphicsQueueAccess_DontCare); s_GraphicsVulkan->CommandRecordingState(&state, kUnityVulkanGraphicsQueueAccess_DontCare);
if (feature.dispatchFrameValue > state.safeFrameNumber)
if (dispatchFrameValue > state.safeFrameNumber)
{ {
UnityVulkanInstance instance = s_GraphicsVulkan->Instance(); UnityVulkanInstance instance = s_GraphicsVulkan->Instance();
vkQueueWaitIdle(instance.graphicsQueue); vkQueueWaitIdle(instance.graphicsQueue);
} }
} }
std::lock_guard<std::mutex> lock(s_FeatureMutex);
auto& feature = s_Features[featureSlot];
ffxFsr2ContextDestroy(&feature.upscalingContext); ffxFsr2ContextDestroy(&feature.upscalingContext);
FreeFeatureSlot(featureSlot); FreeFeatureSlot(featureSlot);
} }
extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_ShutdownApi() extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_ShutdownApi()
{ {
size_t numFeatures = 0;
{
std::lock_guard<std::mutex> lock(s_FeatureMutex); std::lock_guard<std::mutex> 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); DestroyFeature(slot);
} }
std::lock_guard<std::mutex> lock(s_FeatureMutex);
if (s_Fsr2Interface.scratchBuffer != nullptr) if (s_Fsr2Interface.scratchBuffer != nullptr)
{ {
free(s_Fsr2Interface.scratchBuffer); free(s_Fsr2Interface.scratchBuffer);
@ -367,8 +397,6 @@ static void DestroyVulkanImageView(UnityVulkanInstance& instance, FSR2TextureDes
// Plugin function to handle a specific rendering event // Plugin function to handle a specific rendering event
static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data)
{ {
std::lock_guard<std::mutex> lock(s_FeatureMutex);
if (s_Device == nullptr) if (s_Device == nullptr)
return; return;
@ -386,6 +414,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data)
} }
case BaseEventId + FSR2PluginEvent::eExecute: case BaseEventId + FSR2PluginEvent::eExecute:
{ {
std::lock_guard<std::mutex> lock(s_FeatureMutex);
auto* params = (FSR2CommandExecutionData*)data; auto* params = (FSR2CommandExecutionData*)data;
if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) if (params->featureSlot < 0 || params->featureSlot >= s_Features.size())
return; return;
@ -484,6 +514,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data)
} }
case BaseEventId + FSR2PluginEvent::ePostExecute: case BaseEventId + FSR2PluginEvent::ePostExecute:
{ {
std::lock_guard<std::mutex> lock(s_FeatureMutex);
auto* params = (FSR2CommandExecutionData*)data; auto* params = (FSR2CommandExecutionData*)data;
if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) if (params->featureSlot < 0 || params->featureSlot >= s_Features.size())
return; return;
@ -506,6 +538,8 @@ static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data)
} }
case BaseEventId + FSR2PluginEvent::eInit: case BaseEventId + FSR2PluginEvent::eInit:
{ {
std::lock_guard<std::mutex> lock(s_FeatureMutex);
auto* params = (FSR2CommandInitializationData*)data; auto* params = (FSR2CommandInitializationData*)data;
if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) if (params->featureSlot < 0 || params->featureSlot >= s_Features.size())
return; return;

Loading…
Cancel
Save