From 93850ac6dc4c58e1db34e9ed457584dfda3d5ccf Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 00:01:45 -0700 Subject: [PATCH] Add Path A/B/C FSR3 runtime detection with clear FG fallback status --- include/rendering/amd_fsr3_runtime.hpp | 10 +++++ include/rendering/renderer.hpp | 4 ++ include/third_party/ffx_fsr2_compat.h | 2 + src/rendering/amd_fsr3_runtime.cpp | 58 ++++++++++++++++++++------ src/rendering/performance_hud.cpp | 2 +- src/rendering/renderer.cpp | 20 +++++++++ src/ui/game_screen.cpp | 13 ++++-- 7 files changed, 92 insertions(+), 17 deletions(-) diff --git a/include/rendering/amd_fsr3_runtime.hpp b/include/rendering/amd_fsr3_runtime.hpp index 3b465f07..07f63cad 100644 --- a/include/rendering/amd_fsr3_runtime.hpp +++ b/include/rendering/amd_fsr3_runtime.hpp @@ -48,6 +48,12 @@ struct AmdFsr3RuntimeDispatchDesc { class AmdFsr3Runtime { public: + enum class LoadPathKind { + None, + Official, + Wrapper + }; + AmdFsr3Runtime(); ~AmdFsr3Runtime(); @@ -59,6 +65,8 @@ public: bool isReady() const { return ready_; } bool isFrameGenerationReady() const { return frameGenerationReady_; } const std::string& loadedLibraryPath() const { return loadedLibraryPath_; } + LoadPathKind loadPathKind() const { return loadPathKind_; } + const std::string& lastError() const { return lastError_; } private: void* libHandle_ = nullptr; @@ -67,6 +75,8 @@ private: size_t scratchBufferSize_ = 0; bool ready_ = false; bool frameGenerationReady_ = false; + LoadPathKind loadPathKind_ = LoadPathKind::None; + std::string lastError_; struct RuntimeFns; RuntimeFns* fns_ = nullptr; diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index ae929d21..3666cdd8 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -294,6 +294,8 @@ public: #endif bool isAmdFsr3FramegenRuntimeActive() const { return fsr2_.amdFsr3FramegenRuntimeActive; } bool isAmdFsr3FramegenRuntimeReady() const { return fsr2_.amdFsr3FramegenRuntimeReady; } + const char* getAmdFsr3FramegenRuntimePath() const; + const std::string& getAmdFsr3FramegenRuntimeError() const { return fsr2_.amdFsr3RuntimeLastError; } size_t getAmdFsr3UpscaleDispatchCount() const { return fsr2_.amdFsr3UpscaleDispatchCount; } size_t getAmdFsr3FramegenDispatchCount() const { return fsr2_.amdFsr3FramegenDispatchCount; } size_t getAmdFsr3FallbackCount() const { return fsr2_.amdFsr3FallbackCount; } @@ -448,6 +450,8 @@ private: bool amdFsr3FramegenEnabled = false; bool amdFsr3FramegenRuntimeActive = false; bool amdFsr3FramegenRuntimeReady = false; + std::string amdFsr3RuntimePath = "Path C"; + std::string amdFsr3RuntimeLastError{}; size_t amdFsr3UpscaleDispatchCount = 0; size_t amdFsr3FramegenDispatchCount = 0; size_t amdFsr3FallbackCount = 0; diff --git a/include/third_party/ffx_fsr2_compat.h b/include/third_party/ffx_fsr2_compat.h index 81be76ed..a63b364a 100644 --- a/include/third_party/ffx_fsr2_compat.h +++ b/include/third_party/ffx_fsr2_compat.h @@ -3,6 +3,8 @@ // Compatibility shim for building AMD FSR2 SDK sources on non-MSVC toolchains. #include #include +#include +#include #include #include diff --git a/src/rendering/amd_fsr3_runtime.cpp b/src/rendering/amd_fsr3_runtime.cpp index 7663c873..ab1b3fdd 100644 --- a/src/rendering/amd_fsr3_runtime.cpp +++ b/src/rendering/amd_fsr3_runtime.cpp @@ -145,9 +145,12 @@ FfxResourceDescription makeResourceDescription(VkFormat format, bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { shutdown(); + lastError_.clear(); + loadPathKind_ = LoadPathKind::None; #if !WOWEE_HAS_AMD_FSR3_FRAMEGEN (void)desc; + lastError_ = "FSR3 runtime support not compiled in"; return false; #else if (!desc.physicalDevice || !desc.device || !desc.getDeviceProcAddr || @@ -155,38 +158,58 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { desc.displayWidth == 0 || desc.displayHeight == 0 || desc.colorFormat == VK_FORMAT_UNDEFINED) { LOG_WARNING("FSR3 runtime: invalid initialization descriptors."); + lastError_ = "invalid initialization descriptors"; return false; } - std::vector candidates; + struct Candidate { + std::string path; + LoadPathKind kind = LoadPathKind::Official; + }; + std::vector candidates; if (const char* envPath = std::getenv("WOWEE_FFX_SDK_RUNTIME_LIB")) { - if (*envPath) candidates.emplace_back(envPath); + if (*envPath) candidates.push_back({envPath, LoadPathKind::Official}); + } + if (const char* wrapperEnv = std::getenv("WOWEE_FFX_SDK_RUNTIME_WRAPPER_LIB")) { + if (*wrapperEnv) candidates.push_back({wrapperEnv, LoadPathKind::Wrapper}); } #if defined(_WIN32) - candidates.emplace_back("ffx_fsr3_vk.dll"); - candidates.emplace_back("ffx_fsr3.dll"); + candidates.push_back({"ffx_fsr3_vk.dll", LoadPathKind::Official}); + candidates.push_back({"ffx_fsr3.dll", LoadPathKind::Official}); + candidates.push_back({"ffx_fsr3_vk_wrapper.dll", LoadPathKind::Wrapper}); + candidates.push_back({"ffx_fsr3_bridge.dll", LoadPathKind::Wrapper}); #elif defined(__APPLE__) - candidates.emplace_back("libffx_fsr3_vk.dylib"); - candidates.emplace_back("libffx_fsr3.dylib"); + candidates.push_back({"libffx_fsr3_vk.dylib", LoadPathKind::Official}); + candidates.push_back({"libffx_fsr3.dylib", LoadPathKind::Official}); + candidates.push_back({"libffx_fsr3_vk_wrapper.dylib", LoadPathKind::Wrapper}); + candidates.push_back({"libffx_fsr3_bridge.dylib", LoadPathKind::Wrapper}); #else - candidates.emplace_back("libffx_fsr3_vk.so"); - candidates.emplace_back("libffx_fsr3.so"); + candidates.push_back({"./libffx_fsr3_vk.so", LoadPathKind::Official}); + candidates.push_back({"libffx_fsr3_vk.so", LoadPathKind::Official}); + candidates.push_back({"libffx_fsr3.so", LoadPathKind::Official}); + candidates.push_back({"./libffx_fsr3_vk_wrapper.so", LoadPathKind::Wrapper}); + candidates.push_back({"libffx_fsr3_vk_wrapper.so", LoadPathKind::Wrapper}); + candidates.push_back({"libffx_fsr3_bridge.so", LoadPathKind::Wrapper}); #endif - for (const std::string& candidate : candidates) { + for (const Candidate& candidate : candidates) { #if defined(_WIN32) - HMODULE h = LoadLibraryA(candidate.c_str()); + HMODULE h = LoadLibraryA(candidate.path.c_str()); if (!h) continue; libHandle_ = reinterpret_cast(h); #else - void* h = dlopen(candidate.c_str(), RTLD_NOW | RTLD_LOCAL); + void* h = dlopen(candidate.path.c_str(), RTLD_NOW | RTLD_LOCAL); if (!h) continue; libHandle_ = h; #endif - loadedLibraryPath_ = candidate; + loadedLibraryPath_ = candidate.path; + loadPathKind_ = candidate.kind; break; } - if (!libHandle_) return false; + if (!libHandle_) { + lastError_ = "no official runtime (Path A) or wrapper runtime (Path B) found"; + return false; + } auto resolveSym = [&](const char* name) -> void* { #if defined(_WIN32) @@ -212,6 +235,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { !fns_->getCommandListVK || !fns_->getResourceVK || !fns_->fsr3ContextCreate || !fns_->fsr3ContextDispatchUpscale || !fns_->fsr3ContextDestroy) { LOG_WARNING("FSR3 runtime: required symbols not found in ", loadedLibraryPath_); + lastError_ = "missing required Vulkan FSR3 symbols in runtime library"; shutdown(); return false; } @@ -219,12 +243,14 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { scratchBufferSize_ = fns_->getScratchMemorySizeVK(desc.physicalDevice, FFX_FSR3_CONTEXT_COUNT); if (scratchBufferSize_ == 0) { LOG_WARNING("FSR3 runtime: scratch buffer size query returned 0."); + lastError_ = "scratch buffer size query returned 0"; shutdown(); return false; } scratchBuffer_ = std::malloc(scratchBufferSize_); if (!scratchBuffer_) { LOG_WARNING("FSR3 runtime: failed to allocate scratch buffer."); + lastError_ = "failed to allocate scratch buffer"; shutdown(); return false; } @@ -239,6 +265,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { 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), ")."); + lastError_ = "ffxGetInterfaceVK failed"; shutdown(); return false; } @@ -268,6 +295,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { contextStorage_ = std::malloc(sizeof(FfxFsr3Context)); if (!contextStorage_) { LOG_WARNING("FSR3 runtime: failed to allocate context storage."); + lastError_ = "failed to allocate context storage"; shutdown(); return false; } @@ -276,6 +304,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { FfxErrorCode createErr = fns_->fsr3ContextCreate(reinterpret_cast(contextStorage_), &ctxDesc); if (createErr != FFX_OK) { LOG_WARNING("FSR3 runtime: ffxFsr3ContextCreate failed (", static_cast(createErr), ")."); + lastError_ = "ffxFsr3ContextCreate failed"; shutdown(); return false; } @@ -283,6 +312,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { if (desc.enableFrameGeneration) { if (!fns_->fsr3ConfigureFrameGeneration || !fns_->fsr3DispatchFrameGeneration) { LOG_WARNING("FSR3 runtime: frame generation symbols unavailable in ", loadedLibraryPath_); + lastError_ = "frame generation entry points unavailable"; shutdown(); return false; } @@ -295,6 +325,7 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { reinterpret_cast(contextStorage_), &fgCfg); if (cfgErr != FFX_OK) { LOG_WARNING("FSR3 runtime: ffxFsr3ConfigureFrameGeneration failed (", static_cast(cfgErr), ")."); + lastError_ = "ffxFsr3ConfigureFrameGeneration failed"; shutdown(); return false; } @@ -419,6 +450,7 @@ void AmdFsr3Runtime::shutdown() { #endif libHandle_ = nullptr; loadedLibraryPath_.clear(); + loadPathKind_ = LoadPathKind::None; } } // namespace wowee::rendering diff --git a/src/rendering/performance_hud.cpp b/src/rendering/performance_hud.cpp index 6476b26e..92fba1c4 100644 --- a/src/rendering/performance_hud.cpp +++ b/src/rendering/performance_hud.cpp @@ -210,7 +210,7 @@ void PerformanceHUD::render(const Renderer* renderer, const Camera* camera) { if (fgEnabled) { fgStatus = fgActive ? "Active" : (fgReady ? "Ready (waiting/fallback)" : "Unavailable"); } - ImGui::Text(" FSR3 FG: %s", fgStatus); + ImGui::Text(" FSR3 FG: %s (%s)", fgStatus, renderer->getAmdFsr3FramegenRuntimePath()); ImGui::Text(" FG Dispatches: %zu", renderer->getAmdFsr3FramegenDispatchCount()); ImGui::Text(" Upscale Dispatches: %zu", renderer->getAmdFsr3UpscaleDispatchCount()); ImGui::Text(" FG Fallbacks: %zu", renderer->getAmdFsr3FallbackCount()); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 68d6bdf2..4cf49b58 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3759,6 +3759,8 @@ bool Renderer::initFSR2Resources() { fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; fsr2_.framegenOutputValid = false; + fsr2_.amdFsr3RuntimePath = "Path C"; + fsr2_.amdFsr3RuntimeLastError.clear(); fsr2_.amdFsr3UpscaleDispatchCount = 0; fsr2_.amdFsr3FramegenDispatchCount = 0; fsr2_.amdFsr3FallbackCount = 0; @@ -3881,9 +3883,17 @@ bool Renderer::initFSR2Resources() { fgInit.enableFrameGeneration = true; fsr2_.amdFsr3FramegenRuntimeReady = fsr2_.amdFsr3Runtime->initialize(fgInit); if (fsr2_.amdFsr3FramegenRuntimeReady) { + fsr2_.amdFsr3RuntimeLastError.clear(); + if (fsr2_.amdFsr3Runtime->loadPathKind() == AmdFsr3Runtime::LoadPathKind::Wrapper) { + fsr2_.amdFsr3RuntimePath = "Path B"; + } else { + fsr2_.amdFsr3RuntimePath = "Path A"; + } LOG_INFO("FSR3 framegen runtime library loaded from ", fsr2_.amdFsr3Runtime->loadedLibraryPath(), " (upscale+framegen dispatch enabled)"); } else { + fsr2_.amdFsr3RuntimePath = "Path C"; + fsr2_.amdFsr3RuntimeLastError = fsr2_.amdFsr3Runtime->lastError(); LOG_WARNING("FSR3 framegen toggle is enabled, but runtime initialization failed. ", "Set WOWEE_FFX_SDK_RUNTIME_LIB to the SDK runtime binary path."); } @@ -4189,6 +4199,8 @@ void Renderer::destroyFSR2Resources() { fsr2_.amdFsr3FramegenRuntimeActive = false; fsr2_.amdFsr3FramegenRuntimeReady = false; fsr2_.framegenOutputValid = false; + fsr2_.amdFsr3RuntimePath = "Path C"; + fsr2_.amdFsr3RuntimeLastError.clear(); #if WOWEE_HAS_AMD_FSR3_FRAMEGEN if (fsr2_.amdFsr3Runtime) { fsr2_.amdFsr3Runtime->shutdown(); @@ -4603,6 +4615,8 @@ void Renderer::setAmdFsr3FramegenEnabled(bool enabled) { fsr2_.needsRecreate = true; fsr2_.needsHistoryReset = true; fsr2_.amdFsr3FramegenRuntimeReady = false; + fsr2_.amdFsr3RuntimePath = "Path C"; + fsr2_.amdFsr3RuntimeLastError.clear(); LOG_INFO("FSR3 framegen requested; runtime will initialize on next FSR2 resource creation."); } else { fsr2_.amdFsr3FramegenRuntimeActive = false; @@ -4610,6 +4624,8 @@ void Renderer::setAmdFsr3FramegenEnabled(bool enabled) { fsr2_.framegenOutputValid = false; fsr2_.needsHistoryReset = true; fsr2_.needsRecreate = true; + fsr2_.amdFsr3RuntimePath = "Path C"; + fsr2_.amdFsr3RuntimeLastError = "disabled by user"; if (fsr2_.amdFsr3Runtime) { fsr2_.amdFsr3Runtime->shutdown(); fsr2_.amdFsr3Runtime.reset(); @@ -5327,6 +5343,10 @@ void Renderer::setTerrainStreaming(bool enabled) { } } +const char* Renderer::getAmdFsr3FramegenRuntimePath() const { + return fsr2_.amdFsr3RuntimePath.c_str(); +} + void Renderer::renderHUD() { if (currentCmd == VK_NULL_HANDLE) return; if (performanceHUD && camera) { diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 8b6e2175..318a4e23 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6346,11 +6346,18 @@ void GameScreen::renderSettingsWindow() { if (renderer->isAmdFsr3FramegenRuntimeActive()) { runtimeStatus = "Active"; } else if (renderer->isAmdFsr3FramegenRuntimeReady()) { - runtimeStatus = "Library loaded (dispatch staged)"; + runtimeStatus = "Ready"; } else { - runtimeStatus = "Library missing"; + runtimeStatus = "Unavailable"; + } + ImGui::TextDisabled("Runtime: %s (%s)", + runtimeStatus, renderer->getAmdFsr3FramegenRuntimePath()); + if (!renderer->isAmdFsr3FramegenRuntimeReady()) { + const std::string& runtimeErr = renderer->getAmdFsr3FramegenRuntimeError(); + if (!runtimeErr.empty()) { + ImGui::TextDisabled("Reason: %s", runtimeErr.c_str()); + } } - ImGui::TextDisabled("Runtime: %s", runtimeStatus); } else { ImGui::BeginDisabled(); bool disabledFg = false;