Add Path A/B/C FSR3 runtime detection with clear FG fallback status

This commit is contained in:
Kelsi 2026-03-09 00:01:45 -07:00
parent 5ad4b9be2d
commit 93850ac6dc
7 changed files with 92 additions and 17 deletions

View file

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

View file

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

View file

@ -3,6 +3,8 @@
// Compatibility shim for building AMD FSR2 SDK sources on non-MSVC toolchains.
#include <stddef.h>
#include <wchar.h>
#include <string.h>
#include <stdio.h>
#include <locale>
#include <codecvt>

View file

@ -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<std::string> candidates;
struct Candidate {
std::string path;
LoadPathKind kind = LoadPathKind::Official;
};
std::vector<Candidate> 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<void*>(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<int>(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<FfxFsr3Context*>(contextStorage_), &ctxDesc);
if (createErr != FFX_OK) {
LOG_WARNING("FSR3 runtime: ffxFsr3ContextCreate failed (", static_cast<int>(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<FfxFsr3Context*>(contextStorage_), &fgCfg);
if (cfgErr != FFX_OK) {
LOG_WARNING("FSR3 runtime: ffxFsr3ConfigureFrameGeneration failed (", static_cast<int>(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

View file

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

View file

@ -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) {

View file

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