aboutsummaryrefslogtreecommitdiff
path: root/src/video_core/renderer_vulkan
diff options
context:
space:
mode:
Diffstat (limited to 'src/video_core/renderer_vulkan')
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp166
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h25
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp227
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h8
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;