diff --git a/CMakeLists.txt b/CMakeLists.txt index 9e0ba658..bed96594 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,24 @@ if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN ${WOWEE_AMD_FFX_SDK_DIR}/include ) target_link_libraries(wowee_fsr3_framegen_amd_vk_probe PUBLIC Vulkan::Vulkan) + + add_library(wowee_fsr3_vk_wrapper SHARED + src/rendering/amd_fsr3_wrapper_impl.cpp + include/rendering/amd_fsr3_wrapper_abi.h + ) + set_target_properties(wowee_fsr3_vk_wrapper PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + OUTPUT_NAME ffx_fsr3_vk_wrapper + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_ARCHIVE_OUTPUT_DIRECTORY} + ) + target_include_directories(wowee_fsr3_vk_wrapper PUBLIC + ${CMAKE_SOURCE_DIR}/include + ${WOWEE_AMD_FFX_SDK_DIR}/include + ) + target_link_libraries(wowee_fsr3_vk_wrapper PUBLIC Vulkan::Vulkan ${CMAKE_DL_LIBS}) else() add_compile_definitions(WOWEE_HAS_AMD_FSR3_FRAMEGEN=0) if(WOWEE_ENABLE_AMD_FSR3_FRAMEGEN) diff --git a/README.md b/README.md index 5ba51f60..9973b3a4 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,9 @@ make -j$(nproc) - `https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git` - Ref: - `v1.1.4` (depth-1 clone) + - Override with env vars: + - `WOWEE_FFX_SDK_REPO=https://github.com//FidelityFX-SDK.git` + - `WOWEE_FFX_SDK_REF=` - This ref includes Vulkan framegen building blocks (`frameinterpolation` + `opticalflow`) and Vulkan shader manifests: - `sdk/src/backends/vk/CMakeShadersFrameinterpolation.txt` - `sdk/src/backends/vk/CMakeShadersOpticalflow.txt` @@ -202,6 +205,9 @@ make -j$(nproc) - `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (platform extension varies). - Wrapper override path: - `WOWEE_FFX_SDK_RUNTIME_WRAPPER_LIB=/absolute/path/to/libffx_fsr3_vk_wrapper.so` (platform extension varies). + - In-tree wrapper build: + - `wowee_fsr3_vk_wrapper` now builds automatically when FSR3 SDK headers are present and outputs to `build/bin/libffx_fsr3_vk_wrapper.*`. + - Wrapper backend override (what the wrapper loads underneath): `WOWEE_FSR3_WRAPPER_BACKEND_LIB=/absolute/path/to/libffx_fsr3_vk.so`. - Path B wrapper libraries must export the clean wrapper ABI (`include/rendering/amd_fsr3_wrapper_abi.h`): - `wowee_fsr3_wrapper_get_abi_version` - `wowee_fsr3_wrapper_initialize` diff --git a/build.ps1 b/build.ps1 index 35487f76..206b9909 100644 --- a/build.ps1 +++ b/build.ps1 @@ -33,7 +33,8 @@ function Ensure-Fsr2Sdk { function Ensure-FidelityFxSdk { $sdkDir = Join-Path $ScriptDir "extern\FidelityFX-SDK" $sdkHeader = Join-Path $sdkDir "sdk\include\FidelityFX\host\ffx_frameinterpolation.h" - $sdkRef = "v1.1.4" + $sdkRepo = if ($env:WOWEE_FFX_SDK_REPO) { $env:WOWEE_FFX_SDK_REPO } else { "https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git" } + $sdkRef = if ($env:WOWEE_FFX_SDK_REF) { $env:WOWEE_FFX_SDK_REF } else { "v1.1.4" } if (Test-Path $sdkHeader) { return } if (-not (Get-Command git -ErrorAction SilentlyContinue)) { @@ -41,9 +42,9 @@ function Ensure-FidelityFxSdk { return } - Write-Host "Fetching AMD FidelityFX SDK ($sdkRef) into $sdkDir ..." + Write-Host "Fetching AMD FidelityFX SDK ($sdkRef from $sdkRepo) into $sdkDir ..." New-Item -ItemType Directory -Path (Join-Path $ScriptDir "extern") -Force | Out-Null - & git clone --depth 1 --branch $sdkRef https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git $sdkDir + & git clone --depth 1 --branch $sdkRef $sdkRepo $sdkDir if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to clone AMD FidelityFX SDK. FSR3 framegen extern will be unavailable." } diff --git a/build.sh b/build.sh index 8279cddc..4a7f3f41 100755 --- a/build.sh +++ b/build.sh @@ -25,7 +25,8 @@ ensure_fsr2_sdk() { ensure_fidelityfx_sdk() { local sdk_dir="extern/FidelityFX-SDK" local sdk_header="$sdk_dir/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" - local sdk_ref="v1.1.4" + local sdk_repo="${WOWEE_FFX_SDK_REPO:-https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git}" + local sdk_ref="${WOWEE_FFX_SDK_REF:-v1.1.4}" if [ -f "$sdk_header" ]; then return fi @@ -33,9 +34,9 @@ ensure_fidelityfx_sdk() { echo "Warning: git not found; cannot auto-fetch AMD FidelityFX SDK." return fi - echo "Fetching AMD FidelityFX SDK ($sdk_ref) into $sdk_dir ..." + echo "Fetching AMD FidelityFX SDK ($sdk_ref from $sdk_repo) into $sdk_dir ..." mkdir -p extern - git clone --depth 1 --branch "$sdk_ref" https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git "$sdk_dir" || { + git clone --depth 1 --branch "$sdk_ref" "$sdk_repo" "$sdk_dir" || { echo "Warning: failed to clone AMD FidelityFX SDK. FSR3 framegen extern will be unavailable." } } diff --git a/docs/AMD_FSR2_INTEGRATION.md b/docs/AMD_FSR2_INTEGRATION.md index 9de2c238..b96d5fef 100644 --- a/docs/AMD_FSR2_INTEGRATION.md +++ b/docs/AMD_FSR2_INTEGRATION.md @@ -15,6 +15,11 @@ FidelityFX SDK checkout path (framegen extern): `extern/FidelityFX-SDK` (pinned to `v1.1.4` in build scripts and CI) +Override knobs for local build scripts: + +- `WOWEE_FFX_SDK_REPO` (default: `https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git`) +- `WOWEE_FFX_SDK_REF` (default: `v1.1.4`) + Detection expects: - `extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h` @@ -43,6 +48,10 @@ Runtime note: - `WOWEE_FFX_SDK_RUNTIME_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`). - You can point to an explicit wrapper binary with: - `WOWEE_FFX_SDK_RUNTIME_WRAPPER_LIB=/absolute/path/to/libffx_fsr3_vk_wrapper.so` (or `.dll` / `.dylib`). +- WoWee now ships an in-tree wrapper target: + - `wowee_fsr3_vk_wrapper` (output in `build/bin`). +- Wrapper backend runtime override: + - `WOWEE_FSR3_WRAPPER_BACKEND_LIB=/absolute/path/to/libffx_fsr3_vk.so` (or `.dll` / `.dylib`). - Path B wrapper ABI contract is declared in: - `include/rendering/amd_fsr3_wrapper_abi.h` - Required wrapper exports: diff --git a/rebuild.ps1 b/rebuild.ps1 index b0f74f6d..bcc951ba 100644 --- a/rebuild.ps1 +++ b/rebuild.ps1 @@ -33,7 +33,8 @@ function Ensure-Fsr2Sdk { function Ensure-FidelityFxSdk { $sdkDir = Join-Path $ScriptDir "extern\FidelityFX-SDK" $sdkHeader = Join-Path $sdkDir "sdk\include\FidelityFX\host\ffx_frameinterpolation.h" - $sdkRef = "v1.1.4" + $sdkRepo = if ($env:WOWEE_FFX_SDK_REPO) { $env:WOWEE_FFX_SDK_REPO } else { "https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git" } + $sdkRef = if ($env:WOWEE_FFX_SDK_REF) { $env:WOWEE_FFX_SDK_REF } else { "v1.1.4" } if (Test-Path $sdkHeader) { return } if (-not (Get-Command git -ErrorAction SilentlyContinue)) { @@ -41,9 +42,9 @@ function Ensure-FidelityFxSdk { return } - Write-Host "Fetching AMD FidelityFX SDK ($sdkRef) into $sdkDir ..." + Write-Host "Fetching AMD FidelityFX SDK ($sdkRef from $sdkRepo) into $sdkDir ..." New-Item -ItemType Directory -Path (Join-Path $ScriptDir "extern") -Force | Out-Null - & git clone --depth 1 --branch $sdkRef https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git $sdkDir + & git clone --depth 1 --branch $sdkRef $sdkRepo $sdkDir if ($LASTEXITCODE -ne 0) { Write-Warning "Failed to clone AMD FidelityFX SDK. FSR3 framegen extern will be unavailable." } diff --git a/rebuild.sh b/rebuild.sh index 000426a8..04e02b88 100755 --- a/rebuild.sh +++ b/rebuild.sh @@ -25,7 +25,8 @@ ensure_fsr2_sdk() { ensure_fidelityfx_sdk() { local sdk_dir="extern/FidelityFX-SDK" local sdk_header="$sdk_dir/sdk/include/FidelityFX/host/ffx_frameinterpolation.h" - local sdk_ref="v1.1.4" + local sdk_repo="${WOWEE_FFX_SDK_REPO:-https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git}" + local sdk_ref="${WOWEE_FFX_SDK_REF:-v1.1.4}" if [ -f "$sdk_header" ]; then return fi @@ -33,9 +34,9 @@ ensure_fidelityfx_sdk() { echo "Warning: git not found; cannot auto-fetch AMD FidelityFX SDK." return fi - echo "Fetching AMD FidelityFX SDK ($sdk_ref) into $sdk_dir ..." + echo "Fetching AMD FidelityFX SDK ($sdk_ref from $sdk_repo) into $sdk_dir ..." mkdir -p extern - git clone --depth 1 --branch "$sdk_ref" https://github.com/GPUOpen-LibrariesAndSDKs/FidelityFX-SDK.git "$sdk_dir" || { + git clone --depth 1 --branch "$sdk_ref" "$sdk_repo" "$sdk_dir" || { echo "Warning: failed to clone AMD FidelityFX SDK. FSR3 framegen extern will be unavailable." } } diff --git a/src/rendering/amd_fsr3_wrapper_impl.cpp b/src/rendering/amd_fsr3_wrapper_impl.cpp new file mode 100644 index 00000000..c415a1f4 --- /dev/null +++ b/src/rendering/amd_fsr3_wrapper_impl.cpp @@ -0,0 +1,477 @@ +#include "rendering/amd_fsr3_wrapper_abi.h" + +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#else +#include +#endif + +#if defined(_WIN32) +#define WOWEE_FSR3_WRAPPER_EXPORT extern "C" __declspec(dllexport) +#else +#define WOWEE_FSR3_WRAPPER_EXPORT extern "C" __attribute__((visibility("default"))) +#endif + +namespace { + +void writeError(char* outErrorText, uint32_t outErrorTextCapacity, const char* message) { + if (!outErrorText || outErrorTextCapacity == 0) return; + if (!message) message = "unknown error"; + std::strncpy(outErrorText, message, static_cast(outErrorTextCapacity - 1)); + outErrorText[outErrorTextCapacity - 1] = '\0'; +} + +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN +FfxErrorCode vkSwapchainConfigureNoop(const FfxFrameGenerationConfig*) { + return FFX_OK; +} + +template +struct HasUpscaleOutputSize : std::false_type {}; + +template +struct HasUpscaleOutputSize().upscaleOutputSize)>> : std::true_type {}; + +template +inline void setUpscaleOutputSizeIfPresent(T& ctxDesc, uint32_t width, uint32_t height) { + if constexpr (HasUpscaleOutputSize::value) { + ctxDesc.upscaleOutputSize.width = width; + ctxDesc.upscaleOutputSize.height = height; + } else { + (void)ctxDesc; + (void)width; + (void)height; + } +} + +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; +} + +struct 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(&ffxFsr3ConfigureFrameGeneration) fsr3ConfigureFrameGeneration = nullptr; + decltype(&ffxFsr3DispatchFrameGeneration) fsr3DispatchFrameGeneration = nullptr; + decltype(&ffxFsr3ContextDestroy) fsr3ContextDestroy = nullptr; +}; + +struct WrapperContext { + void* backendLibHandle = nullptr; + RuntimeFns fns{}; + void* scratchBuffer = nullptr; + size_t scratchBufferSize = 0; + void* fsr3ContextStorage = nullptr; + bool frameGenerationReady = false; +}; + +void closeLibrary(void* handle) { + if (!handle) return; +#if defined(_WIN32) + FreeLibrary(reinterpret_cast(handle)); +#else + dlclose(handle); +#endif +} + +void* openLibrary(const char* path) { + if (!path || !*path) return nullptr; +#if defined(_WIN32) + return reinterpret_cast(LoadLibraryA(path)); +#else + return dlopen(path, RTLD_NOW | RTLD_LOCAL); +#endif +} + +void* resolveSymbol(void* handle, const char* symbol) { + if (!handle || !symbol) return nullptr; +#if defined(_WIN32) + return reinterpret_cast(GetProcAddress(reinterpret_cast(handle), symbol)); +#else + return dlsym(handle, symbol); +#endif +} + +void destroyContext(WrapperContext* ctx) { + if (!ctx) return; + if (ctx->fsr3ContextStorage && ctx->fns.fsr3ContextDestroy) { + ctx->fns.fsr3ContextDestroy(reinterpret_cast(ctx->fsr3ContextStorage)); + } + if (ctx->fsr3ContextStorage) { + std::free(ctx->fsr3ContextStorage); + ctx->fsr3ContextStorage = nullptr; + } + if (ctx->scratchBuffer) { + std::free(ctx->scratchBuffer); + ctx->scratchBuffer = nullptr; + } + ctx->scratchBufferSize = 0; + closeLibrary(ctx->backendLibHandle); + ctx->backendLibHandle = nullptr; + delete ctx; +} +#endif + +} // namespace + +WOWEE_FSR3_WRAPPER_EXPORT uint32_t wowee_fsr3_wrapper_get_abi_version(void) { + return WOWEE_FSR3_WRAPPER_ABI_VERSION; +} + +WOWEE_FSR3_WRAPPER_EXPORT const char* wowee_fsr3_wrapper_get_name(void) { + return "WoWee FSR3 DX12/VK Wrapper"; +} + +WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_initialize(const WoweeFsr3WrapperInitDesc* initDesc, + WoweeFsr3WrapperContext* outContext, + char* outErrorText, + uint32_t outErrorTextCapacity) { + if (outContext) *outContext = nullptr; +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)initDesc; + writeError(outErrorText, outErrorTextCapacity, "wrapper built without WOWEE_HAS_AMD_FSR3_FRAMEGEN"); + return -1; +#else + if (!initDesc || !outContext) { + writeError(outErrorText, outErrorTextCapacity, "invalid init args"); + return -1; + } + if (initDesc->structSize < sizeof(WoweeFsr3WrapperInitDesc) || + initDesc->abiVersion != WOWEE_FSR3_WRAPPER_ABI_VERSION) { + writeError(outErrorText, outErrorTextCapacity, "wrapper ABI/version mismatch"); + return -1; + } + if (!initDesc->physicalDevice || !initDesc->device || !initDesc->getDeviceProcAddr || + initDesc->maxRenderWidth == 0 || initDesc->maxRenderHeight == 0 || + initDesc->displayWidth == 0 || initDesc->displayHeight == 0 || + initDesc->colorFormat == VK_FORMAT_UNDEFINED) { + writeError(outErrorText, outErrorTextCapacity, "invalid init descriptor values"); + return -1; + } + + std::vector candidates; + if (const char* backendEnv = std::getenv("WOWEE_FSR3_WRAPPER_BACKEND_LIB")) { + if (*backendEnv) candidates.emplace_back(backendEnv); + } + if (const char* runtimeEnv = std::getenv("WOWEE_FFX_SDK_RUNTIME_LIB")) { + if (*runtimeEnv) candidates.emplace_back(runtimeEnv); + } +#if defined(_WIN32) + candidates.emplace_back("ffx_fsr3_vk.dll"); + candidates.emplace_back("ffx_fsr3.dll"); + candidates.emplace_back("ffx_fsr3_bridge.dll"); +#elif defined(__APPLE__) + candidates.emplace_back("libffx_fsr3_vk.dylib"); + candidates.emplace_back("libffx_fsr3.dylib"); + candidates.emplace_back("libffx_fsr3_bridge.dylib"); +#else + candidates.emplace_back("./libffx_fsr3_vk.so"); + candidates.emplace_back("libffx_fsr3_vk.so"); + candidates.emplace_back("libffx_fsr3.so"); + candidates.emplace_back("libffx_fsr3_bridge.so"); +#endif + + WrapperContext* ctx = new WrapperContext{}; + for (const std::string& path : candidates) { + ctx->backendLibHandle = openLibrary(path.c_str()); + if (ctx->backendLibHandle) break; + } + if (!ctx->backendLibHandle) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "no FSR3 backend runtime found for wrapper"); + return -1; + } + + ctx->fns.getScratchMemorySizeVK = reinterpret_castfns.getScratchMemorySizeVK)>(resolveSymbol(ctx->backendLibHandle, "ffxGetScratchMemorySizeVK")); + ctx->fns.getDeviceVK = reinterpret_castfns.getDeviceVK)>(resolveSymbol(ctx->backendLibHandle, "ffxGetDeviceVK")); + ctx->fns.getInterfaceVK = reinterpret_castfns.getInterfaceVK)>(resolveSymbol(ctx->backendLibHandle, "ffxGetInterfaceVK")); + ctx->fns.getCommandListVK = reinterpret_castfns.getCommandListVK)>(resolveSymbol(ctx->backendLibHandle, "ffxGetCommandListVK")); + ctx->fns.getResourceVK = reinterpret_castfns.getResourceVK)>(resolveSymbol(ctx->backendLibHandle, "ffxGetResourceVK")); + ctx->fns.fsr3ContextCreate = reinterpret_castfns.fsr3ContextCreate)>(resolveSymbol(ctx->backendLibHandle, "ffxFsr3ContextCreate")); + ctx->fns.fsr3ContextDispatchUpscale = reinterpret_castfns.fsr3ContextDispatchUpscale)>(resolveSymbol(ctx->backendLibHandle, "ffxFsr3ContextDispatchUpscale")); + ctx->fns.fsr3ConfigureFrameGeneration = reinterpret_castfns.fsr3ConfigureFrameGeneration)>(resolveSymbol(ctx->backendLibHandle, "ffxFsr3ConfigureFrameGeneration")); + ctx->fns.fsr3DispatchFrameGeneration = reinterpret_castfns.fsr3DispatchFrameGeneration)>(resolveSymbol(ctx->backendLibHandle, "ffxFsr3DispatchFrameGeneration")); + ctx->fns.fsr3ContextDestroy = reinterpret_castfns.fsr3ContextDestroy)>(resolveSymbol(ctx->backendLibHandle, "ffxFsr3ContextDestroy")); + + if (!ctx->fns.getScratchMemorySizeVK || !ctx->fns.getDeviceVK || !ctx->fns.getInterfaceVK || + !ctx->fns.getCommandListVK || !ctx->fns.getResourceVK || !ctx->fns.fsr3ContextCreate || + !ctx->fns.fsr3ContextDispatchUpscale || !ctx->fns.fsr3ContextDestroy) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "backend missing required ffx symbols"); + return -1; + } + + const bool enableFrameGeneration = (initDesc->enableFlags & WOWEE_FSR3_WRAPPER_ENABLE_FRAME_GENERATION) != 0u; + if (enableFrameGeneration && (!ctx->fns.fsr3ConfigureFrameGeneration || !ctx->fns.fsr3DispatchFrameGeneration)) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "backend missing frame generation symbols"); + return -1; + } + + ctx->scratchBufferSize = ctx->fns.getScratchMemorySizeVK(initDesc->physicalDevice, FFX_FSR3_CONTEXT_COUNT); + if (ctx->scratchBufferSize == 0) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "scratch buffer size query returned 0"); + return -1; + } + ctx->scratchBuffer = std::malloc(ctx->scratchBufferSize); + if (!ctx->scratchBuffer) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "scratch buffer allocation failed"); + return -1; + } + + VkDeviceContext vkDevCtx{}; + vkDevCtx.vkDevice = initDesc->device; + vkDevCtx.vkPhysicalDevice = initDesc->physicalDevice; + vkDevCtx.vkDeviceProcAddr = initDesc->getDeviceProcAddr; + + FfxDevice ffxDevice = ctx->fns.getDeviceVK(&vkDevCtx); + FfxInterface backendShared{}; + FfxErrorCode ifaceErr = ctx->fns.getInterfaceVK( + &backendShared, ffxDevice, ctx->scratchBuffer, ctx->scratchBufferSize, FFX_FSR3_CONTEXT_COUNT); + if (ifaceErr != FFX_OK) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "ffxGetInterfaceVK failed"); + return -1; + } + + FfxFsr3ContextDescription fsr3Desc{}; + fsr3Desc.flags = FFX_FSR3_ENABLE_AUTO_EXPOSURE | FFX_FSR3_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION; + if (!enableFrameGeneration) fsr3Desc.flags |= FFX_FSR3_ENABLE_UPSCALING_ONLY; + if (initDesc->enableFlags & WOWEE_FSR3_WRAPPER_ENABLE_HDR_INPUT) fsr3Desc.flags |= FFX_FSR3_ENABLE_HIGH_DYNAMIC_RANGE; + if (initDesc->enableFlags & WOWEE_FSR3_WRAPPER_ENABLE_DEPTH_INVERTED) fsr3Desc.flags |= FFX_FSR3_ENABLE_DEPTH_INVERTED; + fsr3Desc.maxRenderSize.width = initDesc->maxRenderWidth; + fsr3Desc.maxRenderSize.height = initDesc->maxRenderHeight; + setUpscaleOutputSizeIfPresent(fsr3Desc, initDesc->displayWidth, initDesc->displayHeight); + fsr3Desc.displaySize.width = initDesc->displayWidth; + fsr3Desc.displaySize.height = initDesc->displayHeight; + if (!backendShared.fpSwapChainConfigureFrameGeneration) { + backendShared.fpSwapChainConfigureFrameGeneration = vkSwapchainConfigureNoop; + } + fsr3Desc.backendInterfaceSharedResources = backendShared; + fsr3Desc.backendInterfaceUpscaling = backendShared; + fsr3Desc.backendInterfaceFrameInterpolation = backendShared; + fsr3Desc.backBufferFormat = mapVkFormatToFfxSurfaceFormat(initDesc->colorFormat, false); + + ctx->fsr3ContextStorage = std::malloc(sizeof(FfxFsr3Context)); + if (!ctx->fsr3ContextStorage) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "FSR3 context allocation failed"); + return -1; + } + std::memset(ctx->fsr3ContextStorage, 0, sizeof(FfxFsr3Context)); + + FfxErrorCode createErr = ctx->fns.fsr3ContextCreate(reinterpret_cast(ctx->fsr3ContextStorage), &fsr3Desc); + if (createErr != FFX_OK) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "ffxFsr3ContextCreate failed"); + return -1; + } + + if (enableFrameGeneration) { + FfxFrameGenerationConfig fgCfg{}; + fgCfg.frameGenerationEnabled = true; + fgCfg.allowAsyncWorkloads = false; + fgCfg.flags = 0; + fgCfg.onlyPresentInterpolated = false; + FfxErrorCode cfgErr = ctx->fns.fsr3ConfigureFrameGeneration(reinterpret_cast(ctx->fsr3ContextStorage), &fgCfg); + if (cfgErr != FFX_OK) { + destroyContext(ctx); + writeError(outErrorText, outErrorTextCapacity, "ffxFsr3ConfigureFrameGeneration failed"); + return -1; + } + ctx->frameGenerationReady = true; + } + + *outContext = reinterpret_cast(ctx); + writeError(outErrorText, outErrorTextCapacity, ""); + return 0; +#endif +} + +WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_upscale(WoweeFsr3WrapperContext context, + const WoweeFsr3WrapperDispatchDesc* dispatchDesc) { +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)context; + (void)dispatchDesc; + return -1; +#else + if (!context || !dispatchDesc) return -1; + if (dispatchDesc->structSize < sizeof(WoweeFsr3WrapperDispatchDesc)) return -1; + if (!dispatchDesc->commandBuffer || !dispatchDesc->colorImage || !dispatchDesc->depthImage || + !dispatchDesc->motionVectorImage || !dispatchDesc->outputImage) return -1; + WrapperContext* ctx = reinterpret_cast(context); + + FfxResourceDescription colorDesc = makeResourceDescription( + dispatchDesc->colorFormat, dispatchDesc->renderWidth, dispatchDesc->renderHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription depthDesc = makeResourceDescription( + dispatchDesc->depthFormat, dispatchDesc->renderWidth, dispatchDesc->renderHeight, FFX_RESOURCE_USAGE_DEPTHTARGET, true); + FfxResourceDescription mvDesc = makeResourceDescription( + dispatchDesc->motionVectorFormat, dispatchDesc->renderWidth, dispatchDesc->renderHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription outDesc = makeResourceDescription( + dispatchDesc->outputFormat, dispatchDesc->outputWidth, dispatchDesc->outputHeight, FFX_RESOURCE_USAGE_UAV); + + 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"; + FfxFsr3DispatchUpscaleDescription dispatch{}; + dispatch.commandList = ctx->fns.getCommandListVK(dispatchDesc->commandBuffer); + dispatch.color = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->colorImage), colorDesc, kColorName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.depth = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->depthImage), depthDesc, kDepthName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.motionVectors = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->motionVectorImage), mvDesc, kMotionName, FFX_RESOURCE_STATE_COMPUTE_READ); + dispatch.exposure = FfxResource{}; + dispatch.reactive = FfxResource{}; + dispatch.transparencyAndComposition = FfxResource{}; + dispatch.upscaleOutput = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->outputImage), outDesc, kOutputName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); + dispatch.jitterOffset.x = dispatchDesc->jitterX; + dispatch.jitterOffset.y = dispatchDesc->jitterY; + dispatch.motionVectorScale.x = dispatchDesc->motionScaleX; + dispatch.motionVectorScale.y = dispatchDesc->motionScaleY; + dispatch.renderSize.width = dispatchDesc->renderWidth; + dispatch.renderSize.height = dispatchDesc->renderHeight; + dispatch.enableSharpening = false; + dispatch.sharpness = 0.0f; + dispatch.frameTimeDelta = std::max(0.001f, dispatchDesc->frameTimeDeltaMs); + dispatch.preExposure = 1.0f; + dispatch.reset = (dispatchDesc->reset != 0u); + dispatch.cameraNear = dispatchDesc->cameraNear; + dispatch.cameraFar = dispatchDesc->cameraFar; + dispatch.cameraFovAngleVertical = dispatchDesc->cameraFovYRadians; + dispatch.viewSpaceToMetersFactor = 1.0f; + + return (ctx->fns.fsr3ContextDispatchUpscale(reinterpret_cast(ctx->fsr3ContextStorage), &dispatch) == FFX_OK) ? 0 : -1; +#endif +} + +WOWEE_FSR3_WRAPPER_EXPORT int32_t wowee_fsr3_wrapper_dispatch_framegen(WoweeFsr3WrapperContext context, + const WoweeFsr3WrapperDispatchDesc* dispatchDesc) { +#if !WOWEE_HAS_AMD_FSR3_FRAMEGEN + (void)context; + (void)dispatchDesc; + return -1; +#else + if (!context || !dispatchDesc) return -1; + if (dispatchDesc->structSize < sizeof(WoweeFsr3WrapperDispatchDesc)) return -1; + if (!dispatchDesc->commandBuffer || !dispatchDesc->outputImage || !dispatchDesc->frameGenOutputImage) return -1; + WrapperContext* ctx = reinterpret_cast(context); + if (!ctx->frameGenerationReady || !ctx->fns.fsr3DispatchFrameGeneration) return -1; + + FfxResourceDescription presentDesc = makeResourceDescription( + dispatchDesc->outputFormat, dispatchDesc->outputWidth, dispatchDesc->outputHeight, FFX_RESOURCE_USAGE_READ_ONLY); + FfxResourceDescription fgOutDesc = makeResourceDescription( + dispatchDesc->outputFormat, dispatchDesc->outputWidth, dispatchDesc->outputHeight, FFX_RESOURCE_USAGE_UAV); + + static wchar_t kPresentName[] = L"FSR3_PresentColor"; + static wchar_t kInterpolatedName[] = L"FSR3_InterpolatedOutput"; + FfxFrameGenerationDispatchDescription fgDispatch{}; + fgDispatch.commandList = ctx->fns.getCommandListVK(dispatchDesc->commandBuffer); + fgDispatch.presentColor = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->outputImage), presentDesc, kPresentName, FFX_RESOURCE_STATE_COMPUTE_READ); + fgDispatch.outputs[0] = ctx->fns.getResourceVK(reinterpret_cast(dispatchDesc->frameGenOutputImage), fgOutDesc, kInterpolatedName, FFX_RESOURCE_STATE_UNORDERED_ACCESS); + fgDispatch.numInterpolatedFrames = 1; + fgDispatch.reset = (dispatchDesc->reset != 0u); + fgDispatch.backBufferTransferFunction = FFX_BACKBUFFER_TRANSFER_FUNCTION_SRGB; + fgDispatch.minMaxLuminance[0] = 0.0f; + fgDispatch.minMaxLuminance[1] = 1.0f; + + return (ctx->fns.fsr3DispatchFrameGeneration(&fgDispatch) == FFX_OK) ? 0 : -1; +#endif +} + +WOWEE_FSR3_WRAPPER_EXPORT void wowee_fsr3_wrapper_shutdown(WoweeFsr3WrapperContext context) { +#if WOWEE_HAS_AMD_FSR3_FRAMEGEN + WrapperContext* ctx = reinterpret_cast(context); + destroyContext(ctx); +#else + (void)context; +#endif +}