diff --git a/FSR3UnityPlugin.vcxproj b/FSR3UnityPlugin.vcxproj index 56260f4..522b818 100644 --- a/FSR3UnityPlugin.vcxproj +++ b/FSR3UnityPlugin.vcxproj @@ -152,9 +152,12 @@ + + + diff --git a/FSR3UnityPlugin.vcxproj.filters b/FSR3UnityPlugin.vcxproj.filters index 5268b4b..3e2551b 100644 --- a/FSR3UnityPlugin.vcxproj.filters +++ b/FSR3UnityPlugin.vcxproj.filters @@ -18,10 +18,19 @@ Source Files + + Source Files + Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/FSR3Upscaler_DX12.cpp b/FSR3Upscaler_DX12.cpp new file mode 100644 index 0000000..d4e2c5b --- /dev/null +++ b/FSR3Upscaler_DX12.cpp @@ -0,0 +1,177 @@ +#include "FSR3Upscaler_DX12.h" + +#include +#include + +// TODO: find a better place to put this, including the UnityLog and FFX API Loader includes +#include +#include "ffx_api/ffx_api_loader.h" +#include "UnityPluginAPI/IUnityLog.h" +static IUnityLog* s_Log = nullptr; +static HMODULE s_ffxModule = nullptr; +static ffxFunctions s_ffxFunctions{}; +static bool LoadFidelityFXLibrary(_In_ LPCWSTR lpLibFileName, void* device) +{ + s_ffxModule = LoadLibrary(lpLibFileName); + if (s_ffxModule == nullptr) + { + UNITY_LOG_ERROR(s_Log, "Failed to load FidelityFX library!"); + return false; + } + + ffxLoadFunctions(&s_ffxFunctions, s_ffxModule); + if (s_ffxFunctions.CreateContext == nullptr) + { + UNITY_LOG_ERROR(s_Log, "Failed to load FidelityFX library functions!"); + return false; + } + + // Check that we can actually create an upscaler with this library + ffx::QueryDescGetVersions versionQuery{}; + versionQuery.createDescType = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + versionQuery.device = device; + uint64_t versionCount = 0; + versionQuery.outputCount = &versionCount; + s_ffxFunctions.Query(nullptr, &versionQuery.header); + + if (versionCount == 0) + { + UNITY_LOG_ERROR(s_Log, "Failed to load FidelityFX upscaler versions!"); + return false; + } + + // Obtain the default upscaler version and log its name + std::vector versionNames; + std::vector versionIds; + versionIds.resize(versionCount); + versionNames.resize(versionCount); + versionQuery.versionIds = versionIds.data(); + versionQuery.versionNames = versionNames.data(); + s_ffxFunctions.Query(nullptr, &versionQuery.header); + + std::stringstream ss; + ss << "Loaded FidelityFX upscaler: FSR " << versionNames[0]; + UNITY_LOG(s_Log, ss.str().c_str()); + + return true; +} + +bool FSR3Upscaler_DX12::Init() +{ + ID3D12Device* device = m_GraphicsDevice->GetDevice(); + if (device == nullptr) + return false; + + m_DX12BackendDesc.device = device; + m_FrameFenceEventHandle = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS); + + return LoadFidelityFXLibrary(TEXT("amd_fidelityfx_dx12.dll"), device); +} + +void FSR3Upscaler_DX12::Shutdown() +{ + UpscalerGraphicsDevice::Shutdown(); // TODO: change to FSR3Upscaler_FFXBase::Shutdown + + std::lock_guard lock(m_Mutex); // TODO: see if we can rearrange this to only require mutexes in UpscalerGraphicsDevice + + if (s_ffxModule != nullptr) // TODO: move to base FSR3Upscaler_FFXBase class + { + FreeLibrary(s_ffxModule); + s_ffxModule = nullptr; + } + + if (m_FrameFenceEventHandle != nullptr) + { + CloseHandle(m_FrameFenceEventHandle); + m_FrameFenceEventHandle = nullptr; + } + + m_DX12BackendDesc.device = nullptr; +} + +bool FSR3Upscaler_DX12::InitFeature(FSR3Feature_DX12& feature, const FSR3CommandInitializationData* initData) +{ + ffx::CreateContextDescUpscale createUpscaling; + createUpscaling.maxUpscaleSize = { initData->displaySizeWidth, initData->displaySizeHeight }; + createUpscaling.maxRenderSize = { initData->maxRenderSizeWidth, initData->maxRenderSizeHeight }; + createUpscaling.flags = initData->flags; + + return FFX_API_RETURN_OK == s_ffxFunctions.CreateContext(&feature.upscalingContext, ffx::LinkHeaders(createUpscaling.header, m_DX12BackendDesc.header), nullptr); +} + +void FSR3Upscaler_DX12::SetTexture(FSR3TextureDesc* textureDesc, UnityTextureID textureID) +{ + textureDesc->image = (intptr_t)m_GraphicsDevice->TextureFromNativeTexture(textureID); +} + +void FSR3Upscaler_DX12::Execute(FSR3Feature_DX12& feature, const FSR3CommandExecutionData* execData) +{ + ffx::DispatchDescUpscale dispatchUpscale{}; + + UnityGraphicsD3D12RecordingState state; + m_GraphicsDevice->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 = m_GraphicsDevice->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); + dispatchUpscale.exposure = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.exposureTexture.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); + dispatchUpscale.reactive = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.reactiveMask.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); + dispatchUpscale.transparencyAndComposition = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.transparencyMask.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); + dispatchUpscale.output = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.colorOutput.image, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); + + if (dispatchUpscale.reactive.resource == nullptr) + dispatchUpscale.reactive = ffxApiGetResourceDX12((ID3D12Resource*)feature.textureTable.biasColorMask.image, FFX_API_RESOURCE_STATE_PIXEL_COMPUTE_READ); + + dispatchUpscale.jitterOffset.x = execData->jitterOffsetX; + dispatchUpscale.jitterOffset.y = execData->jitterOffsetY; + dispatchUpscale.motionVectorScale.x = execData->MVScaleX; + dispatchUpscale.motionVectorScale.y = execData->MVScaleY; + dispatchUpscale.reset = execData->reset; + dispatchUpscale.enableSharpening = execData->enableSharpening; + dispatchUpscale.sharpness = execData->sharpness; + dispatchUpscale.frameTimeDelta = execData->frameTimeDelta; + dispatchUpscale.preExposure = execData->preExposure; + dispatchUpscale.renderSize.width = execData->renderSizeWidth; + dispatchUpscale.renderSize.height = execData->renderSizeHeight; + dispatchUpscale.upscaleSize.width = feature.upscaleSizeWidth; + dispatchUpscale.upscaleSize.height = feature.upscaleSizeHeight; + dispatchUpscale.cameraFovAngleVertical = execData->cameraFovAngleVertical; + + if (feature.flags & FFX_UPSCALE_ENABLE_DEPTH_INVERTED) + { + dispatchUpscale.cameraFar = execData->cameraNear; + dispatchUpscale.cameraNear = execData->cameraFar; + } + else + { + dispatchUpscale.cameraFar = execData->cameraFar; + dispatchUpscale.cameraNear = execData->cameraNear; + } + + s_ffxFunctions.Dispatch(&feature.upscalingContext, &dispatchUpscale.header); +} + +bool FSR3Upscaler_DX12::IsValidFeature(FSR3Feature_DX12& feature) +{ + return feature.upscalingContext != nullptr; +} + +void FSR3Upscaler_DX12::AwaitEndOfFrame(uint64_t frameValue) +{ + // 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 = m_GraphicsDevice->GetFrameFence(); + if (frameValue > frameFence->GetCompletedValue()) + { + frameFence->SetEventOnCompletion(frameValue, m_FrameFenceEventHandle); + WaitForSingleObjectEx(m_FrameFenceEventHandle, INFINITE, false); + } +} + +void FSR3Upscaler_DX12::DestroyContext(FSR3Feature_DX12& feature) +{ + s_ffxFunctions.DestroyContext(&feature.upscalingContext, nullptr); +} diff --git a/FSR3Upscaler_DX12.h b/FSR3Upscaler_DX12.h new file mode 100644 index 0000000..68447c8 --- /dev/null +++ b/FSR3Upscaler_DX12.h @@ -0,0 +1,47 @@ +#pragma once +#include "UpscalerGraphicsDevice.h" + +#include "ffx_api/ffx_upscale.hpp" +#include "ffx_api/dx12/ffx_api_dx12.hpp" + +#include "UnityPluginAPI/IUnityGraphicsD3D12.h" + +struct FSR3Feature_DX12 +{ + ffx::Context upscalingContext; + + uint32_t upscaleSizeWidth; + uint32_t upscaleSizeHeight; + uint32_t flags; + + uint64_t dispatchFrameValue; + + FSR3TextureTable textureTable; +}; + +class FSR3Upscaler_DX12 : UpscalerGraphicsDevice +{ +public: + FSR3Upscaler_DX12(IUnityGraphicsD3D12v7* graphicsDevice): + m_GraphicsDevice(graphicsDevice), m_DX12BackendDesc(), m_FrameFenceEventHandle(nullptr) + { + } + + bool Init() override; + void Shutdown() override; + +protected: + bool IsValidFeature(FSR3Feature_DX12& feature) override; + + bool InitFeature(FSR3Feature_DX12& feature, const FSR3CommandInitializationData* initData) override; + void SetTexture(FSR3TextureDesc* textureDesc, UnityTextureID textureID) override; + void Execute(FSR3Feature_DX12& feature, const FSR3CommandExecutionData* execData) override; + + void AwaitEndOfFrame(uint64_t frameValue) override; + void DestroyContext(FSR3Feature_DX12& feature) override; + +private: + IUnityGraphicsD3D12v7* m_GraphicsDevice; + ffx::CreateBackendDX12Desc m_DX12BackendDesc; + HANDLE m_FrameFenceEventHandle; +}; diff --git a/UpscalerGraphicsDevice.h b/UpscalerGraphicsDevice.h new file mode 100644 index 0000000..e1250ef --- /dev/null +++ b/UpscalerGraphicsDevice.h @@ -0,0 +1,171 @@ +#pragma once +#include + +#include +#include +#include + +#include "UnityPluginAPI/IUnityInterface.h" + +#include "FSR3UnityTypes.h" + +template class UpscalerGraphicsDevice +{ +public: + virtual bool Init() = 0; // Called by AMDUP_InitAPI, does FidelityFX library loading + + virtual void Shutdown() + { + // Called by AMDUP_ShutdownAPI, destroys all features, cleans up internal resources + size_t numFeatures = 0; + { + std::lock_guard lock(m_Mutex); + numFeatures = m_Features.size(); + } + + for (uint32_t slot = 0; slot < numFeatures; ++slot) + { + DestroyFeature(slot); + } + } + + uint32_t CreateFeatureSlot() + { + std::lock_guard lock(m_Mutex); + return AllocateFeatureSlot(); + } + + virtual bool InitFeature(const FSR3CommandInitializationData* initData) + { + std::lock_guard lock(m_Mutex); + + // Called by FSR3PluginEvent::eInit event + if (initData->featureSlot < 0 || initData->featureSlot >= m_Features.size()) + return false; + + auto& feature = m_Features[initData->featureSlot]; + return InitFeature(feature, initData); + } + + void DestroyFeature(uint32_t featureSlot) + { + // Called by FSR3PluginEvent::eDestroyFeature event and Shutdown + 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(m_Mutex); + + if (featureSlot < 0 || featureSlot >= m_Features.size()) + return; + + auto& feature = m_Features[featureSlot]; + if (!IsValidFeature(feature)) + return; + + dispatchFrameValue = feature.dispatchFrameValue; + } + + // If there's still an upscale dispatch executing on the current frame, wait until rendering is finished before destroying its context. + AwaitEndOfFrame(dispatchFrameValue); + + std::lock_guard lock(m_Mutex); + + DestroyContext(m_Features[featureSlot]); + FreeFeatureSlot(featureSlot); + } + + virtual void ClearTextureTable(uint32_t featureSlot) + { + if (featureSlot < 0 || featureSlot >= m_Features.size()) + return; + + memset(&m_Features[featureSlot].textureTable, 0, sizeof(FSR3TextureTable)); + } + + virtual void SetTextureSlot(uint32_t featureSlot, uint32_t textureSlot, UnityTextureID textureID) + { + if (featureSlot < 0 || featureSlot >= m_Features.size()) + return; + + // 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 + FSR3TextureDesc* textureDesc = ((FSR3TextureDesc*)&m_Features[featureSlot].textureTable) + textureSlot; + SetTexture(textureDesc, textureID); + } + + virtual void Execute(const FSR3CommandExecutionData* execData) + { + std::lock_guard lock(m_Mutex); + + // Called by FSR3PluginEvent::eExecute event + if (execData->featureSlot < 0 || execData->featureSlot >= m_Features.size()) + return; + + auto& feature = m_Features[execData->featureSlot]; + if (!IsValidFeature(feature)) + return; + + Execute(feature, execData); + } + + virtual void PostExecute(const FSR3CommandExecutionData* execData) + { + std::lock_guard lock(m_Mutex); + + // Called by FSR3PluginEvent::ePostExecute event + if (execData->featureSlot < 0 || execData->featureSlot >= m_Features.size()) + return; + + auto& feature = m_Features[execData->featureSlot]; + if (!IsValidFeature(feature)) + return; + + PostExecute(feature, execData); + } + +protected: + virtual bool IsValidFeature(TFeature& feature) = 0; + + virtual bool InitFeature(TFeature& feature, const FSR3CommandInitializationData* initData) = 0; + virtual void SetTexture(FSR3TextureDesc* textureDesc, UnityTextureID textureID) = 0; + virtual void Execute(TFeature& feature, const FSR3CommandExecutionData* execData) = 0; + virtual void PostExecute(TFeature& feature, const FSR3CommandExecutionData* execData) { } + + virtual void AwaitEndOfFrame(uint64_t frameValue) = 0; + virtual void DestroyContext(TFeature& feature) = 0; + + std::mutex m_Mutex; + +private: + uint32_t AllocateFeatureSlot() + { + if (m_FeatureSlots.empty()) + { + // Create a new feature if there are no free slots + uint32_t featureSlot = (uint32_t)m_Features.size(); + m_Features.push_back(std::move(TFeature())); + memset(&m_Features[featureSlot], 0, sizeof(TFeature)); + return featureSlot; + } + + // Reallocate an existing free slot + uint32_t featureSlot = m_FeatureSlots.front(); + m_FeatureSlots.pop(); + return featureSlot; + } + + void FreeFeatureSlot(uint32_t featureSlot) + { + // Prevent duplicate free slots + auto& slots = m_FeatureSlots._Get_container(); + if (std::find(slots.begin(), slots.end(), featureSlot) != slots.end()) + return; + + m_FeatureSlots.push(featureSlot); + memset(&m_Features[featureSlot], 0, sizeof(TFeature)); + } + + std::vector m_Features; + std::queue m_FeatureSlots; +};