From 538a1db8667761341488a81b5b4b4f10980eb575 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 8 Mar 2026 23:13:08 -0700 Subject: [PATCH] Fix FSR3 runtime wrapper for local SDK API and real Vulkan resource dispatch --- CMakeLists.txt | 1 + include/rendering/amd_fsr3_runtime.hpp | 72 ++++++ include/rendering/renderer.hpp | 2 + src/rendering/amd_fsr3_runtime.cpp | 337 +++++++++++++++++++++++++ src/rendering/renderer.cpp | 164 ++++++------ 5 files changed, 486 insertions(+), 90 deletions(-) create mode 100644 include/rendering/amd_fsr3_runtime.hpp create mode 100644 src/rendering/amd_fsr3_runtime.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 57f33580..9e0ba658 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -397,6 +397,7 @@ set(WOWEE_SOURCES # Rendering src/rendering/renderer.cpp + src/rendering/amd_fsr3_runtime.cpp src/rendering/shader.cpp src/rendering/texture.cpp src/rendering/mesh.cpp diff --git a/include/rendering/amd_fsr3_runtime.hpp b/include/rendering/amd_fsr3_runtime.hpp new file mode 100644 index 00000000..2515601e --- /dev/null +++ b/include/rendering/amd_fsr3_runtime.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +namespace wowee::rendering { + +struct AmdFsr3RuntimeInitDesc { + VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; + VkDevice device = VK_NULL_HANDLE; + PFN_vkGetDeviceProcAddr getDeviceProcAddr = nullptr; + uint32_t maxRenderWidth = 0; + uint32_t maxRenderHeight = 0; + uint32_t displayWidth = 0; + uint32_t displayHeight = 0; + VkFormat colorFormat = VK_FORMAT_UNDEFINED; + bool hdrInput = false; + bool depthInverted = false; +}; + +struct AmdFsr3RuntimeDispatchDesc { + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; + VkImage colorImage = VK_NULL_HANDLE; + VkImage depthImage = VK_NULL_HANDLE; + VkImage motionVectorImage = VK_NULL_HANDLE; + VkImage outputImage = VK_NULL_HANDLE; + uint32_t renderWidth = 0; + uint32_t renderHeight = 0; + uint32_t outputWidth = 0; + uint32_t outputHeight = 0; + VkFormat colorFormat = VK_FORMAT_UNDEFINED; + VkFormat depthFormat = VK_FORMAT_UNDEFINED; + VkFormat motionVectorFormat = VK_FORMAT_R16G16_SFLOAT; + VkFormat outputFormat = VK_FORMAT_UNDEFINED; + float jitterX = 0.0f; + float jitterY = 0.0f; + float motionScaleX = 1.0f; + float motionScaleY = 1.0f; + float frameTimeDeltaMs = 16.67f; + float cameraNear = 0.1f; + float cameraFar = 1000.0f; + float cameraFovYRadians = 1.0f; + bool reset = false; +}; + +class AmdFsr3Runtime { +public: + AmdFsr3Runtime(); + ~AmdFsr3Runtime(); + + bool initialize(const AmdFsr3RuntimeInitDesc& desc); + bool dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc); + void shutdown(); + + bool isReady() const { return ready_; } + const std::string& loadedLibraryPath() const { return loadedLibraryPath_; } + +private: + void* libHandle_ = nullptr; + std::string loadedLibraryPath_; + void* scratchBuffer_ = nullptr; + size_t scratchBufferSize_ = 0; + bool ready_ = false; + + struct RuntimeFns; + RuntimeFns* fns_ = nullptr; + void* contextStorage_ = nullptr; +}; + +} // namespace wowee::rendering + diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 4b764baf..55e944e8 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -51,6 +51,7 @@ class WorldMap; class QuestMarkerRenderer; class CharacterPreview; class Shader; +class AmdFsr3Runtime; class Renderer { public: @@ -450,6 +451,7 @@ private: void* amdScratchBuffer = nullptr; size_t amdScratchBufferSize = 0; #endif + std::unique_ptr amdFsr3Runtime; // Convergent accumulation: jitter for N frames then freeze int convergenceFrame = 0; diff --git a/src/rendering/amd_fsr3_runtime.cpp b/src/rendering/amd_fsr3_runtime.cpp new file mode 100644 index 00000000..160fcb30 --- /dev/null +++ b/src/rendering/amd_fsr3_runtime.cpp @@ -0,0 +1,337 @@ +#include "rendering/amd_fsr3_runtime.hpp" + +#include +#include +#include +#include + +#include "core/logger.hpp" + +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN +#include +#include +#if defined(_WIN32) +#include +#else +#include +#endif +#endif + +namespace wowee::rendering { + +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN +struct AmdFsr3Runtime::RuntimeFns { + decltype(&ffxGetScratchMemorySizeVK) getScratchMemorySizeVK = nullptr; + decltype(&ffxGetDeviceVK) getDeviceVK = nullptr; + decltype(&ffxGetInterfaceVK) getInterfaceVK = nullptr; + decltype(&ffxGetCommandListVK) getCommandListVK = nullptr; + decltype(&ffxGetResourceVK) getResourceVK = nullptr; + decltype(&ffxFsr3ContextCreate) fsr3ContextCreate = nullptr; + decltype(&ffxFsr3ContextDispatchUpscale) fsr3ContextDispatchUpscale = nullptr; + decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr; +}; +#else +struct AmdFsr3Runtime::RuntimeFns {}; +#endif + +AmdFsr3Runtime::AmdFsr3Runtime() = default; + +AmdFsr3Runtime::~AmdFsr3Runtime() { + shutdown(); +} + +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN +namespace { +FfxSurfaceFormat mapVkFormatToFfxSurfaceFormat(VkFormat format, bool isDepth) { + if (isDepth) { + switch (format) { + case VK_FORMAT_D32_SFLOAT: + return FFX_SURFACE_FORMAT_R32_FLOAT; + case VK_FORMAT_D16_UNORM: + return FFX_SURFACE_FORMAT_R16_UNORM; + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + return FFX_SURFACE_FORMAT_R32_FLOAT; + default: + return FFX_SURFACE_FORMAT_R32_FLOAT; + } + } + + switch (format) { + case VK_FORMAT_R16G16B16A16_SFLOAT: + return FFX_SURFACE_FORMAT_R16G16B16A16_FLOAT; + case VK_FORMAT_R8G8B8A8_UNORM: + case VK_FORMAT_B8G8R8A8_UNORM: + return FFX_SURFACE_FORMAT_R8G8B8A8_UNORM; + case VK_FORMAT_R8G8B8A8_SRGB: + case VK_FORMAT_B8G8R8A8_SRGB: + return FFX_SURFACE_FORMAT_R8G8B8A8_SRGB; + case VK_FORMAT_A2R10G10B10_UNORM_PACK32: + case VK_FORMAT_A2B10G10R10_UNORM_PACK32: + return FFX_SURFACE_FORMAT_R10G10B10A2_UNORM; + case VK_FORMAT_B10G11R11_UFLOAT_PACK32: + return FFX_SURFACE_FORMAT_R11G11B10_FLOAT; + case VK_FORMAT_R16G16_SFLOAT: + return FFX_SURFACE_FORMAT_R16G16_FLOAT; + case VK_FORMAT_R16G16_UINT: + return FFX_SURFACE_FORMAT_R16G16_UINT; + case VK_FORMAT_R16_SFLOAT: + return FFX_SURFACE_FORMAT_R16_FLOAT; + case VK_FORMAT_R16_UINT: + return FFX_SURFACE_FORMAT_R16_UINT; + case VK_FORMAT_R16_UNORM: + return FFX_SURFACE_FORMAT_R16_UNORM; + case VK_FORMAT_R16_SNORM: + return FFX_SURFACE_FORMAT_R16_SNORM; + case VK_FORMAT_R8_UNORM: + return FFX_SURFACE_FORMAT_R8_UNORM; + case VK_FORMAT_R8_UINT: + return FFX_SURFACE_FORMAT_R8_UINT; + case VK_FORMAT_R8G8_UNORM: + return FFX_SURFACE_FORMAT_R8G8_UNORM; + case VK_FORMAT_R32_SFLOAT: + return FFX_SURFACE_FORMAT_R32_FLOAT; + case VK_FORMAT_R32_UINT: + return FFX_SURFACE_FORMAT_R32_UINT; + default: + return FFX_SURFACE_FORMAT_UNKNOWN; + } +} + +FfxResourceDescription makeResourceDescription(VkFormat format, + uint32_t width, + uint32_t height, + FfxResourceUsage usage, + bool isDepth = false) { + FfxResourceDescription description{}; + description.type = FFX_RESOURCE_TYPE_TEXTURE2D; + description.format = mapVkFormatToFfxSurfaceFormat(format, isDepth); + description.width = width; + description.height = height; + description.depth = 1; + description.mipCount = 1; + description.flags = FFX_RESOURCE_FLAGS_NONE; + description.usage = usage; + return description; +} +} // namespace +#endif + +bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { + shutdown(); + +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)desc; + return false; +#else + if (!desc.physicalDevice || !desc.device || !desc.getDeviceProcAddr || + desc.maxRenderWidth == 0 || desc.maxRenderHeight == 0 || + desc.displayWidth == 0 || desc.displayHeight == 0 || + desc.colorFormat == VK_FORMAT_UNDEFINED) { + LOG_WARNING("FSR3 runtime: invalid initialization descriptors."); + return false; + } + + std::vector candidates; + if (const char* envPath = std::getenv("WOWEE_FFX_SDK_RUNTIME_LIB")) { + if (*envPath) candidates.emplace_back(envPath); + } +#if defined(_WIN32) + candidates.emplace_back("ffx_fsr3_vk.dll"); + candidates.emplace_back("ffx_fsr3.dll"); +#elif defined(__APPLE__) + candidates.emplace_back("libffx_fsr3_vk.dylib"); + candidates.emplace_back("libffx_fsr3.dylib"); +#else + candidates.emplace_back("libffx_fsr3_vk.so"); + candidates.emplace_back("libffx_fsr3.so"); +#endif + + for (const std::string& candidate : candidates) { +#if defined(_WIN32) + HMODULE h = LoadLibraryA(candidate.c_str()); + if (!h) continue; + libHandle_ = reinterpret_cast(h); +#else + void* h = dlopen(candidate.c_str(), RTLD_NOW | RTLD_LOCAL); + if (!h) continue; + libHandle_ = h; +#endif + loadedLibraryPath_ = candidate; + break; + } + if (!libHandle_) return false; + + auto resolveSym = [&](const char* name) -> void* { +#if defined(_WIN32) + return reinterpret_cast(GetProcAddress(reinterpret_cast(libHandle_), name)); +#else + return dlsym(libHandle_, name); +#endif + }; + + fns_ = new RuntimeFns{}; + fns_->getScratchMemorySizeVK = reinterpret_castgetScratchMemorySizeVK)>(resolveSym("ffxGetScratchMemorySizeVK")); + fns_->getDeviceVK = reinterpret_castgetDeviceVK)>(resolveSym("ffxGetDeviceVK")); + fns_->getInterfaceVK = reinterpret_castgetInterfaceVK)>(resolveSym("ffxGetInterfaceVK")); + fns_->getCommandListVK = reinterpret_castgetCommandListVK)>(resolveSym("ffxGetCommandListVK")); + fns_->getResourceVK = reinterpret_castgetResourceVK)>(resolveSym("ffxGetResourceVK")); + fns_->fsr3ContextCreate = reinterpret_castfsr3ContextCreate)>(resolveSym("ffxFsr3ContextCreate")); + fns_->fsr3ContextDispatchUpscale = reinterpret_castfsr3ContextDispatchUpscale)>(resolveSym("ffxFsr3ContextDispatchUpscale")); + fns_->fsr3ContextDestroy = reinterpret_castfsr3ContextDestroy)>(resolveSym("ffxFsr3ContextDestroy")); + + if (!fns_->getScratchMemorySizeVK || !fns_->getDeviceVK || !fns_->getInterfaceVK || + !fns_->getCommandListVK || !fns_->getResourceVK || !fns_->fsr3ContextCreate || !fns_->fsr3ContextDispatchUpscale || + !fns_->fsr3ContextDestroy) { + LOG_WARNING("FSR3 runtime: required symbols not found in ", loadedLibraryPath_); + shutdown(); + return false; + } + + scratchBufferSize_ = fns_->getScratchMemorySizeVK(desc.physicalDevice, FFX_FSR3_CONTEXT_COUNT); + if (scratchBufferSize_ == 0) { + LOG_WARNING("FSR3 runtime: scratch buffer size query returned 0."); + shutdown(); + return false; + } + scratchBuffer_ = std::malloc(scratchBufferSize_); + if (!scratchBuffer_) { + LOG_WARNING("FSR3 runtime: failed to allocate scratch buffer."); + shutdown(); + return false; + } + + VkDeviceContext vkDevCtx{}; + vkDevCtx.vkDevice = desc.device; + vkDevCtx.vkPhysicalDevice = desc.physicalDevice; + vkDevCtx.vkDeviceProcAddr = desc.getDeviceProcAddr; + + FfxDevice ffxDevice = fns_->getDeviceVK(&vkDevCtx); + FfxInterface backendShared{}; + FfxErrorCode ifaceErr = fns_->getInterfaceVK(&backendShared, ffxDevice, scratchBuffer_, scratchBufferSize_, FFX_FSR3_CONTEXT_COUNT); + if (ifaceErr != FFX_OK) { + LOG_WARNING("FSR3 runtime: ffxGetInterfaceVK failed (", static_cast(ifaceErr), ")."); + shutdown(); + return false; + } + + FfxFsr3ContextDescription ctxDesc{}; + ctxDesc.flags = FFX_FSR3_ENABLE_AUTO_EXPOSURE | + FFX_FSR3_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION | + FFX_FSR3_ENABLE_UPSCALING_ONLY; + if (desc.hdrInput) ctxDesc.flags |= FFX_FSR3_ENABLE_HIGH_DYNAMIC_RANGE; + if (desc.depthInverted) ctxDesc.flags |= FFX_FSR3_ENABLE_DEPTH_INVERTED; + ctxDesc.maxRenderSize.width = desc.maxRenderWidth; + ctxDesc.maxRenderSize.height = desc.maxRenderHeight; + ctxDesc.upscaleOutputSize.width = desc.displayWidth; + ctxDesc.upscaleOutputSize.height = desc.displayHeight; + ctxDesc.displaySize.width = desc.displayWidth; + ctxDesc.displaySize.height = desc.displayHeight; + ctxDesc.backendInterfaceSharedResources = backendShared; + ctxDesc.backendInterfaceUpscaling = backendShared; + ctxDesc.backendInterfaceFrameInterpolation = backendShared; + ctxDesc.fpMessage = nullptr; + ctxDesc.backBufferFormat = mapVkFormatToFfxSurfaceFormat(desc.colorFormat, false); + + contextStorage_ = std::malloc(sizeof(FfxFsr3Context)); + if (!contextStorage_) { + LOG_WARNING("FSR3 runtime: failed to allocate context storage."); + shutdown(); + return false; + } + std::memset(contextStorage_, 0, sizeof(FfxFsr3Context)); + + FfxErrorCode createErr = fns_->fsr3ContextCreate(reinterpret_cast(contextStorage_), &ctxDesc); + if (createErr != FFX_OK) { + LOG_WARNING("FSR3 runtime: ffxFsr3ContextCreate failed (", static_cast(createErr), ")."); + shutdown(); + return false; + } + + ready_ = true; + return true; +#endif +} + +bool AmdFsr3Runtime::dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc) { +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)desc; + return false; +#else + if (!ready_ || !contextStorage_ || !fns_ || !fns_->fsr3ContextDispatchUpscale) return false; + if (!desc.commandBuffer || !desc.colorImage || !desc.depthImage || !desc.motionVectorImage || !desc.outputImage) return false; + + FfxResourceDescription colorDesc = makeResourceDescription( + desc.colorFormat, desc.renderWidth, desc.renderHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription depthDesc = makeResourceDescription( + desc.depthFormat, desc.renderWidth, desc.renderHeight, FFX_RESOURCE_USAGE_DEPTHTARGET, true); + FfxResourceDescription mvDesc = makeResourceDescription( + desc.motionVectorFormat, desc.renderWidth, desc.renderHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription outDesc = makeResourceDescription( + desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_RESOURCE_USAGE_UAV); + + FfxFsr3DispatchUpscaleDescription dispatch{}; + dispatch.commandList = fns_->getCommandListVK(desc.commandBuffer); + static wchar_t kColorName[] = L"FSR3_Color"; + static wchar_t kDepthName[] = L"FSR3_Depth"; + static wchar_t kMotionName[] = L"FSR3_MotionVectors"; + static wchar_t kOutputName[] = L"FSR3_Output"; + dispatch.color = fns_->getResourceVK(reinterpret_cast(desc.colorImage), colorDesc, kColorName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.depth = fns_->getResourceVK(reinterpret_cast(desc.depthImage), depthDesc, kDepthName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.motionVectors = fns_->getResourceVK(reinterpret_cast(desc.motionVectorImage), mvDesc, kMotionName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.exposure = FfxResource{}; + dispatch.reactive = FfxResource{}; + dispatch.transparencyAndComposition = FfxResource{}; + dispatch.upscaleOutput = fns_->getResourceVK(reinterpret_cast(desc.outputImage), outDesc, kOutputName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); + dispatch.jitterOffset.x = desc.jitterX; + dispatch.jitterOffset.y = desc.jitterY; + dispatch.motionVectorScale.x = desc.motionScaleX; + dispatch.motionVectorScale.y = desc.motionScaleY; + dispatch.renderSize.width = desc.renderWidth; + dispatch.renderSize.height = desc.renderHeight; + dispatch.enableSharpening = false; + dispatch.sharpness = 0.0f; + dispatch.frameTimeDelta = std::max(0.001f, desc.frameTimeDeltaMs); + dispatch.preExposure = 1.0f; + dispatch.reset = desc.reset; + dispatch.cameraNear = desc.cameraNear; + dispatch.cameraFar = desc.cameraFar; + dispatch.cameraFovAngleVertical = desc.cameraFovYRadians; + dispatch.viewSpaceToMetersFactor = 1.0f; + + FfxErrorCode err = fns_->fsr3ContextDispatchUpscale( + reinterpret_cast(contextStorage_), &dispatch); + return err == FFX_OK; +#endif +} + +void AmdFsr3Runtime::shutdown() { +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN + if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) { + fns_->fsr3ContextDestroy(reinterpret_cast(contextStorage_)); + } +#endif + if (contextStorage_) { + std::free(contextStorage_); + contextStorage_ = nullptr; + } + if (scratchBuffer_) { + std::free(scratchBuffer_); + scratchBuffer_ = nullptr; + } + scratchBufferSize_ = 0; + ready_ = false; + if (fns_) { + delete fns_; + fns_ = nullptr; + } +#if defined(_WIN32) + if (libHandle_) FreeLibrary(reinterpret_cast(libHandle_)); +#else + if (libHandle_) dlclose(libHandle_); +#endif + libHandle_ = nullptr; + loadedLibraryPath_.clear(); +} + +} // namespace wowee::rendering diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 1db52d51..290b51dd 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -57,6 +57,7 @@ #include "rendering/vk_shader.hpp" #include "rendering/vk_pipeline.hpp" #include "rendering/vk_utils.hpp" +#include "rendering/amd_fsr3_runtime.hpp" #include #include #include @@ -71,13 +72,6 @@ #include #include #include -#if WOWEE_HAS_AMD_FSR3_FRAMEGEN -#if defined(_WIN32) -#include -#else -#include -#endif -#endif namespace wowee { namespace rendering { @@ -116,63 +110,6 @@ static int envIntOrDefault(const char* key, int defaultValue) { return static_cast(n); } -#if WOWEE_HAS_AMD_FSR3_FRAMEGEN -struct AmdFsr3RuntimeApi { - void* libHandle = nullptr; - std::string loadedPath; - - bool load() { - if (libHandle) return true; - - std::vector candidates; - if (const char* envPath = std::getenv("WOWEE_FFX_SDK_RUNTIME_LIB")) { - if (*envPath) candidates.emplace_back(envPath); - } -#if defined(_WIN32) - candidates.emplace_back("ffx_fsr3_vk.dll"); - candidates.emplace_back("ffx_fsr3.dll"); -#elif defined(__APPLE__) - candidates.emplace_back("libffx_fsr3_vk.dylib"); - candidates.emplace_back("libffx_fsr3.dylib"); -#else - candidates.emplace_back("libffx_fsr3_vk.so"); - candidates.emplace_back("libffx_fsr3.so"); -#endif - - for (const std::string& path : candidates) { -#if defined(_WIN32) - HMODULE h = LoadLibraryA(path.c_str()); - if (!h) continue; - libHandle = reinterpret_cast(h); -#else - void* h = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); - if (!h) continue; - libHandle = h; -#endif - loadedPath = path; - return true; - } - return false; - } - - void unload() { - if (!libHandle) return; -#if defined(_WIN32) - FreeLibrary(reinterpret_cast(libHandle)); -#else - dlclose(libHandle); -#endif - libHandle = nullptr; - loadedPath.clear(); - } -}; - -static AmdFsr3RuntimeApi& getAmdFsr3RuntimeApi() { - static AmdFsr3RuntimeApi s_runtimeApi; - return s_runtimeApi; -} -#endif - static std::vector parseEmoteCommands(const std::string& raw) { std::vector out; std::string cur; @@ -1223,8 +1160,15 @@ void Renderer::endFrame() { if (fsr2_.useAmdBackend) { // Compute passes: motion vectors -> temporal accumulation dispatchMotionVectors(); - dispatchAmdFsr2(); - dispatchAmdFsr3Framegen(); + if (fsr2_.amdFsr3FramegenEnabled && fsr2_.amdFsr3FramegenRuntimeReady) { + dispatchAmdFsr3Framegen(); + if (!fsr2_.amdFsr3FramegenRuntimeActive) { + dispatchAmdFsr2(); + } + } else { + dispatchAmdFsr2(); + dispatchAmdFsr3Framegen(); + } // Transition history output: GENERAL -> SHADER_READ_ONLY for sharpen pass transitionImageLayout(currentCmd, fsr2_.history[fsr2_.currentHistory].image, @@ -3908,10 +3852,22 @@ bool Renderer::initFSR2Resources() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (fsr2_.amdFsr3FramegenEnabled) { fsr2_.amdFsr3FramegenRuntimeActive = false; - fsr2_.amdFsr3FramegenRuntimeReady = getAmdFsr3RuntimeApi().load(); + if (!fsr2_.amdFsr3Runtime) fsr2_.amdFsr3Runtime = std::make_unique(); + AmdFsr3RuntimeInitDesc fgInit{}; + fgInit.physicalDevice = vkCtx->getPhysicalDevice(); + fgInit.device = vkCtx->getDevice(); + fgInit.getDeviceProcAddr = vkGetDeviceProcAddr; + fgInit.maxRenderWidth = fsr2_.internalWidth; + fgInit.maxRenderHeight = fsr2_.internalHeight; + fgInit.displayWidth = swapExtent.width; + fgInit.displayHeight = swapExtent.height; + fgInit.colorFormat = vkCtx->getSwapchainFormat(); + fgInit.hdrInput = false; + fgInit.depthInverted = false; + fsr2_.amdFsr3FramegenRuntimeReady = fsr2_.amdFsr3Runtime->initialize(fgInit); if (fsr2_.amdFsr3FramegenRuntimeReady) { - LOG_INFO("FSR3 framegen runtime library loaded from ", getAmdFsr3RuntimeApi().loadedPath, - " (dispatch staged)"); + LOG_INFO("FSR3 framegen runtime library loaded from ", fsr2_.amdFsr3Runtime->loadedLibraryPath(), + " (upscale dispatch enabled)"); } else { LOG_WARNING("FSR3 framegen toggle is enabled, but runtime library was not found. ", "Set WOWEE_FFX_SDK_RUNTIME_LIB to the SDK runtime binary path."); @@ -4218,7 +4174,10 @@ void Renderer::destroyFSR2Resources() { fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; #if WOWEE_HAS_AMD_FSR3_FRAMEGEN - getAmdFsr3RuntimeApi().unload(); + if (fsr2_.amdFsr3Runtime) { + fsr2_.amdFsr3Runtime->shutdown(); + fsr2_.amdFsr3Runtime.reset(); + } #endif if (fsr2_.sharpenPipeline) { vkDestroyPipeline(device, fsr2_.sharpenPipeline, nullptr); fsr2_.sharpenPipeline = VK_NULL_HANDLE; } @@ -4444,20 +4403,47 @@ void Renderer::dispatchAmdFsr3Framegen() { return; } #if WOWEE_HAS_AMD_FSR3_FRAMEGEN - // Runtime FI/OF dispatch requires linked FidelityFX-SDK implementation binaries. - // The integration hook is intentionally placed here (right after FSR2 dispatch), - // so we can enable real frame generation without refactoring the frame pipeline. - if (!fsr2_.amdFsr3FramegenRuntimeReady) { - fsr2_.amdFsr3FramegenRuntimeReady = getAmdFsr3RuntimeApi().load(); + if (!fsr2_.amdFsr3Runtime || !fsr2_.amdFsr3FramegenRuntimeReady) { + fsr2_.amdFsr3FramegenRuntimeActive = false; + return; } - if (!fsr2_.amdFsr3FramegenRuntimeReady) { - static bool warnedMissingRuntime = false; - if (!warnedMissingRuntime) { - warnedMissingRuntime = true; - LOG_WARNING("FSR3 framegen runtime library not found; skipping frame generation dispatch."); + + AmdFsr3RuntimeDispatchDesc fgDispatch{}; + fgDispatch.commandBuffer = currentCmd; + fgDispatch.colorImage = fsr2_.sceneColor.image; + fgDispatch.depthImage = fsr2_.sceneDepth.image; + fgDispatch.motionVectorImage = fsr2_.motionVectors.image; + fgDispatch.outputImage = fsr2_.history[fsr2_.currentHistory].image; + fgDispatch.renderWidth = fsr2_.internalWidth; + fgDispatch.renderHeight = fsr2_.internalHeight; + fgDispatch.outputWidth = vkCtx->getSwapchainExtent().width; + fgDispatch.outputHeight = vkCtx->getSwapchainExtent().height; + fgDispatch.colorFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + fgDispatch.depthFormat = vkCtx->getDepthFormat(); + fgDispatch.motionVectorFormat = VK_FORMAT_R16G16_SFLOAT; + fgDispatch.outputFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + glm::vec2 jitterNdc = camera ? camera->getJitter() : glm::vec2(0.0f); + fgDispatch.jitterX = jitterNdc.x * 0.5f * static_cast(fsr2_.internalWidth); + fgDispatch.jitterY = jitterNdc.y * 0.5f * static_cast(fsr2_.internalHeight); + fgDispatch.motionScaleX = static_cast(fsr2_.internalWidth) * fsr2_.motionVecScaleX; + fgDispatch.motionScaleY = static_cast(fsr2_.internalHeight) * fsr2_.motionVecScaleY; + fgDispatch.frameTimeDeltaMs = glm::max(0.001f, lastDeltaTime_ * 1000.0f); + fgDispatch.cameraNear = camera ? camera->getNearPlane() : 0.1f; + fgDispatch.cameraFar = camera ? camera->getFarPlane() : 1000.0f; + fgDispatch.cameraFovYRadians = camera ? glm::radians(camera->getFovDegrees()) : 1.0f; + fgDispatch.reset = fsr2_.needsHistoryReset; + + bool ok = fsr2_.amdFsr3Runtime->dispatchUpscale(fgDispatch); + if (!ok) { + static bool warnedRuntimeDispatch = false; + if (!warnedRuntimeDispatch) { + warnedRuntimeDispatch = true; + LOG_WARNING("FSR3 runtime dispatch failed; falling back to FSR2 dispatch output."); } + fsr2_.amdFsr3FramegenRuntimeActive = false; + return; } - fsr2_.amdFsr3FramegenRuntimeActive = false; + fsr2_.amdFsr3FramegenRuntimeActive = true; #else fsr2_.amdFsr3FramegenRuntimeActive = false; #endif @@ -4543,18 +4529,16 @@ void Renderer::setAmdFsr3FramegenEnabled(bool enabled) { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (enabled) { fsr2_.amdFsr3FramegenRuntimeActive = false; - fsr2_.amdFsr3FramegenRuntimeReady = getAmdFsr3RuntimeApi().load(); - if (fsr2_.amdFsr3FramegenRuntimeReady) { - LOG_INFO("FSR3 framegen runtime library loaded from ", getAmdFsr3RuntimeApi().loadedPath, - " (dispatch staged)."); - } else { - LOG_WARNING("FSR3 framegen enabled, but runtime library not found. ", - "Set WOWEE_FFX_SDK_RUNTIME_LIB to the runtime binary path."); - } + fsr2_.needsRecreate = true; + fsr2_.amdFsr3FramegenRuntimeReady = false; + LOG_INFO("FSR3 framegen requested; runtime will initialize on next FSR2 resource creation."); } else { fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; - getAmdFsr3RuntimeApi().unload(); + if (fsr2_.amdFsr3Runtime) { + fsr2_.amdFsr3Runtime->shutdown(); + fsr2_.amdFsr3Runtime.reset(); + } } #else fsr2_.amdFsr3FramegenRuntimeActive = false;