aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan/blit_image.cpp
diff options
context:
space:
mode:
authorDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-02 16:07:08 -0500
committerDuck912 <350528965+Duck912@users.noreply.github.com>2026-11-02 16:28:01 -0500
commit960ebcc73d998c7c596c3377b0ec8a1d6eefccf1 (patch)
tree3382749be8d24a053c5f74d28e2388a9dd704eb8 /src/video_core/renderer_vulkan/blit_image.cpp
parentbb8fdfac21434db0bfd62c56b0a409ce26070a38 (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/blit_image.cpp')
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp166
1 files changed, 144 insertions, 22 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()) {