diff options
| author | Duck912 <350528965+Duck912@users.noreply.github.com> | 2026-11-02 16:07:08 -0500 |
|---|---|---|
| committer | Duck912 <350528965+Duck912@users.noreply.github.com> | 2026-11-02 16:28:01 -0500 |
| commit | 960ebcc73d998c7c596c3377b0ec8a1d6eefccf1 (patch) | |
| tree | 3382749be8d24a053c5f74d28e2388a9dd704eb8 /src/video_core/renderer_vulkan | |
| parent | bb8fdfac21434db0bfd62c56b0a409ce26070a38 (diff) | |
video_core: sharpen the temporal DLSS stand-in
Fire Emblem: Fortune's Weave's DLSS input matches Eden's (same 720p
image, same detail), but the stand-in's 1440p output was blurrier than a
plain bilinear 2x of it (Laplacian 0.0066 vs 0.0081; Eden's real DLSS
0.0111), and the image drifted darker over time:
- the sample kernel was about twice too wide for the output resolution;
- the history was kept in the output's B10G11R11 format, whose 5-6 bit
mantissas round away a frame's 0.03-0.1 share;
- the 1-sigma neighbourhood clip removed detail that only some jitter
positions sample (railings, thin edges).
The history is now an RGBA16F pair the shader writes as a storage image,
with no per-frame copy. Still pixels use a third of the kernel width and
a 1.5-sigma clip, so detail from many jitter positions accumulates.
Moving pixels keep the old kernel and 1-sigma clip, and so do pixels
that moved recently or whose history falls far outside the
neighbourhood (content that changes without motion vectors), remembered
in the history's alpha. Static output goes from 0.0066 to 0.0102 with
no brightness drift and no new ghosting.
With resolution scaling the stand-in used to fall back to a bilinear
blit. The images are now scaled up as BlitImage does, the jitter is
scaled to host pixels and the history follows the framebuffer's render
area.
RCAS (temporal_sharpen.frag) sharpens a copy of each result, never the
history, in range-compressed space since the output is HDR. Its strength
is the sharpness slider, relabelled "RCAS Sharpness" (0% turns it off).
EDEN_NX2_TEMPORAL_KERNEL, _CLIP, _KERNEL_MOTION, _CLIP_MOTION and
_WEIGHT tune the filter (all four filter values at 1 give the old one),
and EDEN_NX2_TEMPORAL_SHARPEN=<stops>|off overrides the slider.
Assisted-by: Claude Opus 5.5 (debugging, implementation and drafting)
Signed-off-by: Duck912 <350528965+Duck912@users.noreply.github.com>
Diffstat (limited to 'src/video_core/renderer_vulkan')
| -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 |
4 files changed, 302 insertions, 124 deletions
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; |
