mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-04-20 03:24:49 +00:00
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:
parent
ba1eb298de
commit
f663176a5d
26 changed files with 4984 additions and 324 deletions
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
|
|
15
src/common/string_literal.h
Normal file
15
src/common/string_literal.h
Normal 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]{};
|
||||
};
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ set(SHADER_FILES
|
|||
detilers/micro_64bpp.comp
|
||||
detilers/micro_8bpp.comp
|
||||
fs_tri.vert
|
||||
fsr.comp
|
||||
post_process.frag
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
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
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
445
src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp
Normal file
445
src/video_core/renderer_vulkan/host_passes/fsr_pass.cpp
Normal 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
|
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
|
255
src/video_core/renderer_vulkan/host_passes/pp_pass.cpp
Normal file
255
src/video_core/renderer_vulkan/host_passes/pp_pass.cpp
Normal 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
|
34
src/video_core/renderer_vulkan/host_passes/pp_pass.h
Normal file
34
src/video_core/renderer_vulkan/host_passes/pp_pass.h
Normal 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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {};
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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…
Add table
Reference in a new issue