From bae32c1823c117d8e87090a02f243b00e59a44de Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 12:51:59 -0700 Subject: [PATCH] Add FSR3 Generic API path and harden runtime diagnostics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AmdFsr3Runtime now probes both the legacy ffxFsr3* API and the newer generic ffxCreateContext/ffxDispatch API; selects whichever the loaded runtime library exports (GenericApi takes priority fallback) - Generic API path implements full upscale + frame-generation context creation, configure, dispatch, and destroy lifecycle - dlopen error captured and surfaced in lastError_ on Linux so runtime initialization failures are actionable - FSR3 runtime init failure log now includes path kind, error string, and loaded library path for easier debugging - tools/generate_ffx_sdk_vk_permutations.sh added: auto-bootstraps missing VK permutation headers; DXC auto-downloaded on Linux/Windows MSYS2; macOS reads from PATH (CI installs via brew dxc) - CMakeLists: add upscalers/include to probe include dirs, invoke permutation script before SDK build, scope FFX pragma/ODR warning suppressions to affected TUs, add runtime-copy dependency on wowee - UI labels updated from "FSR2" → "FSR3" in settings, tuning panel, performance HUD, and combo boxes - CI macOS job now installs dxc via Homebrew for permutation codegen --- .github/workflows/build.yml | 2 +- CMakeLists.txt | 22 ++ README.md | 1 + docs/AMD_FSR2_INTEGRATION.md | 5 + include/rendering/amd_fsr3_runtime.hpp | 9 + include/ui/game_screen.hpp | 2 +- src/rendering/amd_fsr3_runtime.cpp | 301 +++++++++++++++++++++- src/rendering/performance_hud.cpp | 2 +- src/rendering/renderer.cpp | 4 +- src/ui/game_screen.cpp | 10 +- tools/generate_ffx_sdk_vk_permutations.sh | 195 ++++++++++++++ 11 files changed, 537 insertions(+), 16 deletions(-) create mode 100755 tools/generate_ffx_sdk_vk_permutations.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6893f717..b5c9f543 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -138,7 +138,7 @@ jobs: - name: Install dependencies run: | - brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn \ + brew install cmake pkg-config sdl2 glew glm openssl@3 zlib ffmpeg unicorn dxc \ stormlib vulkan-loader vulkan-headers shaderc dylibbundler || true # dylibbundler may not be in all brew mirrors; install separately to not block others brew install dylibbundler 2>/dev/null || true diff --git a/CMakeLists.txt b/CMakeLists.txt index 718d657f..0c5a960d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,7 @@ if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN AND WOWEE_AMD_FFX_SDK_KITS_READY) CXX_STANDARD_REQUIRED ON ) target_include_directories(wowee_fsr3_framegen_amd_vk_probe PUBLIC + ${WOWEE_AMD_FFX_SDK_KITS_DIR}/upscalers/include ${WOWEE_AMD_FFX_SDK_KITS_DIR}/upscalers/fsr3/include ${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/fsr3/include ${WOWEE_AMD_FFX_SDK_KITS_DIR}/framegeneration/include @@ -163,6 +164,8 @@ if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN AND WOWEE_AMD_FFX_SDK_KITS_READY) -DFFX_BUILD_VK=ON -DFFX_BUILD_FRAMEGENERATION=ON -DFFX_BUILD_UPSCALER=ON + COMMAND bash ${CMAKE_SOURCE_DIR}/tools/generate_ffx_sdk_vk_permutations.sh + ${CMAKE_SOURCE_DIR}/extern/FidelityFX-SDK COMMAND ${CMAKE_COMMAND} --build ${WOWEE_AMD_FSR3_RUNTIME_BUILD_DIR} --config $ @@ -641,6 +644,14 @@ if(TARGET opcodes-generate) add_dependencies(wowee opcodes-generate) endif() +# FidelityFX-SDK headers can trigger compiler-specific pragma/unused-static noise +# when included through the runtime bridge; keep suppression scoped to that TU. +if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") + set_source_files_properties(src/rendering/amd_fsr3_runtime.cpp PROPERTIES + COMPILE_OPTIONS "-Wno-unknown-pragmas;-Wno-unused-variable" + ) +endif() + # Compile GLSL shaders to SPIR-V if(GLSLC) compile_shaders(wowee) @@ -715,6 +726,10 @@ endif() if(TARGET wowee_fsr3_framegen_amd_vk_probe) target_link_libraries(wowee PRIVATE wowee_fsr3_framegen_amd_vk_probe) endif() +if(TARGET wowee_fsr3_official_runtime_copy) + # Ensure Path A runtime is available in bin/ whenever wowee is built. + add_dependencies(wowee wowee_fsr3_official_runtime_copy) +endif() # Link Unicorn if available if(HAVE_UNICORN) @@ -735,6 +750,13 @@ if(MSVC) target_compile_options(wowee PRIVATE /W4) else() target_compile_options(wowee PRIVATE -Wall -Wextra -Wpedantic -Wno-missing-field-initializers) + # GCC LTO emits -Wodr for FFX enum-name mismatches across SDK generations. + # We intentionally keep FSR2+FSR3 integrations in separate TUs and suppress + # this linker-time diagnostic to avoid CI noise. + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + target_compile_options(wowee PRIVATE -Wno-odr) + target_link_options(wowee PRIVATE -Wno-odr) + endif() endif() # Debug build flags diff --git a/README.md b/README.md index b1b1b300..54ae7eaa 100644 --- a/README.md +++ b/README.md @@ -278,6 +278,7 @@ make -j$(nproc) - All build jobs are AMD-FSR2-only (`WOWEE_ENABLE_AMD_FSR2=ON`) and explicitly build `wowee_fsr2_amd_vk` - Each job clones AMD's FSR2 SDK and FidelityFX-SDK (`Kelsidavis/FidelityFX-SDK`, `main` by default) - Linux CI validates FidelityFX-SDK Kits framegen headers +- FSR3 Path A runtime build auto-bootstraps missing VK permutation headers via `tools/generate_ffx_sdk_vk_permutations.sh` - CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated for the detected SDK layout - If FSR2 generated Vulkan permutation headers are absent upstream, WoWee bootstraps them from `third_party/fsr2_vk_permutations` - Container build via `container/build-in-container.sh` (Podman) diff --git a/docs/AMD_FSR2_INTEGRATION.md b/docs/AMD_FSR2_INTEGRATION.md index bc7bffe8..03d829f4 100644 --- a/docs/AMD_FSR2_INTEGRATION.md +++ b/docs/AMD_FSR2_INTEGRATION.md @@ -42,6 +42,7 @@ Runtime note: - Renderer/UI expose a persisted experimental framegen toggle. - Runtime loader is Path A only (official AMD runtime library). +- Path A runtime build now auto-runs `tools/generate_ffx_sdk_vk_permutations.sh` to ensure required VK permutation headers exist for FSR2/FSR3 upscaler shader blobs. - You can point to an explicit runtime binary with: - `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`). - If no official runtime is found, frame generation is disabled cleanly (Path C). @@ -76,6 +77,10 @@ Runtime note: - `framegeneration/fsr3/include/ffx_frameinterpolation.h` - `framegeneration/fsr3/include/ffx_opticalflow.h` - `backend/vk/ffx_vk.h` +- Runtime build path auto-bootstrap: + - Linux downloads DXC automatically when missing. + - Windows (MSYS2) downloads DXC automatically when missing. + - macOS expects `dxc` to be available in `PATH` (CI installs it via Homebrew). - CI builds `wowee_fsr3_framegen_amd_vk_probe` when that target is generated by CMake for the detected SDK layout. - Some upstream SDK checkouts do not include generated Vulkan permutation headers. - WoWee bootstraps those headers from the vendored snapshot so AMD backend builds remain cross-platform and deterministic. diff --git a/include/rendering/amd_fsr3_runtime.hpp b/include/rendering/amd_fsr3_runtime.hpp index c2b379d7..2ad7a94c 100644 --- a/include/rendering/amd_fsr3_runtime.hpp +++ b/include/rendering/amd_fsr3_runtime.hpp @@ -68,6 +68,11 @@ public: const std::string& lastError() const { return lastError_; } private: + enum class ApiMode { + LegacyFsr3, + GenericApi + }; + void* libHandle_ = nullptr; std::string loadedLibraryPath_; void* scratchBuffer_ = nullptr; @@ -80,6 +85,10 @@ private: struct RuntimeFns; RuntimeFns* fns_ = nullptr; void* contextStorage_ = nullptr; + ApiMode apiMode_ = ApiMode::LegacyFsr3; + void* genericUpscaleContext_ = nullptr; + void* genericFramegenContext_ = nullptr; + uint64_t genericFrameId_ = 1; }; } // namespace wowee::rendering diff --git a/include/ui/game_screen.hpp b/include/ui/game_screen.hpp index 76d70b84..2a65abab 100644 --- a/include/ui/game_screen.hpp +++ b/include/ui/game_screen.hpp @@ -117,7 +117,7 @@ private: bool pendingPOM = true; // on by default int pendingPOMQuality = 1; // 0=Low(16), 1=Medium(32), 2=High(64) bool pendingFSR = false; - int pendingUpscalingMode = 0; // 0=Off, 1=FSR1, 2=FSR2 + int pendingUpscalingMode = 0; // 0=Off, 1=FSR1, 2=FSR3 int pendingFSRQuality = 3; // 0=UltraQuality, 1=Quality, 2=Balanced, 3=Native(100%) float pendingFSRSharpness = 1.6f; float pendingFSR2JitterSign = 0.38f; diff --git a/src/rendering/amd_fsr3_runtime.cpp b/src/rendering/amd_fsr3_runtime.cpp index 773935b8..e7606fb6 100644 --- a/src/rendering/amd_fsr3_runtime.cpp +++ b/src/rendering/amd_fsr3_runtime.cpp @@ -17,7 +17,11 @@ #if WOWEE_HAS_AMD_FSR3_FRAMEGEN #include "third_party/ffx_fsr3_legacy_compat.h" +#include +#include +#include #include +#include #endif namespace wowee::rendering { @@ -34,6 +38,10 @@ struct AmdFsr3Runtime::RuntimeFns { decltype(&ffxFsr3ConfigureFrameGeneration) fsr3ConfigureFrameGeneration = nullptr; decltype(&ffxFsr3DispatchFrameGeneration) fsr3DispatchFrameGeneration = nullptr; decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr; + PfnFfxCreateContext createContext = nullptr; + PfnFfxDestroyContext destroyContext = nullptr; + PfnFfxConfigure configure = nullptr; + PfnFfxDispatch dispatch = nullptr; }; #else struct AmdFsr3Runtime::RuntimeFns {}; @@ -51,6 +59,43 @@ FfxErrorCode vkSwapchainConfigureNoop(const FfxFrameGenerationConfig*) { return FFX_OK; } +std::string narrowWString(const wchar_t* msg) { + if (!msg) return {}; + std::string out; + for (const wchar_t* p = msg; *p; ++p) { + const wchar_t wc = *p; + if (wc >= 0 && wc <= 0x7f) { + out.push_back(static_cast(wc)); + } else { + out.push_back('?'); + } + } + return out; +} + +void ffxApiLogMessage(uint32_t type, const wchar_t* message) { + const std::string narrowed = narrowWString(message); + if (type == FFX_API_MESSAGE_TYPE_ERROR) { + LOG_ERROR("FSR3 runtime/API: ", narrowed); + } else { + LOG_WARNING("FSR3 runtime/API: ", narrowed); + } +} + +const char* ffxApiReturnCodeName(ffxReturnCode_t rc) { + switch (rc) { + case FFX_API_RETURN_OK: return "OK"; + case FFX_API_RETURN_ERROR: return "ERROR"; + case FFX_API_RETURN_ERROR_UNKNOWN_DESCTYPE: return "ERROR_UNKNOWN_DESCTYPE"; + case FFX_API_RETURN_ERROR_RUNTIME_ERROR: return "ERROR_RUNTIME_ERROR"; + case FFX_API_RETURN_NO_PROVIDER: return "NO_PROVIDER"; + case FFX_API_RETURN_ERROR_MEMORY: return "ERROR_MEMORY"; + case FFX_API_RETURN_ERROR_PARAMETER: return "ERROR_PARAMETER"; + case FFX_API_RETURN_PROVIDER_NO_SUPPORT_NEW_DESCTYPE: return "PROVIDER_NO_SUPPORT_NEW_DESCTYPE"; + default: return "UNKNOWN"; + } +} + template struct HasUpscaleOutputSize : std::false_type {}; @@ -141,6 +186,7 @@ FfxResourceDescription makeResourceDescription(VkFormat format, description.usage = usage; return description; } + } // namespace #endif @@ -184,23 +230,36 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { candidates.emplace_back("libffx_fsr3.so"); #endif + std::string lastDlopenError; for (const std::string& path : candidates) { #if defined(_WIN32) HMODULE h = LoadLibraryA(path.c_str()); if (!h) continue; libHandle_ = reinterpret_cast(h); #else + dlerror(); void* h = dlopen(path.c_str(), RTLD_NOW | RTLD_LOCAL); - if (!h) continue; + if (!h) { + const char* err = dlerror(); + if (err && *err) lastDlopenError = err; + continue; + } libHandle_ = h; #endif loadedLibraryPath_ = path; loadPathKind_ = LoadPathKind::Official; + LOG_INFO("FSR3 runtime: opened library candidate ", loadedLibraryPath_); break; } if (!libHandle_) { lastError_ = "no official runtime (Path A) found"; +#if !defined(_WIN32) + if (!lastDlopenError.empty()) { + lastError_ += " dlopen error: "; + lastError_ += lastDlopenError; + } +#endif return false; } @@ -223,16 +282,127 @@ bool AmdFsr3Runtime::initialize(const AmdFsr3RuntimeInitDesc& desc) { fns_->fsr3ConfigureFrameGeneration = reinterpret_castfsr3ConfigureFrameGeneration)>(resolveSym("ffxFsr3ConfigureFrameGeneration")); fns_->fsr3DispatchFrameGeneration = reinterpret_castfsr3DispatchFrameGeneration)>(resolveSym("ffxFsr3DispatchFrameGeneration")); fns_->fsr3ContextDestroy = reinterpret_castfsr3ContextDestroy)>(resolveSym("ffxFsr3ContextDestroy")); + fns_->createContext = reinterpret_castcreateContext)>(resolveSym("ffxCreateContext")); + fns_->destroyContext = reinterpret_castdestroyContext)>(resolveSym("ffxDestroyContext")); + fns_->configure = reinterpret_castconfigure)>(resolveSym("ffxConfigure")); + fns_->dispatch = reinterpret_castdispatch)>(resolveSym("ffxDispatch")); - 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_); - lastError_ = "missing required Vulkan FSR3 symbols in runtime library"; + const bool hasLegacyApi = (fns_->getScratchMemorySizeVK && fns_->getDeviceVK && fns_->getInterfaceVK && + fns_->getCommandListVK && fns_->getResourceVK && fns_->fsr3ContextCreate && + fns_->fsr3ContextDispatchUpscale && fns_->fsr3ContextDestroy); + const bool hasGenericApi = (fns_->createContext && fns_->destroyContext && fns_->configure && fns_->dispatch); + + if (!hasLegacyApi && !hasGenericApi) { + LOG_WARNING("FSR3 runtime: required symbols not found in ", loadedLibraryPath_, + " (need legacy ffxFsr3* or generic ffxCreateContext/ffxDispatch)"); + lastError_ = "missing required Vulkan FSR3 symbols in runtime library (legacy and generic APIs unavailable)"; shutdown(); return false; } + apiMode_ = hasLegacyApi ? ApiMode::LegacyFsr3 : ApiMode::GenericApi; + if (apiMode_ == ApiMode::GenericApi) { + ffxConfigureDescGlobalDebug1 globalDebug{}; + globalDebug.header.type = FFX_API_CONFIGURE_DESC_TYPE_GLOBALDEBUG1; + globalDebug.header.pNext = nullptr; + globalDebug.fpMessage = &ffxApiLogMessage; + globalDebug.debugLevel = FFX_API_CONFIGURE_GLOBALDEBUG_LEVEL_VERBOSE; + (void)fns_->configure(nullptr, reinterpret_cast(&globalDebug)); + + ffxCreateBackendVKDesc backendDesc{}; + backendDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_BACKEND_VK; + backendDesc.header.pNext = nullptr; + backendDesc.vkDevice = desc.device; + backendDesc.vkPhysicalDevice = desc.physicalDevice; + + ffxCreateContextDescUpscaleVersion upVerDesc{}; + upVerDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE_VERSION; + upVerDesc.header.pNext = nullptr; + upVerDesc.version = FFX_UPSCALER_VERSION; + + ffxCreateContextDescUpscale upDesc{}; + upDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_UPSCALE; + upDesc.header.pNext = reinterpret_cast(&backendDesc); + upDesc.flags = FFX_UPSCALE_ENABLE_AUTO_EXPOSURE | FFX_UPSCALE_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; + upDesc.flags |= FFX_UPSCALE_ENABLE_DEBUG_CHECKING; + if (desc.hdrInput) upDesc.flags |= FFX_UPSCALE_ENABLE_HIGH_DYNAMIC_RANGE; + if (desc.depthInverted) upDesc.flags |= FFX_UPSCALE_ENABLE_DEPTH_INVERTED; + upDesc.maxRenderSize.width = desc.maxRenderWidth; + upDesc.maxRenderSize.height = desc.maxRenderHeight; + upDesc.maxUpscaleSize.width = desc.displayWidth; + upDesc.maxUpscaleSize.height = desc.displayHeight; + upDesc.fpMessage = &ffxApiLogMessage; + backendDesc.header.pNext = reinterpret_cast(&upVerDesc); + + ffxContext upscaleCtx = nullptr; + const ffxReturnCode_t upCreateRc = + fns_->createContext(&upscaleCtx, reinterpret_cast(&upDesc), nullptr); + if (upCreateRc != FFX_API_RETURN_OK) { + const std::string loadedPath = loadedLibraryPath_; + lastError_ = "ffxCreateContext (upscale) failed rc=" + std::to_string(upCreateRc) + + " (" + ffxApiReturnCodeName(upCreateRc) + "), runtimeLib=" + loadedPath; + shutdown(); + return false; + } + genericUpscaleContext_ = upscaleCtx; + backendDesc.header.pNext = nullptr; + + if (desc.enableFrameGeneration) { + ffxCreateContextDescFrameGenerationVersion fgVerDesc{}; + fgVerDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION_VERSION; + fgVerDesc.header.pNext = nullptr; + fgVerDesc.version = FFX_FRAMEGENERATION_VERSION; + + ffxCreateContextDescFrameGeneration fgDesc{}; + fgDesc.header.type = FFX_API_CREATE_CONTEXT_DESC_TYPE_FRAMEGENERATION; + fgDesc.header.pNext = reinterpret_cast(&backendDesc); + fgDesc.flags = FFX_FRAMEGENERATION_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; + fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_DEBUG_CHECKING; + if (desc.hdrInput) fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_HIGH_DYNAMIC_RANGE; + if (desc.depthInverted) fgDesc.flags |= FFX_FRAMEGENERATION_ENABLE_DEPTH_INVERTED; + fgDesc.displaySize.width = desc.displayWidth; + fgDesc.displaySize.height = desc.displayHeight; + fgDesc.maxRenderSize.width = desc.maxRenderWidth; + fgDesc.maxRenderSize.height = desc.maxRenderHeight; + fgDesc.backBufferFormat = ffxApiGetSurfaceFormatVK(desc.colorFormat); + backendDesc.header.pNext = reinterpret_cast(&fgVerDesc); + + ffxContext fgCtx = nullptr; + const ffxReturnCode_t fgCreateRc = + fns_->createContext(&fgCtx, reinterpret_cast(&fgDesc), nullptr); + if (fgCreateRc != FFX_API_RETURN_OK) { + const std::string loadedPath = loadedLibraryPath_; + lastError_ = "ffxCreateContext (framegeneration) failed rc=" + std::to_string(fgCreateRc) + + " (" + ffxApiReturnCodeName(fgCreateRc) + "), runtimeLib=" + loadedPath; + shutdown(); + return false; + } + genericFramegenContext_ = fgCtx; + backendDesc.header.pNext = nullptr; + + ffxConfigureDescFrameGeneration fgCfg{}; + fgCfg.header.type = FFX_API_CONFIGURE_DESC_TYPE_FRAMEGENERATION; + fgCfg.header.pNext = nullptr; + fgCfg.frameGenerationEnabled = true; + fgCfg.allowAsyncWorkloads = false; + fgCfg.flags = FFX_FRAMEGENERATION_FLAG_NO_SWAPCHAIN_CONTEXT_NOTIFY; + fgCfg.onlyPresentGenerated = false; + fgCfg.frameID = genericFrameId_; + if (fns_->configure(reinterpret_cast(&genericFramegenContext_), + reinterpret_cast(&fgCfg)) != FFX_API_RETURN_OK) { + lastError_ = "ffxConfigure (framegeneration) failed"; + shutdown(); + return false; + } + frameGenerationReady_ = true; + } + + ready_ = true; + LOG_INFO("FSR3 runtime: loaded generic API from ", loadedLibraryPath_, + " framegenReady=", frameGenerationReady_ ? "yes" : "no"); + return true; + } + scratchBufferSize_ = fns_->getScratchMemorySizeVK(FFX_FSR3_CONTEXT_COUNT); if (scratchBufferSize_ == 0) { LOG_WARNING("FSR3 runtime: scratch buffer size query returned 0."); @@ -344,6 +514,49 @@ bool AmdFsr3Runtime::dispatchUpscale(const AmdFsr3RuntimeDispatchDesc& desc) { lastError_ = "invalid upscale dispatch resources"; return false; } + if (apiMode_ == ApiMode::GenericApi) { + if (!genericUpscaleContext_ || !fns_->dispatch) { + lastError_ = "generic API upscale context unavailable"; + return false; + } + ffxDispatchDescUpscale up{}; + up.header.type = FFX_API_DISPATCH_DESC_TYPE_UPSCALE; + up.header.pNext = nullptr; + up.commandList = reinterpret_cast(desc.commandBuffer); + up.color = ffxApiGetResourceVK(desc.colorImage, desc.colorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + up.depth = ffxApiGetResourceVK(desc.depthImage, desc.depthFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + up.motionVectors = ffxApiGetResourceVK(desc.motionVectorImage, desc.motionVectorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + up.exposure = FfxApiResource{}; + up.reactive = FfxApiResource{}; + up.transparencyAndComposition = FfxApiResource{}; + up.output = ffxApiGetResourceVK(desc.outputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); + up.jitterOffset.x = desc.jitterX; + up.jitterOffset.y = desc.jitterY; + up.motionVectorScale.x = desc.motionScaleX; + up.motionVectorScale.y = desc.motionScaleY; + up.renderSize.width = desc.renderWidth; + up.renderSize.height = desc.renderHeight; + up.upscaleSize.width = desc.outputWidth; + up.upscaleSize.height = desc.outputHeight; + up.enableSharpening = false; + up.sharpness = 0.0f; + up.frameTimeDelta = std::max(0.001f, desc.frameTimeDeltaMs); + up.preExposure = 1.0f; + up.reset = desc.reset; + up.cameraNear = desc.cameraNear; + up.cameraFar = desc.cameraFar; + up.cameraFovAngleVertical = desc.cameraFovYRadians; + up.viewSpaceToMetersFactor = 1.0f; + up.flags = 0; + if (fns_->dispatch(reinterpret_cast(&genericUpscaleContext_), + reinterpret_cast(&up)) != FFX_API_RETURN_OK) { + lastError_ = "ffxDispatch (upscale) failed"; + return false; + } + lastError_.clear(); + return true; + } + if (!contextStorage_ || !fns_->fsr3ContextDispatchUpscale) { lastError_ = "official runtime upscale context unavailable"; return false; @@ -413,6 +626,67 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d lastError_ = "invalid frame generation dispatch resources"; return false; } + if (apiMode_ == ApiMode::GenericApi) { + if (!genericFramegenContext_ || !fns_->dispatch) { + lastError_ = "generic API frame generation context unavailable"; + return false; + } + ffxDispatchDescFrameGenerationPrepareV2 prep{}; + prep.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION_PREPARE_V2; + prep.header.pNext = nullptr; + prep.frameID = genericFrameId_; + prep.flags = 0; + prep.commandList = reinterpret_cast(desc.commandBuffer); + prep.renderSize.width = desc.renderWidth; + prep.renderSize.height = desc.renderHeight; + prep.jitterOffset.x = desc.jitterX; + prep.jitterOffset.y = desc.jitterY; + prep.motionVectorScale.x = desc.motionScaleX; + prep.motionVectorScale.y = desc.motionScaleY; + prep.frameTimeDelta = std::max(0.001f, desc.frameTimeDeltaMs); + prep.reset = desc.reset; + prep.cameraNear = desc.cameraNear; + prep.cameraFar = desc.cameraFar; + prep.cameraFovAngleVertical = desc.cameraFovYRadians; + prep.viewSpaceToMetersFactor = 1.0f; + prep.depth = ffxApiGetResourceVK(desc.depthImage, desc.depthFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + prep.motionVectors = ffxApiGetResourceVK(desc.motionVectorImage, desc.motionVectorFormat, desc.renderWidth, desc.renderHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + prep.cameraPosition[0] = prep.cameraPosition[1] = prep.cameraPosition[2] = 0.0f; + prep.cameraUp[0] = 0.0f; prep.cameraUp[1] = 1.0f; prep.cameraUp[2] = 0.0f; + prep.cameraRight[0] = 1.0f; prep.cameraRight[1] = 0.0f; prep.cameraRight[2] = 0.0f; + prep.cameraForward[0] = 0.0f; prep.cameraForward[1] = 0.0f; prep.cameraForward[2] = -1.0f; + if (fns_->dispatch(reinterpret_cast(&genericFramegenContext_), + reinterpret_cast(&prep)) != FFX_API_RETURN_OK) { + lastError_ = "ffxDispatch (framegeneration prepare) failed"; + return false; + } + + ffxDispatchDescFrameGeneration fg{}; + fg.header.type = FFX_API_DISPATCH_DESC_TYPE_FRAMEGENERATION; + fg.header.pNext = nullptr; + fg.commandList = reinterpret_cast(desc.commandBuffer); + fg.presentColor = ffxApiGetResourceVK(desc.outputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_COMPUTE_READ); + fg.outputs[0] = ffxApiGetResourceVK(desc.frameGenOutputImage, desc.outputFormat, desc.outputWidth, desc.outputHeight, FFX_API_RESOURCE_STATE_UNORDERED_ACCESS); + fg.numGeneratedFrames = 1; + fg.reset = desc.reset; + fg.backbufferTransferFunction = FFX_API_BACKBUFFER_TRANSFER_FUNCTION_SRGB; + fg.minMaxLuminance[0] = 0.0f; + fg.minMaxLuminance[1] = 1.0f; + fg.generationRect.left = 0; + fg.generationRect.top = 0; + fg.generationRect.width = desc.outputWidth; + fg.generationRect.height = desc.outputHeight; + fg.frameID = genericFrameId_; + if (fns_->dispatch(reinterpret_cast(&genericFramegenContext_), + reinterpret_cast(&fg)) != FFX_API_RETURN_OK) { + lastError_ = "ffxDispatch (framegeneration) failed"; + return false; + } + ++genericFrameId_; + lastError_.clear(); + return true; + } + if (!contextStorage_ || !fns_->fsr3DispatchFrameGeneration) { lastError_ = "official runtime frame generation context unavailable"; return false; @@ -449,7 +723,18 @@ bool AmdFsr3Runtime::dispatchFrameGeneration(const AmdFsr3RuntimeDispatchDesc& d void AmdFsr3Runtime::shutdown() { #if WOWEE_HAS_AMD_FSR3_FRAMEGEN - if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) { + if (apiMode_ == ApiMode::GenericApi && fns_ && fns_->destroyContext) { + if (genericFramegenContext_) { + auto ctx = reinterpret_cast(&genericFramegenContext_); + fns_->destroyContext(ctx, nullptr); + genericFramegenContext_ = nullptr; + } + if (genericUpscaleContext_) { + auto ctx = reinterpret_cast(&genericUpscaleContext_); + fns_->destroyContext(ctx, nullptr); + genericUpscaleContext_ = nullptr; + } + } else if (contextStorage_ && fns_ && fns_->fsr3ContextDestroy) { fns_->fsr3ContextDestroy(reinterpret_cast(contextStorage_)); } #endif @@ -476,6 +761,8 @@ void AmdFsr3Runtime::shutdown() { libHandle_ = nullptr; loadedLibraryPath_.clear(); loadPathKind_ = LoadPathKind::None; + apiMode_ = ApiMode::LegacyFsr3; + genericFrameId_ = 1; } } // namespace wowee::rendering diff --git a/src/rendering/performance_hud.cpp b/src/rendering/performance_hud.cpp index c78c61c3..09430dce 100644 --- a/src/rendering/performance_hud.cpp +++ b/src/rendering/performance_hud.cpp @@ -201,7 +201,7 @@ void PerformanceHUD::render(const Renderer* renderer, const Camera* camera) { } } if (renderer->isFSR2Enabled()) { - ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "FSR 2.2: ON"); + ImGui::TextColored(ImVec4(0.4f, 0.9f, 1.0f, 1.0f), "FSR 3 Upscale: ON"); ImGui::Text(" JitterSign=%.2f", renderer->getFSR2JitterSign()); const bool fgEnabled = renderer->isAmdFsr3FramegenEnabled(); const bool fgReady = renderer->isAmdFsr3FramegenRuntimeReady(); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index e53b261b..e0bff734 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -3899,7 +3899,9 @@ bool Renderer::initFSR2Resources() { 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."); + "path=", fsr2_.amdFsr3RuntimePath, + " error=", fsr2_.amdFsr3RuntimeLastError.empty() ? "(none)" : fsr2_.amdFsr3RuntimeLastError, + " runtimeLib=", fsr2_.amdFsr3Runtime->loadedLibraryPath().empty() ? "(not loaded)" : fsr2_.amdFsr3Runtime->loadedLibraryPath()); } } #endif diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index 318a4e23..10d5aa54 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -6308,7 +6308,7 @@ void GameScreen::renderSettingsWindow() { if (fsr2Active) { ImGui::BeginDisabled(); int disabled = 0; - ImGui::Combo("Anti-Aliasing (FSR2)", &disabled, "Off (FSR2 active)\0", 1); + ImGui::Combo("Anti-Aliasing (FSR3)", &disabled, "Off (FSR3 active)\0", 1); ImGui::EndDisabled(); } else if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) { static const VkSampleCountFlagBits aaSamples[] = { @@ -6321,8 +6321,8 @@ void GameScreen::renderSettingsWindow() { } // FSR Upscaling { - // FSR mode selection: Off, FSR 1.0 (Spatial), FSR 2.2 (Temporal) - const char* fsrModeLabels[] = { "Off", "FSR 1.0 (Spatial)", "FSR 2.2 (Temporal)" }; + // FSR mode selection: Off, FSR 1.0 (Spatial), FSR 3.x (Temporal) + const char* fsrModeLabels[] = { "Off", "FSR 1.0 (Spatial)", "FSR 3.x (Temporal)" }; int fsrMode = pendingUpscalingMode; if (ImGui::Combo("Upscaling", &fsrMode, fsrModeLabels, 3)) { pendingUpscalingMode = fsrMode; @@ -6335,7 +6335,7 @@ void GameScreen::renderSettingsWindow() { } if (fsrMode > 0) { if (fsrMode == 2 && renderer) { - ImGui::TextDisabled("FSR2 backend: %s", + ImGui::TextDisabled("FSR3 backend: %s", renderer->isAmdFsr2SdkAvailable() ? "AMD FidelityFX SDK" : "Internal fallback"); if (renderer->isAmdFsr3FramegenSdkAvailable()) { if (ImGui::Checkbox("AMD FSR3 Frame Generation (Experimental)", &pendingAMDFramegen)) { @@ -6387,7 +6387,7 @@ void GameScreen::renderSettingsWindow() { saveSettings(); } if (fsrMode == 2) { - ImGui::SeparatorText("FSR2 Tuning"); + ImGui::SeparatorText("FSR3 Tuning"); if (ImGui::SliderFloat("Jitter Sign", &pendingFSR2JitterSign, -2.0f, 2.0f, "%.2f")) { if (renderer) { renderer->setFSR2DebugTuning( diff --git a/tools/generate_ffx_sdk_vk_permutations.sh b/tools/generate_ffx_sdk_vk_permutations.sh new file mode 100755 index 00000000..f3dba149 --- /dev/null +++ b/tools/generate_ffx_sdk_vk_permutations.sh @@ -0,0 +1,195 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +SDK_ROOT="${1:-$ROOT_DIR/extern/FidelityFX-SDK}" +KITS_DIR="$SDK_ROOT/Kits/FidelityFX" +FFX_SC="$KITS_DIR/tools/ffx_sc/ffx_sc.py" +OUT_DIR="$KITS_DIR/framegeneration/fsr3/internal/permutations/vk" +SHADER_DIR="$KITS_DIR/upscalers/fsr3/internal/shaders" + +if [[ ! -f "$FFX_SC" ]]; then + echo "Missing ffx_sc.py at $FFX_SC" >&2 + exit 1 +fi + +required_headers=( + "$OUT_DIR/ffx_fsr2_accumulate_pass_wave64_16bit_permutations.h" + "$OUT_DIR/ffx_fsr3upscaler_accumulate_pass_wave64_16bit_permutations.h" + "$OUT_DIR/ffx_fsr3upscaler_autogen_reactive_pass_permutations.h" +) +if [[ "${WOWEE_FORCE_REGEN_PERMS:-0}" != "1" ]]; then + missing=0 + for h in "${required_headers[@]}"; do + [[ -f "$h" ]] || missing=1 + done + if [[ $missing -eq 0 ]]; then + echo "FidelityFX VK permutation headers already present." + exit 0 + fi +fi + +if [[ -z "${DXC:-}" ]]; then + if [[ -x /tmp/dxc/bin/dxc ]]; then + export DXC=/tmp/dxc/bin/dxc + elif command -v dxc >/dev/null 2>&1; then + export DXC="$(command -v dxc)" + elif [[ "$(uname -s)" == "Linux" ]]; then + echo "DXC not found; downloading Linux DXC release to /tmp/dxc ..." + tmp_json="$(mktemp)" + curl -sS https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest > "$tmp_json" + dxc_url="$(python3 - << 'PY' "$tmp_json" +import json, sys +with open(sys.argv[1], 'r', encoding='utf-8') as f: + data = json.load(f) +for a in data.get('assets', []): + name = a.get('name', '') + if name.startswith('linux_dxc_') and name.endswith('.x86_64.tar.gz'): + print(a.get('browser_download_url', '')) + break +PY +)" + rm -f "$tmp_json" + if [[ -z "$dxc_url" ]]; then + echo "Failed to locate Linux DXC release asset URL." >&2 + exit 1 + fi + rm -rf /tmp/dxc /tmp/linux_dxc.tar.gz + curl -L --fail "$dxc_url" -o /tmp/linux_dxc.tar.gz + mkdir -p /tmp/dxc + tar -xzf /tmp/linux_dxc.tar.gz -C /tmp/dxc --strip-components=1 + export DXC=/tmp/dxc/bin/dxc + elif [[ "$(uname -s)" =~ MINGW|MSYS|CYGWIN ]]; then + echo "DXC not found; downloading Windows DXC release to /tmp/dxc ..." + tmp_json="$(mktemp)" + curl -sS https://api.github.com/repos/microsoft/DirectXShaderCompiler/releases/latest > "$tmp_json" + dxc_url="$(python3 - << 'PY' "$tmp_json" +import json, sys +with open(sys.argv[1], 'r', encoding='utf-8') as f: + data = json.load(f) +for a in data.get('assets', []): + name = a.get('name', '') + if name.startswith('dxc_') and name.endswith('.zip'): + print(a.get('browser_download_url', '')) + break +PY +)" + rm -f "$tmp_json" + if [[ -z "$dxc_url" ]]; then + echo "Failed to locate Windows DXC release asset URL." >&2 + exit 1 + fi + rm -rf /tmp/dxc /tmp/dxc_win.zip + curl -L --fail "$dxc_url" -o /tmp/dxc_win.zip + mkdir -p /tmp/dxc + unzip -q /tmp/dxc_win.zip -d /tmp/dxc + if [[ -x /tmp/dxc/bin/x64/dxc.exe ]]; then + export DXC=/tmp/dxc/bin/x64/dxc.exe + elif [[ -x /tmp/dxc/bin/x86/dxc.exe ]]; then + export DXC=/tmp/dxc/bin/x86/dxc.exe + else + echo "DXC download succeeded, but dxc.exe was not found." >&2 + exit 1 + fi + else + echo "DXC not found. Set DXC=/path/to/dxc or install to /tmp/dxc/bin/dxc" >&2 + exit 1 + fi +fi + +mkdir -p "$OUT_DIR" + +# First generate frame interpolation + optical flow permutations via SDK script. +( + cd "$SDK_ROOT" + ./generate_vk_permutations.sh +) + +BASE_ARGS=(-reflection -embed-arguments -E CS -Wno-for-redefinition -Wno-ambig-lit-shift -DFFX_GPU=1 -DFFX_HLSL=1 -DFFX_IMPLICIT_SHADER_REGISTER_BINDING_HLSL=0) +WAVE32=(-DFFX_HLSL_SM=62 -T cs_6_2) +WAVE64=("-DFFX_PREFER_WAVE64=[WaveSize(64)]" -DFFX_HLSL_SM=66 -T cs_6_6) +BIT16=(-DFFX_HALF=1 -enable-16bit-types) + +compile_shader() { + local file="$1"; shift + local name="$1"; shift + python3 "$FFX_SC" "${BASE_ARGS[@]}" "$@" -name="$name" -output="$OUT_DIR" "$file" +} + +# FSR2 (for upscalers/fsr3/internal/ffx_fsr2_shaderblobs.cpp) +FSR2_COMMON=( + -DFFX_FSR2_EMBED_ROOTSIG=0 + -DFFX_FSR2_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR2_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR2_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=1 + -DFFX_FSR2_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR2_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2 + "-DFFX_FSR2_OPTION_REPROJECT_USE_LANCZOS_TYPE={0,1}" + "-DFFX_FSR2_OPTION_HDR_COLOR_INPUT={0,1}" + "-DFFX_FSR2_OPTION_LOW_RESOLUTION_MOTION_VECTORS={0,1}" + "-DFFX_FSR2_OPTION_JITTERED_MOTION_VECTORS={0,1}" + "-DFFX_FSR2_OPTION_INVERTED_DEPTH={0,1}" + "-DFFX_FSR2_OPTION_APPLY_SHARPENING={0,1}" + -I "$KITS_DIR/api/internal/include/gpu" + -I "$KITS_DIR/upscalers/fsr3/include/gpu" +) +FSR2_SHADERS=( + ffx_fsr2_autogen_reactive_pass + ffx_fsr2_accumulate_pass + ffx_fsr2_compute_luminance_pyramid_pass + ffx_fsr2_depth_clip_pass + ffx_fsr2_lock_pass + ffx_fsr2_reconstruct_previous_depth_pass + ffx_fsr2_rcas_pass + ffx_fsr2_tcr_autogen_pass +) + +for shader in "${FSR2_SHADERS[@]}"; do + file="$SHADER_DIR/$shader.hlsl" + [[ -f "$file" ]] || continue + compile_shader "$file" "$shader" -DFFX_HALF=0 "${WAVE32[@]}" "${FSR2_COMMON[@]}" + compile_shader "$file" "${shader}_wave64" -DFFX_HALF=0 "${WAVE64[@]}" "${FSR2_COMMON[@]}" + compile_shader "$file" "${shader}_16bit" "${BIT16[@]}" "${WAVE32[@]}" "${FSR2_COMMON[@]}" + compile_shader "$file" "${shader}_wave64_16bit" "${BIT16[@]}" "${WAVE64[@]}" "${FSR2_COMMON[@]}" +done + +# FSR3 upscaler (for upscalers/fsr3/internal/ffx_fsr3upscaler_shaderblobs.cpp) +FSR3_COMMON=( + -DFFX_FSR3UPSCALER_EMBED_ROOTSIG=0 + -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR3UPSCALER_OPTION_ACCUMULATE_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR3UPSCALER_OPTION_REPROJECT_SAMPLERS_USE_DATA_HALF=1 + -DFFX_FSR3UPSCALER_OPTION_POSTPROCESSLOCKSTATUS_SAMPLERS_USE_DATA_HALF=0 + -DFFX_FSR3UPSCALER_OPTION_UPSAMPLE_USE_LANCZOS_TYPE=2 + "-DFFX_FSR3UPSCALER_OPTION_REPROJECT_USE_LANCZOS_TYPE={0,1}" + "-DFFX_FSR3UPSCALER_OPTION_HDR_COLOR_INPUT={0,1}" + "-DFFX_FSR3UPSCALER_OPTION_LOW_RESOLUTION_MOTION_VECTORS={0,1}" + "-DFFX_FSR3UPSCALER_OPTION_JITTERED_MOTION_VECTORS={0,1}" + "-DFFX_FSR3UPSCALER_OPTION_INVERTED_DEPTH={0,1}" + "-DFFX_FSR3UPSCALER_OPTION_APPLY_SHARPENING={0,1}" + -I "$KITS_DIR/api/internal/gpu" + -I "$KITS_DIR/upscalers/fsr3/include/gpu" +) +FSR3_SHADERS=( + ffx_fsr3upscaler_autogen_reactive_pass + ffx_fsr3upscaler_accumulate_pass + ffx_fsr3upscaler_luma_pyramid_pass + ffx_fsr3upscaler_prepare_reactivity_pass + ffx_fsr3upscaler_prepare_inputs_pass + ffx_fsr3upscaler_shading_change_pass + ffx_fsr3upscaler_rcas_pass + ffx_fsr3upscaler_shading_change_pyramid_pass + ffx_fsr3upscaler_luma_instability_pass + ffx_fsr3upscaler_debug_view_pass +) + +for shader in "${FSR3_SHADERS[@]}"; do + file="$SHADER_DIR/$shader.hlsl" + [[ -f "$file" ]] || continue + compile_shader "$file" "$shader" -DFFX_HALF=0 "${WAVE32[@]}" "${FSR3_COMMON[@]}" + compile_shader "$file" "${shader}_wave64" -DFFX_HALF=0 "${WAVE64[@]}" "${FSR3_COMMON[@]}" + compile_shader "$file" "${shader}_16bit" "${BIT16[@]}" "${WAVE32[@]}" "${FSR3_COMMON[@]}" + compile_shader "$file" "${shader}_wave64_16bit" "${BIT16[@]}" "${WAVE64[@]}" "${FSR3_COMMON[@]}" +done + +echo "Generated VK permutation headers in $OUT_DIR"