diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/qt_common/config/shared_translation.cpp | 6 | ||||
| -rw-r--r-- | src/video_core/host_shaders/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | src/video_core/host_shaders/temporal_sharpen.frag | 49 | ||||
| -rw-r--r-- | src/video_core/host_shaders/temporal_upscale.frag | 85 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/blit_image.cpp | 166 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/blit_image.h | 25 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.cpp | 227 | ||||
| -rw-r--r-- | src/video_core/renderer_vulkan/vk_texture_cache.h | 8 | ||||
| -rw-r--r-- | src/video_core/texture_cache/texture_cache.h | 20 |
9 files changed, 440 insertions, 147 deletions
diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index e346828888..fda319cc6d 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -145,8 +145,10 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) { "Higher resolutions require more VRAM and bandwidth.\n" "Options lower than 1X can cause artifacts.")); INSERT(Settings, scaling_filter, tr("Window Adapting Filter:"), QString()); - INSERT(Settings, fsr_sharpening_slider, tr("FSR Sharpness:"), - tr("Determines how sharpened the image will look using FSR's or SGSR's dynamic contrast.")); + INSERT(Settings, fsr_sharpening_slider, tr("RCAS Sharpness:"), + tr("Strength of RCAS (AMD's contrast-adaptive sharpening) on the temporal upscaler used " + "in place of a game's DLSS; 0% turns it off.\nAlso sets the sharpening of the AMD FSR 1 " + "and SGSR window adapting filters.")); INSERT(Settings, anti_aliasing, tr("Anti-Aliasing Method:"), tr("The anti-aliasing method to use.\nSMAA offers the best quality.\nFXAA " "can produce a more stable picture in lower resolutions.")); diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index 9ff7563ff7..1678395679 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -72,6 +72,7 @@ set(SHADER_FILES ${CMAKE_CURRENT_SOURCE_DIR}/smaa_blending_weight_calculation.frag ${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.vert ${CMAKE_CURRENT_SOURCE_DIR}/smaa_neighborhood_blending.frag + ${CMAKE_CURRENT_SOURCE_DIR}/temporal_sharpen.frag ${CMAKE_CURRENT_SOURCE_DIR}/temporal_upscale.frag ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_blit_depth_stencil.frag ${CMAKE_CURRENT_SOURCE_DIR}/vulkan_color_clear.frag diff --git a/src/video_core/host_shaders/temporal_sharpen.frag b/src/video_core/host_shaders/temporal_sharpen.frag new file mode 100644 index 0000000000..45a13013cb --- /dev/null +++ b/src/video_core/host_shaders/temporal_sharpen.frag @@ -0,0 +1,49 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// RCAS (FidelityFX FSR 1's contrast-adaptive sharpening) on the DLSS stand-in's result, before the +// game post-processes it. The input is linear HDR; RCAS's limits assume 0 to 1, so it sharpens in +// the stand-in's range-compressed space. + +#version 460 core +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_shader_explicit_arithmetic_types : require + +layout(binding = 0) uniform sampler2D InputTexture; + +layout(push_constant) uniform PushConstants { + // The vertex stage's texture scale and offset come first. + layout(offset = 16) uvec4 Const0; // FsrRcasCon +}; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out vec4 color; + +#define A_GPU 1 +#define A_GLSL 1 +#include "ffx_a.h" + +vec3 Compress(vec3 c) { + return c / (1.0 + max(max(c.r, c.g), c.b)); +} + +vec3 Expand(vec3 c) { + c = clamp(c, vec3(0.0), vec3(1.0 - 1.0 / 65504.0)); + return c / (1.0 - max(max(c.r, c.g), c.b)); +} + +#define FSR_RCAS_F 1 +AF4 FsrRcasLoadF(ASU2 p) { + return AF4(Compress(texelFetch(InputTexture, p, 0).rgb), 1.0); +} +void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} + +#include "ffx_fsr1.h" + +void main() { + AF3 c; + FsrRcasF(c.r, c.g, c.b, AU2(gl_FragCoord.xy), Const0); + color = vec4(Expand(c), 1.0); +} diff --git a/src/video_core/host_shaders/temporal_upscale.frag b/src/video_core/host_shaders/temporal_upscale.frag index 0e76df2b43..ee31e035b1 100644 --- a/src/video_core/host_shaders/temporal_upscale.frag +++ b/src/video_core/host_shaders/temporal_upscale.frag @@ -11,12 +11,17 @@ layout(binding = 0) uniform sampler2D input_color; layout(binding = 1) uniform sampler2D motion_vectors; layout(binding = 2) uniform sampler2D history; +layout(binding = 3, rgba16f) uniform writeonly image2D next_history; layout(push_constant) uniform PushConstants { // The vertex stage's texture scale and offset come first. layout(offset = 16) vec2 jitter; // this frame's sample offset, in input pixels vec2 motion_scale; // motion vector -> UV offset to the previous frame float current_weight; // largest weight of this frame; 1 ignores the history + float kernel_scale; // accumulation kernel distance scale + float clip_width; // neighbourhood clip of still pixels, in deviations + float clip_motion; // clip of pixels moving a pixel or more per frame + float kernel_motion; // kernel distance scale of moving pixels }; layout(location = 0) in vec2 texcoord; // 0 to 1 across the output @@ -83,50 +88,92 @@ void main() { const vec2 target = texcoord * vec2(input_size); const ivec2 nearest = ivec2(floor(target + jitter)); + // The longest motion around the pixel, so that an edge follows the object in front. + vec2 motion = vec2(0.0); + float longest = -1.0; + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + // The motion vectors can be at another scale than the colour (resolution scaling). + const ivec2 input_texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1); + const ivec2 texel = clamp(ivec2((vec2(input_texel) + 0.5) * vec2(motion_size) / + vec2(input_size)), + ivec2(0), motion_size - 1); + const vec2 candidate = texelFetch(motion_vectors, texel, 0).rg; + const float length_squared = dot(candidate, candidate); + if (length_squared > longest) { + longest = length_squared; + motion = candidate; + } + } + } + const vec2 previous = texcoord + motion * motion_scale; + // Still pixels: a narrow kernel (detail from many jitter positions accumulates) + // and a looser clip (keeps detail only some positions sample, e.g. thin bars). Moving pixels + // (a quarter pixel or more per frame): the wide kernel and tight clip, as their history is unreliable. + // The history's alpha remembers recent motion, decaying over a few frames: background that a + // moving object has just uncovered has no motion of its own but a history of that object. + const vec2 history_position = previous * vec2(textureSize(history, 0)); + const float moved = smoothstep(0.02, 0.25, length(motion * motion_scale * vec2(textureSize(history, 0)))); + const float moving = max(moved, texture(history, history_position).a); + const float kernel = mix(kernel_scale, kernel_motion, moving); + const float clip = mix(clip_width, clip_motion, moving); + vec3 filtered = vec3(0.0); float total_weight = 0.0; float nearest_weight = 0.0; vec3 sum = vec3(0.0); vec3 squares = vec3(0.0); - vec2 motion = vec2(0.0); - float longest = -1.0; for (int y = -1; y <= 1; ++y) { for (int x = -1; x <= 1; ++x) { const ivec2 texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1); const vec3 sample_color = RgbToYCoCg(Compress(texelFetch(input_color, texel, 0).rgb)); // A Blackman-Harris-like window over the sample's distance to the output pixel. - const vec2 distance = vec2(texel) + 0.5 - jitter - target; + const vec2 distance = (vec2(texel) + 0.5 - jitter - target) * kernel; const float weight = exp(-2.29 * dot(distance, distance)); filtered += sample_color * weight; total_weight += weight; nearest_weight = max(nearest_weight, weight); sum += sample_color; squares += sample_color * sample_color; - // The longest motion around the pixel, so that an edge follows the object in front. - const vec2 candidate = - texelFetch(motion_vectors, clamp(texel, ivec2(0), motion_size - 1), 0).rg; - const float length_squared = dot(candidate, candidate); - if (length_squared > longest) { - longest = length_squared; - motion = candidate; - } } } - const vec3 current = filtered / total_weight; + const vec3 current = filtered / max(total_weight, 1e-6); const vec3 mean = sum / 9.0; const vec3 deviation = sqrt(max(squares / 9.0 - mean * mean, vec3(0.0))); - const vec3 low = mean - deviation; - const vec3 high = mean + deviation; + const vec3 low = mean - clip * deviation; + const vec3 high = mean + clip * deviation; - const vec2 previous = texcoord + motion * motion_scale; // A frame whose samples land close to this pixel counts more; one whose samples land between // pixels mostly keeps the history (from other jitter positions) there. float weight = current_weight * nearest_weight; + vec3 result = current; + float remembered = max(moved, 0.85 * moving); if (current_weight >= 1.0 || any(lessThan(previous, vec2(0.0))) || any(greaterThan(previous, vec2(1.0)))) { - weight = 1.0; + // No usable history: the wide kernel, as a narrow one would show the input's pixels. + vec3 wide = vec3(0.0); + float wide_total = 0.0; + for (int y = -1; y <= 1; ++y) { + for (int x = -1; x <= 1; ++x) { + const ivec2 texel = clamp(nearest + ivec2(x, y), ivec2(0), input_size - 1); + const vec2 distance = vec2(texel) + 0.5 - jitter - target; + const float wide_weight = exp(-2.29 * dot(distance, distance)); + wide += RgbToYCoCg(Compress(texelFetch(input_color, texel, 0).rgb)) * wide_weight; + wide_total += wide_weight; + } + } + result = wide / wide_total; + remembered = 1.0; + } else { + const vec3 unclipped = RgbToYCoCg(Compress(SampleHistory(history_position))); + const vec3 past = ClipToBox(unclipped, low, high); + result = mix(past, current, weight); + // A history far outside the neighbourhood means the content changed without + // motion vectors (some animations have none): treat the pixel as moving for a while. + const vec3 extent = 0.5 * (high - low) + 1.0 / 65504.0; + const vec3 units = abs(unclipped - 0.5 * (low + high)) / extent; + remembered = max(remembered, smoothstep(1.25, 2.5, max(units.x, max(units.y, units.z)))); } - const vec2 history_position = previous * vec2(textureSize(history, 0)); - const vec3 past = ClipToBox(RgbToYCoCg(Compress(SampleHistory(history_position))), low, high); - color = vec4(Expand(YCoCgToRgb(mix(past, current, weight))), 1.0); + color = vec4(Expand(YCoCgToRgb(result)), 1.0); + imageStore(next_history, ivec2(gl_FragCoord.xy), vec4(color.rgb, remembered)); } diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp index edd151b563..b2ff7d9bf2 100644 --- a/src/video_core/renderer_vulkan/blit_image.cpp +++ b/src/video_core/renderer_vulkan/blit_image.cpp @@ -7,6 +7,7 @@ #include <algorithm> #include "video_core/renderer_vulkan/vk_texture_cache.h" +#include "video_core/fsr.h" #include "common/div_ceil.h" #include "common/settings.h" @@ -33,6 +34,7 @@ #include "video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h" #include "video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h" +#include "video_core/host_shaders/temporal_sharpen_frag_spv.h" #include "video_core/host_shaders/temporal_upscale_frag_spv.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" #include "video_core/host_shaders/vulkan_color_clear_frag_spv.h" @@ -98,9 +100,19 @@ struct TemporalUpscalePushConstants { std::array<float, 2> jitter; std::array<float, 2> motion_scale; float current_weight; + float kernel_scale; + float clip_width; + float clip_motion; + float kernel_motion; float padding; }; +// The vertex stage's PushConstants first, then temporal_sharpen.frag's FsrRcasCon. +struct TemporalSharpenPushConstants { + PushConstants vertex; + std::array<u32, 4> rcas; +}; + struct MSAACopyPushConstants { std::array<s32, 2> dst_offset; std::array<s32, 2> src_offset; @@ -119,10 +131,27 @@ constexpr std::array TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{ TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>, TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>, }; -constexpr std::array THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS{ +// temporal_upscale.frag: input colour, motion vectors and history, then the next history (storage). +constexpr std::array TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS{ TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<0>, TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<1>, TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING<2>, + VkDescriptorSetLayoutBinding{ + .binding = 3, + .descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .descriptorCount = 1, + .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, + .pImmutableSamplers = nullptr, + }, +}; +constexpr DescriptorBankInfo TEMPORAL_UPSCALE_DESCRIPTOR_BANK_INFO{ + .uniform_buffers = 0, + .storage_buffers = 0, + .texture_buffers = 0, + .image_buffers = 0, + .textures = 3, + .images = 1, + .score = 2, }; constexpr VkDescriptorSetLayoutCreateInfo ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, @@ -148,12 +177,12 @@ constexpr VkDescriptorSetLayoutCreateInfo TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CRE .bindingCount = static_cast<u32>(TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()), .pBindings = TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(), }; -constexpr VkDescriptorSetLayoutCreateInfo THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{ +constexpr VkDescriptorSetLayoutCreateInfo TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO{ .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .pNext = nullptr, .flags = 0, - .bindingCount = static_cast<u32>(THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()), - .pBindings = THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(), + .bindingCount = static_cast<u32>(TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS.size()), + .pBindings = TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_BINDINGS.data(), }; template <VkShaderStageFlags stageFlags, size_t size> inline constexpr VkPushConstantRange PUSH_CONSTANT_RANGE{ @@ -379,13 +408,14 @@ void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descrip device.GetLogical().UpdateDescriptorSets(write_descriptor_set, nullptr); } -void UpdateThreeTexturesDescriptorSet(const Device& device, VkDescriptorSet descriptor_set, - VkSampler sampler, std::array<VkImageView, 3> image_views) { - std::array<VkDescriptorImageInfo, 3> image_infos{}; - std::array<VkWriteDescriptorSet, 3> write_descriptor_sets{}; - for (u32 binding = 0; binding < 3; ++binding) { +void UpdateTemporalUpscaleDescriptorSet(const Device& device, VkDescriptorSet descriptor_set, + VkSampler sampler, std::array<VkImageView, 4> image_views) { + std::array<VkDescriptorImageInfo, 4> image_infos{}; + std::array<VkWriteDescriptorSet, 4> write_descriptor_sets{}; + for (u32 binding = 0; binding < 4; ++binding) { + const bool storage = binding == 3; image_infos[binding] = VkDescriptorImageInfo{ - .sampler = sampler, + .sampler = storage ? VK_NULL_HANDLE : sampler, .imageView = image_views[binding], .imageLayout = VK_IMAGE_LAYOUT_GENERAL, }; @@ -396,7 +426,8 @@ void UpdateThreeTexturesDescriptorSet(const Device& device, VkDescriptorSet desc .dstBinding = binding, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .descriptorType = storage ? VK_DESCRIPTOR_TYPE_STORAGE_IMAGE + : VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImageInfo = &image_infos[binding], .pBufferInfo = nullptr, .pTexelBufferView = nullptr, @@ -638,14 +669,14 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, ONE_TEXTURE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), two_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout( TWO_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), - three_textures_set_layout(device.GetLogical().CreateDescriptorSetLayout( - THREE_TEXTURES_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), + temporal_upscale_set_layout(device.GetLogical().CreateDescriptorSetLayout( + TEMPORAL_UPSCALE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO)), one_texture_descriptor_allocator{ descriptor_pool.Allocator(device_, scheduler_, *one_texture_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<1>)}, two_textures_descriptor_allocator{ descriptor_pool.Allocator(device_, scheduler_, *two_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<2>)}, - three_textures_descriptor_allocator{ - descriptor_pool.Allocator(device_, scheduler_, *three_textures_set_layout, TEXTURE_DESCRIPTOR_BANK_INFO<3>)}, + temporal_upscale_descriptor_allocator{descriptor_pool.Allocator( + device_, scheduler_, *temporal_upscale_set_layout, TEMPORAL_UPSCALE_DESCRIPTOR_BANK_INFO)}, one_texture_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( one_texture_set_layout.address(), PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))), @@ -655,9 +686,14 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT, sizeof(PushConstants)>))), temporal_upscale_pipeline_layout( device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( - three_textures_set_layout.address(), + temporal_upscale_set_layout.address(), PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(TemporalUpscalePushConstants)>))), + temporal_sharpen_pipeline_layout( + device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + one_texture_set_layout.address(), + PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + sizeof(TemporalSharpenPushConstants)>))), clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( nullptr, PUSH_CONSTANT_RANGE<VK_SHADER_STAGE_FRAGMENT_BIT, sizeof(float) * 4>))), msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( @@ -670,6 +706,7 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)), temporal_upscale_frag(BuildShader(device, TEMPORAL_UPSCALE_FRAG_SPV)), + temporal_sharpen_frag(BuildShader(device, TEMPORAL_SHARPEN_FRAG_SPV)), blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)), blit_depth_stencil_frag(device.IsExtShaderStencilExportSupported() ? BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV) @@ -762,20 +799,26 @@ void BlitImageHelper::BlitColor(const Framebuffer* dst_framebuffer, VkImageView void BlitImageHelper::TemporalUpscale(const Framebuffer* dst_framebuffer, const ImageView& input_color, const ImageView& motion_vectors, - VkImageView history, const Region2D& dst_region, + VkImageView history, VkImageView next_history, + const Region2D& dst_region, std::array<f32, 2> jitter, std::array<f32, 2> motion_scale, - f32 current_weight) { + f32 current_weight, f32 kernel_scale, f32 clip_width, + f32 clip_motion, f32 kernel_motion) { const VkPipelineLayout layout = *temporal_upscale_pipeline_layout; const VkPipeline pipeline = FindOrEmplaceTemporalUpscalePipeline(dst_framebuffer->RenderPass()); const VkSampler sampler = *linear_sampler; - const std::array<VkImageView, 3> views{input_color.Handle(Shader::TextureType::Color2D), + const std::array<VkImageView, 4> views{input_color.Handle(Shader::TextureType::Color2D), motion_vectors.Handle(Shader::TextureType::Color2D), - history}; + history, next_history}; const TemporalUpscalePushConstants push_constants{ .vertex = {.tex_scale = {1.0f, 1.0f}, .tex_offset = {0.0f, 0.0f}}, .jitter = jitter, .motion_scale = motion_scale, .current_weight = current_weight, + .kernel_scale = kernel_scale, + .clip_width = clip_width, + .clip_motion = clip_motion, + .kernel_motion = kernel_motion, .padding = 0.0f, }; RecordShaderReadBarrier(scheduler, input_color); @@ -783,8 +826,34 @@ void BlitImageHelper::TemporalUpscale(const Framebuffer* dst_framebuffer, scheduler.RequestRenderpass(dst_framebuffer); scheduler.Record([this, dst_region, pipeline, layout, sampler, views, push_constants](vk::CommandBuffer cmdbuf) { - const VkDescriptorSet descriptor_set = three_textures_descriptor_allocator.Commit(); - UpdateThreeTexturesDescriptorSet(device, descriptor_set, sampler, views); + const VkDescriptorSet descriptor_set = temporal_upscale_descriptor_allocator.Commit(); + UpdateTemporalUpscaleDescriptorSet(device, descriptor_set, sampler, views); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, dst_region); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, + push_constants); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +void BlitImageHelper::TemporalSharpen(const Framebuffer* dst_framebuffer, VkImageView source, + const Region2D& dst_region, f32 sharpness) { + const VkPipelineLayout layout = *temporal_sharpen_pipeline_layout; + const VkPipeline pipeline = FindOrEmplaceTemporalSharpenPipeline(dst_framebuffer->RenderPass()); + const VkSampler sampler = *nearest_sampler; + TemporalSharpenPushConstants push_constants{ + .vertex = {.tex_scale = {1.0f, 1.0f}, .tex_offset = {0.0f, 0.0f}}, + .rcas = {}, + }; + FSR::FsrRcasCon(push_constants.rcas.data(), sharpness); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([this, dst_region, pipeline, layout, sampler, source, + push_constants](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, source); cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, nullptr); @@ -1432,6 +1501,59 @@ VkPipeline BlitImageHelper::FindOrEmplaceTemporalUpscalePipeline(VkRenderPass re return *temporal_upscale_pipelines.back(); } +VkPipeline BlitImageHelper::FindOrEmplaceTemporalSharpenPipeline(VkRenderPass renderpass) { + const auto it = std::ranges::find(temporal_sharpen_keys, renderpass); + if (it != temporal_sharpen_keys.end()) { + return *temporal_sharpen_pipelines[std::distance(temporal_sharpen_keys.begin(), it)]; + } + temporal_sharpen_keys.push_back(renderpass); + const std::array stages = MakeStages(*full_screen_vert, *temporal_sharpen_frag); + const VkPipelineColorBlendAttachmentState blend_attachment{ + .blendEnable = VK_FALSE, + .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, + .colorBlendOp = VK_BLEND_OP_ADD, + .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, + .alphaBlendOp = VK_BLEND_OP_ADD, + .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, + }; + const VkPipelineColorBlendStateCreateInfo color_blend_create_info{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .logicOpEnable = VK_FALSE, + .logicOp = VK_LOGIC_OP_CLEAR, + .attachmentCount = 1, + .pAttachments = &blend_attachment, + .blendConstants = {0.0f, 0.0f, 0.0f, 0.0f}, + }; + const VkPipelineInputAssemblyStateCreateInfo input_assembly_ci = GetPipelineInputAssemblyStateCreateInfo(device); + temporal_sharpen_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast<u32>(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &input_assembly_ci, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pDepthStencilState = nullptr, + .pColorBlendState = &color_blend_create_info, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *temporal_sharpen_pipeline_layout, + .renderPass = renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + }, device.StaticPipelineCache())); + return *temporal_sharpen_pipelines.back(); +} + VkPipeline BlitImageHelper::FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key) { const auto it = std::ranges::find(blit_depth_stencil_keys, key); if (it != blit_depth_stencil_keys.end()) { diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h index 303beeea5c..6c3685a0a6 100644 --- a/src/video_core/renderer_vulkan/blit_image.h +++ b/src/video_core/renderer_vulkan/blit_image.h @@ -83,11 +83,20 @@ public: const Region2D& src_region, const Extent3D& src_size); /// Temporal upscale (temporal_upscale.frag) into dst_framebuffer: the jittered input colour, - /// accumulated with the history image reprojected along the motion vectors. + /// accumulated with the history image reprojected along the motion vectors. The result is also + /// stored to next_history (a storage image of the output's size). void TemporalUpscale(const Framebuffer* dst_framebuffer, const ImageView& input_color, const ImageView& motion_vectors, VkImageView history, - const Region2D& dst_region, std::array<f32, 2> jitter, - std::array<f32, 2> motion_scale, f32 current_weight); + VkImageView next_history, const Region2D& dst_region, + std::array<f32, 2> jitter, + std::array<f32, 2> motion_scale, f32 current_weight, + f32 kernel_scale, f32 clip_width, f32 clip_motion, + f32 kernel_motion); + + /// RCAS (temporal_sharpen.frag) of source into dst_framebuffer; sharpness in stops as + /// FsrRcasCon takes it (0 is the strongest). + void TemporalSharpen(const Framebuffer* dst_framebuffer, VkImageView source, + const Region2D& dst_region, f32 sharpness); void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, const Region2D& dst_region, const Region2D& src_region); @@ -179,6 +188,8 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceTemporalUpscalePipeline(VkRenderPass renderpass); + [[nodiscard]] VkPipeline FindOrEmplaceTemporalSharpenPipeline(VkRenderPass renderpass); + [[nodiscard]] VkPipeline FindOrEmplaceDepthStencilPipeline(const BlitImagePipelineKey& key); [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); @@ -216,19 +227,21 @@ private: vk::DescriptorSetLayout one_texture_set_layout; vk::DescriptorSetLayout two_textures_set_layout; - vk::DescriptorSetLayout three_textures_set_layout; + vk::DescriptorSetLayout temporal_upscale_set_layout; DescriptorAllocator one_texture_descriptor_allocator; DescriptorAllocator two_textures_descriptor_allocator; - DescriptorAllocator three_textures_descriptor_allocator; + DescriptorAllocator temporal_upscale_descriptor_allocator; vk::PipelineLayout one_texture_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; vk::PipelineLayout temporal_upscale_pipeline_layout; + vk::PipelineLayout temporal_sharpen_pipeline_layout; vk::PipelineLayout clear_color_pipeline_layout; vk::PipelineLayout msaa_copy_pipeline_layout; vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule blit_color_to_color_frag; vk::ShaderModule temporal_upscale_frag; + vk::ShaderModule temporal_sharpen_frag; vk::ShaderModule blit_color_msaa_frag; vk::ShaderModule blit_depth_stencil_frag; vk::ShaderModule blit_depth_frag; @@ -261,6 +274,8 @@ private: std::vector<vk::Pipeline> blit_color_pipelines; std::vector<VkRenderPass> temporal_upscale_keys; std::vector<vk::Pipeline> temporal_upscale_pipelines; + std::vector<VkRenderPass> temporal_sharpen_keys; + std::vector<vk::Pipeline> temporal_sharpen_pipelines; std::vector<BlitImagePipelineKey> blit_depth_stencil_keys; std::vector<vk::Pipeline> blit_depth_stencil_pipelines; std::vector<BlitImagePipelineKey> clear_color_keys; diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index 30981fe413..ceee8b5dc5 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp @@ -1544,48 +1544,55 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie } const VkFormat format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, dst.format).format; - const VkExtent2D extent{dst.size.width, dst.size.height}; - const VkImage output = dst.ImageHandle(); + // The framebuffer's area is the output's size on the host (scaled with resolution scaling). + const VkExtent2D extent = dst_framebuffer->RenderArea(); + // The history is kept at half-float precision: in the output's format (B10G11R11 here, 5-6 + // mantissa bits) a frame's small share of the blend rounded away, so the history never + // converged on the detail the jittered frames add. + constexpr VkFormat history_format = VK_FORMAT_R16G16B16A16_SFLOAT; bool reset = false; - if (!temporal_history.image || temporal_history.format != format || + if (!temporal_history.images[0] || temporal_history.format != format || temporal_history.extent.width != extent.width || temporal_history.extent.height != extent.height) { - if (temporal_history.image) { - scheduler.Finish(); // the old history may still be sampled + if (temporal_history.images[0]) { + scheduler.Finish(); // the old history may still be in use } - temporal_history.image = memory_allocator.CreateImage(VkImageCreateInfo{ - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .imageType = VK_IMAGE_TYPE_2D, - .format = format, - .extent = {extent.width, extent.height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = VK_SAMPLE_COUNT_1_BIT, - .tiling = VK_IMAGE_TILING_OPTIMAL, - .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .queueFamilyIndexCount = 0, - .pQueueFamilyIndices = nullptr, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - }); - temporal_history.view = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .pNext = nullptr, - .flags = 0, - .image = *temporal_history.image, - .viewType = VK_IMAGE_VIEW_TYPE_2D, - .format = format, - .components{}, - .subresourceRange{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1, - }, - }); + for (size_t i = 0; i < temporal_history.images.size(); ++i) { + temporal_history.images[i] = memory_allocator.CreateImage(VkImageCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .imageType = VK_IMAGE_TYPE_2D, + .format = history_format, + .extent = {extent.width, extent.height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = VK_SAMPLE_COUNT_1_BIT, + .tiling = VK_IMAGE_TILING_OPTIMAL, + .usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .queueFamilyIndexCount = 0, + .pQueueFamilyIndices = nullptr, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + }); + temporal_history.views[i] = device.GetLogical().CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = *temporal_history.images[i], + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = history_format, + .components{}, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1, + }, + }); + } + temporal_history.current = 0; temporal_history.format = format; temporal_history.extent = extent; reset = true; @@ -1593,24 +1600,15 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie static_cast<u32>(format)); } - // The history holds this pass's previous result, copied right after drawing it: the game may - // draw into its DLSS output afterwards (post-processing), which must not feed back. The history - // stays in the GENERAL layout, which the blit helper's descriptors use. - const VkImage history = *temporal_history.image; - const VkImageSubresourceLayers output_layers{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = static_cast<u32>(dst.range.base.level), - .baseArrayLayer = static_cast<u32>(dst.range.base.layer), - .layerCount = 1, - }; - static constexpr VkImageSubresourceLayers history_layers{ - .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .mipLevel = 0, - .baseArrayLayer = 0, - .layerCount = 1, - }; + // The shader reads the previous result from one history image and stores this one to the + // other: the game may draw into its DLSS output afterwards (post-processing), which must not + // feed back. Both stay in the GENERAL layout, which the blit helper's descriptors use. + const size_t read_index = temporal_history.current; + const size_t write_index = read_index ^ 1; + const VkImage history = *temporal_history.images[read_index]; + const VkImage next_history = *temporal_history.images[write_index]; const auto barrier = [](VkImage image, VkAccessFlags src_access, VkAccessFlags dst_access, - VkImageLayout old_layout, const VkImageSubresourceLayers& layers) { + VkImageLayout old_layout) { return VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .pNext = nullptr, @@ -1623,20 +1621,24 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie .image = image, .subresourceRange{ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, - .baseMipLevel = layers.mipLevel, + .baseMipLevel = 0, .levelCount = 1, - .baseArrayLayer = layers.baseArrayLayer, + .baseArrayLayer = 0, .layerCount = 1, }, }; }; if (reset) { - // A new history image has no contents yet; the first frame does not read it. + // New history images have no contents yet; the first frame does not use what it reads. scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([history, barrier](vk::CommandBuffer cmdbuf) { - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - 0, barrier(history, 0, VK_ACCESS_SHADER_READ_BIT, - VK_IMAGE_LAYOUT_UNDEFINED, history_layers)); + scheduler.Record([history, next_history, barrier](vk::CommandBuffer cmdbuf) { + constexpr VkAccessFlags access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + const std::array barriers{ + barrier(history, 0, access, VK_IMAGE_LAYOUT_UNDEFINED), + barrier(next_history, 0, access, VK_IMAGE_LAYOUT_UNDEFINED), + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, {}, {}, barriers); }); } const Region2D dst_region{ @@ -1644,42 +1646,79 @@ bool TextureCacheRuntime::TemporalUpscale(Framebuffer* dst_framebuffer, ImageVie .end = {static_cast<s32>(extent.width), static_cast<s32>(extent.height)}, }; // Each frame weighs at most 0.1 against the history (scaled by how close its samples land to - // the pixel; the neighbourhood clip limits what older samples contribute). - constexpr f32 current_weight = 0.1f; + // the pixel; the neighbourhood clip limits what older samples contribute). Still pixels use a + // sample kernel a third as wide as for moving ones (the jittered frames fill in the detail) and + // a clip of 1.5 deviations instead of 1. + // EDEN_NX2_TEMPORAL_WEIGHT / _KERNEL / _CLIP / _CLIP_MOTION / _KERNEL_MOTION + // override them (all 1 = the previous stand-in's filter). + const auto tuned = [](const char* name, f32 fallback) { + const char* value = std::getenv(name); + return value ? static_cast<f32>(std::strtod(value, nullptr)) : fallback; + }; + static const f32 current_weight = tuned("EDEN_NX2_TEMPORAL_WEIGHT", 0.1f); + static const f32 kernel_scale = tuned("EDEN_NX2_TEMPORAL_KERNEL", 3.0f); + static const f32 clip_width = tuned("EDEN_NX2_TEMPORAL_CLIP", 1.5f); + static const f32 clip_motion = tuned("EDEN_NX2_TEMPORAL_CLIP_MOTION", 1.0f); + static const f32 kernel_motion = tuned("EDEN_NX2_TEMPORAL_KERNEL_MOTION", 1.0f); blit_image_helper.TemporalUpscale(dst_framebuffer, input_color, motion_vectors, - *temporal_history.view, dst_region, jitter, motion_scale, - reset ? 1.0f : current_weight); + *temporal_history.views[read_index], + *temporal_history.views[write_index], dst_region, jitter, + motion_scale, reset ? 1.0f : current_weight, kernel_scale, + clip_width, clip_motion, kernel_motion); + // RCAS on the output, set by the FSR sharpness slider (stops as FSR takes them, 0% = stored 200 + // turns it off). It reads the unsharpened result just stored, which stays the history. + // EDEN_NX2_TEMPORAL_SHARPEN=<stops>|off overrides the slider. + static const char* const sharpen_override = std::getenv("EDEN_NX2_TEMPORAL_SHARPEN"); + const int slider = Settings::values.fsr_sharpening_slider.GetValue(); + bool sharpen = slider < 200; + f32 sharpness = static_cast<f32>(slider) / 100.0f; + if (sharpen_override) { + sharpen = std::string_view{sharpen_override} != "off"; + sharpness = static_cast<f32>(std::strtod(sharpen_override, nullptr)); + } + const VkImage output = dst.ImageHandle(); + const u32 output_level = static_cast<u32>(dst.range.base.level); + const u32 output_layer = static_cast<u32>(dst.range.base.layer); scheduler.RequestOutsideRenderPassOperationContext(); - scheduler.Record([output, history, extent, output_layers, barrier](vk::CommandBuffer cmdbuf) { - constexpr VkAccessFlags written = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - const std::array before{ - barrier(output, written, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, - output_layers), - barrier(history, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, - VK_IMAGE_LAYOUT_GENERAL, history_layers), - }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, before); - const VkImageCopy copy{ - .srcSubresource = output_layers, - .srcOffset = {0, 0, 0}, - .dstSubresource = history_layers, - .dstOffset = {0, 0, 0}, - .extent = {extent.width, extent.height, 1}, - }; - cmdbuf.CopyImage(output, VK_IMAGE_LAYOUT_GENERAL, history, VK_IMAGE_LAYOUT_GENERAL, copy); - const std::array after{ - barrier(output, VK_ACCESS_TRANSFER_READ_BIT, - VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | - VK_ACCESS_TRANSFER_READ_BIT, - VK_IMAGE_LAYOUT_GENERAL, output_layers), - barrier(history, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - VK_IMAGE_LAYOUT_GENERAL, history_layers), + scheduler.Record([history, next_history, barrier, output, output_level, output_layer, + sharpen](vk::CommandBuffer cmdbuf) { + // The next frame reads what was stored now and stores over what was read now; the + // sharpening pass reads what was stored now and draws over the output again. + const std::array barriers{ + barrier(next_history, VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_IMAGE_LAYOUT_GENERAL), + barrier(history, VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT, + VK_IMAGE_LAYOUT_GENERAL), + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = output, + .subresourceRange{ + .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, + .baseMipLevel = output_level, + .levelCount = 1, + .baseArrayLayer = output_layer, + .layerCount = 1, + }, + }, }; - cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, - {}, {}, after); + constexpr VkPipelineStageFlags stages = + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + cmdbuf.PipelineBarrier(stages, stages, 0, {}, {}, + vk::Span<VkImageMemoryBarrier>(barriers.data(), + sharpen ? barriers.size() : 2)); }); + if (sharpen) { + blit_image_helper.TemporalSharpen(dst_framebuffer, *temporal_history.views[write_index], + dst_region, sharpness); + } + temporal_history.current = write_index; return true; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index 564b2cbf9e..29687fda9c 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -188,10 +188,12 @@ public: BlitImageHelper& blit_image_helper; RenderPassCache& render_pass_cache; - /// TemporalUpscale's history: its previous result, copied right after drawing it. + /// TemporalUpscale's history: its previous result (images[current]) and the image the next + /// result is stored to. struct TemporalHistory { - vk::Image image; - vk::ImageView view; + std::array<vk::Image, 2> images; + std::array<vk::ImageView, 2> views; + size_t current{}; VkFormat format{}; VkExtent2D extent{}; } temporal_history; diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 3f3ae9cafd..365137e7d6 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -1131,11 +1131,27 @@ bool TextureCache<P>::TemporalUpscale(const Tegra::Engines::Fermi2D::Surface& ds PrepareImage(motion_id, false, false); PrepareImage(dst_id, true, false); for (const ImageId id : {src_id, motion_id, dst_id}) { - if (True(slot_images[id].flags & ImageFlagBits::Rescaled) || - slot_images[id].info.num_samples != 1) { + if (slot_images[id].info.num_samples != 1) { return false; } } + // With resolution scaling, bring the images that can follow to the same scale, as BlitImage + // does; the stand-in reads each image at its own size, so any left behind still work. + const auto rescaled = [&](ImageId id) { + return True(slot_images[id].flags & ImageFlagBits::Rescaled); + }; + if (rescaled(src_id) || rescaled(motion_id) || rescaled(dst_id)) { + for (const ImageId id : {src_id, motion_id, dst_id}) { + if (!rescaled(id) && ImageCanRescale(slot_images[id])) { + ScaleUp(slot_images[id]); + } + } + } + // The jitter is in the input's guest pixels. + if (rescaled(src_id)) { + const f32 factor = Settings::values.resolution_info.up_factor; + jitter = {jitter[0] * factor, jitter[1] * factor}; + } const auto view = [&](ImageId id, const Tegra::Engines::Fermi2D::Surface& surface, PixelFormat format) -> std::optional<std::pair<FramebufferId, ImageViewId>> { const std::optional base = slot_images[id].TryFindBase(surface.Address()); |
