From a1c4244a27b4c84880500d6e758f4fcac7a7048e Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 00:08:22 -0700 Subject: [PATCH] Implement clean Path-B FSR3 wrapper ABI for framegen --- README.md | 9 +- docs/AMD_FSR2_INTEGRATION.md | 16 ++- include/rendering/amd_fsr3_runtime.hpp | 8 ++ include/rendering/amd_fsr3_wrapper_abi.h | 75 +++++++++++ src/rendering/amd_fsr3_runtime.cpp | 151 ++++++++++++++++++++++- 5 files changed, 254 insertions(+), 5 deletions(-) create mode 100644 include/rendering/amd_fsr3_wrapper_abi.h diff --git a/README.md b/README.md index 4df25ec1..5ba51f60 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,14 @@ make -j$(nproc) - Runtime library auto-probe checks for `libffx_fsr3_vk`/`ffx_fsr3_vk` in default loader paths. - Override runtime library path with: - `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (platform extension varies). - - Current state is still dispatch-staged until full FI/OF dispatch activation is linked. + - Wrapper override path: + - `WOWEE_FFX_SDK_RUNTIME_WRAPPER_LIB=/absolute/path/to/libffx_fsr3_vk_wrapper.so` (platform extension varies). + - Path B wrapper libraries must export the clean wrapper ABI (`include/rendering/amd_fsr3_wrapper_abi.h`): + - `wowee_fsr3_wrapper_get_abi_version` + - `wowee_fsr3_wrapper_initialize` + - `wowee_fsr3_wrapper_dispatch_upscale` + - `wowee_fsr3_wrapper_shutdown` + - Optional FG hook: `wowee_fsr3_wrapper_dispatch_framegen` ### Current FSR Defaults diff --git a/docs/AMD_FSR2_INTEGRATION.md b/docs/AMD_FSR2_INTEGRATION.md index 867571ad..9de2c238 100644 --- a/docs/AMD_FSR2_INTEGRATION.md +++ b/docs/AMD_FSR2_INTEGRATION.md @@ -36,10 +36,22 @@ Detection expects: Runtime note: - Renderer/UI now expose a persisted experimental framegen toggle. -- Runtime loader now probes for AMD FSR3 SDK runtime binaries. +- Runtime loader now supports: + - Path A: AMD SDK runtime binaries (`ffx_fsr3_vk`). + - Path B: wrapper runtime libraries implementing WoWee's wrapper ABI. - You can point to an explicit runtime binary with: - `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`). -- Current runtime status is still `dispatch staged` (FI/OF dispatch activation pending). +- You can point to an explicit wrapper binary with: + - `WOWEE_FFX_SDK_RUNTIME_WRAPPER_LIB=/absolute/path/to/libffx_fsr3_vk_wrapper.so` (or `.dll` / `.dylib`). +- Path B wrapper ABI contract is declared in: + - `include/rendering/amd_fsr3_wrapper_abi.h` +- Required wrapper exports: + - `wowee_fsr3_wrapper_get_abi_version` + - `wowee_fsr3_wrapper_initialize` + - `wowee_fsr3_wrapper_dispatch_upscale` + - `wowee_fsr3_wrapper_shutdown` +- Optional wrapper export: + - `wowee_fsr3_wrapper_dispatch_framegen` ## Current Status diff --git a/include/rendering/amd_fsr3_runtime.hpp b/include/rendering/amd_fsr3_runtime.hpp index 07f63cad..e4e8bc5c 100644 --- a/include/rendering/amd_fsr3_runtime.hpp +++ b/include/rendering/amd_fsr3_runtime.hpp @@ -69,6 +69,12 @@ public: const std::string& lastError() const { return lastError_; } private: + enum class RuntimeBackend { + None, + Official, + Wrapper + }; + void* libHandle_ = nullptr; std::string loadedLibraryPath_; void* scratchBuffer_ = nullptr; @@ -81,6 +87,8 @@ private: struct RuntimeFns; RuntimeFns* fns_ = nullptr; void* contextStorage_ = nullptr; + void* wrapperContext_ = nullptr; + RuntimeBackend backend_ = RuntimeBackend::None; }; } // namespace wowee::rendering diff --git a/include/rendering/amd_fsr3_wrapper_abi.h b/include/rendering/amd_fsr3_wrapper_abi.h new file mode 100644 index 00000000..6968b46a --- /dev/null +++ b/include/rendering/amd_fsr3_wrapper_abi.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WOWEE_FSR3_WRAPPER_ABI_VERSION 1u + +typedef void* WoweeFsr3WrapperContext; + +typedef struct WoweeFsr3WrapperInitDesc { + uint32_t structSize; + uint32_t abiVersion; + VkPhysicalDevice physicalDevice; + VkDevice device; + PFN_vkGetDeviceProcAddr getDeviceProcAddr; + uint32_t maxRenderWidth; + uint32_t maxRenderHeight; + uint32_t displayWidth; + uint32_t displayHeight; + VkFormat colorFormat; + uint32_t enableFlags; +} WoweeFsr3WrapperInitDesc; + +typedef struct WoweeFsr3WrapperDispatchDesc { + uint32_t structSize; + VkCommandBuffer commandBuffer; + VkImage colorImage; + VkImage depthImage; + VkImage motionVectorImage; + VkImage outputImage; + VkImage frameGenOutputImage; + uint32_t renderWidth; + uint32_t renderHeight; + uint32_t outputWidth; + uint32_t outputHeight; + VkFormat colorFormat; + VkFormat depthFormat; + VkFormat motionVectorFormat; + VkFormat outputFormat; + float jitterX; + float jitterY; + float motionScaleX; + float motionScaleY; + float frameTimeDeltaMs; + float cameraNear; + float cameraFar; + float cameraFovYRadians; + uint32_t reset; +} WoweeFsr3WrapperDispatchDesc; + +enum { + WOWEE_FSR3_WRAPPER_ENABLE_HDR_INPUT = 1u << 0, + WOWEE_FSR3_WRAPPER_ENABLE_DEPTH_INVERTED = 1u << 1, + WOWEE_FSR3_WRAPPER_ENABLE_FRAME_GENERATION = 1u << 2 +}; + +uint32_t wowee_fsr3_wrapper_get_abi_version(void); +const char* wowee_fsr3_wrapper_get_name(void); +int32_t wowee_fsr3_wrapper_initialize(const WoweeFsr3WrapperInitDesc* initDesc, + WoweeFsr3WrapperContext* outContext, + char* outErrorText, + uint32_t outErrorTextCapacity); +int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3WrapperContext context, + const WoweeFsr3WrapperDispatchDesc* dispatchDesc); +int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3WrapperContext context, + const WoweeFsr3WrapperDispatchDesc* dispatchDesc); +void wowee_fsr3_wrapper_shutdown(WoweeFsr3WrapperContext context); + +#ifdef __cplusplus +} +#endif diff --git a/src/rendering/amd_fsr3_runtime.cpp b/src/rendering/amd_fsr3_runtime.cpp index ab1b3fdd..57cf6096 100644 --- a/src/rendering/amd_fsr3_runtime.cpp +++ b/src/rendering/amd_fsr3_runtime.cpp @@ -1,5 +1,7 @@ #include "rendering/amd_fsr3_runtime.hpp" +#include "rendering/amd_fsr3_wrapper_abi.h" + #include #include #include @@ -23,6 +25,13 @@ namespace wowee::rendering { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN struct AmdFsr3Runtime::RuntimeFns { + uint32_t (*wrapperGetAbiVersion)() = nullptr; + const char* (*wrapperGetName)() = nullptr; + int32_t (*wrapperInitialize)(const WoweeFsr3WrapperInitDesc*, WoweeFsr3WrapperContext*, char*, uint32_t) = nullptr; + int32_t (*wrapperDispatchUpscale)(WoweeFsr3WrapperContext, const WoweeFsr3WrapperDispatchDesc*) = nullptr; + int32_t (*wrapperDispatchFramegen)(WoweeFsr3WrapperContext, const WoweeFsr3WrapperDispatchDesc*) = nullptr; + void (*wrapperShutdown)(WoweeFsr3WrapperContext) = nullptr; + decltype(&ffxGetScratchMemorySizeVK) getScratchMemorySizeVK = nullptr; decltype(&ffxGetDeviceVK) getDeviceVK = nullptr; decltype(&ffxGetInterfaceVK) getInterfaceVK = nullptr; @@ -147,6 +156,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { shutdown(); lastError_.clear(); loadPathKind_ = LoadPathKind::None; + backend_ = RuntimeBackend::None; #if !WOWEE_HAS_AMD_FSR3_FRAMEGEN (void)desc; @@ -220,6 +230,75 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { }; fns_ = new RuntimeFns{}; + if (loadPathKind_ == LoadPathKind::Wrapper) { + fns_->wrapperGetAbiVersion = reinterpret_castwrapperGetAbiVersion)>(resolveSym("wowee_fsr3_wrapper_get_abi_version")); + fns_->wrapperGetName = reinterpret_castwrapperGetName)>(resolveSym("wowee_fsr3_wrapper_get_name")); + fns_->wrapperInitialize = reinterpret_castwrapperInitialize)>(resolveSym("wowee_fsr3_wrapper_initialize")); + fns_->wrapperDispatchUpscale = reinterpret_castwrapperDispatchUpscale)>(resolveSym("wowee_fsr3_wrapper_dispatch_upscale")); + fns_->wrapperDispatchFramegen = reinterpret_castwrapperDispatchFramegen)>(resolveSym("wowee_fsr3_wrapper_dispatch_framegen")); + fns_->wrapperShutdown = reinterpret_castwrapperShutdown)>(resolveSym("wowee_fsr3_wrapper_shutdown")); + + if (!fns_->wrapperGetAbiVersion || !fns_->wrapperInitialize || + !fns_->wrapperDispatchUpscale || !fns_->wrapperShutdown) { + LOG_WARNING("FSR3 runtime: required wrapper ABI symbols not found in ", loadedLibraryPath_); + lastError_ = "missing required wowee_fsr3_wrapper_* symbols"; + shutdown(); + return false; + } + + const uint32_t abiVersion = fns_->wrapperGetAbiVersion(); + if (abiVersion != WOWEE_FSR3_WRAPPER_ABI_VERSION) { + LOG_WARNING("FSR3 runtime: wrapper ABI mismatch. expected=", WOWEE_FSR3_WRAPPER_ABI_VERSION, + " got=", abiVersion); + lastError_ = "wrapper ABI version mismatch"; + shutdown(); + return false; + } + if (desc.enableFrameGeneration && !fns_->wrapperDispatchFramegen) { + LOG_WARNING("FSR3 runtime: wrapper runtime missing framegen dispatch symbol."); + lastError_ = "wrapper missing frame generation entry points"; + shutdown(); + return false; + } + + WoweeFsr3WrapperInitDesc wrapperInit{}; + wrapperInit.structSize = sizeof(wrapperInit); + wrapperInit.abiVersion = WOWEE_FSR3_WRAPPER_ABI_VERSION; + wrapperInit.physicalDevice = desc.physicalDevice; + wrapperInit.device = desc.device; + wrapperInit.getDeviceProcAddr = desc.getDeviceProcAddr; + wrapperInit.maxRenderWidth = desc.maxRenderWidth; + wrapperInit.maxRenderHeight = desc.maxRenderHeight; + wrapperInit.displayWidth = desc.displayWidth; + wrapperInit.displayHeight = desc.displayHeight; + wrapperInit.colorFormat = desc.colorFormat; + wrapperInit.enableFlags = 0; + if (desc.hdrInput) wrapperInit.enableFlags |= WOWEE_FSR3_WRAPPER_ENABLE_HDR_INPUT; + if (desc.depthInverted) wrapperInit.enableFlags |= WOWEE_FSR3_WRAPPER_ENABLE_DEPTH_INVERTED; + if (desc.enableFrameGeneration) wrapperInit.enableFlags |= WOWEE_FSR3_WRAPPER_ENABLE_FRAME_GENERATION; + + char errorText[256] = {}; + WoweeFsr3WrapperContext wrapperCtx = nullptr; + if (fns_->wrapperInitialize(&wrapperInit, &wrapperCtx, errorText, static_cast(sizeof(errorText))) != 0 || !wrapperCtx) { + LOG_WARNING("FSR3 runtime: wrapper initialization failed: ", errorText[0] ? errorText : "unknown error"); + lastError_ = errorText[0] ? errorText : "wrapper initialization failed"; + shutdown(); + return false; + } + + wrapperContext_ = wrapperCtx; + frameGenerationReady_ = desc.enableFrameGeneration; + ready_ = true; + backend_ = RuntimeBackend::Wrapper; + if (fns_->wrapperGetName) { + const char* wrapperName = fns_->wrapperGetName(); + if (wrapperName && *wrapperName) { + LOG_INFO("FSR3 runtime: wrapper active: ", wrapperName); + } + } + return true; + } + fns_->getScratchMemorySizeVK = reinterpret_castgetScratchMemorySizeVK)>(resolveSym("ffxGetScratchMemorySizeVK")); fns_->getDeviceVK = reinterpret_castgetDeviceVK)>(resolveSym("ffxGetDeviceVK")); fns_->getInterfaceVK = reinterpret_castgetInterfaceVK)>(resolveSym("ffxGetInterfaceVK")); @@ -333,6 +412,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { } ready_ = true; + backend_ = RuntimeBackend::Official; return true; #endif } @@ -342,8 +422,39 @@ bool AmdFsr3Runtime::dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc) { (void)desc; return false; #else - if (!ready_ || !contextStorage_ || !fns_ || !fns_->fsr3ContextDispatchUpscale) return false; + if (!ready_ || !fns_) return false; if (!desc.commandBuffer || !desc.colorImage || !desc.depthImage || !desc.motionVectorImage || !desc.outputImage) return false; + if (backend_ == RuntimeBackend::Wrapper) { + if (!wrapperContext_ || !fns_->wrapperDispatchUpscale) return false; + WoweeFsr3WrapperDispatchDesc wrapperDesc{}; + wrapperDesc.structSize = sizeof(wrapperDesc); + wrapperDesc.commandBuffer = desc.commandBuffer; + wrapperDesc.colorImage = desc.colorImage; + wrapperDesc.depthImage = desc.depthImage; + wrapperDesc.motionVectorImage = desc.motionVectorImage; + wrapperDesc.outputImage = desc.outputImage; + wrapperDesc.frameGenOutputImage = desc.frameGenOutputImage; + wrapperDesc.renderWidth = desc.renderWidth; + wrapperDesc.renderHeight = desc.renderHeight; + wrapperDesc.outputWidth = desc.outputWidth; + wrapperDesc.outputHeight = desc.outputHeight; + wrapperDesc.colorFormat = desc.colorFormat; + wrapperDesc.depthFormat = desc.depthFormat; + wrapperDesc.motionVectorFormat = desc.motionVectorFormat; + wrapperDesc.outputFormat = desc.outputFormat; + wrapperDesc.jitterX = desc.jitterX; + wrapperDesc.jitterY = desc.jitterY; + wrapperDesc.motionScaleX = desc.motionScaleX; + wrapperDesc.motionScaleY = desc.motionScaleY; + wrapperDesc.frameTimeDeltaMs = desc.frameTimeDeltaMs; + wrapperDesc.cameraNear = desc.cameraNear; + wrapperDesc.cameraFar = desc.cameraFar; + wrapperDesc.cameraFovYRadians = desc.cameraFovYRadians; + wrapperDesc.reset = desc.reset ? 1u : 0u; + return fns_->wrapperDispatchUpscale(static_cast(wrapperContext_), &wrapperDesc) == 0; + } + + if (!contextStorage_ || !fns_->fsr3ContextDispatchUpscale) return false; FfxResourceDescription colorDesc = makeResourceDescription( desc.colorFormat, desc.renderWidth, desc.renderHeight, FFX_RESOURCE_USAGE_READ_ONLY); @@ -394,9 +505,40 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d (void)desc; return false; #else - if (!ready_ || !frameGenerationReady_ || !contextStorage_ || !fns_ || !fns_->fsr3DispatchFrameGeneration) return false; + if (!ready_ || !frameGenerationReady_ || !fns_) return false; if (!desc.commandBuffer || !desc.outputImage || !desc.frameGenOutputImage || desc.outputWidth == 0 || desc.outputHeight == 0 || desc.outputFormat == VK_FORMAT_UNDEFINED) return false; + if (backend_ == RuntimeBackend::Wrapper) { + if (!wrapperContext_ || !fns_->wrapperDispatchFramegen) return false; + WoweeFsr3WrapperDispatchDesc wrapperDesc{}; + wrapperDesc.structSize = sizeof(wrapperDesc); + wrapperDesc.commandBuffer = desc.commandBuffer; + wrapperDesc.colorImage = desc.colorImage; + wrapperDesc.depthImage = desc.depthImage; + wrapperDesc.motionVectorImage = desc.motionVectorImage; + wrapperDesc.outputImage = desc.outputImage; + wrapperDesc.frameGenOutputImage = desc.frameGenOutputImage; + wrapperDesc.renderWidth = desc.renderWidth; + wrapperDesc.renderHeight = desc.renderHeight; + wrapperDesc.outputWidth = desc.outputWidth; + wrapperDesc.outputHeight = desc.outputHeight; + wrapperDesc.colorFormat = desc.colorFormat; + wrapperDesc.depthFormat = desc.depthFormat; + wrapperDesc.motionVectorFormat = desc.motionVectorFormat; + wrapperDesc.outputFormat = desc.outputFormat; + wrapperDesc.jitterX = desc.jitterX; + wrapperDesc.jitterY = desc.jitterY; + wrapperDesc.motionScaleX = desc.motionScaleX; + wrapperDesc.motionScaleY = desc.motionScaleY; + wrapperDesc.frameTimeDeltaMs = desc.frameTimeDeltaMs; + wrapperDesc.cameraNear = desc.cameraNear; + wrapperDesc.cameraFar = desc.cameraFar; + wrapperDesc.cameraFovYRadians = desc.cameraFovYRadians; + wrapperDesc.reset = desc.reset ? 1u : 0u; + return fns_->wrapperDispatchFramegen(static_cast(wrapperContext_), &wrapperDesc) == 0; + } + + if (!contextStorage_ || !fns_->fsr3DispatchFrameGeneration) return false; FfxResourceDescription presentDesc = makeResourceDescription( desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_RESOURCE_USAGE_READ_ONLY); @@ -424,6 +566,10 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d void AmdFsr3Runtime::shutdown() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN + if (wrapperContext_ && fns_ && fns_->wrapperShutdown) { + fns_->wrapperShutdown(static_cast(wrapperContext_)); + } + wrapperContext_ = nullptr; if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) { fns_->fsr3ContextDestroy(reinterpret_cast(contextStorage_)); } @@ -451,6 +597,7 @@ void AmdFsr3Runtime::shutdown() { libHandle_ = nullptr; loadedLibraryPath_.clear(); loadPathKind_ = LoadPathKind::None; + backend_ = RuntimeBackend::None; } } // namespace wowee::rendering