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
Nico de Poel 10 months ago
parent
commit
a1e30f9af4
  1. 3
      FSR3UnityPlugin.vcxproj
  2. 9
      FSR3UnityPlugin.vcxproj.filters
  3. 177
      FSR3Upscaler_DX12.cpp
  4. 47
      FSR3Upscaler_DX12.h
  5. 171
      UpscalerGraphicsDevice.h

3
FSR3UnityPlugin.vcxproj

@ -152,9 +152,12 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="FSR3UnityPlugin.cpp" />
<ClCompile Include="FSR3Upscaler_DX12.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="FSR3UnityTypes.h" />
<ClInclude Include="FSR3Upscaler_DX12.h" />
<ClInclude Include="UpscalerGraphicsDevice.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">

9
FSR3UnityPlugin.vcxproj.filters

@ -18,10 +18,19 @@
<ClCompile Include="FSR3UnityPlugin.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="FSR3Upscaler_DX12.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="FSR3UnityTypes.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="UpscalerGraphicsDevice.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="FSR3Upscaler_DX12.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>

177
FSR3Upscaler_DX12.cpp

@ -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);
}

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

171
UpscalerGraphicsDevice.h

@ -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;
};
Loading…
Cancel
Save