#include #include #include #include #include "UnityPluginAPI/IUnityInterface.h" #include "UnityPluginAPI/IUnityLog.h" #include "UnityPluginAPI/IUnityGraphics.h" #include "UnityPluginAPI/IUnityRenderingExtensions.h" #include #include #include "UnityPluginAPI/IUnityGraphicsD3D12.h" #include "UnityPluginAPI/IUnityGraphicsVulkan.h" #include "ffx_api/ffx_upscale.hpp" #include "ffx_api/dx12/ffx_api_dx12.hpp" #include "ffx_api/vk/ffx_api_vk.hpp" #include "FSR3UnityTypes.h" static const int32_t BaseEventId = 0; static IUnityInterfaces* s_UnityInterfaces = nullptr; static IUnityLog* s_Log = nullptr; static IUnityGraphics* s_Graphics = nullptr; static IUnityGraphicsD3D12v7* s_GraphicsD3D12 = nullptr; static UnityGfxRenderer s_RendererType = kUnityGfxRendererNull; static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType); static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data); static void UNITY_INTERFACE_API OnSetTextureEvent(int eventID, void* data); struct FSR3Feature { ffx::Context upscalingContext; uint32_t upscaleSizeWidth; uint32_t upscaleSizeHeight; uint32_t flags; FSR3TextureTable textureTable; }; static std::vector s_Features; static std::queue s_FeatureSlots; static std::mutex s_FeatureMutex; static ffx::CreateBackendDX12Desc s_BackendDesc{}; // Unity plugin load event extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginLoad(IUnityInterfaces* unityInterfaces) { s_UnityInterfaces = unityInterfaces; s_Log = unityInterfaces->Get(); s_Graphics = unityInterfaces->Get(); s_Graphics->RegisterDeviceEventCallback(OnGraphicsDeviceEvent); // Run OnGraphicsDeviceEvent(initialize) manually on plugin load // to not miss the event in case the graphics device is already initialized OnGraphicsDeviceEvent(kUnityGfxDeviceEventInitialize); } // Unity plugin unload event extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API UnityPluginUnload() { s_Graphics->UnregisterDeviceEventCallback(OnGraphicsDeviceEvent); } static void UNITY_INTERFACE_API OnGraphicsDeviceEvent(UnityGfxDeviceEventType eventType) { switch (eventType) { case kUnityGfxDeviceEventInitialize: { s_RendererType = s_Graphics->GetRenderer(); if (s_RendererType != kUnityGfxRendererD3D12) return; s_GraphicsD3D12 = s_UnityInterfaces->Get(); if (s_GraphicsD3D12 == nullptr) { UNITY_LOG_ERROR(s_Log, "Could not obtain D3D12 Graphics interface!"); return; } break; } case kUnityGfxDeviceEventShutdown: { s_GraphicsD3D12 = nullptr; s_RendererType = kUnityGfxRendererNull; break; } case kUnityGfxDeviceEventBeforeReset: { break; } case kUnityGfxDeviceEventAfterReset: { break; } }; } // Thread-safe allocation of feature slot static uint32_t AllocateFeatureSlot() { std::lock_guard lock(s_FeatureMutex); if (s_FeatureSlots.empty()) { // Create a new feature if there are no free slots uint32_t featureSlot = (uint32_t)s_Features.size(); s_Features.push_back(std::move(FSR3Feature())); return featureSlot; } // Reallocate an existing free slot uint32_t featureSlot = s_FeatureSlots.front(); s_FeatureSlots.pop(); return featureSlot; } // Thread-safe freeing and clearing of feature slot static void FreeFeatureSlot(uint32_t featureSlot) { std::lock_guard lock(s_FeatureMutex); s_FeatureSlots.push(featureSlot); memset(&s_Features[featureSlot], 0, sizeof(FSR3Feature)); } extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_InitApi() { if (s_GraphicsD3D12 == nullptr) return false; ID3D12Device* device = s_GraphicsD3D12->GetDevice(); if (device == nullptr) return false; s_BackendDesc.device = device; return true; } extern "C" void UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_ShutdownApi() { for (uint32_t slot = 0; slot < s_Features.size(); ++slot) { auto& feature = s_Features[slot]; if (feature.upscalingContext != nullptr) { ffx::DestroyContext(feature.upscalingContext); FreeFeatureSlot(slot); } } s_BackendDesc.device = nullptr; } extern "C" uint32_t UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetDeviceVersion() { return 0x0u; } extern "C" UnityRenderingEventAndData UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetRenderEventCallback() { return OnRenderEventAndData; } extern "C" UnityRenderingEventAndData UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetSetTextureEventCallback() { return OnSetTextureEvent; } extern "C" uint32_t UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_CreateFeatureSlot() { return AllocateFeatureSlot(); } extern "C" float UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetUpscaleRatioFromQualityMode(FSR3Quality qualityMode) { switch (qualityMode) { case FSR3Quality::qQuality: return 1.5f; case FSR3Quality::qBalanced: return 1.7f; case FSR3Quality::qPerformance: return 2.0f; case FSR3Quality::qUltraPerformance: return 3.0f; default: return 1.0f; } } extern "C" bool UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetRenderResolutionFromQualityMode(FSR3Quality qualityMode, uint32_t displayWidth, uint32_t displayHeight, uint32_t* renderWidth, uint32_t* renderHeight) { if (renderWidth == nullptr || renderHeight == nullptr) return false; float ratio = AMDUP_GetUpscaleRatioFromQualityMode(qualityMode); *renderWidth = (uint32_t)roundf(displayWidth / ratio); *renderHeight = (uint32_t)roundf(displayHeight / ratio); return true; } extern "C" int32_t UNITY_INTERFACE_EXPORT UNITY_INTERFACE_API AMDUP_GetBaseEventId() { return BaseEventId; } // Plugin function to handle a specific rendering event static void UNITY_INTERFACE_API OnRenderEventAndData(int eventID, void* data) { if (s_GraphicsD3D12 == nullptr || s_BackendDesc.device == nullptr) return; // User rendering code switch (eventID) { case BaseEventId + FSR3PluginEvent::eDestroyFeature: { uint32_t featureSlot = (uint32_t)(int64_t)data; if (featureSlot < 0 || featureSlot >= s_Features.size()) return; auto& feature = s_Features[featureSlot]; if (feature.upscalingContext != nullptr) { ffx::DestroyContext(feature.upscalingContext); FreeFeatureSlot(featureSlot); } break; } case BaseEventId + FSR3PluginEvent::eExecute: { auto* params = (FSR3CommandExecutionData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return; auto& feature = s_Features[params->featureSlot]; UnityGraphicsD3D12RecordingState state; s_GraphicsD3D12->CommandRecordingState(&state); ffx::DispatchDescUpscale dispatchUpscale{}; dispatchUpscale.commandList = state.commandList; dispatchUpscale.color = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.colorInput, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.depth = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.depth, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.motionVectors = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.motionVectors, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.exposure = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.exposureTexture, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.reactive = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.reactiveMask, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.transparencyAndComposition = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.transparencyMask, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); dispatchUpscale.output = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.colorOutput, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); dispatchUpscale.jitterOffset.x = params->jitterOffsetX; dispatchUpscale.jitterOffset.y = params->jitterOffsetY; dispatchUpscale.motionVectorScale.x = params->MVScaleX; dispatchUpscale.motionVectorScale.y = params->MVScaleY; dispatchUpscale.reset = params->reset; dispatchUpscale.enableSharpening = params->enableSharpening; dispatchUpscale.sharpness = params->sharpness; dispatchUpscale.frameTimeDelta = params->frameTimeDelta; dispatchUpscale.preExposure = params->preExposure; dispatchUpscale.renderSize.width = params->renderSizeWidth; dispatchUpscale.renderSize.height = params->renderSizeHeight; dispatchUpscale.upscaleSize.width = feature.upscaleSizeWidth; dispatchUpscale.upscaleSize.height = feature.upscaleSizeHeight; dispatchUpscale.cameraFovAngleVertical = params->cameraFovAngleVertical; if (feature.flags & FFX_UPSCALE_ENABLE_DEPTH_INVERTED) { dispatchUpscale.cameraFar = params->cameraNear; dispatchUpscale.cameraNear = params->cameraFar; } else { dispatchUpscale.cameraFar = params->cameraFar; dispatchUpscale.cameraNear = params->cameraNear; } ffx::Dispatch(feature.upscalingContext, dispatchUpscale); break; } case BaseEventId + FSR3PluginEvent::ePostExecute: { auto* params = (FSR3CommandExecutionData*)data; break; } case BaseEventId + FSR3PluginEvent::eInit: { auto* params = (FSR3CommandInitializationData*)data; if (params->featureSlot < 0 || params->featureSlot >= s_Features.size()) return; auto& feature = s_Features[params->featureSlot]; feature.upscaleSizeWidth = params->displaySizeWidth; feature.upscaleSizeHeight = params->displaySizeHeight; feature.flags = params->flags; ffx::CreateContextDescUpscale createUpscaling; createUpscaling.maxUpscaleSize = { params->displaySizeWidth, params->displaySizeHeight }; createUpscaling.maxRenderSize = { params->maxRenderSizeWidth, params->maxRenderSizeHeight }; createUpscaling.flags = params->flags; ffx::CreateContext(feature.upscalingContext, nullptr, createUpscaling, s_BackendDesc); break; } } } static void UNITY_INTERFACE_API OnSetTextureEvent(int eventID, void* data) { if (s_GraphicsD3D12 == nullptr) return; auto* params = (UnityRenderingExtTextureUpdateParamsV2*)data; // userData = (featureId & (int) ushort.MaxValue) << 16 | (textureSlot & (int) short.MaxValue) << 1 | (clearTextureTable ? 1 : 0); uint32_t featureSlot = (params->userData >> 16) & 0xFFFF; uint32_t textureSlot = (params->userData >> 1) & 0x7FFF; uint32_t clearTextureTable = params->userData & 0x1; if (featureSlot < 0 || featureSlot >= s_Features.size()) return; auto& feature = s_Features[featureSlot]; // User rendering code switch (eventID) { case kUnityRenderingExtEventUpdateTextureBeginV2: { if (clearTextureTable) { memset(&feature.textureTable, 0, sizeof(FSR3TextureTable)); } break; } case kUnityRenderingExtEventUpdateTextureEndV2: { // We organized the texture table struct to be ordered the same as the texture slot enum // This way we can use the texture slot value simply as a pointer offset into the texture table *(((intptr_t*)&feature.textureTable) + textureSlot) = (intptr_t)s_GraphicsD3D12->TextureFromNativeTexture((UnityTextureID)params->textureID); break; } } }