mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-08-03 16:02:26 +00:00
video_core: fsr implementation
This commit is contained in:
parent
d0f0685e53
commit
c478e55305
@ -845,6 +845,8 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
||||
src/video_core/renderer_vulkan/vk_shader_util.h
|
||||
src/video_core/renderer_vulkan/vk_swapchain.cpp
|
||||
src/video_core/renderer_vulkan/vk_swapchain.h
|
||||
src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp
|
||||
src/video_core/renderer_vulkan/host_passes/fsr_pass.h
|
||||
src/video_core/renderer_vulkan/host_passes/pp_pass.cpp
|
||||
src/video_core/renderer_vulkan/host_passes/pp_pass.h
|
||||
src/video_core/texture_cache/image.cpp
|
||||
|
@ -110,4 +110,9 @@ SPDX-License-Identifier = "CC0-1.0"
|
||||
[[annotations]]
|
||||
path = "cmake/CMakeRC.cmake"
|
||||
SPDX-FileCopyrightText = "Copyright (c) 2017 vector-of-bool <vectorofbool@gmail.com>"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
||||
[[annotations]]
|
||||
path = "src/video_core/host_shaders/fsr/*"
|
||||
SPDX-FileCopyrightText = "Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved."
|
||||
SPDX-License-Identifier = "MIT"
|
||||
|
@ -86,6 +86,21 @@ void L::DrawMenuBar() {
|
||||
SliderFloat("Gamma", &pp_settings.gamma, 0.1f, 2.0f);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (BeginMenu("FSR")) {
|
||||
auto& fsr = presenter->GetFsrSettingsRef();
|
||||
Checkbox("FSR Enabled", &fsr.enable);
|
||||
BeginDisabled(!fsr.enable);
|
||||
{
|
||||
Checkbox("RCAS", &fsr.use_rcas);
|
||||
BeginDisabled(!fsr.use_rcas);
|
||||
{
|
||||
SliderFloat("RCAS Attenuation", &fsr.rcas_attenuation, 0.0, 3.0);
|
||||
}
|
||||
EndDisabled();
|
||||
}
|
||||
EndDisabled();
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
if (BeginMenu("Debug")) {
|
||||
|
@ -12,6 +12,7 @@ set(SHADER_FILES
|
||||
detilers/micro_64bpp.comp
|
||||
detilers/micro_8bpp.comp
|
||||
fs_tri.vert
|
||||
fsr.comp
|
||||
post_process.frag
|
||||
)
|
||||
|
||||
|
91
src/video_core/host_shaders/fsr.comp
Normal file
91
src/video_core/host_shaders/fsr.comp
Normal file
@ -0,0 +1,91 @@
|
||||
// SPDX-FileCopyrightText: Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
#version 450
|
||||
#extension GL_ARB_separate_shader_objects: enable
|
||||
#extension GL_ARB_shading_language_420pack: enable
|
||||
|
||||
// FidelityFX Super Resolution Sample
|
||||
//
|
||||
// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved.
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files(the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions :
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
layout (push_constant) uniform const_buffer
|
||||
{
|
||||
uvec4 Const0;
|
||||
uvec4 Const1;
|
||||
uvec4 Const2;
|
||||
uvec4 Const3;
|
||||
uvec4 Sample;
|
||||
};
|
||||
|
||||
#define A_GPU 1
|
||||
#define A_GLSL 1
|
||||
|
||||
#define A_HALF
|
||||
#include "fsr/ffx_a.h"
|
||||
|
||||
layout (set = 0, binding = 0) uniform texture2D InputTexture;
|
||||
layout (set = 0, binding = 1, rgba16f) uniform image2D OutputTexture;
|
||||
layout (set = 0, binding = 2) uniform sampler InputSampler;
|
||||
|
||||
#if SAMPLE_EASU
|
||||
#define FSR_EASU_H 1
|
||||
AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 0)); return res; }
|
||||
AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 1)); return res; }
|
||||
AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(sampler2D(InputTexture, InputSampler), p, 2)); return res; }
|
||||
#endif// SAMPLE_EASU
|
||||
|
||||
#if SAMPLE_RCAS
|
||||
#define FSR_RCAS_H
|
||||
AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(sampler2D(InputTexture, InputSampler), ASU2(p), 0)); }
|
||||
void FsrRcasInputH(inout AH1 r, inout AH1 g, inout AH1 b) { }
|
||||
#endif// SAMPLE_RCAS
|
||||
|
||||
#include "fsr/ffx_fsr1.h"
|
||||
|
||||
void CurrFilter(AU2 pos)
|
||||
{
|
||||
#if SAMPLE_EASU
|
||||
AH3 c;
|
||||
FsrEasuH(c, pos, Const0, Const1, Const2, Const3);
|
||||
if (Sample.x == 1)
|
||||
c *= c;
|
||||
imageStore(OutputTexture, ASU2(pos), AH4(c, 1));
|
||||
#endif// SAMPLE_EASU
|
||||
#if SAMPLE_RCAS
|
||||
AH3 c;
|
||||
FsrRcasH(c.r, c.g, c.b, pos, Const0);
|
||||
if (Sample.x == 1)
|
||||
c *= c;
|
||||
imageStore(OutputTexture, ASU2(pos), AH4(c, 1));
|
||||
#endif// SAMPLE_RCAS
|
||||
}
|
||||
|
||||
layout (local_size_x = 64) in;
|
||||
void main()
|
||||
{
|
||||
// Do remapping of local xy in workgroup for a more PS-like swizzle pattern.
|
||||
AU2 gxy = ARmp8x8(gl_LocalInvocationID.x) + AU2(gl_WorkGroupID.x << 4u, gl_WorkGroupID.y << 4u);
|
||||
CurrFilter(gxy);
|
||||
gxy.x += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.y += 8u;
|
||||
CurrFilter(gxy);
|
||||
gxy.x -= 8u;
|
||||
CurrFilter(gxy);
|
||||
}
|
2657
src/video_core/host_shaders/fsr/ffx_a.h
Normal file
2657
src/video_core/host_shaders/fsr/ffx_a.h
Normal file
File diff suppressed because it is too large
Load Diff
1200
src/video_core/host_shaders/fsr/ffx_fsr1.h
Normal file
1200
src/video_core/host_shaders/fsr/ffx_fsr1.h
Normal file
File diff suppressed because it is too large
Load Diff
440
src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp
Normal file
440
src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp
Normal file
@ -0,0 +1,440 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "fsr_pass.h"
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "video_core/host_shaders/fsr_comp.h"
|
||||
#include "video_core/renderer_vulkan/vk_platform.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
#define A_CPU
|
||||
#include "video_core/host_shaders/fsr/ffx_a.h"
|
||||
#include "video_core/host_shaders/fsr/ffx_fsr1.h"
|
||||
|
||||
typedef u32 uvec4[4];
|
||||
|
||||
struct FSRConstants {
|
||||
uvec4 Const0;
|
||||
uvec4 Const1;
|
||||
uvec4 Const2;
|
||||
uvec4 Const3;
|
||||
uvec4 Sample;
|
||||
};
|
||||
|
||||
namespace Vulkan::HostPasses {
|
||||
|
||||
void FsrPass::Create(vk::Device device, VmaAllocator allocator, u32 num_images) {
|
||||
this->device = device;
|
||||
this->num_images = num_images;
|
||||
|
||||
sampler = Check<"create upscaling sampler">(device.createSamplerUnique(vk::SamplerCreateInfo{
|
||||
.magFilter = vk::Filter::eLinear,
|
||||
.minFilter = vk::Filter::eLinear,
|
||||
.mipmapMode = vk::SamplerMipmapMode::eNearest,
|
||||
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
|
||||
.addressModeW = vk::SamplerAddressMode::eClampToEdge,
|
||||
.maxAnisotropy = 1.0f,
|
||||
.minLod = -1000.0f,
|
||||
.maxLod = 1000.0f,
|
||||
}));
|
||||
|
||||
std::array<vk::DescriptorSetLayoutBinding, 3> layoutBindings{{
|
||||
{
|
||||
.binding = 0,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
},
|
||||
{
|
||||
.binding = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
},
|
||||
{
|
||||
.binding = 2,
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.descriptorCount = 1,
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
.pImmutableSamplers = &sampler.get(),
|
||||
},
|
||||
}};
|
||||
|
||||
descriptor_set_layout =
|
||||
Check<"create fsr descriptor set layout">(device.createDescriptorSetLayoutUnique({
|
||||
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptor,
|
||||
.bindingCount = layoutBindings.size(),
|
||||
.pBindings = layoutBindings.data(),
|
||||
}));
|
||||
|
||||
const vk::PushConstantRange push_constants{
|
||||
.stageFlags = vk::ShaderStageFlagBits::eCompute,
|
||||
.offset = 0,
|
||||
.size = sizeof(FSRConstants),
|
||||
};
|
||||
|
||||
const auto& cs_easu_module =
|
||||
Compile(HostShaders::FSR_COMP, vk::ShaderStageFlagBits::eCompute, device,
|
||||
{
|
||||
"SAMPLE_EASU=1",
|
||||
});
|
||||
ASSERT(cs_easu_module);
|
||||
SetObjectName(device, cs_easu_module, "fsr.comp [EASU]");
|
||||
|
||||
const auto& cs_rcas_module =
|
||||
Compile(HostShaders::FSR_COMP, vk::ShaderStageFlagBits::eCompute, device,
|
||||
{
|
||||
"SAMPLE_RCAS=1",
|
||||
});
|
||||
ASSERT(cs_rcas_module);
|
||||
SetObjectName(device, cs_rcas_module, "fsr.comp [RCAS]");
|
||||
|
||||
pipeline_layout = Check<"fsp pipeline layout">(device.createPipelineLayoutUnique({
|
||||
.setLayoutCount = 1,
|
||||
.pSetLayouts = &descriptor_set_layout.get(),
|
||||
.pushConstantRangeCount = 1,
|
||||
.pPushConstantRanges = &push_constants,
|
||||
}));
|
||||
SetObjectName(device, pipeline_layout.get(), "fsr pipeline layout");
|
||||
|
||||
const vk::ComputePipelineCreateInfo easu_pinfo{
|
||||
.stage{
|
||||
.stage = vk::ShaderStageFlagBits::eCompute,
|
||||
.module = cs_easu_module,
|
||||
.pName = "main",
|
||||
},
|
||||
.layout = pipeline_layout.get(),
|
||||
};
|
||||
easu_pipeline =
|
||||
Check<"fsp easu compute pipelines">(device.createComputePipelineUnique({}, easu_pinfo));
|
||||
SetObjectName(device, easu_pipeline.get(), "fsr easu pipeline");
|
||||
|
||||
const vk::ComputePipelineCreateInfo rcas_pinfo{
|
||||
.stage{
|
||||
.stage = vk::ShaderStageFlagBits::eCompute,
|
||||
.module = cs_rcas_module,
|
||||
.pName = "main",
|
||||
},
|
||||
.layout = pipeline_layout.get(),
|
||||
};
|
||||
rcas_pipeline =
|
||||
Check<"fsp rcas compute pipelines">(device.createComputePipelineUnique({}, rcas_pinfo));
|
||||
SetObjectName(device, rcas_pipeline.get(), "fsr rcas pipeline");
|
||||
|
||||
device.destroyShaderModule(cs_easu_module);
|
||||
device.destroyShaderModule(cs_rcas_module);
|
||||
|
||||
available_imgs.resize(num_images);
|
||||
for (int i = 0; i < num_images; ++i) {
|
||||
auto& img = available_imgs[i];
|
||||
img.id = i;
|
||||
img.intermediary_image = VideoCore::UniqueImage(device, allocator);
|
||||
img.output_image = VideoCore::UniqueImage(device, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
vk::ImageView FsrPass::Render(vk::CommandBuffer cmdbuf, vk::ImageView input,
|
||||
vk::Extent2D input_size, vk::Extent2D output_size, Settings settings,
|
||||
bool hdr) {
|
||||
if (!settings.enable) {
|
||||
return input;
|
||||
}
|
||||
if (input_size.width >= output_size.width && input_size.height >= output_size.height) {
|
||||
return input;
|
||||
}
|
||||
|
||||
if (output_size != cur_size) {
|
||||
ResizeAndInvalidate(output_size.width, output_size.height);
|
||||
}
|
||||
auto [width, height] = cur_size;
|
||||
|
||||
auto& img = available_imgs[cur_image];
|
||||
if (++cur_image >= available_imgs.size()) {
|
||||
cur_image = 0;
|
||||
}
|
||||
|
||||
if (img.dirty) {
|
||||
CreateImages(img);
|
||||
}
|
||||
|
||||
static const int thread_group_work_region_dim = 16;
|
||||
int dispatch_x = (width + (thread_group_work_region_dim - 1)) / thread_group_work_region_dim;
|
||||
int dispatch_y = (height + (thread_group_work_region_dim - 1)) / thread_group_work_region_dim;
|
||||
|
||||
constexpr vk::ImageSubresourceRange simple_subresource = {
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.levelCount = 1,
|
||||
.layerCount = 1,
|
||||
};
|
||||
const std::array enter_barrier{
|
||||
vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eShaderRead,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eShaderWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.image = img.intermediary_image,
|
||||
.subresourceRange = simple_subresource,
|
||||
},
|
||||
};
|
||||
cmdbuf.pipelineBarrier2({
|
||||
.imageMemoryBarrierCount = enter_barrier.size(),
|
||||
.pImageMemoryBarriers = enter_barrier.data(),
|
||||
});
|
||||
|
||||
FSRConstants consts{};
|
||||
FsrEasuCon(reinterpret_cast<AU1*>(&consts.Const0), reinterpret_cast<AU1*>(&consts.Const1),
|
||||
reinterpret_cast<AU1*>(&consts.Const2), reinterpret_cast<AU1*>(&consts.Const3),
|
||||
static_cast<AF1>(input_size.width), static_cast<AF1>(input_size.height),
|
||||
static_cast<AF1>(input_size.width), static_cast<AF1>(input_size.height), (AF1)width,
|
||||
(AF1)height);
|
||||
consts.Sample[0] = hdr && !settings.use_rcas ? 1 : 0;
|
||||
|
||||
if (settings.use_rcas) {
|
||||
|
||||
{ // easu
|
||||
std::array<vk::DescriptorImageInfo, 3> img_info{{
|
||||
{
|
||||
.imageView = input,
|
||||
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
},
|
||||
{
|
||||
.imageView = img.intermediary_image_view.get(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
},
|
||||
{
|
||||
.sampler = sampler.get(),
|
||||
},
|
||||
}};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> set_writes{{
|
||||
{
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &img_info[0],
|
||||
},
|
||||
{
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.pImageInfo = &img_info[1],
|
||||
},
|
||||
{
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.pImageInfo = &img_info[2],
|
||||
},
|
||||
}};
|
||||
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, easu_pipeline.get());
|
||||
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0,
|
||||
set_writes);
|
||||
cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(FSRConstants), &consts);
|
||||
cmdbuf.dispatch(dispatch_x, dispatch_y, 1);
|
||||
}
|
||||
|
||||
std::array img_barrier{
|
||||
vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eShaderStorageWrite,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eShaderRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
.image = img.intermediary_image,
|
||||
.subresourceRange = simple_subresource,
|
||||
},
|
||||
vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eTopOfPipe,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eNone,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eShaderStorageWrite,
|
||||
.oldLayout = vk::ImageLayout::eUndefined,
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.image = img.output_image,
|
||||
.subresourceRange = simple_subresource,
|
||||
},
|
||||
};
|
||||
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
|
||||
.imageMemoryBarrierCount = img_barrier.size(),
|
||||
.pImageMemoryBarriers = img_barrier.data(),
|
||||
});
|
||||
|
||||
{ // rcas
|
||||
consts = {};
|
||||
FsrRcasCon(reinterpret_cast<AU1*>(&consts.Const0), settings.rcas_attenuation);
|
||||
consts.Sample[0] = hdr ? 1 : 0;
|
||||
|
||||
std::array<vk::DescriptorImageInfo, 3> img_info{{
|
||||
{
|
||||
.imageView = img.intermediary_image_view.get(),
|
||||
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
},
|
||||
{
|
||||
.imageView = img.output_image_view.get(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
},
|
||||
{
|
||||
.sampler = sampler.get(),
|
||||
},
|
||||
}};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> set_writes{{
|
||||
{
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &img_info[0],
|
||||
},
|
||||
{
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.pImageInfo = &img_info[1],
|
||||
},
|
||||
{
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.pImageInfo = &img_info[2],
|
||||
},
|
||||
}};
|
||||
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, rcas_pipeline.get());
|
||||
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0,
|
||||
set_writes);
|
||||
cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(FSRConstants), &consts);
|
||||
cmdbuf.dispatch(dispatch_x, dispatch_y, 1);
|
||||
}
|
||||
|
||||
} else {
|
||||
// only easu
|
||||
std::array<vk::DescriptorImageInfo, 3> img_info{{
|
||||
{
|
||||
.imageView = input,
|
||||
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
},
|
||||
{
|
||||
.imageView = img.output_image_view.get(),
|
||||
.imageLayout = vk::ImageLayout::eGeneral,
|
||||
},
|
||||
{
|
||||
.sampler = sampler.get(),
|
||||
},
|
||||
}};
|
||||
|
||||
std::array<vk::WriteDescriptorSet, 3> set_writes{{
|
||||
{
|
||||
.dstBinding = 0,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampledImage,
|
||||
.pImageInfo = &img_info[0],
|
||||
},
|
||||
{
|
||||
.dstBinding = 1,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eStorageImage,
|
||||
.pImageInfo = &img_info[1],
|
||||
},
|
||||
{
|
||||
.dstBinding = 2,
|
||||
.descriptorCount = 1,
|
||||
.descriptorType = vk::DescriptorType::eSampler,
|
||||
.pImageInfo = &img_info[2],
|
||||
},
|
||||
}};
|
||||
|
||||
cmdbuf.bindPipeline(vk::PipelineBindPoint::eCompute, easu_pipeline.get());
|
||||
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eCompute, pipeline_layout.get(), 0,
|
||||
set_writes);
|
||||
cmdbuf.pushConstants(pipeline_layout.get(), vk::ShaderStageFlagBits::eCompute, 0,
|
||||
sizeof(FSRConstants), &consts);
|
||||
cmdbuf.dispatch(dispatch_x, dispatch_y, 1);
|
||||
}
|
||||
|
||||
const std::array return_barrier{
|
||||
vk::ImageMemoryBarrier2{
|
||||
.srcStageMask = vk::PipelineStageFlagBits2::eComputeShader,
|
||||
.srcAccessMask = vk::AccessFlagBits2::eShaderStorageWrite,
|
||||
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
|
||||
.dstAccessMask = vk::AccessFlagBits2::eShaderRead,
|
||||
.oldLayout = vk::ImageLayout::eGeneral,
|
||||
.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
|
||||
.image = img.output_image,
|
||||
.subresourceRange = simple_subresource,
|
||||
},
|
||||
};
|
||||
cmdbuf.pipelineBarrier2({
|
||||
.imageMemoryBarrierCount = return_barrier.size(),
|
||||
.pImageMemoryBarriers = return_barrier.data(),
|
||||
});
|
||||
|
||||
return img.output_image_view.get();
|
||||
}
|
||||
|
||||
void FsrPass::ResizeAndInvalidate(u32 width, u32 height) {
|
||||
this->cur_size = vk::Extent2D{
|
||||
.width = width,
|
||||
.height = height,
|
||||
};
|
||||
for (int i = 0; i < num_images; ++i) {
|
||||
available_imgs[i].dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void FsrPass::CreateImages(Img& img) const {
|
||||
img.dirty = false;
|
||||
|
||||
vk::ImageCreateInfo image_create_info{
|
||||
.imageType = vk::ImageType::e2D,
|
||||
.format = vk::Format::eR16G16B16A16Sfloat,
|
||||
.extent{
|
||||
.width = cur_size.width,
|
||||
.height = cur_size.height,
|
||||
.depth = 1,
|
||||
},
|
||||
.mipLevels = 1,
|
||||
.arrayLayers = 1,
|
||||
.samples = vk::SampleCountFlagBits::e1,
|
||||
// .tiling = vk::ImageTiling::eOptimal,
|
||||
.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eStorage,
|
||||
.initialLayout = vk::ImageLayout::eUndefined,
|
||||
};
|
||||
img.intermediary_image.Create(image_create_info);
|
||||
SetObjectName(device, static_cast<vk::Image>(img.intermediary_image),
|
||||
"FSR Intermediary Image #{}", img.id);
|
||||
image_create_info.usage = vk::ImageUsageFlagBits::eTransferSrc |
|
||||
vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eStorage |
|
||||
vk::ImageUsageFlagBits::eColorAttachment;
|
||||
img.output_image.Create(image_create_info);
|
||||
SetObjectName(device, static_cast<vk::Image>(img.output_image), "FSR Output Image #{}", img.id);
|
||||
|
||||
vk::ImageViewCreateInfo image_view_create_info{
|
||||
.image = img.intermediary_image,
|
||||
.viewType = vk::ImageViewType::e2D,
|
||||
.format = vk::Format::eR16G16B16A16Sfloat,
|
||||
.subresourceRange{
|
||||
.aspectMask = vk::ImageAspectFlagBits::eColor,
|
||||
.levelCount = 1,
|
||||
.layerCount = 1,
|
||||
},
|
||||
};
|
||||
img.intermediary_image_view = Check<"create fsr intermediary image view">(
|
||||
device.createImageViewUnique(image_view_create_info));
|
||||
SetObjectName(device, img.intermediary_image_view.get(), "FSR Intermediary ImageView #{}",
|
||||
img.id);
|
||||
|
||||
image_view_create_info.image = img.output_image;
|
||||
img.output_image_view =
|
||||
Check<"create fsr output image view">(device.createImageViewUnique(image_view_create_info));
|
||||
SetObjectName(device, img.output_image_view.get(), "FSR Output ImageView #{}", img.id);
|
||||
}
|
||||
|
||||
} // namespace Vulkan::HostPasses
|
56
src/video_core/renderer_vulkan/host_passes/fsr_pass.h
Normal file
56
src/video_core/renderer_vulkan/host_passes/fsr_pass.h
Normal file
@ -0,0 +1,56 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/types.h"
|
||||
#include "video_core/renderer_vulkan/vk_common.h"
|
||||
#include "video_core/texture_cache/image.h"
|
||||
|
||||
namespace Vulkan::HostPasses {
|
||||
|
||||
class FsrPass {
|
||||
public:
|
||||
struct Settings {
|
||||
bool enable{true};
|
||||
bool use_rcas{true};
|
||||
float rcas_attenuation{0.25f};
|
||||
};
|
||||
|
||||
void Create(vk::Device device, VmaAllocator allocator, u32 num_images);
|
||||
|
||||
vk::ImageView Render(vk::CommandBuffer cmdbuf, vk::ImageView input, vk::Extent2D input_size,
|
||||
vk::Extent2D output_size, Settings settings, bool hdr);
|
||||
|
||||
private:
|
||||
struct Img {
|
||||
u8 id{};
|
||||
bool dirty{true};
|
||||
|
||||
VideoCore::UniqueImage intermediary_image;
|
||||
vk::UniqueImageView intermediary_image_view;
|
||||
|
||||
VideoCore::UniqueImage output_image;
|
||||
vk::UniqueImageView output_image_view;
|
||||
};
|
||||
|
||||
void ResizeAndInvalidate(u32 width, u32 height);
|
||||
void CreateImages(Img& img) const;
|
||||
|
||||
vk::Device device{};
|
||||
u32 num_images{};
|
||||
|
||||
vk::UniqueDescriptorSetLayout descriptor_set_layout{};
|
||||
vk::UniqueDescriptorSet easu_descriptor_set{};
|
||||
vk::UniqueDescriptorSet rcas_descriptor_set{};
|
||||
vk::UniqueSampler sampler{};
|
||||
vk::UniquePipelineLayout pipeline_layout{};
|
||||
vk::UniquePipeline easu_pipeline{};
|
||||
vk::UniquePipeline rcas_pipeline{};
|
||||
|
||||
vk::Extent2D cur_size{};
|
||||
u32 cur_image{};
|
||||
std::vector<Img> available_imgs;
|
||||
};
|
||||
|
||||
} // namespace Vulkan::HostPasses
|
@ -121,12 +121,14 @@ Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_
|
||||
present_frames.resize(num_images);
|
||||
for (u32 i = 0; i < num_images; i++) {
|
||||
Frame& frame = present_frames[i];
|
||||
frame.id = i;
|
||||
auto fence = Check<"create present done fence">(
|
||||
device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}));
|
||||
frame.present_done = fence;
|
||||
free_queue.push(&frame);
|
||||
}
|
||||
|
||||
fsr_pass.Create(device, instance.GetAllocator(), num_images);
|
||||
pp_pass.Create(device);
|
||||
|
||||
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
|
||||
@ -189,7 +191,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
|
||||
UNREACHABLE();
|
||||
}
|
||||
frame->image = vk::Image{unsafe_image};
|
||||
SetObjectName(device, frame->image, "Frame image");
|
||||
SetObjectName(device, frame->image, "Frame image #{}", frame->id);
|
||||
|
||||
const vk::ImageViewCreateInfo view_info = {
|
||||
.image = frame->image,
|
||||
@ -372,13 +374,9 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||
// Request a free presentation frame.
|
||||
Frame* frame = GetRenderFrame();
|
||||
|
||||
if (image_id != VideoCore::NULL_IMAGE_ID) {
|
||||
const auto& image = texture_cache.GetImage(image_id);
|
||||
const auto extent = image.info.size;
|
||||
if (frame->width != extent.width || frame->height != extent.height ||
|
||||
frame->is_hdr != swapchain.GetHDR()) {
|
||||
RecreateFrame(frame, extent.width, extent.height);
|
||||
}
|
||||
if (frame->width != expected_frame_width || frame->height != expected_frame_height ||
|
||||
frame->is_hdr != swapchain.GetHDR()) {
|
||||
RecreateFrame(frame, expected_frame_width, expected_frame_height);
|
||||
}
|
||||
|
||||
// EOP flips are triggered from GPU thread so use the drawing scheduler to record
|
||||
@ -387,7 +385,9 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||
auto& scheduler = is_eop ? draw_scheduler : flip_scheduler;
|
||||
scheduler.EndRendering();
|
||||
const auto cmdbuf = scheduler.CommandBuffer();
|
||||
if (Config::getVkHostMarkersEnabled()) {
|
||||
|
||||
bool vk_host_markers_enabled = Config::getVkHostMarkersEnabled();
|
||||
if (vk_host_markers_enabled) {
|
||||
const auto label = fmt::format("PrepareFrameInternal:{}", image_id.index);
|
||||
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
|
||||
.pLabelName = label.c_str(),
|
||||
@ -439,7 +439,25 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||
imageView = *texture_cache.RegisterImageView(image_id, info).image_view;
|
||||
}
|
||||
|
||||
if (vk_host_markers_enabled) {
|
||||
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
|
||||
.pLabelName = "Host/FSR",
|
||||
});
|
||||
}
|
||||
|
||||
imageView = fsr_pass.Render(cmdbuf, imageView, image_size, {frame->width, frame->height},
|
||||
fsr_settings, frame->is_hdr);
|
||||
|
||||
if (vk_host_markers_enabled) {
|
||||
cmdbuf.endDebugUtilsLabelEXT();
|
||||
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
|
||||
.pLabelName = "Host/Post processing",
|
||||
});
|
||||
}
|
||||
pp_pass.Render(cmdbuf, imageView, image_size, *frame, pp_settings);
|
||||
if (vk_host_markers_enabled) {
|
||||
cmdbuf.endDebugUtilsLabelEXT();
|
||||
}
|
||||
} else {
|
||||
// Fix display of garbage images on startup on some drivers
|
||||
const std::array<vk::RenderingAttachmentInfo, 1> attachments = {{
|
||||
@ -477,7 +495,7 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
|
||||
.pImageMemoryBarriers = &post_barrier,
|
||||
});
|
||||
|
||||
if (Config::getVkHostMarkersEnabled()) {
|
||||
if (vk_host_markers_enabled) {
|
||||
cmdbuf.endDebugUtilsLabelEXT();
|
||||
}
|
||||
|
||||
@ -607,6 +625,7 @@ void Presenter::Present(Frame* frame, bool is_reusing_frame) {
|
||||
ImVec2 contentArea = ImGui::GetContentRegionAvail();
|
||||
const vk::Rect2D imgRect =
|
||||
FitImage(frame->width, frame->height, (s32)contentArea.x, (s32)contentArea.y);
|
||||
SetExpectedGameSize((s32)contentArea.x, (s32)contentArea.y);
|
||||
ImGui::SetCursorPos(ImGui::GetCursorStartPos() + ImVec2{
|
||||
(float)imgRect.offset.x,
|
||||
(float)imgRect.offset.y,
|
||||
@ -685,12 +704,20 @@ Frame* Presenter::GetRenderFrame() {
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize default frame image
|
||||
if (frame->width == 0 || frame->height == 0 || frame->is_hdr != swapchain.GetHDR()) {
|
||||
RecreateFrame(frame, Config::getScreenWidth(), Config::getScreenHeight());
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
void Presenter::SetExpectedGameSize(s32 width, s32 height) {
|
||||
constexpr float expectedRatio = 1920.0 / 1080.0f;
|
||||
const float ratio = (float)width / (float)height;
|
||||
|
||||
expected_frame_height = height;
|
||||
expected_frame_width = width;
|
||||
if (ratio > expectedRatio) {
|
||||
expected_frame_width = static_cast<s32>(height * expectedRatio);
|
||||
} else {
|
||||
expected_frame_height = static_cast<s32>(width / expectedRatio);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include "imgui/imgui_config.h"
|
||||
#include "video_core/amdgpu/liverpool.h"
|
||||
#include "video_core/renderer_vulkan/host_passes/fsr_pass.h"
|
||||
#include "video_core/renderer_vulkan/host_passes/pp_pass.h"
|
||||
#include "video_core/renderer_vulkan/vk_instance.h"
|
||||
#include "video_core/renderer_vulkan/vk_scheduler.h"
|
||||
@ -33,6 +34,7 @@ struct Frame {
|
||||
vk::Semaphore ready_semaphore;
|
||||
u64 ready_tick;
|
||||
bool is_hdr{false};
|
||||
u8 id{};
|
||||
|
||||
ImTextureID imgui_texture;
|
||||
};
|
||||
@ -54,6 +56,10 @@ public:
|
||||
return pp_settings;
|
||||
}
|
||||
|
||||
HostPasses::FsrPass::Settings& GetFsrSettingsRef() {
|
||||
return fsr_settings;
|
||||
}
|
||||
|
||||
Frontend::WindowSDL& GetWindow() const {
|
||||
return window;
|
||||
}
|
||||
@ -116,7 +122,14 @@ private:
|
||||
Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true);
|
||||
Frame* GetRenderFrame();
|
||||
|
||||
void SetExpectedGameSize(s32 width, s32 height);
|
||||
|
||||
private:
|
||||
u32 expected_frame_width{1920};
|
||||
u32 expected_frame_height{1080};
|
||||
|
||||
HostPasses::FsrPass fsr_pass;
|
||||
HostPasses::FsrPass::Settings fsr_settings{};
|
||||
HostPasses::PostProcessingPass::Settings pp_settings{};
|
||||
HostPasses::PostProcessingPass pp_pass;
|
||||
Frontend::WindowSDL& window;
|
||||
|
@ -113,6 +113,8 @@ static vk::FormatFeatureFlags2 FormatFeatureFlags(const vk::ImageUsageFlags usag
|
||||
return feature_flags;
|
||||
}
|
||||
|
||||
UniqueImage::UniqueImage() {}
|
||||
|
||||
UniqueImage::UniqueImage(vk::Device device_, VmaAllocator allocator_)
|
||||
: device{device_}, allocator{allocator_} {}
|
||||
|
||||
@ -123,6 +125,9 @@ UniqueImage::~UniqueImage() {
|
||||
}
|
||||
|
||||
void UniqueImage::Create(const vk::ImageCreateInfo& image_ci) {
|
||||
if (image) {
|
||||
vmaDestroyImage(allocator, image, allocation);
|
||||
}
|
||||
const VmaAllocationCreateInfo alloc_info = {
|
||||
.flags = VMA_ALLOCATION_CREATE_WITHIN_BUDGET_BIT,
|
||||
.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,
|
||||
|
@ -35,6 +35,7 @@ enum ImageFlagBits : u32 {
|
||||
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
|
||||
|
||||
struct UniqueImage {
|
||||
explicit UniqueImage();
|
||||
explicit UniqueImage(vk::Device device, VmaAllocator allocator);
|
||||
~UniqueImage();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user