Integrate AMD FSR2 backend and document SDK bootstrap

This commit is contained in:
Kelsi 2026-03-08 19:56:52 -07:00
parent a24ff375fb
commit 51a8cf565f
11 changed files with 329 additions and 28 deletions

View file

@ -217,3 +217,8 @@ You can also specify an expansion: `.\extract_assets.ps1 "C:\Games\WoW\Data" wot
```bash
git submodule update --init --recursive
```
- AMD FSR2 SDK is fetched automatically by `build.sh` / `rebuild.sh` / `build.ps1` / `rebuild.ps1` from:
- `https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git`
- target path: `extern/FidelityFX-FSR2`
- AMD backend is enabled only when SDK headers and generated Vulkan permutation headers are present.
If generated permutation headers are missing, the build uses the internal FSR2 fallback path.

View file

@ -27,18 +27,49 @@ option(WOWEE_ENABLE_AMD_FSR2 "Enable AMD FidelityFX FSR2 backend when SDK is pre
# AMD FidelityFX FSR2 SDK detection (drop-in under extern/FidelityFX-FSR2)
set(WOWEE_AMD_FSR2_DIR ${CMAKE_SOURCE_DIR}/extern/FidelityFX-FSR2)
set(WOWEE_AMD_FSR2_HEADER ${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/ffx_fsr2.h)
if(WOWEE_ENABLE_AMD_FSR2 AND EXISTS ${WOWEE_AMD_FSR2_HEADER})
set(WOWEE_AMD_FSR2_VK_PERM_HEADER ${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/vk/shaders/ffx_fsr2_accumulate_pass_permutations.h)
if(WOWEE_ENABLE_AMD_FSR2 AND EXISTS ${WOWEE_AMD_FSR2_HEADER} AND EXISTS ${WOWEE_AMD_FSR2_VK_PERM_HEADER})
message(STATUS "AMD FSR2 SDK detected at ${WOWEE_AMD_FSR2_DIR}")
add_compile_definitions(WOWEE_HAS_AMD_FSR2=1)
include_directories(
add_compile_definitions(FFX_GCC=1)
# AMD FSR2 Vulkan backend sources (official SDK implementation)
set(WOWEE_AMD_FSR2_SOURCES
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/ffx_assert.cpp
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/ffx_fsr2.cpp
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/vk/ffx_fsr2_vk.cpp
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/vk/shaders/ffx_fsr2_shaders_vk.cpp
)
add_library(wowee_fsr2_amd_vk STATIC ${WOWEE_AMD_FSR2_SOURCES})
set_target_properties(wowee_fsr2_amd_vk PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
target_include_directories(wowee_fsr2_amd_vk PUBLIC
${WOWEE_AMD_FSR2_DIR}/src
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/vk
${WOWEE_AMD_FSR2_DIR}/src/ffx-fsr2-api/vk/shaders
)
set(WOWEE_FFX_COMPAT_HEADER ${CMAKE_SOURCE_DIR}/include/third_party/ffx_fsr2_compat.h)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(wowee_fsr2_amd_vk PRIVATE
"-include${WOWEE_FFX_COMPAT_HEADER}"
)
elseif(MSVC)
target_compile_options(wowee_fsr2_amd_vk PRIVATE
"/FI${WOWEE_FFX_COMPAT_HEADER}"
)
endif()
target_link_libraries(wowee_fsr2_amd_vk PUBLIC Vulkan::Vulkan)
else()
add_compile_definitions(WOWEE_HAS_AMD_FSR2=0)
if(WOWEE_ENABLE_AMD_FSR2)
message(WARNING "AMD FSR2 SDK not found at ${WOWEE_AMD_FSR2_DIR}; using internal fallback implementation.")
if(NOT EXISTS ${WOWEE_AMD_FSR2_HEADER})
message(WARNING "AMD FSR2 SDK not found at ${WOWEE_AMD_FSR2_DIR}; using internal fallback implementation.")
elseif(NOT EXISTS ${WOWEE_AMD_FSR2_VK_PERM_HEADER})
message(WARNING "AMD FSR2 SDK found, but generated Vulkan permutation headers are missing (e.g. ${WOWEE_AMD_FSR2_VK_PERM_HEADER}); using internal fallback implementation.")
endif()
endif()
endif()
@ -556,6 +587,10 @@ if(TARGET vk-bootstrap)
target_link_libraries(wowee PRIVATE vk-bootstrap)
endif()
if(TARGET wowee_fsr2_amd_vk)
target_link_libraries(wowee PRIVATE wowee_fsr2_amd_vk)
endif()
# Link Unicorn if available
if(HAVE_UNICORN)
target_link_libraries(wowee PRIVATE ${UNICORN_LIBRARY})

View file

@ -168,6 +168,17 @@ make -j$(nproc)
./bin/wowee
```
### AMD FSR2 SDK (External)
- Build scripts (`build.sh`, `rebuild.sh`, `build.ps1`, `rebuild.ps1`) auto-fetch the AMD SDK to:
- `extern/FidelityFX-FSR2`
- Source URL:
- `https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git`
- The renderer enables the AMD backend only when both are present:
- `extern/FidelityFX-FSR2/src/ffx-fsr2-api/ffx_fsr2.h`
- `extern/FidelityFX-FSR2/src/ffx-fsr2-api/vk/shaders/ffx_fsr2_accumulate_pass_permutations.h`
- If SDK files or generated Vulkan permutation headers are missing, CMake falls back to the internal non-AMD FSR2 path automatically.
## Controls
### Camera & Movement

View file

@ -12,7 +12,26 @@ $ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $ScriptDir
function Ensure-Fsr2Sdk {
$sdkDir = Join-Path $ScriptDir "extern\FidelityFX-FSR2"
$sdkHeader = Join-Path $sdkDir "src\ffx-fsr2-api\ffx_fsr2.h"
if (Test-Path $sdkHeader) { return }
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
Write-Warning "git not found; cannot auto-fetch AMD FSR2 SDK."
return
}
Write-Host "Fetching AMD FidelityFX FSR2 SDK into $sdkDir ..."
New-Item -ItemType Directory -Path (Join-Path $ScriptDir "extern") -Force | Out-Null
& git clone --depth 1 https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git $sdkDir
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to clone AMD FSR2 SDK. Build will use internal fallback path."
}
}
Write-Host "Building wowee..."
Ensure-Fsr2Sdk
# Create build directory if it doesn't exist
if (-not (Test-Path "build")) {

View file

@ -5,7 +5,25 @@ set -e # Exit on error
cd "$(dirname "$0")"
ensure_fsr2_sdk() {
local sdk_dir="extern/FidelityFX-FSR2"
local sdk_header="$sdk_dir/src/ffx-fsr2-api/ffx_fsr2.h"
if [ -f "$sdk_header" ]; then
return
fi
if ! command -v git >/dev/null 2>&1; then
echo "Warning: git not found; cannot auto-fetch AMD FSR2 SDK."
return
fi
echo "Fetching AMD FidelityFX FSR2 SDK into $sdk_dir ..."
mkdir -p extern
git clone --depth 1 https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git "$sdk_dir" || {
echo "Warning: failed to clone AMD FSR2 SDK. Build will use internal fallback path."
}
}
echo "Building wowee..."
ensure_fsr2_sdk
# Create build directory if it doesn't exist
mkdir -p build

View file

@ -27,6 +27,9 @@ public:
glm::mat4 getViewProjectionMatrix() const { return projectionMatrix * viewMatrix; }
glm::mat4 getUnjitteredViewProjectionMatrix() const { return unjitteredProjectionMatrix * viewMatrix; }
float getAspectRatio() const { return aspectRatio; }
float getFovDegrees() const { return fov; }
float getNearPlane() const { return nearPlane; }
float getFarPlane() const { return farPlane; }
// Sub-pixel jitter for temporal upscaling (FSR 2)
void setJitter(float jx, float jy);

View file

@ -11,6 +11,10 @@
#include "rendering/vk_frame_data.hpp"
#include "rendering/vk_utils.hpp"
#include "rendering/sky_system.hpp"
#if WOWEE_HAS_AMD_FSR2
#include "ffx_fsr2.h"
#include "ffx_fsr2_vk.h"
#endif
namespace wowee {
namespace core { class Window; }
@ -420,6 +424,13 @@ private:
glm::vec2 prevJitter = glm::vec2(0.0f);
uint32_t frameIndex = 0;
bool needsHistoryReset = true;
bool useAmdBackend = false;
#if WOWEE_HAS_AMD_FSR2
FfxFsr2Context amdContext{};
FfxFsr2Interface amdInterface{};
void* amdScratchBuffer = nullptr;
size_t amdScratchBufferSize = 0;
#endif
// Convergent accumulation: jitter for N frames then freeze
int convergenceFrame = 0;
@ -431,6 +442,7 @@ private:
void destroyFSR2Resources();
void dispatchMotionVectors();
void dispatchTemporalAccumulate();
void dispatchAmdFsr2();
void renderFSR2Sharpen();
static float halton(uint32_t index, uint32_t base);

15
include/third_party/ffx_fsr2_compat.h vendored Normal file
View file

@ -0,0 +1,15 @@
#pragma once
// Compatibility shim for building AMD FSR2 SDK sources on non-MSVC toolchains.
#include <stddef.h>
#include <wchar.h>
#include <locale>
#include <codecvt>
#ifndef _countof
#define _countof(x) (sizeof(x) / sizeof((x)[0]))
#endif
#ifndef wcscpy_s
#define wcscpy_s(dst, src) wcscpy((dst), (src))
#endif

View file

@ -12,7 +12,26 @@ $ErrorActionPreference = "Stop"
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $ScriptDir
function Ensure-Fsr2Sdk {
$sdkDir = Join-Path $ScriptDir "extern\FidelityFX-FSR2"
$sdkHeader = Join-Path $sdkDir "src\ffx-fsr2-api\ffx_fsr2.h"
if (Test-Path $sdkHeader) { return }
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
Write-Warning "git not found; cannot auto-fetch AMD FSR2 SDK."
return
}
Write-Host "Fetching AMD FidelityFX FSR2 SDK into $sdkDir ..."
New-Item -ItemType Directory -Path (Join-Path $ScriptDir "extern") -Force | Out-Null
& git clone --depth 1 https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git $sdkDir
if ($LASTEXITCODE -ne 0) {
Write-Warning "Failed to clone AMD FSR2 SDK. Build will use internal fallback path."
}
}
Write-Host "Clean rebuilding wowee..."
Ensure-Fsr2Sdk
# Remove build directory completely
if (Test-Path "build") {

View file

@ -5,7 +5,25 @@ set -e # Exit on error
cd "$(dirname "$0")"
ensure_fsr2_sdk() {
local sdk_dir="extern/FidelityFX-FSR2"
local sdk_header="$sdk_dir/src/ffx-fsr2-api/ffx_fsr2.h"
if [ -f "$sdk_header" ]; then
return
fi
if ! command -v git >/dev/null 2>&1; then
echo "Warning: git not found; cannot auto-fetch AMD FSR2 SDK."
return
fi
echo "Fetching AMD FidelityFX FSR2 SDK into $sdk_dir ..."
mkdir -p extern
git clone --depth 1 https://github.com/GPUOpen-Effects/FidelityFX-FSR2.git "$sdk_dir" || {
echo "Warning: failed to clone AMD FSR2 SDK. Build will use internal fallback path."
}
}
echo "Clean rebuilding wowee..."
ensure_fsr2_sdk
# Remove build directory completely
if [ -d "build" ]; then

View file

@ -1022,32 +1022,48 @@ void Renderer::beginFrame() {
return;
}
// FSR2 jitter pattern for temporal accumulation.
constexpr bool kFsr2TemporalEnabled = false;
// FSR2 jitter pattern.
if (fsr2_.enabled && fsr2_.sceneFramebuffer && camera) {
if (!kFsr2TemporalEnabled) {
if (!fsr2_.useAmdBackend) {
camera->setJitter(0.0f, 0.0f);
} else {
glm::mat4 currentVP = camera->getViewProjectionMatrix();
glm::mat4 currentVP = camera->getViewProjectionMatrix();
// Reset history only for clear camera movement.
bool cameraMoved = false;
for (int i = 0; i < 4 && !cameraMoved; i++) {
for (int j = 0; j < 4 && !cameraMoved; j++) {
if (std::abs(currentVP[i][j] - fsr2_.lastStableVP[i][j]) > 1e-3f) {
cameraMoved = true;
// Reset history only for clear camera movement.
bool cameraMoved = false;
for (int i = 0; i < 4 && !cameraMoved; i++) {
for (int j = 0; j < 4 && !cameraMoved; j++) {
if (std::abs(currentVP[i][j] - fsr2_.lastStableVP[i][j]) > 1e-3f) {
cameraMoved = true;
}
}
}
}
if (cameraMoved) {
fsr2_.lastStableVP = currentVP;
fsr2_.needsHistoryReset = true;
}
if (cameraMoved) {
fsr2_.lastStableVP = currentVP;
fsr2_.needsHistoryReset = true;
}
const float jitterScale = 0.5f;
float jx = (halton(fsr2_.frameIndex + 1, 2) - 0.5f) * 2.0f * jitterScale / static_cast<float>(fsr2_.internalWidth);
float jy = (halton(fsr2_.frameIndex + 1, 3) - 0.5f) * 2.0f * jitterScale / static_cast<float>(fsr2_.internalHeight);
camera->setJitter(jx, jy);
#if WOWEE_HAS_AMD_FSR2
// AMD-recommended jitter sequence in pixel space, converted to NDC projection offset.
int32_t phaseCount = ffxFsr2GetJitterPhaseCount(
static_cast<int32_t>(fsr2_.internalWidth),
static_cast<int32_t>(vkCtx->getSwapchainExtent().width));
float jitterX = 0.0f;
float jitterY = 0.0f;
if (phaseCount > 0 &&
ffxFsr2GetJitterOffset(&jitterX, &jitterY, static_cast<int32_t>(fsr2_.frameIndex % static_cast<uint32_t>(phaseCount)), phaseCount) == FFX_OK) {
float ndcJx = (2.0f * jitterX) / static_cast<float>(fsr2_.internalWidth);
float ndcJy = (2.0f * jitterY) / static_cast<float>(fsr2_.internalHeight);
camera->setJitter(ndcJx, ndcJy);
} else {
camera->setJitter(0.0f, 0.0f);
}
#else
const float jitterScale = 0.5f;
float jx = (halton(fsr2_.frameIndex + 1, 2) - 0.5f) * 2.0f * jitterScale / static_cast<float>(fsr2_.internalWidth);
float jy = (halton(fsr2_.frameIndex + 1, 3) - 0.5f) * 2.0f * jitterScale / static_cast<float>(fsr2_.internalHeight);
camera->setJitter(jx, jy);
#endif
}
}
@ -1152,14 +1168,13 @@ void Renderer::endFrame() {
if (!vkCtx || currentCmd == VK_NULL_HANDLE) return;
if (fsr2_.enabled && fsr2_.sceneFramebuffer) {
constexpr bool kFsr2TemporalEnabled = false;
// End the off-screen scene render pass
vkCmdEndRenderPass(currentCmd);
if (kFsr2TemporalEnabled) {
if (fsr2_.useAmdBackend) {
// Compute passes: motion vectors -> temporal accumulation
dispatchMotionVectors();
dispatchTemporalAccumulate();
dispatchAmdFsr2();
// Transition history output: GENERAL -> SHADER_READ_ONLY for sharpen pass
transitionImageLayout(currentCmd, fsr2_.history[fsr2_.currentHistory].image,
@ -1209,7 +1224,7 @@ void Renderer::endFrame() {
fsr2_.prevViewProjection = camera->getViewProjectionMatrix();
fsr2_.prevJitter = camera->getJitter();
camera->clearJitter();
if (kFsr2TemporalEnabled) {
if (fsr2_.useAmdBackend) {
fsr2_.currentHistory = 1 - fsr2_.currentHistory;
}
fsr2_.frameIndex = (fsr2_.frameIndex + 1) % 256; // Wrap to keep Halton values well-distributed
@ -3739,6 +3754,7 @@ bool Renderer::initFSR2Resources() {
LOG_INFO("FSR2: initializing at ", fsr2_.internalWidth, "x", fsr2_.internalHeight,
" -> ", swapExtent.width, "x", swapExtent.height,
" (scale=", fsr2_.scaleFactor, ")");
fsr2_.useAmdBackend = false;
#if WOWEE_HAS_AMD_FSR2
LOG_INFO("FSR2: AMD FidelityFX SDK detected at build time.");
#else
@ -3802,6 +3818,51 @@ bool Renderer::initFSR2Resources() {
samplerInfo.magFilter = VK_FILTER_NEAREST;
vkCreateSampler(device, &samplerInfo, nullptr, &fsr2_.nearestSampler);
#if WOWEE_HAS_AMD_FSR2
// Initialize AMD FSR2 context; fall back to internal path on any failure.
fsr2_.amdScratchBufferSize = ffxFsr2GetScratchMemorySizeVK(vkCtx->getPhysicalDevice());
if (fsr2_.amdScratchBufferSize > 0) {
fsr2_.amdScratchBuffer = std::malloc(fsr2_.amdScratchBufferSize);
}
if (!fsr2_.amdScratchBuffer) {
LOG_WARNING("FSR2 AMD: failed to allocate scratch buffer, using internal fallback.");
} else {
FfxErrorCode ifaceErr = ffxFsr2GetInterfaceVK(
&fsr2_.amdInterface,
fsr2_.amdScratchBuffer,
fsr2_.amdScratchBufferSize,
vkCtx->getPhysicalDevice(),
vkGetDeviceProcAddr);
if (ifaceErr != FFX_OK) {
LOG_WARNING("FSR2 AMD: ffxFsr2GetInterfaceVK failed (", static_cast<int>(ifaceErr), "), using internal fallback.");
std::free(fsr2_.amdScratchBuffer);
fsr2_.amdScratchBuffer = nullptr;
fsr2_.amdScratchBufferSize = 0;
} else {
FfxFsr2ContextDescription ctxDesc{};
ctxDesc.flags = FFX_FSR2_ENABLE_AUTO_EXPOSURE | FFX_FSR2_ENABLE_MOTION_VECTORS_JITTER_CANCELLATION;
ctxDesc.maxRenderSize.width = fsr2_.internalWidth;
ctxDesc.maxRenderSize.height = fsr2_.internalHeight;
ctxDesc.displaySize.width = swapExtent.width;
ctxDesc.displaySize.height = swapExtent.height;
ctxDesc.callbacks = fsr2_.amdInterface;
ctxDesc.device = ffxGetDeviceVK(vkCtx->getDevice());
ctxDesc.fpMessage = nullptr;
FfxErrorCode ctxErr = ffxFsr2ContextCreate(&fsr2_.amdContext, &ctxDesc);
if (ctxErr == FFX_OK) {
fsr2_.useAmdBackend = true;
LOG_INFO("FSR2 AMD: context created successfully.");
} else {
LOG_WARNING("FSR2 AMD: context creation failed (", static_cast<int>(ctxErr), "), using internal fallback.");
std::free(fsr2_.amdScratchBuffer);
fsr2_.amdScratchBuffer = nullptr;
fsr2_.amdScratchBufferSize = 0;
}
}
}
#endif
// --- Motion Vector Compute Pipeline ---
{
// Descriptor set layout: binding 0 = depth (sampler), binding 1 = motion vectors (storage image)
@ -4078,6 +4139,18 @@ void Renderer::destroyFSR2Resources() {
vkDeviceWaitIdle(device);
#if WOWEE_HAS_AMD_FSR2
if (fsr2_.useAmdBackend) {
ffxFsr2ContextDestroy(&fsr2_.amdContext);
fsr2_.useAmdBackend = false;
}
if (fsr2_.amdScratchBuffer) {
std::free(fsr2_.amdScratchBuffer);
fsr2_.amdScratchBuffer = nullptr;
}
fsr2_.amdScratchBufferSize = 0;
#endif
if (fsr2_.sharpenPipeline) { vkDestroyPipeline(device, fsr2_.sharpenPipeline, nullptr); fsr2_.sharpenPipeline = VK_NULL_HANDLE; }
if (fsr2_.sharpenPipelineLayout) { vkDestroyPipelineLayout(device, fsr2_.sharpenPipelineLayout, nullptr); fsr2_.sharpenPipelineLayout = VK_NULL_HANDLE; }
if (fsr2_.sharpenDescPool) { vkDestroyDescriptorPool(device, fsr2_.sharpenDescPool, nullptr); fsr2_.sharpenDescPool = VK_NULL_HANDLE; fsr2_.sharpenDescSets[0] = fsr2_.sharpenDescSets[1] = VK_NULL_HANDLE; }
@ -4220,9 +4293,82 @@ void Renderer::dispatchTemporalAccumulate() {
fsr2_.needsHistoryReset = false;
}
void Renderer::dispatchAmdFsr2() {
if (currentCmd == VK_NULL_HANDLE || !camera) return;
#if WOWEE_HAS_AMD_FSR2
if (!fsr2_.useAmdBackend) return;
VkExtent2D swapExtent = vkCtx->getSwapchainExtent();
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);
FfxFsr2DispatchDescription desc{};
desc.commandList = ffxGetCommandListVK(currentCmd);
desc.color = ffxGetTextureResourceVK(&fsr2_.amdContext,
fsr2_.sceneColor.image, fsr2_.sceneColor.imageView,
fsr2_.internalWidth, fsr2_.internalHeight, vkCtx->getSwapchainFormat(),
L"FSR2_InputColor", FFX_RESOURCE_STATE_COMPUTE_READ);
desc.depth = ffxGetTextureResourceVK(&fsr2_.amdContext,
fsr2_.sceneDepth.image, fsr2_.sceneDepth.imageView,
fsr2_.internalWidth, fsr2_.internalHeight, vkCtx->getDepthFormat(),
L"FSR2_InputDepth", FFX_RESOURCE_STATE_COMPUTE_READ);
desc.motionVectors = ffxGetTextureResourceVK(&fsr2_.amdContext,
fsr2_.motionVectors.image, fsr2_.motionVectors.imageView,
fsr2_.internalWidth, fsr2_.internalHeight, VK_FORMAT_R16G16_SFLOAT,
L"FSR2_InputMotionVectors", FFX_RESOURCE_STATE_COMPUTE_READ);
desc.output = ffxGetTextureResourceVK(&fsr2_.amdContext,
fsr2_.history[outputIdx].image, fsr2_.history[outputIdx].imageView,
swapExtent.width, swapExtent.height, VK_FORMAT_R16G16B16A16_SFLOAT,
L"FSR2_Output", FFX_RESOURCE_STATE_UNORDERED_ACCESS);
// Camera jitter is stored as NDC projection offsets; convert to render-pixel offsets.
glm::vec2 jitterNdc = camera->getJitter();
desc.jitterOffset.x = jitterNdc.x * 0.5f * static_cast<float>(fsr2_.internalWidth);
desc.jitterOffset.y = jitterNdc.y * 0.5f * static_cast<float>(fsr2_.internalHeight);
desc.motionVectorScale.x = static_cast<float>(fsr2_.internalWidth);
desc.motionVectorScale.y = static_cast<float>(fsr2_.internalHeight);
desc.renderSize.width = fsr2_.internalWidth;
desc.renderSize.height = fsr2_.internalHeight;
desc.enableSharpening = false; // Keep existing RCAS post pass.
desc.sharpness = 0.0f;
desc.frameTimeDelta = glm::max(0.001f, lastDeltaTime_ * 1000.0f);
desc.preExposure = 1.0f;
desc.reset = fsr2_.needsHistoryReset;
desc.cameraNear = camera->getNearPlane();
desc.cameraFar = camera->getFarPlane();
desc.cameraFovAngleVertical = glm::radians(camera->getFovDegrees());
desc.viewSpaceToMetersFactor = 1.0f;
desc.enableAutoReactive = false;
FfxErrorCode dispatchErr = ffxFsr2ContextDispatch(&fsr2_.amdContext, &desc);
if (dispatchErr != FFX_OK) {
LOG_WARNING("FSR2 AMD: dispatch failed (", static_cast<int>(dispatchErr), "), forcing history reset.");
fsr2_.needsHistoryReset = true;
} else {
fsr2_.needsHistoryReset = false;
}
#endif
}
void Renderer::renderFSR2Sharpen() {
if (!fsr2_.sharpenPipeline || currentCmd == VK_NULL_HANDLE) return;
constexpr bool kFsr2TemporalEnabled = false;
VkExtent2D ext = vkCtx->getSwapchainExtent();
uint32_t outputIdx = fsr2_.currentHistory;
@ -4234,7 +4380,7 @@ void Renderer::renderFSR2Sharpen() {
// Update sharpen descriptor to point at current history output
VkDescriptorImageInfo imgInfo{};
imgInfo.sampler = fsr2_.linearSampler;
imgInfo.imageView = kFsr2TemporalEnabled
imgInfo.imageView = fsr2_.useAmdBackend
? fsr2_.history[outputIdx].imageView
: fsr2_.sceneColor.imageView;
imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;