From aa43aa6fc82e1b4e3bff79691fe4ac13ec5cea09 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sun, 8 Mar 2026 23:20:50 -0700 Subject: [PATCH] Bridge FSR3 Vulkan framegen dispatch and route sharpen to interpolated output --- include/rendering/amd_fsr3_runtime.hpp | 6 +- include/rendering/renderer.hpp | 2 + src/rendering/amd_fsr3_runtime.cpp | 72 ++++++++++++++++++- src/rendering/renderer.cpp | 96 +++++++++++++++++++++----- 4 files changed, 154 insertions(+), 22 deletions(-) diff --git a/include/rendering/amd_fsr3_runtime.hpp b/include/rendering/amd_fsr3_runtime.hpp index 2515601e..3b465f07 100644 --- a/include/rendering/amd_fsr3_runtime.hpp +++ b/include/rendering/amd_fsr3_runtime.hpp @@ -17,6 +17,7 @@ struct AmdFsr3RuntimeInitDesc { VkFormat colorFormat = VK_FORMAT_UNDEFINED; bool hdrInput = false; bool depthInverted = false; + bool enableFrameGeneration = false; }; struct AmdFsr3RuntimeDispatchDesc { @@ -25,6 +26,7 @@ struct AmdFsr3RuntimeDispatchDesc { VkImage depthImage = VK_NULL_HANDLE; VkImage motionVectorImage = VK_NULL_HANDLE; VkImage outputImage = VK_NULL_HANDLE; + VkImage frameGenOutputImage = VK_NULL_HANDLE; uint32_t renderWidth = 0; uint32_t renderHeight = 0; uint32_t outputWidth = 0; @@ -51,9 +53,11 @@ public: bool initialize(const AmdFsr3RuntimeInitDesc& desc); bool dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc); + bool dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& desc); void shutdown(); bool isReady() const { return ready_; } + bool isFrameGenerationReady() const { return frameGenerationReady_; } const std::string& loadedLibraryPath() const { return loadedLibraryPath_; } private: @@ -62,6 +66,7 @@ private: void* scratchBuffer_ = nullptr; size_t scratchBufferSize_ = 0; bool ready_ = false; + bool frameGenerationReady_ = false; struct RuntimeFns; RuntimeFns* fns_ = nullptr; @@ -69,4 +74,3 @@ private: }; } // namespace wowee::rendering - diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 55e944e8..e299bd6b 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -411,6 +411,8 @@ private: // History buffers (display resolution, ping-pong) AllocatedImage history[2]{}; + AllocatedImage framegenOutput{}; + bool framegenOutputValid = false; uint32_t currentHistory = 0; // Output index (0 or 1) // Compute pipelines diff --git a/src/rendering/amd_fsr3_runtime.cpp b/src/rendering/amd_fsr3_runtime.cpp index 160fcb30..e2b10bdc 100644 --- a/src/rendering/amd_fsr3_runtime.cpp +++ b/src/rendering/amd_fsr3_runtime.cpp @@ -28,6 +28,8 @@ struct AmdFsr3Runtime::RuntimeFns { decltype(&ffxGetResourceVK) getResourceVK = nullptr; decltype(&ffxFsr3ContextCreate) fsr3ContextCreate = nullptr; decltype(&ffxFsr3ContextDispatchUpscale) fsr3ContextDispatchUpscale = nullptr; + decltype(&ffxFsr3ConfigureFrameGeneration) fsr3ConfigureFrameGeneration = nullptr; + decltype(&ffxFsr3DispatchFrameGeneration) fsr3DispatchFrameGeneration = nullptr; decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr; }; #else @@ -42,6 +44,10 @@ AmdFsr3Runtime::~AmdFsr3Runtime() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN namespace { +FfxErrorCode vkSwapchainConfigureNoop(const FfxFrameGenerationConfig*) { + return FFX_OK; +} + FfxSurfaceFormat mapVkFormatToFfxSurfaceFormat(VkFormat format, bool isDepth) { if (isDepth) { switch (format) { @@ -178,6 +184,8 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { fns_->getResourceVK = reinterpret_castgetResourceVK)>(resolveSym("ffxGetResourceVK")); fns_->fsr3ContextCreate = reinterpret_castfsr3ContextCreate)>(resolveSym("ffxFsr3ContextCreate")); fns_->fsr3ContextDispatchUpscale = reinterpret_castfsr3ContextDispatchUpscale)>(resolveSym("ffxFsr3ContextDispatchUpscale")); + fns_->fsr3ConfigureFrameGeneration = reinterpret_castfsr3ConfigureFrameGeneration)>(resolveSym("ffxFsr3ConfigureFrameGeneration")); + fns_->fsr3DispatchFrameGeneration = reinterpret_castfsr3DispatchFrameGeneration)>(resolveSym("ffxFsr3DispatchFrameGeneration")); fns_->fsr3ContextDestroy = reinterpret_castfsr3ContextDestroy)>(resolveSym("ffxFsr3ContextDestroy")); if (!fns_->getScratchMemorySizeVK || !fns_->getDeviceVK || !fns_->getInterfaceVK || @@ -217,8 +225,10 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { FfxFsr3ContextDescription ctxDesc{}; ctxDesc.flags = FFX_FSR3_ENABLE_AUTO_EXPOSURE | - FFX_FSR3_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION | - FFX_FSR3_ENABLE_UPSCALING_ONLY; + FFX_FSR3_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; + if (!desc.enableFrameGeneration) { + ctxDesc.flags |= 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; @@ -227,6 +237,9 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { ctxDesc.upscaleOutputSize.height = desc.displayHeight; ctxDesc.displaySize.width = desc.displayWidth; ctxDesc.displaySize.height = desc.displayHeight; + if (!backendShared.fpSwapChainConfigureFrameGeneration) { + backendShared.fpSwapChainConfigureFrameGeneration = vkSwapchainConfigureNoop; + } ctxDesc.backendInterfaceSharedResources = backendShared; ctxDesc.backendInterfaceUpscaling = backendShared; ctxDesc.backendInterfaceFrameInterpolation = backendShared; @@ -248,6 +261,27 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { return false; } + if (desc.enableFrameGeneration) { + if (!fns_->fsr3ConfigureFrameGeneration || !fns_->fsr3DispatchFrameGeneration) { + LOG_WARNING("FSR3 runtime: frame generation symbols unavailable in ", loadedLibraryPath_); + shutdown(); + return false; + } + FfxFrameGenerationConfig fgCfg{}; + fgCfg.frameGenerationEnabled = true; + fgCfg.allowAsyncWorkloads = false; + fgCfg.flags = 0; + fgCfg.onlyPresentInterpolated = false; + FfxErrorCode cfgErr = fns_->fsr3ConfigureFrameGeneration( + reinterpret_cast(contextStorage_), &fgCfg); + if (cfgErr != FFX_OK) { + LOG_WARNING("FSR3 runtime: ffxFsr3ConfigureFrameGeneration failed (", static_cast(cfgErr), ")."); + shutdown(); + return false; + } + frameGenerationReady_ = true; + } + ready_ = true; return true; #endif @@ -305,6 +339,39 @@ bool AmdFsr3Runtime::dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc) { #endif } +bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& desc) { +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)desc; + return false; +#else + if (!ready_ || !frameGenerationReady_ || !contextStorage_ || !fns_ || !fns_->fsr3DispatchFrameGeneration) return false; + if (!desc.commandBuffer || !desc.outputImage || !desc.frameGenOutputImage || + desc.outputWidth == 0 || desc.outputHeight == 0 || desc.outputFormat == VK_FORMAT_UNDEFINED) return false; + + FfxResourceDescription presentDesc = makeResourceDescription( + desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription fgOutDesc = makeResourceDescription( + desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_RESOURCE_USAGE_UAV); + + static wchar_t kPresentName[] = L"FSR3_PresentColor"; + static wchar_t kInterpolatedName[] = L"FSR3_InterpolatedOutput"; + FfxFrameGenerationDispatchDescription fgDispatch{}; + fgDispatch.commandList = fns_->getCommandListVK(desc.commandBuffer); + fgDispatch.presentColor = fns_->getResourceVK( + reinterpret_cast(desc.outputImage), presentDesc, kPresentName, FFX_RESOURCE_STATE_COMPUTE_READ); + fgDispatch.outputs[0] = fns_->getResourceVK( + reinterpret_cast(desc.frameGenOutputImage), fgOutDesc, kInterpolatedName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); + fgDispatch.numInterpolatedFrames = 1; + fgDispatch.reset = desc.reset; + fgDispatch.backBufferTransferFunction = FFX_BACKBUFFER_TRANSFER_FUNCTION_SRGB; + fgDispatch.minMaxLuminance[0] = 0.0f; + fgDispatch.minMaxLuminance[1] = 1.0f; + + FfxErrorCode err = fns_->fsr3DispatchFrameGeneration(&fgDispatch); + return err == FFX_OK; +#endif +} + void AmdFsr3Runtime::shutdown() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) { @@ -321,6 +388,7 @@ void AmdFsr3Runtime::shutdown() { } scratchBufferSize_ = 0; ready_ = false; + frameGenerationReady_ = false; if (fns_) { delete fns_; fns_ = nullptr; diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 290b51dd..a3b160b6 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -1167,14 +1167,21 @@ void Renderer::endFrame() { } } else { dispatchAmdFsr2(); - dispatchAmdFsr3Framegen(); } - // Transition history output: GENERAL -> SHADER_READ_ONLY for sharpen pass - transitionImageLayout(currentCmd, fsr2_.history[fsr2_.currentHistory].image, - VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + // Transition post-FSR input for sharpen pass. + if (fsr2_.amdFsr3FramegenRuntimeActive && fsr2_.framegenOutput.image) { + transitionImageLayout(currentCmd, fsr2_.framegenOutput.image, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + fsr2_.framegenOutputValid = true; + } else { + transitionImageLayout(currentCmd, fsr2_.history[fsr2_.currentHistory].image, + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + } } else { transitionImageLayout(currentCmd, fsr2_.sceneColor.image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, @@ -3751,6 +3758,7 @@ bool Renderer::initFSR2Resources() { fsr2_.useAmdBackend = false; fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; + fsr2_.framegenOutputValid = false; #if WOWEE_HAS_AMD_FSR2 LOG_INFO("FSR2: AMD FidelityFX SDK detected at build time."); #else @@ -3782,6 +3790,9 @@ bool Renderer::initFSR2Resources() { VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); if (!fsr2_.history[i].image) { LOG_ERROR("FSR2: failed to create history buffer ", i); destroyFSR2Resources(); return false; } } + fsr2_.framegenOutput = createImage(device, alloc, swapExtent.width, swapExtent.height, + VK_FORMAT_R16G16B16A16_SFLOAT, VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + if (!fsr2_.framegenOutput.image) { LOG_ERROR("FSR2: failed to create framegen output"); destroyFSR2Resources(); return false; } // Scene framebuffer (non-MSAA: [color, depth]) // Must use the same render pass as the swapchain — which must be non-MSAA when FSR2 is active @@ -3861,15 +3872,16 @@ bool Renderer::initFSR2Resources() { fgInit.maxRenderHeight = fsr2_.internalHeight; fgInit.displayWidth = swapExtent.width; fgInit.displayHeight = swapExtent.height; - fgInit.colorFormat = vkCtx->getSwapchainFormat(); + fgInit.colorFormat = VK_FORMAT_R16G16B16A16_SFLOAT; fgInit.hdrInput = false; fgInit.depthInverted = false; + fgInit.enableFrameGeneration = true; fsr2_.amdFsr3FramegenRuntimeReady = fsr2_.amdFsr3Runtime->initialize(fgInit); if (fsr2_.amdFsr3FramegenRuntimeReady) { LOG_INFO("FSR3 framegen runtime library loaded from ", fsr2_.amdFsr3Runtime->loadedLibraryPath(), - " (upscale dispatch enabled)"); + " (upscale+framegen dispatch enabled)"); } else { - LOG_WARNING("FSR3 framegen toggle is enabled, but runtime library was not found. ", + LOG_WARNING("FSR3 framegen toggle is enabled, but runtime initialization failed. ", "Set WOWEE_FFX_SDK_RUNTIME_LIB to the SDK runtime binary path."); } } @@ -4173,6 +4185,7 @@ void Renderer::destroyFSR2Resources() { #endif fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; + fsr2_.framegenOutputValid = false; #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (fsr2_.amdFsr3Runtime) { fsr2_.amdFsr3Runtime->shutdown(); @@ -4201,6 +4214,7 @@ void Renderer::destroyFSR2Resources() { destroyImage(device, alloc, fsr2_.motionVectors); for (int i = 0; i < 2; i++) destroyImage(device, alloc, fsr2_.history[i]); + destroyImage(device, alloc, fsr2_.framegenOutput); destroyImage(device, alloc, fsr2_.sceneDepth); destroyImage(device, alloc, fsr2_.sceneColor); @@ -4398,15 +4412,36 @@ void Renderer::dispatchAmdFsr2() { } void Renderer::dispatchAmdFsr3Framegen() { - if (!fsr2_.amdFsr3FramegenEnabled) { - fsr2_.amdFsr3FramegenRuntimeActive = false; - return; - } #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (!fsr2_.amdFsr3Runtime || !fsr2_.amdFsr3FramegenRuntimeReady) { fsr2_.amdFsr3FramegenRuntimeActive = false; return; } + uint32_t outputIdx = fsr2_.currentHistory; + transitionImageLayout(currentCmd, fsr2_.sceneColor.image, + VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + transitionImageLayout(currentCmd, fsr2_.motionVectors.image, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + transitionImageLayout(currentCmd, fsr2_.sceneDepth.image, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + transitionImageLayout(currentCmd, fsr2_.history[outputIdx].image, + fsr2_.needsHistoryReset ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + if (fsr2_.amdFsr3FramegenEnabled && fsr2_.framegenOutput.image) { + transitionImageLayout(currentCmd, fsr2_.framegenOutput.image, + fsr2_.framegenOutputValid ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); + } AmdFsr3RuntimeDispatchDesc fgDispatch{}; fgDispatch.commandBuffer = currentCmd; @@ -4422,6 +4457,7 @@ void Renderer::dispatchAmdFsr3Framegen() { fgDispatch.depthFormat = vkCtx->getDepthFormat(); fgDispatch.motionVectorFormat = VK_FORMAT_R16G16_SFLOAT; fgDispatch.outputFormat = VK_FORMAT_R16G16B16A16_SFLOAT; + fgDispatch.frameGenOutputImage = fsr2_.framegenOutput.image; 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); @@ -4433,16 +4469,34 @@ void Renderer::dispatchAmdFsr3Framegen() { fgDispatch.cameraFovYRadians = camera ? glm::radians(camera->getFovDegrees()) : 1.0f; fgDispatch.reset = fsr2_.needsHistoryReset; - bool ok = fsr2_.amdFsr3Runtime->dispatchUpscale(fgDispatch); - if (!ok) { + if (!fsr2_.amdFsr3Runtime->dispatchUpscale(fgDispatch)) { static bool warnedRuntimeDispatch = false; if (!warnedRuntimeDispatch) { warnedRuntimeDispatch = true; - LOG_WARNING("FSR3 runtime dispatch failed; falling back to FSR2 dispatch output."); + LOG_WARNING("FSR3 runtime upscale dispatch failed; falling back to FSR2 dispatch output."); } fsr2_.amdFsr3FramegenRuntimeActive = false; return; } + + if (!fsr2_.amdFsr3FramegenEnabled) { + fsr2_.amdFsr3FramegenRuntimeActive = false; + return; + } + if (!fsr2_.amdFsr3Runtime->isFrameGenerationReady()) { + fsr2_.amdFsr3FramegenRuntimeActive = false; + return; + } + if (!fsr2_.amdFsr3Runtime->dispatchFrameGeneration(fgDispatch)) { + static bool warnedFgDispatch = false; + if (!warnedFgDispatch) { + warnedFgDispatch = true; + LOG_WARNING("FSR3 runtime frame generation dispatch failed; using upscaled output only."); + } + fsr2_.amdFsr3FramegenRuntimeActive = false; + return; + } + fsr2_.framegenOutputValid = true; fsr2_.amdFsr3FramegenRuntimeActive = true; #else fsr2_.amdFsr3FramegenRuntimeActive = false; @@ -4462,9 +4516,13 @@ void Renderer::renderFSR2Sharpen() { // Update sharpen descriptor to point at current history output VkDescriptorImageInfo imgInfo{}; imgInfo.sampler = fsr2_.linearSampler; - imgInfo.imageView = fsr2_.useAmdBackend - ? fsr2_.history[outputIdx].imageView - : fsr2_.sceneColor.imageView; + if (fsr2_.useAmdBackend) { + imgInfo.imageView = (fsr2_.amdFsr3FramegenRuntimeActive && fsr2_.framegenOutput.imageView) + ? fsr2_.framegenOutput.imageView + : fsr2_.history[outputIdx].imageView; + } else { + imgInfo.imageView = fsr2_.sceneColor.imageView; + } imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};