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;
+};