FidelityFX FSR implementation (#2624)

* host_shaders: support for includes

* video_core: add a simpler vulkan asserts

* video_core: refactored post processing pipeline to another file

* renderer_vulkan: add define param to compile shader utility

* video_core: fsr implementation

* devtools: show resolution & fsr state
This commit is contained in:
Vinicius Rangel 2025-03-12 15:33:30 -03:00 committed by GitHub
parent ba1eb298de
commit f663176a5d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
26 changed files with 4984 additions and 324 deletions

View file

@ -584,6 +584,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/spin_lock.h
src/common/stb.cpp
src/common/stb.h
src/common/string_literal.h
src/common/string_util.cpp
src/common/string_util.h
src/common/thread.cpp
@ -845,6 +846,10 @@ 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
src/video_core/texture_cache/image.h
src/video_core/texture_cache/image_info.cpp

View file

@ -111,4 +111,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"

View file

@ -0,0 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
template <size_t N, typename C = char>
struct StringLiteral {
static constexpr size_t len = N;
constexpr StringLiteral(const C (&str)[N]) {
std::copy_n(str, N, value);
}
C value[N]{};
};

View file

@ -158,6 +158,10 @@ public:
float Framerate = 1.0f / 60.0f;
float FrameDeltaTime;
std::pair<u32, u32> game_resolution{};
std::pair<u32, u32> output_resolution{};
bool is_using_fsr{};
void ShowDebugMessage(std::string message) {
if (message.empty()) {
return;

View file

@ -81,8 +81,24 @@ void L::DrawMenuBar() {
ImGui::EndMenu();
}
if (BeginMenu("Display")) {
auto& pp_settings = presenter->GetPPSettingsRef();
if (BeginMenu("Brightness")) {
SliderFloat("Gamma", &presenter->GetGammaRef(), 0.1f, 2.0f);
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();

View file

@ -74,7 +74,7 @@ void FrameGraph::Draw() {
if (!is_open) {
return;
}
SetNextWindowSize({340.0, 185.0f}, ImGuiCond_FirstUseEver);
SetNextWindowSize({308.0, 270.0f}, ImGuiCond_FirstUseEver);
if (Begin("Video debug info", &is_open)) {
const auto& ctx = *GImGui;
const auto& io = ctx.IO;
@ -88,13 +88,20 @@ void FrameGraph::Draw() {
frameRate = 1000.0f / deltaTime;
}
SeparatorText("Frame graph");
DrawFrameGraph();
SeparatorText("Renderer info");
Text("Frame time: %.3f ms (%.1f FPS)", deltaTime, frameRate);
Text("Presenter time: %.3f ms (%.1f FPS)", io.DeltaTime * 1000.0f, 1.0f / io.DeltaTime);
Text("Flip frame: %d Gnm submit frame: %d", DebugState.flip_frame_count.load(),
DebugState.gnm_frame_count.load());
SeparatorText("Frame graph");
DrawFrameGraph();
Text("Game Res: %dx%d", DebugState.game_resolution.first,
DebugState.game_resolution.second);
Text("Output Res: %dx%d", DebugState.output_resolution.first,
DebugState.output_resolution.second);
Text("FSR: %s", DebugState.is_using_fsr ? "on" : "off");
}
End();
}

View file

@ -5,6 +5,7 @@
#include <algorithm>
#include <fmt/core.h>
#include "common/string_literal.h"
#include "common/types.h"
#include "core/libraries/kernel/orbis_error.h"
@ -18,15 +19,6 @@ void ErrSceToPosix(int result);
int ErrnoToSceKernelError(int e);
void SetPosixErrno(int e);
template <size_t N>
struct StringLiteral {
constexpr StringLiteral(const char (&str)[N]) {
std::copy_n(str, N, value);
}
char value[N];
};
template <StringLiteral name, class F, F f>
struct WrapperImpl;

View file

@ -360,7 +360,7 @@ s32 PS4_SYSV_ABI sceVideoOutAdjustColor(s32 handle, const SceVideoOutColorSettin
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
presenter->GetGammaRef() = settings->gamma;
presenter->GetPPSettingsRef().gamma = settings->gamma;
return ORBIS_OK;
}

View file

@ -12,6 +12,7 @@ set(SHADER_FILES
detilers/micro_64bpp.comp
detilers/micro_8bpp.comp
fs_tri.vert
fsr.comp
post_process.frag
)

View file

@ -9,28 +9,31 @@ get_filename_component(CONTENTS_NAME ${SOURCE_FILE} NAME)
string(REPLACE "." "_" CONTENTS_NAME ${CONTENTS_NAME})
string(TOUPPER ${CONTENTS_NAME} CONTENTS_NAME)
FILE(READ ${SOURCE_FILE} line_contents)
# Function to recursively parse #include directives and replace them with file contents
function(parse_includes file_path output_content)
file(READ ${file_path} file_content)
# This regex includes \n at the begin to (hackish) avoid including comments
string(REGEX MATCHALL "\n#include +\"[^\"]+\"" includes "${file_content}")
# Replace double quotes with single quotes,
# as double quotes will be used to wrap the lines
STRING(REGEX REPLACE "\"" "'" line_contents "${line_contents}")
set(parsed_content "${file_content}")
foreach (include_match ${includes})
string(REGEX MATCH "\"([^\"]+)\"" _ "${include_match}")
set(include_file ${CMAKE_MATCH_1})
get_filename_component(include_full_path "${file_path}" DIRECTORY)
set(include_full_path "${include_full_path}/${include_file}")
# CMake separates list elements with semicolons, but semicolons
# are used extensively in the shader code.
# Replace with a temporary marker, to be reverted later.
STRING(REGEX REPLACE ";" "{{SEMICOLON}}" line_contents "${line_contents}")
if (NOT EXISTS "${include_full_path}")
message(FATAL_ERROR "Included file not found: ${include_full_path} from ${file_path}")
endif ()
# Make every line an individual element in the CMake list.
STRING(REGEX REPLACE "\n" ";" line_contents "${line_contents}")
parse_includes("${include_full_path}" sub_content)
string(REPLACE "${include_match}" "\n${sub_content}" parsed_content "${parsed_content}")
endforeach ()
set(${output_content} "${parsed_content}" PARENT_SCOPE)
endfunction()
# Build the shader string, wrapping each line in double quotes.
foreach(line IN LISTS line_contents)
string(CONCAT CONTENTS "${CONTENTS}" \"${line}\\n\"\n)
endforeach()
# Revert the original semicolons in the source.
STRING(REGEX REPLACE "{{SEMICOLON}}" ";" CONTENTS "${CONTENTS}")
parse_includes("${SOURCE_FILE}" CONTENTS)
get_filename_component(OUTPUT_DIR ${HEADER_FILE} DIRECTORY)
make_directory(${OUTPUT_DIR})
file(MAKE_DIRECTORY ${OUTPUT_DIR})
configure_file(${INPUT_FILE} ${HEADER_FILE} @ONLY)

View 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);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ layout (location = 0) out vec4 color;
layout (binding = 0) uniform sampler2D texSampler;
layout(push_constant) uniform settings {
layout (push_constant) uniform settings {
float gamma;
bool hdr;
} pp;

View file

@ -7,8 +7,8 @@
namespace HostShaders {
constexpr std::string_view @CONTENTS_NAME@ = {
constexpr std::string_view @CONTENTS_NAME@ = R"shader_src(
@CONTENTS@
};
)shader_src";
} // namespace HostShaders

View file

@ -0,0 +1,445 @@
// 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 "core/debug_state.h"
#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) {
DebugState.is_using_fsr = false;
return input;
}
if (input_size.width >= output_size.width && input_size.height >= output_size.height) {
DebugState.is_using_fsr = false;
return input;
}
DebugState.is_using_fsr = true;
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

View 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

View file

@ -0,0 +1,255 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "pp_pass.h"
#include "common/assert.h"
#include "video_core/host_shaders/fs_tri_vert.h"
#include "video_core/host_shaders/post_process_frag.h"
#include "video_core/renderer_vulkan/vk_platform.h"
#include "video_core/renderer_vulkan/vk_presenter.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include <boost/container/static_vector.hpp>
namespace Vulkan::HostPasses {
void PostProcessingPass::Create(vk::Device device) {
static const std::array pp_shaders{
HostShaders::FS_TRI_VERT,
HostShaders::POST_PROCESS_FRAG,
};
boost::container::static_vector<vk::DescriptorSetLayoutBinding, 2> bindings{
{
.binding = 0,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
},
};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci{
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
.bindingCount = static_cast<u32>(bindings.size()),
.pBindings = bindings.data(),
};
desc_set_layout = Check<"create pp descriptor set layout">(
device.createDescriptorSetLayoutUnique(desc_layout_ci));
const vk::PushConstantRange push_constants{
.stageFlags = vk::ShaderStageFlagBits::eFragment,
.offset = 0,
.size = sizeof(Settings),
};
const auto& vs_module = Compile(pp_shaders[0], vk::ShaderStageFlagBits::eVertex, device);
ASSERT(vs_module);
SetObjectName(device, vs_module, "fs_tri.vert");
const auto& fs_module = Compile(pp_shaders[1], vk::ShaderStageFlagBits::eFragment, device);
ASSERT(fs_module);
SetObjectName(device, fs_module, "post_process.frag");
const std::array shaders_ci{
vk::PipelineShaderStageCreateInfo{
.stage = vk::ShaderStageFlagBits::eVertex,
.module = vs_module,
.pName = "main",
},
vk::PipelineShaderStageCreateInfo{
.stage = vk::ShaderStageFlagBits::eFragment,
.module = fs_module,
.pName = "main",
},
};
const vk::PipelineLayoutCreateInfo layout_info{
.setLayoutCount = 1U,
.pSetLayouts = &*desc_set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &push_constants,
};
pipeline_layout =
Check<"create pp pipeline layout">(device.createPipelineLayoutUnique(layout_info));
const std::array pp_color_formats{
vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format,
};
const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci{
.colorAttachmentCount = pp_color_formats.size(),
.pColorAttachmentFormats = pp_color_formats.data(),
};
const vk::PipelineVertexInputStateCreateInfo vertex_input_info{
.vertexBindingDescriptionCount = 0u,
.vertexAttributeDescriptionCount = 0u,
};
const vk::PipelineInputAssemblyStateCreateInfo input_assembly{
.topology = vk::PrimitiveTopology::eTriangleList,
};
const vk::Viewport viewport{
.x = 0.0f,
.y = 0.0f,
.width = 1.0f,
.height = 1.0f,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const vk::Rect2D scissor = {
.offset = {0, 0},
.extent = {1, 1},
};
const vk::PipelineViewportStateCreateInfo viewport_info{
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
const vk::PipelineRasterizationStateCreateInfo raster_state{
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eBack,
.frontFace = vk::FrontFace::eClockwise,
.depthBiasEnable = false,
.lineWidth = 1.0f,
};
const vk::PipelineMultisampleStateCreateInfo multisampling{
.rasterizationSamples = vk::SampleCountFlagBits::e1,
};
const std::array attachments{
vk::PipelineColorBlendAttachmentState{
.blendEnable = false,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
},
};
const vk::PipelineColorBlendStateCreateInfo color_blending{
.logicOpEnable = false,
.logicOp = vk::LogicOp::eCopy,
.attachmentCount = attachments.size(),
.pAttachments = attachments.data(),
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
const std::array dynamic_states{
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
};
const vk::PipelineDynamicStateCreateInfo dynamic_info{
.dynamicStateCount = dynamic_states.size(),
.pDynamicStates = dynamic_states.data(),
};
const vk::GraphicsPipelineCreateInfo pipeline_info{
.pNext = &pipeline_rendering_ci,
.stageCount = shaders_ci.size(),
.pStages = shaders_ci.data(),
.pVertexInputState = &vertex_input_info,
.pInputAssemblyState = &input_assembly,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_state,
.pMultisampleState = &multisampling,
.pColorBlendState = &color_blending,
.pDynamicState = &dynamic_info,
.layout = *pipeline_layout,
};
pipeline = Check<"create post process pipeline">(device.createGraphicsPipelineUnique(
/*pipeline_cache*/ {}, pipeline_info));
// Once pipeline is compiled, we don't need the shader module anymore
device.destroyShaderModule(vs_module);
device.destroyShaderModule(fs_module);
// Create sampler resource
const vk::SamplerCreateInfo sampler_ci{
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eNearest,
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
};
sampler = Check<"create pp sampler">(device.createSamplerUnique(sampler_ci));
}
void PostProcessingPass::Render(vk::CommandBuffer cmdbuf, vk::ImageView input,
vk::Extent2D input_size, Frame& frame, Settings settings) {
const std::array<vk::RenderingAttachmentInfo, 1> attachments{{
{
.imageView = frame.image_view,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
},
}};
const vk::RenderingInfo rendering_info{
.renderArea{
.extent{
.width = frame.width,
.height = frame.height,
},
},
.layerCount = 1,
.colorAttachmentCount = attachments.size(),
.pColorAttachments = attachments.data(),
};
vk::DescriptorImageInfo image_info{
.sampler = *sampler,
.imageView = input,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
};
const std::array set_writes{
vk::WriteDescriptorSet{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &image_info,
},
};
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, *pipeline);
const std::array viewports = {
vk::Viewport{
.width = static_cast<float>(frame.width),
.height = static_cast<float>(frame.height),
.minDepth = 0.0f,
.maxDepth = 1.0f,
},
};
cmdbuf.setViewport(0, viewports);
cmdbuf.setScissor(0, vk::Rect2D{
.extent{
.width = frame.width,
.height = frame.height,
},
});
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pipeline_layout, 0, set_writes);
cmdbuf.pushConstants(*pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0, sizeof(Settings),
&settings);
cmdbuf.beginRendering(rendering_info);
cmdbuf.draw(3, 1, 0, 0);
cmdbuf.endRendering();
}
} // namespace Vulkan::HostPasses

View file

@ -0,0 +1,34 @@
// 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"
namespace Vulkan {
class Frame;
}
namespace Vulkan::HostPasses {
class PostProcessingPass {
public:
struct Settings {
float gamma = 1.0f;
u32 hdr = 0;
};
void Create(vk::Device device);
void Render(vk::CommandBuffer cmdbuf, vk::ImageView input, vk::Extent2D input_size,
Frame& output, Settings settings);
private:
vk::UniquePipeline pipeline{};
vk::UniquePipelineLayout pipeline_layout{};
vk::UniqueDescriptorSetLayout desc_set_layout{};
vk::UniqueSampler sampler{};
};
} // namespace Vulkan::HostPasses

View file

@ -5,7 +5,9 @@
#include <fmt/format.h>
#include "common/assert.h"
#include "common/logging/log.h"
#include "common/string_literal.h"
#include "common/types.h"
#include "video_core/renderer_vulkan/vk_common.h"
@ -48,4 +50,25 @@ void SetObjectName(vk::Device device, const HandleType& handle, const char* form
SetObjectName(device, handle, debug_name);
}
template <StringLiteral msg = "">
static void Check(vk::Result r) {
if constexpr (msg.len <= 1) {
ASSERT_MSG(r == vk::Result::eSuccess, "vk::Result={}", vk::to_string(r));
} else {
ASSERT_MSG(r == vk::Result::eSuccess, "Failed to {}: vk::Result={}", msg.value,
vk::to_string(r));
}
}
template <StringLiteral msg = "", typename T>
static T Check(vk::ResultValue<T> r) {
if constexpr (msg.len <= 1) {
ASSERT_MSG(r.result == vk::Result::eSuccess, "vk::Result={}", vk::to_string(r.result));
} else {
ASSERT_MSG(r.result == vk::Result::eSuccess, "Failed to {}: vk::Result={}", msg.value,
vk::to_string(r.result));
}
return std::move(r.value);
}
} // namespace Vulkan

View file

@ -10,13 +10,13 @@
#include "core/libraries/system/systemservice.h"
#include "imgui/renderer/imgui_core.h"
#include "sdl_window.h"
#include "video_core/renderer_vulkan/vk_platform.h"
#include "video_core/renderer_vulkan/vk_presenter.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/texture_cache/image.h"
#include "video_core/host_shaders/fs_tri_vert.h"
#include "video_core/host_shaders/post_process_frag.h"
#include <vk_mem_alloc.h>
@ -106,191 +106,6 @@ static vk::Rect2D FitImage(s32 frame_width, s32 frame_height, s32 swapchain_widt
dst_rect.offset.x, dst_rect.offset.y);
}
void Presenter::CreatePostProcessPipeline() {
static const std::array pp_shaders{
HostShaders::FS_TRI_VERT,
HostShaders::POST_PROCESS_FRAG,
};
boost::container::static_vector<vk::DescriptorSetLayoutBinding, 2> bindings{
{
.binding = 0,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.descriptorCount = 1,
.stageFlags = vk::ShaderStageFlagBits::eFragment,
},
};
const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = {
.flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR,
.bindingCount = static_cast<u32>(bindings.size()),
.pBindings = bindings.data(),
};
auto desc_layout_result = instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci);
ASSERT_MSG(desc_layout_result.result == vk::Result::eSuccess,
"Failed to create descriptor set layout: {}",
vk::to_string(desc_layout_result.result));
pp_desc_set_layout = std::move(desc_layout_result.value);
const vk::PushConstantRange push_constants = {
.stageFlags = vk::ShaderStageFlagBits::eFragment,
.offset = 0,
.size = sizeof(PostProcessSettings),
};
const auto& vs_module =
Vulkan::Compile(pp_shaders[0], vk::ShaderStageFlagBits::eVertex, instance.GetDevice());
ASSERT(vs_module);
Vulkan::SetObjectName(instance.GetDevice(), vs_module, "fs_tri.vert");
const auto& fs_module =
Vulkan::Compile(pp_shaders[1], vk::ShaderStageFlagBits::eFragment, instance.GetDevice());
ASSERT(fs_module);
Vulkan::SetObjectName(instance.GetDevice(), fs_module, "post_process.frag");
const std::array shaders_ci{
vk::PipelineShaderStageCreateInfo{
.stage = vk::ShaderStageFlagBits::eVertex,
.module = vs_module,
.pName = "main",
},
vk::PipelineShaderStageCreateInfo{
.stage = vk::ShaderStageFlagBits::eFragment,
.module = fs_module,
.pName = "main",
},
};
const vk::DescriptorSetLayout set_layout = *pp_desc_set_layout;
const vk::PipelineLayoutCreateInfo layout_info = {
.setLayoutCount = 1U,
.pSetLayouts = &set_layout,
.pushConstantRangeCount = 1,
.pPushConstantRanges = &push_constants,
};
auto [layout_result, layout] = instance.GetDevice().createPipelineLayoutUnique(layout_info);
ASSERT_MSG(layout_result == vk::Result::eSuccess, "Failed to create pipeline layout: {}",
vk::to_string(layout_result));
pp_pipeline_layout = std::move(layout);
const std::array pp_color_formats{
vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format,
};
const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci = {
.colorAttachmentCount = 1u,
.pColorAttachmentFormats = pp_color_formats.data(),
};
const vk::PipelineVertexInputStateCreateInfo vertex_input_info = {
.vertexBindingDescriptionCount = 0u,
.vertexAttributeDescriptionCount = 0u,
};
const vk::PipelineInputAssemblyStateCreateInfo input_assembly = {
.topology = vk::PrimitiveTopology::eTriangleList,
};
const vk::Viewport viewport = {
.x = 0.0f,
.y = 0.0f,
.width = 1.0f,
.height = 1.0f,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
const vk::Rect2D scissor = {
.offset = {0, 0},
.extent = {1, 1},
};
const vk::PipelineViewportStateCreateInfo viewport_info = {
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
const vk::PipelineRasterizationStateCreateInfo raster_state = {
.depthClampEnable = false,
.rasterizerDiscardEnable = false,
.polygonMode = vk::PolygonMode::eFill,
.cullMode = vk::CullModeFlagBits::eBack,
.frontFace = vk::FrontFace::eClockwise,
.depthBiasEnable = false,
.lineWidth = 1.0f,
};
const vk::PipelineMultisampleStateCreateInfo multisampling = {
.rasterizationSamples = vk::SampleCountFlagBits::e1,
};
const std::array attachments{
vk::PipelineColorBlendAttachmentState{
.blendEnable = false,
.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA,
},
};
const vk::PipelineColorBlendStateCreateInfo color_blending = {
.logicOpEnable = false,
.logicOp = vk::LogicOp::eCopy,
.attachmentCount = attachments.size(),
.pAttachments = attachments.data(),
.blendConstants = std::array{1.0f, 1.0f, 1.0f, 1.0f},
};
const std::array dynamic_states = {
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
};
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
.dynamicStateCount = static_cast<u32>(dynamic_states.size()),
.pDynamicStates = dynamic_states.data(),
};
const vk::GraphicsPipelineCreateInfo pipeline_info = {
.pNext = &pipeline_rendering_ci,
.stageCount = static_cast<u32>(shaders_ci.size()),
.pStages = shaders_ci.data(),
.pVertexInputState = &vertex_input_info,
.pInputAssemblyState = &input_assembly,
.pViewportState = &viewport_info,
.pRasterizationState = &raster_state,
.pMultisampleState = &multisampling,
.pColorBlendState = &color_blending,
.pDynamicState = &dynamic_info,
.layout = *pp_pipeline_layout,
};
auto result = instance.GetDevice().createGraphicsPipelineUnique(
/*pipeline_cache*/ {}, pipeline_info);
if (result.result == vk::Result::eSuccess) {
pp_pipeline = std::move(result.value);
} else {
UNREACHABLE_MSG("Post process pipeline creation failed!");
}
// Once pipeline is compiled, we don't need the shader module anymore
instance.GetDevice().destroyShaderModule(vs_module);
instance.GetDevice().destroyShaderModule(fs_module);
// Create sampler resource
const vk::SamplerCreateInfo sampler_ci = {
.magFilter = vk::Filter::eLinear,
.minFilter = vk::Filter::eLinear,
.mipmapMode = vk::SamplerMipmapMode::eNearest,
.addressModeU = vk::SamplerAddressMode::eClampToEdge,
.addressModeV = vk::SamplerAddressMode::eClampToEdge,
};
auto [sampler_result, smplr] = instance.GetDevice().createSamplerUnique(sampler_ci);
ASSERT_MSG(sampler_result == vk::Result::eSuccess, "Failed to create sampler: {}",
vk::to_string(sampler_result));
pp_sampler = std::move(smplr);
}
Presenter::Presenter(Frontend::WindowSDL& window_, AmdGpu::Liverpool* liverpool_)
: window{window_}, liverpool{liverpool_},
instance{window, Config::getGpuId(), Config::vkValidationEnabled(),
@ -306,15 +121,15 @@ 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];
auto [fence_result, fence] =
device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled});
ASSERT_MSG(fence_result == vk::Result::eSuccess, "Failed to create present done fence: {}",
vk::to_string(fence_result));
frame.id = i;
auto fence = Check<"create present done fence">(
device.createFence({.flags = vk::FenceCreateFlagBits::eSignaled}));
frame.present_done = fence;
free_queue.push(&frame);
}
CreatePostProcessPipeline();
fsr_pass.Create(device, instance.GetAllocator(), num_images);
pp_pass.Create(device);
ImGui::Layer::AddLayer(Common::Singleton<Core::Devtools::Layer>::Instance());
}
@ -376,6 +191,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
UNREACHABLE();
}
frame->image = vk::Image{unsafe_image};
SetObjectName(device, frame->image, "Frame image #{}", frame->id);
const vk::ImageViewCreateInfo view_info = {
.image = frame->image,
@ -389,9 +205,7 @@ void Presenter::RecreateFrame(Frame* frame, u32 width, u32 height) {
.layerCount = 1,
},
};
auto [view_result, view] = device.createImageView(view_info);
ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create frame image view: {}",
vk::to_string(view_result));
auto view = Check<"create frame image view">(device.createImageView(view_info));
frame->image_view = view;
frame->width = width;
frame->height = height;
@ -560,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
@ -575,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(),
@ -605,33 +417,12 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
.pImageMemoryBarriers = &pre_barrier,
});
const std::array attachments = {vk::RenderingAttachmentInfo{
.imageView = frame->image_view,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
}};
const vk::RenderingInfo rendering_info{
.renderArea =
vk::Rect2D{
.offset = {0, 0},
.extent = {frame->width, frame->height},
},
.layerCount = 1,
.colorAttachmentCount = attachments.size(),
.pColorAttachments = attachments.data(),
};
if (image_id != VideoCore::NULL_IMAGE_ID) {
auto& image = texture_cache.GetImage(image_id);
vk::Extent2D image_size = {image.info.size.width, image.info.size.height};
image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {},
cmdbuf);
static vk::DescriptorImageInfo image_info{
.sampler = *pp_sampler,
.imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal,
};
VideoCore::ImageViewInfo info{};
info.format = image.info.pixel_format;
// Exclude alpha from output frame to avoid blending with UI.
@ -641,52 +432,53 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
.b = vk::ComponentSwizzle::eIdentity,
.a = vk::ComponentSwizzle::eOne,
};
vk::ImageView imageView;
if (auto view = image.FindView(info)) {
image_info.imageView = *texture_cache.GetImageView(view).image_view;
imageView = *texture_cache.GetImageView(view).image_view;
} else {
image_info.imageView = *texture_cache.RegisterImageView(image_id, info).image_view;
imageView = *texture_cache.RegisterImageView(image_id, info).image_view;
}
static const std::array set_writes{
vk::WriteDescriptorSet{
.dstSet = VK_NULL_HANDLE,
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = vk::DescriptorType::eCombinedImageSampler,
.pImageInfo = &image_info,
},
};
if (vk_host_markers_enabled) {
cmdbuf.beginDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
.pLabelName = "Host/FSR",
});
}
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, *pp_pipeline);
imageView = fsr_pass.Render(cmdbuf, imageView, image_size, {frame->width, frame->height},
fsr_settings, frame->is_hdr);
const auto& dst_rect =
FitImage(image.info.size.width, image.info.size.height, frame->width, frame->height);
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();
}
const std::array viewports = {
vk::Viewport{
.x = 1.0f * dst_rect.offset.x,
.y = 1.0f * dst_rect.offset.y,
.width = 1.0f * dst_rect.extent.width,
.height = 1.0f * dst_rect.extent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
},
};
cmdbuf.setViewport(0, viewports);
cmdbuf.setScissor(0, {dst_rect});
cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *pp_pipeline_layout, 0,
set_writes);
cmdbuf.pushConstants(*pp_pipeline_layout, vk::ShaderStageFlagBits::eFragment, 0,
sizeof(PostProcessSettings), &pp_settings);
cmdbuf.beginRendering(rendering_info);
cmdbuf.draw(3, 1, 0, 0);
cmdbuf.endRendering();
DebugState.game_resolution = {image_size.width, image_size.height};
DebugState.output_resolution = {frame->width, frame->height};
} else {
// Fix display of garbage images on startup on some drivers
const std::array<vk::RenderingAttachmentInfo, 1> attachments = {{
{
.imageView = frame->image_view,
.imageLayout = vk::ImageLayout::eColorAttachmentOptimal,
.loadOp = vk::AttachmentLoadOp::eClear,
.storeOp = vk::AttachmentStoreOp::eStore,
},
}};
const vk::RenderingInfo rendering_info{
.renderArea{
.extent{frame->width, frame->height},
},
.layerCount = 1,
.colorAttachmentCount = attachments.size(),
.pColorAttachments = attachments.data(),
};
cmdbuf.beginRendering(rendering_info);
cmdbuf.endRendering();
}
@ -706,7 +498,7 @@ Frame* Presenter::PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop)
.pImageMemoryBarriers = &post_barrier,
});
if (Config::getVkHostMarkersEnabled()) {
if (vk_host_markers_enabled) {
cmdbuf.endDebugUtilsLabelEXT();
}
@ -836,6 +628,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,
@ -914,12 +707,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

View file

@ -7,6 +7,8 @@
#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"
#include "video_core/renderer_vulkan/vk_swapchain.h"
@ -32,6 +34,7 @@ struct Frame {
vk::Semaphore ready_semaphore;
u64 ready_tick;
bool is_hdr{false};
u8 id{};
ImTextureID imgui_texture;
};
@ -45,17 +48,16 @@ enum SchedulerType {
class Rasterizer;
class Presenter {
struct PostProcessSettings {
float gamma = 1.0f;
u32 hdr = 0;
};
public:
Presenter(Frontend::WindowSDL& window, AmdGpu::Liverpool* liverpool);
~Presenter();
float& GetGammaRef() {
return pp_settings.gamma;
HostPasses::PostProcessingPass::Settings& GetPPSettingsRef() {
return pp_settings;
}
HostPasses::FsrPass::Settings& GetFsrSettingsRef() {
return fsr_settings;
}
Frontend::WindowSDL& GetWindow() const {
@ -117,16 +119,19 @@ public:
}
private:
void CreatePostProcessPipeline();
Frame* PrepareFrameInternal(VideoCore::ImageId image_id, bool is_eop = true);
Frame* GetRenderFrame();
void SetExpectedGameSize(s32 width, s32 height);
private:
PostProcessSettings pp_settings{};
vk::UniquePipeline pp_pipeline{};
vk::UniquePipelineLayout pp_pipeline_layout{};
vk::UniqueDescriptorSetLayout pp_desc_set_layout{};
vk::UniqueSampler pp_sampler{};
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;
AmdGpu::Liverpool* liverpool;
Instance instance;

View file

@ -159,7 +159,8 @@ bool InitializeCompiler() {
}
} // Anonymous namespace
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device) {
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::vector<std::string> defines) {
if (!InitializeCompiler()) {
return {};
}
@ -178,12 +179,46 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
glslang::EShTargetLanguageVersion::EShTargetSpv_1_3);
shader->setStringsWithLengths(&pass_source_code, &pass_source_code_length, 1);
std::string preambleString;
std::vector<std::string> processes;
for (auto& def : defines) {
processes.push_back("define-macro ");
processes.back().append(def);
preambleString.append("#define ");
if (const size_t equal = def.find_first_of("="); equal != def.npos) {
def[equal] = ' ';
}
preambleString.append(def);
preambleString.append("\n");
}
shader->setPreamble(preambleString.c_str());
shader->addProcesses(processes);
glslang::TShader::ForbidIncluder includer;
std::string preprocessedStr;
if (!shader->preprocess(&DefaultTBuiltInResource, default_version, profile, false, true,
messages, &preprocessedStr, includer)) [[unlikely]] {
LOG_ERROR(Render_Vulkan,
"Shader preprocess error\n"
"Shader Info Log:\n"
"{}\n{}",
shader->getInfoLog(), shader->getInfoDebugLog());
LOG_ERROR(Render_Vulkan, "Shader Source:\n{}", code);
return {};
}
if (!shader->parse(&DefaultTBuiltInResource, default_version, profile, false, true, messages,
includer)) [[unlikely]] {
LOG_INFO(Render_Vulkan, "Shader Info Log:\n{}\n{}", shader->getInfoLog(),
shader->getInfoDebugLog());
LOG_INFO(Render_Vulkan, "Shader Source:\n{}", code);
LOG_ERROR(Render_Vulkan,
"Shader parse error\n"
"Shader Info Log:\n"
"{}\n{}",
shader->getInfoLog(), shader->getInfoDebugLog());
LOG_ERROR(Render_Vulkan, "Shader Source:\n{}", code);
return {};
}
@ -191,8 +226,11 @@ vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, v
auto program = std::make_unique<glslang::TProgram>();
program->addShader(shader.get());
if (!program->link(messages)) {
LOG_INFO(Render_Vulkan, "Program Info Log:\n{}\n{}", program->getInfoLog(),
program->getInfoDebugLog());
LOG_ERROR(Render_Vulkan,
"Shader link error\n"
"Program Info Log:\n"
"{}\n{}",
program->getInfoLog(), program->getInfoDebugLog());
return {};
}

View file

@ -16,7 +16,8 @@ namespace Vulkan {
* @param stage The pipeline stage the shader will be used in.
* @param device The vulkan device handle.
*/
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device);
vk::ShaderModule Compile(std::string_view code, vk::ShaderStageFlagBits stage, vk::Device device,
std::vector<std::string> defines = {});
/**
* @brief Creates a vulkan shader module from SPIR-V bytecode.

View file

@ -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,

View file

@ -35,6 +35,7 @@ enum ImageFlagBits : u32 {
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)
struct UniqueImage {
explicit UniqueImage();
explicit UniqueImage(vk::Device device, VmaAllocator allocator);
~UniqueImage();