Browse Source
First pass at factoring out the feature management and the graphics API-specific code into separate classes, starting with FSR 3.1 for DX12
master
First pass at factoring out the feature management and the graphics API-specific code into separate classes, starting with FSR 3.1 for DX12
master
5 changed files with 407 additions and 0 deletions
-
3FSR3UnityPlugin.vcxproj
-
9FSR3UnityPlugin.vcxproj.filters
-
177FSR3Upscaler_DX12.cpp
-
47FSR3Upscaler_DX12.h
-
171UpscalerGraphicsDevice.h
@ -0,0 +1,177 @@ |
|||||
|
#include "FSR3Upscaler_DX12.h"
|
||||
|
|
||||
|
#include <d3d12.h>
|
||||
|
#include <dxgi.h>
|
||||
|
|
||||
|
// TODO: find a better place to put this, including the UnityLog and FFX API Loader includes
|
||||
|
#include <sstream>
|
||||
|
#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<const char*> versionNames; |
||||
|
std::vector<uint64_t> 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<std::mutex> 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); |
||||
|
} |
||||
@ -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<FSR3Feature_DX12> |
||||
|
{ |
||||
|
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; |
||||
|
}; |
||||
@ -0,0 +1,171 @@ |
|||||
|
#pragma once |
||||
|
#include <stdint.h> |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <queue> |
||||
|
#include <mutex> |
||||
|
|
||||
|
#include "UnityPluginAPI/IUnityInterface.h" |
||||
|
|
||||
|
#include "FSR3UnityTypes.h" |
||||
|
|
||||
|
template<typename TFeature> 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<std::mutex> lock(m_Mutex); |
||||
|
numFeatures = m_Features.size(); |
||||
|
} |
||||
|
|
||||
|
for (uint32_t slot = 0; slot < numFeatures; ++slot) |
||||
|
{ |
||||
|
DestroyFeature(slot); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
uint32_t CreateFeatureSlot() |
||||
|
{ |
||||
|
std::lock_guard<std::mutex> lock(m_Mutex); |
||||
|
return AllocateFeatureSlot(); |
||||
|
} |
||||
|
|
||||
|
virtual bool InitFeature(const FSR3CommandInitializationData* initData) |
||||
|
{ |
||||
|
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<TFeature> m_Features; |
||||
|
std::queue<uint32_t> m_FeatureSlots; |
||||
|
}; |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue