mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
FSR2 temporal upscaling fixes: unjittered reprojection, sharpen Y-flip, MSAA guard, descriptor double-buffering
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
- Motion vectors: single unjittered reprojection matrix (80 bytes) instead of two jittered matrices (160 bytes), eliminating numerical instability from jitter amplification through large world coordinates - Sharpen pass: fix Y-flip for correct UV sampling, double-buffer descriptor sets to avoid race with in-flight command buffers - MSAA: auto-disable when FSR2 enabled, grey out AA setting in UI - Accumulation: variance-based neighborhood clamping in YCoCg space, correct history layout transitions - Frame index: wrap at 256 for stable Halton sequence
This commit is contained in:
parent
52317d1edd
commit
e94eb7f2d1
9 changed files with 95 additions and 106 deletions
|
|
@ -2,25 +2,19 @@
|
|||
|
||||
layout(local_size_x = 8, local_size_y = 8) in;
|
||||
|
||||
// Inputs (internal resolution)
|
||||
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
|
||||
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
|
||||
layout(set = 0, binding = 2) uniform sampler2D motionVectors;
|
||||
|
||||
// History (display resolution)
|
||||
layout(set = 0, binding = 3) uniform sampler2D historyInput;
|
||||
|
||||
// Output (display resolution)
|
||||
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D historyOutput;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
vec4 internalSize; // xy = internal resolution, zw = 1/internal
|
||||
vec4 displaySize; // xy = display resolution, zw = 1/display
|
||||
vec4 jitterOffset; // xy = current jitter (pixel-space), zw = unused
|
||||
vec4 jitterOffset; // xy = current jitter (NDC-space), zw = unused
|
||||
vec4 params; // x = resetHistory (1=reset), y = sharpness, zw = unused
|
||||
} pc;
|
||||
|
||||
// RGB <-> YCoCg for neighborhood clamping
|
||||
vec3 rgbToYCoCg(vec3 rgb) {
|
||||
float y = 0.25 * rgb.r + 0.5 * rgb.g + 0.25 * rgb.b;
|
||||
float co = 0.5 * rgb.r - 0.5 * rgb.b;
|
||||
|
|
@ -40,76 +34,52 @@ void main() {
|
|||
ivec2 outSize = ivec2(pc.displaySize.xy);
|
||||
if (outPixel.x >= outSize.x || outPixel.y >= outSize.y) return;
|
||||
|
||||
// Output UV in display space
|
||||
vec2 outUV = (vec2(outPixel) + 0.5) * pc.displaySize.zw;
|
||||
vec3 currentColor = texture(sceneColor, outUV).rgb;
|
||||
|
||||
// Map display pixel to internal resolution UV (accounting for jitter)
|
||||
vec2 internalUV = outUV;
|
||||
|
||||
// Sample current frame color at internal resolution
|
||||
vec3 currentColor = texture(sceneColor, internalUV).rgb;
|
||||
|
||||
// Sample motion vector at internal resolution
|
||||
vec2 inUV = outUV; // Approximate — display maps to internal via scale
|
||||
vec2 motion = texture(motionVectors, inUV).rg;
|
||||
|
||||
// Reproject: where was this pixel in the previous frame's history?
|
||||
vec2 historyUV = outUV - motion;
|
||||
|
||||
// History reset: on teleport / camera cut, just use current frame
|
||||
if (pc.params.x > 0.5) {
|
||||
imageStore(historyOutput, outPixel, vec4(currentColor, 1.0));
|
||||
return;
|
||||
}
|
||||
|
||||
// Sample reprojected history
|
||||
vec2 motion = texture(motionVectors, outUV).rg;
|
||||
vec2 historyUV = outUV + motion;
|
||||
|
||||
float historyValid = (historyUV.x >= 0.0 && historyUV.x <= 1.0 &&
|
||||
historyUV.y >= 0.0 && historyUV.y <= 1.0) ? 1.0 : 0.0;
|
||||
|
||||
vec3 historyColor = texture(historyInput, historyUV).rgb;
|
||||
|
||||
// Neighborhood clamping in YCoCg space to prevent ghosting
|
||||
// Sample 3x3 neighborhood from current frame
|
||||
// Neighborhood clamping in YCoCg space
|
||||
vec2 texelSize = pc.internalSize.zw;
|
||||
vec3 samples[9];
|
||||
int idx = 0;
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
samples[idx] = rgbToYCoCg(texture(sceneColor, internalUV + vec2(dx, dy) * texelSize).rgb);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
vec3 s0 = rgbToYCoCg(currentColor);
|
||||
vec3 s1 = rgbToYCoCg(texture(sceneColor, outUV + vec2(-texelSize.x, 0.0)).rgb);
|
||||
vec3 s2 = rgbToYCoCg(texture(sceneColor, outUV + vec2( texelSize.x, 0.0)).rgb);
|
||||
vec3 s3 = rgbToYCoCg(texture(sceneColor, outUV + vec2(0.0, -texelSize.y)).rgb);
|
||||
vec3 s4 = rgbToYCoCg(texture(sceneColor, outUV + vec2(0.0, texelSize.y)).rgb);
|
||||
vec3 s5 = rgbToYCoCg(texture(sceneColor, outUV + vec2(-texelSize.x, -texelSize.y)).rgb);
|
||||
vec3 s6 = rgbToYCoCg(texture(sceneColor, outUV + vec2( texelSize.x, -texelSize.y)).rgb);
|
||||
vec3 s7 = rgbToYCoCg(texture(sceneColor, outUV + vec2(-texelSize.x, texelSize.y)).rgb);
|
||||
vec3 s8 = rgbToYCoCg(texture(sceneColor, outUV + vec2( texelSize.x, texelSize.y)).rgb);
|
||||
|
||||
// Compute AABB in YCoCg
|
||||
vec3 boxMin = samples[0];
|
||||
vec3 boxMax = samples[0];
|
||||
for (int i = 1; i < 9; i++) {
|
||||
boxMin = min(boxMin, samples[i]);
|
||||
boxMax = max(boxMax, samples[i]);
|
||||
}
|
||||
vec3 m1 = s0 + s1 + s2 + s3 + s4 + s5 + s6 + s7 + s8;
|
||||
vec3 m2 = s0*s0 + s1*s1 + s2*s2 + s3*s3 + s4*s4 + s5*s5 + s6*s6 + s7*s7 + s8*s8;
|
||||
vec3 mean = m1 / 9.0;
|
||||
vec3 variance = max(m2 / 9.0 - mean * mean, vec3(0.0));
|
||||
vec3 stddev = sqrt(variance);
|
||||
|
||||
// Slightly expand the box to reduce flickering on edges
|
||||
vec3 boxCenter = (boxMin + boxMax) * 0.5;
|
||||
vec3 boxExtent = (boxMax - boxMin) * 0.5;
|
||||
boxMin = boxCenter - boxExtent * 1.25;
|
||||
boxMax = boxCenter + boxExtent * 1.25;
|
||||
float gamma = 1.5;
|
||||
vec3 boxMin = mean - gamma * stddev;
|
||||
vec3 boxMax = mean + gamma * stddev;
|
||||
|
||||
// Clamp history to the neighborhood AABB
|
||||
vec3 historyYCoCg = rgbToYCoCg(historyColor);
|
||||
vec3 clampedHistory = clamp(historyYCoCg, boxMin, boxMax);
|
||||
historyColor = yCoCgToRgb(clampedHistory);
|
||||
|
||||
// Check if history UV is valid (within [0,1])
|
||||
float historyValid = (historyUV.x >= 0.0 && historyUV.x <= 1.0 &&
|
||||
historyUV.y >= 0.0 && historyUV.y <= 1.0) ? 1.0 : 0.0;
|
||||
|
||||
// Blend factor: use more current frame for disoccluded regions
|
||||
// Luminance difference between clamped history and original → confidence
|
||||
float clampDist = length(historyYCoCg - clampedHistory);
|
||||
float blendFactor = mix(0.05, 0.3, clamp(clampDist * 4.0, 0.0, 1.0));
|
||||
|
||||
// If history is off-screen, use current frame entirely
|
||||
float blendFactor = mix(0.05, 0.30, clamp(clampDist * 2.0, 0.0, 1.0));
|
||||
blendFactor = mix(blendFactor, 1.0, 1.0 - historyValid);
|
||||
|
||||
// Final blend
|
||||
vec3 result = mix(historyColor, currentColor, blendFactor);
|
||||
|
||||
imageStore(historyOutput, outPixel, vec4(result, 1.0));
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -6,10 +6,8 @@ layout(set = 0, binding = 0) uniform sampler2D depthBuffer;
|
|||
layout(set = 0, binding = 1, rg16f) uniform writeonly image2D motionVectors;
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 invViewProj; // Inverse of current jittered VP
|
||||
mat4 prevViewProj; // Previous frame unjittered VP
|
||||
mat4 reprojMatrix; // prevUnjitteredVP * inverse(currentUnjitteredVP)
|
||||
vec4 resolution; // xy = internal size, zw = 1/internal size
|
||||
vec4 jitterOffset; // xy = current jitter (NDC), zw = previous jitter
|
||||
} pc;
|
||||
|
||||
void main() {
|
||||
|
|
@ -20,25 +18,18 @@ void main() {
|
|||
// Sample depth (Vulkan: 0 = near, 1 = far)
|
||||
float depth = texelFetch(depthBuffer, pixelCoord, 0).r;
|
||||
|
||||
// Pixel center in NDC [-1, 1]
|
||||
// Pixel center in UV [0,1] and NDC [-1,1]
|
||||
vec2 uv = (vec2(pixelCoord) + 0.5) * pc.resolution.zw;
|
||||
vec2 ndc = uv * 2.0 - 1.0;
|
||||
|
||||
// Reconstruct world position from depth
|
||||
// Clip-to-clip reprojection: current unjittered clip → previous unjittered clip
|
||||
vec4 clipPos = vec4(ndc, depth, 1.0);
|
||||
vec4 worldPos = pc.invViewProj * clipPos;
|
||||
worldPos /= worldPos.w;
|
||||
|
||||
// Project into previous frame's clip space (unjittered)
|
||||
vec4 prevClip = pc.prevViewProj * worldPos;
|
||||
vec4 prevClip = pc.reprojMatrix * clipPos;
|
||||
vec2 prevNdc = prevClip.xy / prevClip.w;
|
||||
vec2 prevUV = prevNdc * 0.5 + 0.5;
|
||||
|
||||
// Remove jitter from current UV to get unjittered position
|
||||
vec2 unjitteredUV = uv - pc.jitterOffset.xy * 0.5;
|
||||
|
||||
// Motion = previous position - current unjittered position (in UV space)
|
||||
vec2 motion = prevUV - unjitteredUV;
|
||||
// Motion = previous position - current position (both unjittered, in UV space)
|
||||
vec2 motion = prevUV - uv;
|
||||
|
||||
imageStore(motionVectors, pixelCoord, vec4(motion, 0.0, 0.0));
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -10,16 +10,20 @@ layout(push_constant) uniform PushConstants {
|
|||
} pc;
|
||||
|
||||
void main() {
|
||||
// Undo the vertex shader Y flip (postprocess.vert flips for Vulkan overlay,
|
||||
// but we need standard UV coords for texture sampling)
|
||||
vec2 tc = vec2(TexCoord.x, 1.0 - TexCoord.y);
|
||||
|
||||
vec2 texelSize = pc.params.xy;
|
||||
float sharpness = pc.params.z;
|
||||
|
||||
// RCAS: Robust Contrast-Adaptive Sharpening
|
||||
// 5-tap cross pattern
|
||||
vec3 center = texture(inputImage, TexCoord).rgb;
|
||||
vec3 north = texture(inputImage, TexCoord + vec2(0.0, -texelSize.y)).rgb;
|
||||
vec3 south = texture(inputImage, TexCoord + vec2(0.0, texelSize.y)).rgb;
|
||||
vec3 west = texture(inputImage, TexCoord + vec2(-texelSize.x, 0.0)).rgb;
|
||||
vec3 east = texture(inputImage, TexCoord + vec2( texelSize.x, 0.0)).rgb;
|
||||
vec3 center = texture(inputImage, tc).rgb;
|
||||
vec3 north = texture(inputImage, tc + vec2(0.0, -texelSize.y)).rgb;
|
||||
vec3 south = texture(inputImage, tc + vec2(0.0, texelSize.y)).rgb;
|
||||
vec3 west = texture(inputImage, tc + vec2(-texelSize.x, 0.0)).rgb;
|
||||
vec3 east = texture(inputImage, tc + vec2( texelSize.x, 0.0)).rgb;
|
||||
|
||||
// Compute local contrast (min/max of neighborhood)
|
||||
vec3 minRGB = min(center, min(min(north, south), min(west, east)));
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -408,7 +408,7 @@ private:
|
|||
VkPipelineLayout sharpenPipelineLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout sharpenDescSetLayout = VK_NULL_HANDLE;
|
||||
VkDescriptorPool sharpenDescPool = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sharpenDescSet = VK_NULL_HANDLE;
|
||||
VkDescriptorSet sharpenDescSets[2] = {};
|
||||
|
||||
// Previous frame state for motion vector reprojection
|
||||
glm::mat4 prevViewProjection = glm::mat4(1.0f);
|
||||
|
|
|
|||
|
|
@ -876,6 +876,9 @@ bool Renderer::isWaterRefractionEnabled() const {
|
|||
void Renderer::setMsaaSamples(VkSampleCountFlagBits samples) {
|
||||
if (!vkCtx) return;
|
||||
|
||||
// FSR2 requires non-MSAA render pass — block MSAA changes while FSR2 is active
|
||||
if (fsr2_.enabled && samples > VK_SAMPLE_COUNT_1_BIT) return;
|
||||
|
||||
// Clamp to device maximum
|
||||
VkSampleCountFlagBits maxSamples = vkCtx->getMaxUsableSampleCount();
|
||||
if (samples > maxSamples) samples = maxSamples;
|
||||
|
|
@ -1178,7 +1181,7 @@ void Renderer::endFrame() {
|
|||
fsr2_.prevJitter = camera->getJitter();
|
||||
camera->clearJitter();
|
||||
fsr2_.currentHistory = 1 - fsr2_.currentHistory;
|
||||
fsr2_.frameIndex++;
|
||||
fsr2_.frameIndex = (fsr2_.frameIndex + 1) % 256; // Wrap to keep Halton values well-distributed
|
||||
|
||||
} else if (fsr_.enabled && fsr_.sceneFramebuffer) {
|
||||
// End the off-screen scene render pass
|
||||
|
|
@ -3782,7 +3785,7 @@ bool Renderer::initFSR2Resources() {
|
|||
VkPushConstantRange pc{};
|
||||
pc.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
pc.offset = 0;
|
||||
pc.size = 2 * sizeof(glm::mat4) + 2 * sizeof(glm::vec4); // 160 bytes
|
||||
pc.size = sizeof(glm::mat4) + sizeof(glm::vec4); // 80 bytes
|
||||
|
||||
VkPipelineLayoutCreateInfo plCI{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
|
||||
plCI.setLayoutCount = 1;
|
||||
|
|
@ -4005,20 +4008,21 @@ bool Renderer::initFSR2Resources() {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Descriptor pool + set for sharpen pass (reads from history output)
|
||||
// Descriptor pool + sets for sharpen pass (double-buffered to avoid race condition)
|
||||
VkDescriptorPoolSize poolSize{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2};
|
||||
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
||||
poolInfo.maxSets = 1;
|
||||
poolInfo.maxSets = 2;
|
||||
poolInfo.poolSizeCount = 1;
|
||||
poolInfo.pPoolSizes = &poolSize;
|
||||
vkCreateDescriptorPool(device, &poolInfo, nullptr, &fsr2_.sharpenDescPool);
|
||||
|
||||
VkDescriptorSetLayout layouts[2] = {fsr2_.sharpenDescSetLayout, fsr2_.sharpenDescSetLayout};
|
||||
VkDescriptorSetAllocateInfo dsAI{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
dsAI.descriptorPool = fsr2_.sharpenDescPool;
|
||||
dsAI.descriptorSetCount = 1;
|
||||
dsAI.pSetLayouts = &fsr2_.sharpenDescSetLayout;
|
||||
vkAllocateDescriptorSets(device, &dsAI, &fsr2_.sharpenDescSet);
|
||||
// Descriptor updated dynamically each frame to point at the correct history buffer
|
||||
dsAI.descriptorSetCount = 2;
|
||||
dsAI.pSetLayouts = layouts;
|
||||
vkAllocateDescriptorSets(device, &dsAI, fsr2_.sharpenDescSets);
|
||||
// Descriptors updated dynamically each frame to point at the correct history buffer
|
||||
}
|
||||
|
||||
fsr2_.needsHistoryReset = true;
|
||||
|
|
@ -4036,7 +4040,7 @@ void Renderer::destroyFSR2Resources() {
|
|||
|
||||
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_.sharpenDescSet = 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; }
|
||||
if (fsr2_.sharpenDescSetLayout) { vkDestroyDescriptorSetLayout(device, fsr2_.sharpenDescSetLayout, nullptr); fsr2_.sharpenDescSetLayout = VK_NULL_HANDLE; }
|
||||
|
||||
if (fsr2_.accumulatePipeline) { vkDestroyPipeline(device, fsr2_.accumulatePipeline, nullptr); fsr2_.accumulatePipeline = VK_NULL_HANDLE; }
|
||||
|
|
@ -4082,24 +4086,22 @@ void Renderer::dispatchMotionVectors() {
|
|||
vkCmdBindDescriptorSets(currentCmd, VK_PIPELINE_BIND_POINT_COMPUTE,
|
||||
fsr2_.motionVecPipelineLayout, 0, 1, &fsr2_.motionVecDescSet, 0, nullptr);
|
||||
|
||||
// Push constants: invViewProj, prevViewProj, resolution, jitterOffset
|
||||
// Single reprojection matrix: prevUnjitteredVP * inv(currentUnjitteredVP)
|
||||
// Both matrices are unjittered — jitter only affects sub-pixel sampling,
|
||||
// not motion vector computation. This avoids numerical instability from
|
||||
// jitter amplification through large world coordinates.
|
||||
struct {
|
||||
glm::mat4 invViewProj;
|
||||
glm::mat4 prevViewProj;
|
||||
glm::mat4 reprojMatrix; // prevUnjitteredVP * inv(currentUnjitteredVP)
|
||||
glm::vec4 resolution;
|
||||
glm::vec4 jitterOffset;
|
||||
} pc;
|
||||
|
||||
glm::mat4 currentVP = camera->getProjectionMatrix() * camera->getViewMatrix();
|
||||
pc.invViewProj = glm::inverse(currentVP);
|
||||
pc.prevViewProj = fsr2_.prevViewProjection;
|
||||
glm::mat4 currentUnjitteredVP = camera->getUnjitteredViewProjectionMatrix();
|
||||
pc.reprojMatrix = fsr2_.prevViewProjection * glm::inverse(currentUnjitteredVP);
|
||||
pc.resolution = glm::vec4(
|
||||
static_cast<float>(fsr2_.internalWidth),
|
||||
static_cast<float>(fsr2_.internalHeight),
|
||||
1.0f / fsr2_.internalWidth,
|
||||
1.0f / fsr2_.internalHeight);
|
||||
glm::vec2 jitter = camera->getJitter();
|
||||
pc.jitterOffset = glm::vec4(jitter.x, jitter.y, fsr2_.prevJitter.x, fsr2_.prevJitter.y);
|
||||
|
||||
vkCmdPushConstants(currentCmd, fsr2_.motionVecPipelineLayout,
|
||||
VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
|
||||
|
|
@ -4128,17 +4130,24 @@ void Renderer::dispatchTemporalAccumulate() {
|
|||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
|
||||
// Transition history input: GENERAL/UNDEFINED → SHADER_READ_ONLY
|
||||
// History layout lifecycle:
|
||||
// First frame: both in UNDEFINED
|
||||
// Subsequent frames: both in SHADER_READ_ONLY (output was transitioned for sharpen,
|
||||
// input was left in SHADER_READ_ONLY from its sharpen read)
|
||||
VkImageLayout historyOldLayout = fsr2_.needsHistoryReset
|
||||
? VK_IMAGE_LAYOUT_UNDEFINED
|
||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
// Transition history input: SHADER_READ_ONLY → SHADER_READ_ONLY (barrier for sync)
|
||||
transitionImageLayout(currentCmd, fsr2_.history[inputIdx].image,
|
||||
fsr2_.needsHistoryReset ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
|
||||
historyOldLayout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, // sharpen read in previous frame
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
|
||||
// Transition history output: UNDEFINED → GENERAL
|
||||
// Transition history output: SHADER_READ_ONLY → GENERAL (for compute write)
|
||||
transitionImageLayout(currentCmd, fsr2_.history[outputIdx].image,
|
||||
VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||||
historyOldLayout, VK_IMAGE_LAYOUT_GENERAL,
|
||||
VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
|
||||
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
|
||||
|
||||
vkCmdBindPipeline(currentCmd, VK_PIPELINE_BIND_POINT_COMPUTE, fsr2_.accumulatePipeline);
|
||||
|
|
@ -4179,6 +4188,10 @@ void Renderer::renderFSR2Sharpen() {
|
|||
VkExtent2D ext = vkCtx->getSwapchainExtent();
|
||||
uint32_t outputIdx = fsr2_.currentHistory;
|
||||
|
||||
// Use per-frame descriptor set to avoid race with in-flight command buffers
|
||||
uint32_t frameIdx = vkCtx->getCurrentFrame();
|
||||
VkDescriptorSet descSet = fsr2_.sharpenDescSets[frameIdx];
|
||||
|
||||
// Update sharpen descriptor to point at current history output
|
||||
VkDescriptorImageInfo imgInfo{};
|
||||
imgInfo.sampler = fsr2_.linearSampler;
|
||||
|
|
@ -4186,7 +4199,7 @@ void Renderer::renderFSR2Sharpen() {
|
|||
imgInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = fsr2_.sharpenDescSet;
|
||||
write.dstSet = descSet;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
|
|
@ -4195,7 +4208,7 @@ void Renderer::renderFSR2Sharpen() {
|
|||
|
||||
vkCmdBindPipeline(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, fsr2_.sharpenPipeline);
|
||||
vkCmdBindDescriptorSets(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
|
||||
fsr2_.sharpenPipelineLayout, 0, 1, &fsr2_.sharpenDescSet, 0, nullptr);
|
||||
fsr2_.sharpenPipelineLayout, 0, 1, &descSet, 0, nullptr);
|
||||
|
||||
glm::vec4 params(1.0f / ext.width, 1.0f / ext.height, fsr2_.sharpness, 0.0f);
|
||||
vkCmdPushConstants(currentCmd, fsr2_.sharpenPipelineLayout,
|
||||
|
|
@ -4214,6 +4227,11 @@ void Renderer::setFSR2Enabled(bool enabled) {
|
|||
fsr_.enabled = false;
|
||||
fsr_.needsRecreate = true;
|
||||
}
|
||||
// FSR2 requires non-MSAA render pass (its framebuffer has 2 attachments)
|
||||
if (vkCtx && vkCtx->getMsaaSamples() > VK_SAMPLE_COUNT_1_BIT) {
|
||||
pendingMsaaSamples_ = VK_SAMPLE_COUNT_1_BIT;
|
||||
msaaChangePending_ = true;
|
||||
}
|
||||
// Use FSR1's scale factor and sharpness as defaults
|
||||
fsr2_.scaleFactor = fsr_.scaleFactor;
|
||||
fsr2_.sharpness = fsr_.sharpness;
|
||||
|
|
|
|||
|
|
@ -6281,7 +6281,13 @@ void GameScreen::renderSettingsWindow() {
|
|||
}
|
||||
{
|
||||
const char* aaLabels[] = { "Off", "2x MSAA", "4x MSAA", "8x MSAA" };
|
||||
if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) {
|
||||
bool fsr2Active = renderer && renderer->isFSR2Enabled();
|
||||
if (fsr2Active) {
|
||||
ImGui::BeginDisabled();
|
||||
int disabled = 0;
|
||||
ImGui::Combo("Anti-Aliasing (FSR2)", &disabled, "Off (FSR2 active)\0", 1);
|
||||
ImGui::EndDisabled();
|
||||
} else if (ImGui::Combo("Anti-Aliasing", &pendingAntiAliasing, aaLabels, 4)) {
|
||||
static const VkSampleCountFlagBits aaSamples[] = {
|
||||
VK_SAMPLE_COUNT_1_BIT, VK_SAMPLE_COUNT_2_BIT,
|
||||
VK_SAMPLE_COUNT_4_BIT, VK_SAMPLE_COUNT_8_BIT
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue