diff --git a/src/host_shaders/host_shaders.vcxproj b/src/host_shaders/host_shaders.vcxproj index c3b56dc7..0964ae75 100644 --- a/src/host_shaders/host_shaders.vcxproj +++ b/src/host_shaders/host_shaders.vcxproj @@ -57,6 +57,24 @@ call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + Document Compiling %(Filename)%(Extension) @@ -99,6 +117,78 @@ call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_comp.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_comp_spv.h + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + + + Document + Compiling %(Filename)%(Extension) + call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)" + $(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h + Document Compiling %(Filename)%(Extension) diff --git a/src/host_shaders/host_shaders.vcxproj.filters b/src/host_shaders/host_shaders.vcxproj.filters index 4afb0145..3fa50568 100644 --- a/src/host_shaders/host_shaders.vcxproj.filters +++ b/src/host_shaders/host_shaders.vcxproj.filters @@ -16,6 +16,15 @@ frag + + frag + + + frag + + + frag + frag @@ -37,6 +46,42 @@ comp + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + + + frag + frag diff --git a/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index 9a2b7bb4..721a9487 100644 --- a/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -499,7 +499,9 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) { void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) { const Id component_id{ctx.Const(component)}; Id pointer_type{ctx.output_f32}; - switch (ctx.runtime_info.color_output_types.at(index)) { + // The secondary output of a dual-source pair has the type of attachment 0. + const u32 type_index = ctx.runtime_info.dual_source_blend && index == 1 ? 0 : index; + switch (ctx.runtime_info.color_output_types.at(type_index)) { case AttributeType::UnsignedInt: pointer_type = ctx.output_u32; value = ctx.OpBitcast(ctx.U32[1], value); diff --git a/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_special.cpp index 63422633..57a137cc 100644 --- a/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_special.cpp +++ b/src/yuzu_shader_recompiler/backend/spirv/emit_spirv_special.cpp @@ -96,6 +96,22 @@ void AlphaTest(EmitContext& ctx) { } // Anonymous namespace void EmitPrologue(EmitContext& ctx) { + if (ctx.stage == Stage::Fragment && ctx.runtime_info.dual_source_blend && + Sirit::ValidId(ctx.frag_color[1])) { + // Vulkan leaves the secondary blend source undefined when it is not written. + Id type{ctx.F32[4]}; + switch (ctx.runtime_info.color_output_types[0]) { + case AttributeType::SignedInt: + type = ctx.S32[4]; + break; + case AttributeType::UnsignedInt: + type = ctx.U32[4]; + break; + default: + break; + } + ctx.OpStore(ctx.frag_color[1], ctx.ConstantNull(type)); + } if (ctx.stage == Stage::VertexB) { const Id zero{ctx.Const(0.0f)}; const Id one{ctx.Const(1.0f)}; diff --git a/src/yuzu_shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/yuzu_shader_recompiler/backend/spirv/spirv_emit_context.cpp index dd08033f..548939e0 100644 --- a/src/yuzu_shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/yuzu_shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -1742,21 +1742,25 @@ void EmitContext::DefineOutputs(const IR::Program& program) { break; case Stage::Fragment: for (u32 index = 0; index < 8; ++index) { - // With dual source blending, color output 1 is the second source of render target 0 - const bool is_second_source{runtime_info.dual_source_blend && index == 1}; - if (!info.stores_frag_color[index] && !profile.need_declared_frag_colors && - !is_second_source) { + // In dual-source mode guest color output 1 is not render target 1: it is the + // secondary source of attachment 0. + const bool dual_source_output = runtime_info.dual_source_blend && index <= 1; + if (!dual_source_output && !info.stores_frag_color[index] && + !profile.need_declared_frag_colors) { continue; } - const Id type{GetAttributeType(*this, runtime_info.color_output_types[index])}; + // Both outputs of a dual-source pair use the type of attachment 0. + const u32 type_index = dual_source_output ? 0 : index; + const Id type{GetAttributeType(*this, runtime_info.color_output_types[type_index])}; frag_color[index] = DefineOutput(*this, type, std::nullopt); - if (is_second_source) { - Decorate(frag_color[index], spv::Decoration::Location, 0U); - Decorate(frag_color[index], spv::Decoration::Index, 1U); + if (dual_source_output) { + Decorate(frag_color[index], spv::Decoration::Location, 0u); + Decorate(frag_color[index], spv::Decoration::Index, index); + Name(frag_color[index], index == 0 ? "frag_color0" : "frag_color0_secondary"); } else { Decorate(frag_color[index], spv::Decoration::Location, index); + Name(frag_color[index], fmt::format("frag_color{}", index)); } - Name(frag_color[index], fmt::format("frag_color{}", index)); } if (info.stores_frag_depth) { frag_depth = DefineOutput(*this, F32[1], std::nullopt); diff --git a/src/yuzu_shader_recompiler/runtime_info.h b/src/yuzu_shader_recompiler/runtime_info.h index 9a7af31b..522ce869 100644 --- a/src/yuzu_shader_recompiler/runtime_info.h +++ b/src/yuzu_shader_recompiler/runtime_info.h @@ -114,7 +114,7 @@ struct RuntimeInfo { // Float remains the default for other backends. std::array color_output_types{}; - /// The first render target blends with a second source color taken from color output 1 + /// Whether fragment outputs 0 and 1 are the primary and secondary sources for attachment 0. bool dual_source_blend{}; }; diff --git a/src/yuzu_video_core/host_shaders/CMakeLists.txt b/src/yuzu_video_core/host_shaders/CMakeLists.txt index 6d8e2eed..4d5a585b 100644 --- a/src/yuzu_video_core/host_shaders/CMakeLists.txt +++ b/src/yuzu_video_core/host_shaders/CMakeLists.txt @@ -16,9 +16,17 @@ set(GLSL_INCLUDES set(SHADER_FILES astc_decoder.comp blit_color_float.frag + blit_color_msaa.frag + blit_color_msaa_sint.frag + blit_color_msaa_uint.frag + blit_depth_msaa.frag + blit_depth_stencil_msaa.frag block_linear_unswizzle_2d.comp block_linear_unswizzle_3d.comp convert_abgr8_to_d24s8.frag + convert_abgr8_to_d24s8_depth.frag + convert_abgr8_to_s8d24.frag + convert_abgr8_to_s8d24_depth.frag convert_abgr8_to_d32f.frag convert_d32f_to_abgr8.frag convert_d24s8_to_abgr8.frag @@ -26,6 +34,13 @@ set(SHADER_FILES convert_float_to_depth.frag convert_msaa_to_non_msaa.comp convert_non_msaa_to_msaa.comp + convert_non_msaa_to_msaa.frag + convert_non_msaa_to_msaa_sint.frag + convert_non_msaa_to_msaa_uint.frag + convert_non_msaa_to_msaa_depth.frag + convert_non_msaa_to_msaa_depth_stencil.frag + convert_packed_depth_to_msaa.frag + convert_packed_depth_stencil_to_msaa.frag convert_s8d24_to_abgr8.frag full_screen_triangle.vert fxaa.frag diff --git a/src/yuzu_video_core/host_shaders/blit_color_msaa.frag b/src/yuzu_video_core/host_shaders/blit_color_msaa.frag new file mode 100644 index 00000000..f83ecafe --- /dev/null +++ b/src/yuzu_video_core/host_shaders/blit_color_msaa.frag @@ -0,0 +1,55 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform sampler2DMS source_tex; +layout(constant_id = 0) const int destination_samples = 1; +layout(constant_id = 2) const int source_samples = 4; +layout(constant_id = 1) const int linear_filter = 0; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out vec4 color; + +// Maxwell stores samples in a spatial grid, with X varying first. +ivec2 grid(int samples) { + int x = samples >= 8 ? 4 : (samples >= 2 ? 2 : 1); + return ivec2(x, samples / x); +} + +// Source sample that sits at the position of the destination sample being shaded. +int source_sample(int samples) { + ivec2 src_grid = grid(samples); + ivec2 dst_grid = grid(destination_samples); + ivec2 dst_sample = ivec2(gl_SampleID % dst_grid.x, gl_SampleID / dst_grid.x); + ivec2 src_sample = min(src_grid - 1, dst_sample * src_grid / dst_grid); + return src_sample.x + src_sample.y * src_grid.x; +} + +vec4 fetch_color(ivec2 coord, int samples) { + coord = clamp(coord, ivec2(0), textureSize(source_tex) - 1); + if (destination_samples == 1) { + vec4 result = vec4(0.0); + for (int i = 0; i < samples; ++i) { + result += texelFetch(source_tex, coord, i); + } + return result / float(samples); + } + return texelFetch(source_tex, coord, source_sample(samples)); +} + +void main() { + int samples = source_samples; + if (linear_filter != 0) { + // texcoord is in texels; the centre of texel (0,0) is (0.5,0.5). + vec2 position = texcoord - vec2(0.5); + ivec2 base = ivec2(floor(position)); + vec2 weight = fract(position); + vec4 top = mix(fetch_color(base, samples), + fetch_color(base + ivec2(1, 0), samples), weight.x); + vec4 bottom = mix(fetch_color(base + ivec2(0, 1), samples), + fetch_color(base + ivec2(1, 1), samples), weight.x); + color = mix(top, bottom, weight.y); + } else { + color = fetch_color(ivec2(floor(texcoord)), samples); + } +} diff --git a/src/yuzu_video_core/host_shaders/blit_color_msaa_sint.frag b/src/yuzu_video_core/host_shaders/blit_color_msaa_sint.frag new file mode 100644 index 00000000..fbb574cd --- /dev/null +++ b/src/yuzu_video_core/host_shaders/blit_color_msaa_sint.frag @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform isampler2DMS source_tex; +layout(constant_id = 0) const int destination_samples = 1; +layout(constant_id = 2) const int source_samples = 4; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out ivec4 color; + +// Maxwell stores samples in a spatial grid, with X varying first. +ivec2 grid(int samples) { + int x = samples >= 8 ? 4 : (samples >= 2 ? 2 : 1); + return ivec2(x, samples / x); +} + +// Source sample that sits at the position of the destination sample being shaded. +int source_sample(int samples) { + ivec2 src_grid = grid(samples); + ivec2 dst_grid = grid(destination_samples); + ivec2 dst_sample = ivec2(gl_SampleID % dst_grid.x, gl_SampleID / dst_grid.x); + ivec2 src_sample = min(src_grid - 1, dst_sample * src_grid / dst_grid); + return src_sample.x + src_sample.y * src_grid.x; +} + +ivec2 source_coord() { + return clamp(ivec2(floor(texcoord)), ivec2(0), textureSize(source_tex) - 1); +} + +void main() { + // Integer formats cannot be averaged. With one destination sample this reads sample 0. + color = texelFetch(source_tex, source_coord(), source_sample(source_samples)); +} diff --git a/src/yuzu_video_core/host_shaders/blit_color_msaa_uint.frag b/src/yuzu_video_core/host_shaders/blit_color_msaa_uint.frag new file mode 100644 index 00000000..c06ba5e0 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/blit_color_msaa_uint.frag @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform usampler2DMS source_tex; +layout(constant_id = 0) const int destination_samples = 1; +layout(constant_id = 2) const int source_samples = 4; + +layout(location = 0) in vec2 texcoord; +layout(location = 0) out uvec4 color; + +// Maxwell stores samples in a spatial grid, with X varying first. +ivec2 grid(int samples) { + int x = samples >= 8 ? 4 : (samples >= 2 ? 2 : 1); + return ivec2(x, samples / x); +} + +// Source sample that sits at the position of the destination sample being shaded. +int source_sample(int samples) { + ivec2 src_grid = grid(samples); + ivec2 dst_grid = grid(destination_samples); + ivec2 dst_sample = ivec2(gl_SampleID % dst_grid.x, gl_SampleID / dst_grid.x); + ivec2 src_sample = min(src_grid - 1, dst_sample * src_grid / dst_grid); + return src_sample.x + src_sample.y * src_grid.x; +} + +ivec2 source_coord() { + return clamp(ivec2(floor(texcoord)), ivec2(0), textureSize(source_tex) - 1); +} + +void main() { + // Integer formats cannot be averaged. With one destination sample this reads sample 0. + color = texelFetch(source_tex, source_coord(), source_sample(source_samples)); +} diff --git a/src/yuzu_video_core/host_shaders/blit_depth_msaa.frag b/src/yuzu_video_core/host_shaders/blit_depth_msaa.frag new file mode 100644 index 00000000..9f1da883 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/blit_depth_msaa.frag @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform sampler2DMS source_tex; +layout(constant_id = 0) const int destination_samples = 1; +layout(constant_id = 2) const int source_samples = 4; + +layout(location = 0) in vec2 texcoord; + +// Maxwell stores samples in a spatial grid, with X varying first. +ivec2 grid(int samples) { + int x = samples >= 8 ? 4 : (samples >= 2 ? 2 : 1); + return ivec2(x, samples / x); +} + +// Source sample that sits at the position of the destination sample being shaded. +int source_sample(int samples) { + ivec2 src_grid = grid(samples); + ivec2 dst_grid = grid(destination_samples); + ivec2 dst_sample = ivec2(gl_SampleID % dst_grid.x, gl_SampleID / dst_grid.x); + ivec2 src_sample = min(src_grid - 1, dst_sample * src_grid / dst_grid); + return src_sample.x + src_sample.y * src_grid.x; +} + +ivec2 source_coord() { + return clamp(ivec2(floor(texcoord)), ivec2(0), textureSize(source_tex) - 1); +} + +void main() { + // Depth cannot be averaged. With one destination sample this reads sample 0. + gl_FragDepth = texelFetch(source_tex, source_coord(), source_sample(source_samples)).r; +} diff --git a/src/yuzu_video_core/host_shaders/blit_depth_stencil_msaa.frag b/src/yuzu_video_core/host_shaders/blit_depth_stencil_msaa.frag new file mode 100644 index 00000000..231b1c4e --- /dev/null +++ b/src/yuzu_video_core/host_shaders/blit_depth_stencil_msaa.frag @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2DMS source_tex; +layout(binding = 1) uniform usampler2DMS stencil_tex; +layout(constant_id = 0) const int destination_samples = 1; +layout(constant_id = 2) const int source_samples = 4; + +layout(location = 0) in vec2 texcoord; + +// Maxwell stores samples in a spatial grid, with X varying first. +ivec2 grid(int samples) { + int x = samples >= 8 ? 4 : (samples >= 2 ? 2 : 1); + return ivec2(x, samples / x); +} + +// Source sample that sits at the position of the destination sample being shaded. +int source_sample(int samples) { + ivec2 src_grid = grid(samples); + ivec2 dst_grid = grid(destination_samples); + ivec2 dst_sample = ivec2(gl_SampleID % dst_grid.x, gl_SampleID / dst_grid.x); + ivec2 src_sample = min(src_grid - 1, dst_sample * src_grid / dst_grid); + return src_sample.x + src_sample.y * src_grid.x; +} + +ivec2 source_coord() { + return clamp(ivec2(floor(texcoord)), ivec2(0), textureSize(source_tex) - 1); +} + +void main() { + // Depth and stencil cannot be averaged. With one destination sample this reads sample 0. + ivec2 coord = source_coord(); + int sample_id = source_sample(source_samples); + gl_FragDepth = texelFetch(source_tex, coord, sample_id).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, sample_id).r); +} diff --git a/src/yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_depth.frag b/src/yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_depth.frag new file mode 100644 index 00000000..3c3c27a2 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_depth.frag @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform sampler2D color_texture; + +// Depth half of convert_abgr8_to_d24s8.frag, for devices without shader stencil export. +void main() { + uvec4 bytes = uvec4(round(texelFetch(color_texture, ivec2(gl_FragCoord.xy), 0) * 255.0)); + uint depth = bytes.r | (bytes.g << 8) | (bytes.b << 16); + gl_FragDepth = float(depth) / 16777215.0; +} diff --git a/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24.frag b/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24.frag new file mode 100644 index 00000000..c83e56ca --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24.frag @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +#extension GL_ARB_shader_stencil_export : require + +layout(binding = 0) uniform sampler2D color_texture; + +void main() { + uvec4 bytes = uvec4(round(texelFetch(color_texture, ivec2(gl_FragCoord.xy), 0) * 255.0)); + // Inverse of convert_s8d24_to_abgr8.frag: RGBA = depth[23:16], depth[15:8], depth[7:0], stencil. + uint depth = bytes.b | (bytes.g << 8) | (bytes.r << 16); + gl_FragDepth = float(depth) / 16777215.0; + gl_FragStencilRefARB = int(bytes.a); +} diff --git a/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24_depth.frag b/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24_depth.frag new file mode 100644 index 00000000..2a74f2cd --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_abgr8_to_s8d24_depth.frag @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 + +layout(binding = 0) uniform sampler2D color_texture; + +// Depth half of convert_abgr8_to_s8d24.frag, for devices without shader stencil export. +void main() { + uvec4 bytes = uvec4(round(texelFetch(color_texture, ivec2(gl_FragCoord.xy), 0) * 255.0)); + uint depth = bytes.b | (bytes.g << 8) | (bytes.r << 16); + gl_FragDepth = float(depth) / 16777215.0; +} diff --git a/src/yuzu_video_core/host_shaders/convert_msaa_to_non_msaa.comp b/src/yuzu_video_core/host_shaders/convert_msaa_to_non_msaa.comp index 66f2ad48..b5133ab9 100644 --- a/src/yuzu_video_core/host_shaders/convert_msaa_to_non_msaa.comp +++ b/src/yuzu_video_core/host_shaders/convert_msaa_to_non_msaa.comp @@ -18,11 +18,12 @@ void main() { const ivec3 msaa_size = imageSize(msaa_in); const ivec3 out_size = imageSize(output_img); const ivec3 scale = out_size / msaa_size; + const int samples_x = num_samples >= 8 ? 4 : (num_samples >= 2 ? 2 : 1); for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { const vec4 pixel = imageLoad(msaa_in, coords, curr_sample); - const int single_sample_x = scale.x * coords.x + (curr_sample & 1); - const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1); + const int single_sample_x = scale.x * coords.x + (curr_sample % samples_x); + const int single_sample_y = scale.y * coords.y + (curr_sample / samples_x); const ivec3 dest_coords = ivec3(single_sample_x, single_sample_y, coords.z); if (any(greaterThanEqual(dest_coords, imageSize(output_img)))) { diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.comp b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.comp index c7ce38ef..89bc8346 100644 --- a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.comp +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.comp @@ -18,9 +18,10 @@ void main() { const ivec3 msaa_size = imageSize(output_msaa); const ivec3 out_size = imageSize(img_in); const ivec3 scale = out_size / msaa_size; + const int samples_x = num_samples >= 8 ? 4 : (num_samples >= 2 ? 2 : 1); for (int curr_sample = 0; curr_sample < num_samples; ++curr_sample) { - const int single_sample_x = scale.x * coords.x + (curr_sample & 1); - const int single_sample_y = scale.y * coords.y + ((curr_sample / 2) & 1); + const int single_sample_x = scale.x * coords.x + (curr_sample % samples_x); + const int single_sample_y = scale.y * coords.y + (curr_sample / samples_x); const ivec3 single_coords = ivec3(single_sample_x, single_sample_y, coords.z); if (any(greaterThanEqual(single_coords, imageSize(img_in)))) { diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.frag b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.frag new file mode 100644 index 00000000..b8bb0db4 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa.frag @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +layout(binding = 0) uniform sampler2D source_tex; +layout(location = 0) out vec4 color; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + color = texelFetch(source_tex, source_coord(), 0); +} diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag new file mode 100644 index 00000000..9cb841e8 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth.frag @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +layout(binding = 0) uniform sampler2D source_tex; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + ivec2 coord = source_coord(); + gl_FragDepth = texelFetch(source_tex, coord, 0).r; +} diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag new file mode 100644 index 00000000..2220e46f --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil.frag @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +#extension GL_ARB_shader_stencil_export : require +layout(binding = 0) uniform sampler2D source_tex; +layout(binding = 1) uniform usampler2D stencil_tex; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + ivec2 coord = source_coord(); + gl_FragDepth = texelFetch(source_tex, coord, 0).r; + gl_FragStencilRefARB = int(texelFetch(stencil_tex, coord, 0).r); +} diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_sint.frag b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_sint.frag new file mode 100644 index 00000000..86d0fec4 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_sint.frag @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +layout(binding = 0) uniform isampler2D source_tex; +layout(location = 0) out ivec4 color; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + color = texelFetch(source_tex, source_coord(), 0); +} diff --git a/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_uint.frag b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_uint.frag new file mode 100644 index 00000000..1f68c88e --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_uint.frag @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +layout(binding = 0) uniform usampler2D source_tex; +layout(location = 0) out uvec4 color; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + color = texelFetch(source_tex, source_coord(), 0); +} diff --git a/src/yuzu_video_core/host_shaders/convert_packed_depth_stencil_to_msaa.frag b/src/yuzu_video_core/host_shaders/convert_packed_depth_stencil_to_msaa.frag new file mode 100644 index 00000000..fe23feb9 --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_packed_depth_stencil_to_msaa.frag @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +#extension GL_ARB_shader_stencil_export : require +layout(binding = 0) uniform usampler2D source_tex; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + uvec2 packed = texelFetch(source_tex, source_coord(), 0).rg; + uint stencil; + if (packed_format == 0) { // D24_UNORM_S8_UINT (Maxwell S8Z24): depth in the low 24 bits. + gl_FragDepth = float(packed.r & 0x00ffffffu) / 16777215.0; + stencil = packed.r >> 24; + } else if (packed_format == 1) { // S8_UINT_D24_UNORM (Maxwell Z24S8): stencil in the low byte. + gl_FragDepth = float(packed.r >> 8) / 16777215.0; + stencil = packed.r & 255u; + } else { // D32_FLOAT_S8_UINT + gl_FragDepth = uintBitsToFloat(packed.r); + stencil = packed.g & 255u; + } + gl_FragStencilRefARB = int(stencil); +} diff --git a/src/yuzu_video_core/host_shaders/convert_packed_depth_to_msaa.frag b/src/yuzu_video_core/host_shaders/convert_packed_depth_to_msaa.frag new file mode 100644 index 00000000..f9dc743d --- /dev/null +++ b/src/yuzu_video_core/host_shaders/convert_packed_depth_to_msaa.frag @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 +layout(binding = 0) uniform usampler2D source_tex; +layout(push_constant) uniform PushConstants { + ivec2 dst_offset; + ivec2 src_offset; + ivec2 scale; + int packed_format; +}; + +ivec2 source_coord() { + // Maxwell stores samples in a spatial grid, with X varying first. + return src_offset + (ivec2(gl_FragCoord.xy) - dst_offset) * scale + + ivec2(gl_SampleID % scale.x, gl_SampleID / scale.x); +} + +void main() { + uvec2 packed = texelFetch(source_tex, source_coord(), 0).rg; + uint stencil; + if (packed_format == 0) { // D24_UNORM_S8_UINT (Maxwell S8Z24): depth in the low 24 bits. + gl_FragDepth = float(packed.r & 0x00ffffffu) / 16777215.0; + stencil = packed.r >> 24; + } else if (packed_format == 1) { // S8_UINT_D24_UNORM (Maxwell Z24S8): stencil in the low byte. + gl_FragDepth = float(packed.r >> 8) / 16777215.0; + stencil = packed.r & 255u; + } else { // D32_FLOAT_S8_UINT + gl_FragDepth = uintBitsToFloat(packed.r); + stencil = packed.g & 255u; + } +} diff --git a/src/yuzu_video_core/renderer_opengl/gl_texture_cache.h b/src/yuzu_video_core/renderer_opengl/gl_texture_cache.h index 7c7d5272..2bd662c7 100644 --- a/src/yuzu_video_core/renderer_opengl/gl_texture_cache.h +++ b/src/yuzu_video_core/renderer_opengl/gl_texture_cache.h @@ -93,7 +93,7 @@ public: return true; } - bool CanUploadMSAA() const noexcept { + bool CanUploadMSAA(const Image&) const noexcept { return true; } diff --git a/src/yuzu_video_core/renderer_vulkan/blit_image.cpp b/src/yuzu_video_core/renderer_vulkan/blit_image.cpp index 5725693c..e6763f53 100644 --- a/src/yuzu_video_core/renderer_vulkan/blit_image.cpp +++ b/src/yuzu_video_core/renderer_vulkan/blit_image.cpp @@ -8,12 +8,27 @@ #include "video_settings.h" #include "yuzu_common/settings.h" #include "yuzu_video_core/host_shaders/blit_color_float_frag_spv.h" +#include "yuzu_video_core/host_shaders/blit_color_msaa_frag_spv.h" +#include "yuzu_video_core/host_shaders/blit_color_msaa_sint_frag_spv.h" +#include "yuzu_video_core/host_shaders/blit_color_msaa_uint_frag_spv.h" +#include "yuzu_video_core/host_shaders/blit_depth_msaa_frag_spv.h" +#include "yuzu_video_core/host_shaders/blit_depth_stencil_msaa_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_depth_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_abgr8_to_d24s8_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_abgr8_to_s8d24_depth_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_abgr8_to_s8d24_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_abgr8_to_d32f_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_d24s8_to_abgr8_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_d32f_to_abgr8_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_depth_to_float_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_float_to_depth_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_sint_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_uint_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_non_msaa_to_msaa_depth_stencil_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_packed_depth_to_msaa_frag_spv.h" +#include "yuzu_video_core/host_shaders/convert_packed_depth_stencil_to_msaa_frag_spv.h" #include "yuzu_video_core/host_shaders/convert_s8d24_to_abgr8_frag_spv.h" #include "yuzu_video_core/host_shaders/full_screen_triangle_vert_spv.h" #include "yuzu_video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h" @@ -24,11 +39,13 @@ #include "yuzu_video_core/host_shaders/vulkan_depthstencil_clear_frag_spv.h" #include "yuzu_video_core/renderer_vulkan/blit_image.h" #include "yuzu_video_core/renderer_vulkan/maxwell_to_vk.h" +#include "yuzu_video_core/renderer_vulkan/vk_render_pass_cache.h" #include "yuzu_video_core/renderer_vulkan/vk_scheduler.h" #include "yuzu_video_core/renderer_vulkan/vk_shader_util.h" #include "yuzu_video_core/renderer_vulkan/vk_state_tracker.h" #include "yuzu_video_core/renderer_vulkan/vk_update_descriptor.h" #include "yuzu_video_core/surface.h" +#include "yuzu_video_core/texture_cache/samples_helper.h" #include "yuzu_video_core/vulkan_common/vulkan_device.h" #include "yuzu_video_core/vulkan_common/vulkan_wrapper.h" @@ -37,11 +54,30 @@ namespace Vulkan { using VideoCommon::ImageViewType; namespace { +template +size_t FindCachedKey(const std::vector & keys, const Key & key, size_t & last) +{ + if (last < keys.size() && keys[last] == key) + { + return last; + } + const auto it = std::ranges::find(keys, key); + last = static_cast(std::distance(keys.begin(), it)); + return last; +} + struct PushConstants { std::array tex_scale; std::array tex_offset; }; +struct MSAACopyPushConstants { + std::array dst_offset; + std::array src_offset; + std::array scale; + s32 packed_format{}; +}; + template inline constexpr VkDescriptorSetLayoutBinding TEXTURE_DESCRIPTOR_SET_LAYOUT_BINDING{ .binding = binding, @@ -189,6 +225,30 @@ constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_STATE_CRE .minDepthBounds = 0.0f, .maxDepthBounds = 0.0f, }; +// The stencil value exported by the fragment shader is only written when the stencil test runs. +constexpr VkStencilOpState STENCIL_EXPORT_OP_STATE{ + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = 0xFF, + .writeMask = 0xFF, + .reference = 0, +}; +constexpr VkPipelineDepthStencilStateCreateInfo PIPELINE_DEPTH_STENCIL_EXPORT_STATE_CREATE_INFO{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = VK_TRUE, + .front = STENCIL_EXPORT_OP_STATE, + .back = STENCIL_EXPORT_OP_STATE, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, +}; template inline constexpr VkSamplerCreateInfo SAMPLER_CREATE_INFO{ @@ -246,6 +306,57 @@ constexpr std::array MakeStages( }; } +[[nodiscard]] VkSampleCountFlagBits SampleCountFlag(u32 num_samples) { + switch (num_samples) { + case 2: + return VK_SAMPLE_COUNT_2_BIT; + case 4: + return VK_SAMPLE_COUNT_4_BIT; + case 8: + return VK_SAMPLE_COUNT_8_BIT; + case 16: + return VK_SAMPLE_COUNT_16_BIT; + default: + return VK_SAMPLE_COUNT_1_BIT; + } +} + +[[nodiscard]] MSAACopyFormatClass FormatClass(VideoCore::Surface::PixelFormat format) { + if (!VideoCore::Surface::IsPixelFormatInteger(format)) { + return MSAACopyFormatClass::Float; + } + if (VideoCore::Surface::IsPixelFormatSignedInteger(format)) { + return MSAACopyFormatClass::SignedInteger; + } + return MSAACopyFormatClass::UnsignedInteger; +} + +[[nodiscard]] vk::ImageView MakeMSAACopyView(const vk::Device& device, VkImage image, + VkFormat format, u32 base_level, u32 base_layer, + VkImageAspectFlags aspect_mask) { + return device.CreateImageView(VkImageViewCreateInfo{ + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .image = image, + .viewType = VK_IMAGE_VIEW_TYPE_2D, + .format = format, + .components{ + .r = VK_COMPONENT_SWIZZLE_IDENTITY, + .g = VK_COMPONENT_SWIZZLE_IDENTITY, + .b = VK_COMPONENT_SWIZZLE_IDENTITY, + .a = VK_COMPONENT_SWIZZLE_IDENTITY, + }, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = base_level, + .levelCount = 1, + .baseArrayLayer = base_layer, + .layerCount = 1, + }, + }); +} + void UpdateOneTextureDescriptorSet(const Device& device, VkDescriptorSet descriptor_set, VkSampler sampler, VkImageView image_view) { const VkDescriptorImageInfo image_info{ @@ -352,6 +463,35 @@ void BindBlitState(vk::CommandBuffer cmdbuf, VkPipelineLayout layout, const Regi cmdbuf.PushConstants(layout, VK_SHADER_STAGE_VERTEX_BIT, push_constants); } +// Makes earlier writes to an image visible to a fragment shader that samples it. +void RecordShaderReadBarrier(Scheduler& scheduler, VkImage image, VkImageAspectFlags aspect_mask) { + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([image, aspect_mask](vk::CommandBuffer cmdbuf) { + const VkImageMemoryBarrier barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange{ + .aspectMask = aspect_mask, + .baseMipLevel = 0, + .levelCount = VK_REMAINING_MIP_LEVELS, + .baseArrayLayer = 0, + .layerCount = VK_REMAINING_ARRAY_LAYERS, + }, + }; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, barrier); + }); +} + VkExtent2D GetConversionExtent(const ImageView& src_image_view) { const auto& resolution = videoSettings.resolution_info; const bool is_rescaled = src_image_view.IsRescaled(); @@ -426,19 +566,58 @@ BlitImageHelper::BlitImageHelper(const Device& device_, Scheduler& scheduler_, PUSH_CONSTANT_RANGE))), clear_color_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( nullptr, PUSH_CONSTANT_RANGE))), + msaa_copy_pipeline_layout(device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + one_texture_set_layout.address(), + PUSH_CONSTANT_RANGE))), + msaa_copy_depth_stencil_pipeline_layout( + device.GetLogical().CreatePipelineLayout(PipelineLayoutCreateInfo( + two_textures_set_layout.address(), + PUSH_CONSTANT_RANGE))), full_screen_vert(BuildShader(device, FULL_SCREEN_TRIANGLE_VERT_SPV)), blit_color_to_color_frag(BuildShader(device, BLIT_COLOR_FLOAT_FRAG_SPV)), + blit_color_msaa_frag(BuildShader(device, BLIT_COLOR_MSAA_FRAG_SPV)), + blit_color_msaa_sint_frag(BuildShader(device, BLIT_COLOR_MSAA_SINT_FRAG_SPV)), + blit_color_msaa_uint_frag(BuildShader(device, BLIT_COLOR_MSAA_UINT_FRAG_SPV)), + blit_depth_msaa_frag(BuildShader(device, BLIT_DEPTH_MSAA_FRAG_SPV)), + blit_depth_stencil_msaa_frag(device.IsExtShaderStencilExportSupported() + ? BuildShader(device, BLIT_DEPTH_STENCIL_MSAA_FRAG_SPV) + : vk::ShaderModule{}), blit_depth_stencil_frag(BuildShader(device, VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV)), clear_color_vert(BuildShader(device, VULKAN_COLOR_CLEAR_VERT_SPV)), clear_color_frag(BuildShader(device, VULKAN_COLOR_CLEAR_FRAG_SPV)), clear_stencil_frag(BuildShader(device, VULKAN_DEPTHSTENCIL_CLEAR_FRAG_SPV)), convert_depth_to_float_frag(BuildShader(device, CONVERT_DEPTH_TO_FLOAT_FRAG_SPV)), convert_float_to_depth_frag(BuildShader(device, CONVERT_FLOAT_TO_DEPTH_FRAG_SPV)), - convert_abgr8_to_d24s8_frag(BuildShader(device, CONVERT_ABGR8_TO_D24S8_FRAG_SPV)), + // Without shader stencil export these conversions only write the depth. + convert_abgr8_to_d24s8_frag(BuildShader( + device, device.IsExtShaderStencilExportSupported() + ? std::span{CONVERT_ABGR8_TO_D24S8_FRAG_SPV} + : std::span{CONVERT_ABGR8_TO_D24S8_DEPTH_FRAG_SPV})), + convert_abgr8_to_s8d24_frag(BuildShader( + device, device.IsExtShaderStencilExportSupported() + ? std::span{CONVERT_ABGR8_TO_S8D24_FRAG_SPV} + : std::span{CONVERT_ABGR8_TO_S8D24_DEPTH_FRAG_SPV})), convert_abgr8_to_d32f_frag(BuildShader(device, CONVERT_ABGR8_TO_D32F_FRAG_SPV)), convert_d32f_to_abgr8_frag(BuildShader(device, CONVERT_D32F_TO_ABGR8_FRAG_SPV)), convert_d24s8_to_abgr8_frag(BuildShader(device, CONVERT_D24S8_TO_ABGR8_FRAG_SPV)), convert_s8d24_to_abgr8_frag(BuildShader(device, CONVERT_S8D24_TO_ABGR8_FRAG_SPV)), + convert_non_msaa_to_msaa_frag(BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_FRAG_SPV)), + convert_non_msaa_to_msaa_sint_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_SINT_FRAG_SPV)), + convert_non_msaa_to_msaa_uint_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_UINT_FRAG_SPV)), + convert_non_msaa_to_msaa_depth_frag( + BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_FRAG_SPV)), + convert_non_msaa_to_msaa_depth_stencil_frag( + device.IsExtShaderStencilExportSupported() + ? BuildShader(device, CONVERT_NON_MSAA_TO_MSAA_DEPTH_STENCIL_FRAG_SPV) + : vk::ShaderModule{}), + convert_packed_depth_to_msaa_frag( + BuildShader(device, CONVERT_PACKED_DEPTH_TO_MSAA_FRAG_SPV)), + convert_packed_depth_stencil_to_msaa_frag( + device.IsExtShaderStencilExportSupported() + ? BuildShader(device, CONVERT_PACKED_DEPTH_STENCIL_TO_MSAA_FRAG_SPV) + : vk::ShaderModule{}), linear_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)), nearest_sampler(device.GetLogical().CreateSampler(SAMPLER_CREATE_INFO)) {} @@ -545,6 +724,239 @@ void BlitImageHelper::BlitDepthStencil(const Framebuffer* dst_framebuffer, scheduler.InvalidateState(); } +void BlitImageHelper::BlitColorMSAA(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter) { + const BlitMSAAPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .samples = dst_framebuffer->Samples(), + .format_class = FormatClass(src_image_view.format), + .linear_filter = filter == Tegra::Engines::Fermi2D::Filter::Bilinear && + !VideoCore::Surface::IsPixelFormatInteger(src_image_view.format), + .source_samples = src_image_view.Samples(), + }; + const VkPipelineLayout layout = *one_texture_pipeline_layout; + const VkPipeline pipeline = FindOrEmplaceBlitColorMSAAPipeline(key); + const VkSampler sampler = *nearest_sampler; + const VkImageView src_view = src_image_view.Handle(Shader::TextureType::Color2D); + RecordShaderReadBarrier(scheduler, src_image_view.ImageHandle(), VK_IMAGE_ASPECT_COLOR_BIT); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, + src_view](vk::CommandBuffer cmdbuf) { + const VkDescriptorSet descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view); + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +void BlitImageHelper::BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, + ImageView& src_image_view, const Region2D& dst_region, + const Region2D& src_region) { + const bool has_stencil = dst_framebuffer->HasAspectStencilBit(); + const bool blit_stencil = has_stencil && device.IsExtShaderStencilExportSupported(); + if (has_stencil && !blit_stencil) { + // Only the depth is copied. This can happen every frame, so do not flood the log. + LOG_DEBUG(Render_Vulkan, "MSAA stencil blit needs VK_EXT_shader_stencil_export"); + } + const BlitMSAAPipelineKey key{ + .renderpass = dst_framebuffer->RenderPass(), + .samples = dst_framebuffer->Samples(), + .format_class = MSAACopyFormatClass::Float, + .source_samples = src_image_view.Samples(), + }; + const VkPipelineLayout layout = + blit_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout; + const VkPipeline pipeline = FindOrEmplaceBlitDepthStencilMSAAPipeline(key, blit_stencil); + const VkSampler sampler = *nearest_sampler; + const VkImageView src_depth_view = src_image_view.DepthView(); + const VkImageView src_stencil_view = + blit_stencil ? src_image_view.StencilView() : VkImageView{VK_NULL_HANDLE}; + const VkImageAspectFlags src_aspect = + VK_IMAGE_ASPECT_DEPTH_BIT | (has_stencil ? VK_IMAGE_ASPECT_STENCIL_BIT : 0); + RecordShaderReadBarrier(scheduler, src_image_view.ImageHandle(), src_aspect); + scheduler.RequestRenderpass(dst_framebuffer); + scheduler.Record([this, dst_region, src_region, pipeline, layout, sampler, src_depth_view, + src_stencil_view, blit_stencil](vk::CommandBuffer cmdbuf) { + VkDescriptorSet descriptor_set{}; + if (blit_stencil) { + descriptor_set = two_textures_descriptor_allocator.Commit(); + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_depth_view, + src_stencil_view); + } else { + descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_depth_view); + } + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, + nullptr); + BindBlitState(cmdbuf, layout, dst_region, src_region); + cmdbuf.Draw(3, 1, 0, 0); + }); + scheduler.InvalidateState(); +} + +// Resolve sample zero while retaining source samples and pixels outside the region. +bool BlitImageHelper::TryResolveDepthStencilMSAA(ImageView & dst, ImageView & src, + const Region2D & region) +{ + const VkFormat format = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, src.format).format; + VkImageAspectFlags aspect{}; + switch (format) + { + case VK_FORMAT_D16_UNORM: + case VK_FORMAT_X8_D24_UNORM_PACK32: + case VK_FORMAT_D32_SFLOAT: + aspect = VK_IMAGE_ASPECT_DEPTH_BIT; + break; + case VK_FORMAT_S8_UINT: + aspect = VK_IMAGE_ASPECT_STENCIL_BIT; + break; + case VK_FORMAT_D16_UNORM_S8_UINT: + case VK_FORMAT_D24_UNORM_S8_UINT: + case VK_FORMAT_D32_SFLOAT_S8_UINT: + aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; + break; + default: return false; + } + const auto is_2d = [](const ImageView & view) { + return view.type == VideoCommon::ImageViewType::e2D || + view.type == VideoCommon::ImageViewType::e2DArray || + view.type == VideoCommon::ImageViewType::Rect; + }; + if (src.Samples() == VK_SAMPLE_COUNT_1_BIT || + dst.Samples() != VK_SAMPLE_COUNT_1_BIT || src.format != dst.format || + !device.SupportsDepthStencilResolve(aspect) || src.IsRescaled() || dst.IsRescaled() || + !is_2d(src) || !is_2d(dst) || src.range.extent.levels != 1 || + dst.range.extent.levels != 1 || src.range.extent.layers != 1 || + dst.range.extent.layers != 1 || region.start.x < 0 || region.start.y < 0 || + region.end.x <= region.start.x || region.end.y <= region.start.y) + { + return false; + } + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(src.Samples())); + const u32 width = static_cast(region.end.x); + const u32 height = static_cast(region.end.y); + if (width > (src.size.width >> samples_x) || height > (src.size.height >> samples_y) || + width > dst.size.width || height > dst.size.height) + { + return false; + } + TickFrame(); + + const u64 key = static_cast(format) | (static_cast(src.Samples()) << 32); + auto found = std::ranges::find_if(native_resolve_passes, [key](const auto & entry) { + return entry.first == key; + }); + if (found == native_resolve_passes.end()) + { + std::array attachments{}; + for (auto & attachment : attachments) + { + attachment.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2; + attachment.format = format; + attachment.loadOp = attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_LOAD; + attachment.storeOp = attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE; + attachment.initialLayout = attachment.finalLayout = VK_IMAGE_LAYOUT_GENERAL; + } + attachments[0].samples = src.Samples(); + attachments[1].samples = VK_SAMPLE_COUNT_1_BIT; + VkAttachmentReference2 source{VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2}; + source.attachment = 0; + source.layout = VK_IMAGE_LAYOUT_GENERAL; + source.aspectMask = aspect; + auto destination = source; + destination.attachment = 1; + VkSubpassDescriptionDepthStencilResolve resolve{ + VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE}; + resolve.depthResolveMode = (aspect & VK_IMAGE_ASPECT_DEPTH_BIT) + ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT + : VK_RESOLVE_MODE_NONE; + resolve.stencilResolveMode = (aspect & VK_IMAGE_ASPECT_STENCIL_BIT) + ? VK_RESOLVE_MODE_SAMPLE_ZERO_BIT + : VK_RESOLVE_MODE_NONE; + resolve.pDepthStencilResolveAttachment = &destination; + VkSubpassDescription2 subpass{VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2}; + subpass.pNext = &resolve; + subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; + subpass.pDepthStencilAttachment = &source; + const VkRenderPassCreateInfo2 ci{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2, + .attachmentCount = 2, + .pAttachments = attachments.data(), + .subpassCount = 1, + .pSubpasses = &subpass}; + native_resolve_passes.emplace_back(key, device.GetLogical().CreateRenderPass2(ci)); + found = std::prev(native_resolve_passes.end()); + } + const VkRenderPass pass = *found->second; + const std::array views{src.RenderTarget(), dst.RenderTarget()}; + auto framebuffer = device.GetLogical().CreateFramebuffer({.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = pass, + .attachmentCount = 2, + .pAttachments = views.data(), + .width = width, + .height = height, + .layers = 1}); + const VkRect2D area{ + {region.start.x, region.start.y}, + {static_cast(region.end.x - region.start.x), + static_cast(region.end.y - region.start.y)}}; + const VkImageSubresourceRange source_range{ + aspect, static_cast(src.range.base.level), 1, + static_cast(src.range.base.layer), 1}; + const VkImageSubresourceRange destination_range{ + aspect, static_cast(dst.range.base.level), 1, + static_cast(dst.range.base.layer), 1}; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([pass, handle = *framebuffer, area, source_range, destination_range, + source = src.ImageHandle(), destination = dst.ImageHandle()](vk::CommandBuffer cmdbuf) { + const auto barrier = [](VkImage image, VkImageSubresourceRange range, + VkAccessFlags from, VkAccessFlags to) { + return VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .srcAccessMask = from, + .dstAccessMask = to, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = image, + .subresourceRange = range}; + }; + const std::array before{ + barrier(source, source_range, VK_ACCESS_MEMORY_WRITE_BIT, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT), + barrier(destination, destination_range, VK_ACCESS_MEMORY_WRITE_BIT, + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT)}; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + 0, nullptr, nullptr, before); + const VkRenderPassBeginInfo bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, + .renderPass = pass, + .framebuffer = handle, + .renderArea = area}; + cmdbuf.BeginRenderPass(bi, VK_SUBPASS_CONTENTS_INLINE); + cmdbuf.EndRenderPass(); + const std::array after{ + barrier(source, source_range, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT), + barrier(destination, destination_range, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT)}; + cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, nullptr, nullptr, after); + }); + msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), .framebuffer = std::move(framebuffer)}); + scheduler.InvalidateState(); + return true; +} + void BlitImageHelper::ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { ConvertDepthToColorPipeline(convert_d32_to_r32_pipeline, dst_framebuffer->RenderPass()); @@ -571,11 +983,20 @@ void BlitImageHelper::ConvertR16ToD16(const Framebuffer* dst_framebuffer, void BlitImageHelper::ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { - ConvertPipelineDepthTargetEx(convert_abgr8_to_d24s8_pipeline, dst_framebuffer->RenderPass(), - convert_abgr8_to_d24s8_frag); + ConvertPipelineDepthStencilTargetEx(convert_abgr8_to_d24s8_pipeline, + dst_framebuffer->RenderPass(), + convert_abgr8_to_d24s8_frag); Convert(*convert_abgr8_to_d24s8_pipeline, dst_framebuffer, src_image_view); } +void BlitImageHelper::ConvertABGR8ToS8D24(const Framebuffer* dst_framebuffer, + const ImageView& src_image_view) { + ConvertPipelineDepthStencilTargetEx(convert_abgr8_to_s8d24_pipeline, + dst_framebuffer->RenderPass(), + convert_abgr8_to_s8d24_frag); + Convert(*convert_abgr8_to_s8d24_pipeline, dst_framebuffer, src_image_view); +} + void BlitImageHelper::ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { ConvertPipelineDepthTargetEx(convert_abgr8_to_d32f_pipeline, dst_framebuffer->RenderPass(), @@ -681,6 +1102,384 @@ void BlitImageHelper::ClearDepthStencil(const Framebuffer* dst_framebuffer, bool scheduler.InvalidateState(); } +void BlitImageHelper::TickFrame() { + while (!msaa_copy_resources.empty() && scheduler.IsFree(msaa_copy_resources.front().tick)) { + msaa_copy_resources.pop_front(); + } +} + +void BlitImageHelper::CopyToMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, + VkPipelineLayout layout, VkImage dst_image, + VkFormat dst_format, VkImage src_image, VkFormat src_format, + s32 scale_x, s32 scale_y, + std::span copies, + const MSAACopyAspectInfo& aspect_info, bool copy_stencil, + s32 packed_format) { + TickFrame(); + if (copies.empty()) return; + const bool batch_regions = src_image != dst_image; + struct Job + { + u32 src_level, src_layer, dst_level, dst_layer; + VkRect2D area; + MSAACopyPushConstants push; + bool SameSubresources(const Job & other) const + { + return src_level == other.src_level && src_layer == other.src_layer && + dst_level == other.dst_level && dst_layer == other.dst_layer; + } + }; + boost::container::small_vector jobs; + for (const auto & copy : copies) + { + const s32 layers = (std::min)(copy.src_subresource.num_layers, copy.dst_subresource.num_layers); + for (s32 layer = 0; layer < layers; ++layer) + { + jobs.push_back(Job{ + static_cast(copy.src_subresource.base_level), + static_cast(copy.src_subresource.base_layer + layer), + static_cast(copy.dst_subresource.base_level), + static_cast(copy.dst_subresource.base_layer + layer), + {{copy.dst_offset.x, copy.dst_offset.y}, {copy.extent.width, copy.extent.height}}, + {{copy.dst_offset.x, copy.dst_offset.y}, {copy.src_offset.x, copy.src_offset.y}, {scale_x, scale_y}, packed_format}}); + } + } + // Bound the linear local cache to avoid quadratic planning on unusually large uploads. + const bool reuse_resources = jobs.size() <= 256; + struct Cached + { + Job subresource; + VkExtent2D extent; + VkImageView source, stencil; + VkFramebuffer framebuffer; + }; + // Only reuse within this upload: both images remain alive and their handles cannot be recycled. + boost::container::small_vector cache; + const VkSampler sampler = *nearest_sampler; + for (size_t begin = 0; begin < jobs.size();) + { + const auto & job = jobs[begin]; + size_t end = begin + 1; + if (batch_regions) + { + while (end < jobs.size() && job.SameSubresources(jobs[end])) + ++end; + } + const auto found = reuse_resources ? std::ranges::find_if(cache, [&](const Cached & c) { + return job.SameSubresources(c.subresource); + }) + : cache.end(); + VkExtent2D extent{static_cast(job.area.offset.x) + job.area.extent.width, + static_cast(job.area.offset.y) + job.area.extent.height}; + if (found != cache.end()) + { + extent = found->extent; + } + else if (reuse_resources || batch_regions) + { + const size_t first = reuse_resources ? 0 : begin; + const size_t last = reuse_resources ? jobs.size() : end; + for (size_t index = first; index < last; ++index) + { + const auto & next = jobs[index]; + if (!job.SameSubresources(next)) continue; + extent.width = (std::max)(extent.width, static_cast(next.area.offset.x) + next.area.extent.width); + extent.height = (std::max)(extent.height, static_cast(next.area.offset.y) + next.area.extent.height); + } + } + Cached handles{}; + if (found != cache.end()) + { + handles = *found; + } + else + { + auto source = MakeMSAACopyView(device.GetLogical(), src_image, src_format, + job.src_level, job.src_layer, aspect_info.src_view_aspect); + auto destination = MakeMSAACopyView(device.GetLogical(), dst_image, dst_format, + job.dst_level, job.dst_layer, aspect_info.attachment_aspect); + vk::ImageView stencil; + if (copy_stencil) stencil = MakeMSAACopyView(device.GetLogical(), src_image, src_format, + job.src_level, job.src_layer, VK_IMAGE_ASPECT_STENCIL_BIT); + auto framebuffer = device.GetLogical().CreateFramebuffer({.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, + .renderPass = renderpass, + .attachmentCount = 1, + .pAttachments = destination.address(), + .width = extent.width, + .height = extent.height, + .layers = 1}); + handles = Cached{job, extent, *source, copy_stencil ? *stencil : VK_NULL_HANDLE, *framebuffer}; + msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), .src_view = std::move(source), .dst_view = std::move(destination), .framebuffer = std::move(framebuffer)}); + if (copy_stencil) msaa_copy_resources.push_back(MSAACopyResources{ + .tick = scheduler.CurrentTick(), .src_view = std::move(stencil)}); + if (reuse_resources) cache.push_back(handles); + } + boost::container::small_vector draws(jobs.begin() + begin, jobs.begin() + end); + const VkRect2D render_area = batch_regions ? VkRect2D{{0, 0}, extent} : job.area; + scheduler.RequestOutsideRenderPassOperationContext(); + scheduler.Record([this, pipeline, layout, sampler, renderpass, + framebuffer_handle = handles.framebuffer, src_view_handle = handles.source, + src_stencil_handle = handles.stencil, src = src_image, dst = dst_image, + src_level = job.src_level, src_layer = job.src_layer, dst_level = job.dst_level, + dst_layer = job.dst_layer, aspect_info, render_area, + draws = std::move(draws)](vk::CommandBuffer cmdbuf) { + const VkImageSubresourceRange src_barrier_range{ + .aspectMask = aspect_info.src_barrier_aspect, + .baseMipLevel = src_level, + .levelCount = 1, + .baseArrayLayer = src_layer, + .layerCount = 1, + }; + const VkImageSubresourceRange dst_barrier_range{ + .aspectMask = aspect_info.dst_barrier_aspect, + .baseMipLevel = dst_level, + .levelCount = 1, + .baseArrayLayer = dst_layer, + .layerCount = 1, + }; + const std::array pre_barriers{ + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.pre_src_access, + .dstAccessMask = aspect_info.pre_src_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = src, + .subresourceRange = src_barrier_range, + }, + VkImageMemoryBarrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.pre_dst_access, + .dstAccessMask = aspect_info.pre_dst_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = dst_barrier_range, + }, + }; + cmdbuf.PipelineBarrier(aspect_info.pre_src_stages, aspect_info.pre_dst_stages, 0, + nullptr, nullptr, pre_barriers); + + const VkRenderPassBeginInfo bi{ + .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = renderpass, .framebuffer = framebuffer_handle, .renderArea = render_area}; + cmdbuf.BeginRenderPass(bi, VK_SUBPASS_CONTENTS_INLINE); + VkDescriptorSet descriptor_set; + if (src_stencil_handle != VK_NULL_HANDLE) + { + descriptor_set = two_textures_descriptor_allocator.Commit(); + UpdateTwoTexturesDescriptorSet(device, descriptor_set, sampler, src_view_handle, src_stencil_handle); + } + else + { + descriptor_set = one_texture_descriptor_allocator.Commit(); + UpdateOneTextureDescriptorSet(device, descriptor_set, sampler, src_view_handle); + } + cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); + cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, descriptor_set, nullptr); + for (const auto & draw : draws) + { + const VkViewport viewport{ + .x = static_cast(draw.area.offset.x), .y = static_cast(draw.area.offset.y), .width = static_cast(draw.area.extent.width), .height = static_cast(draw.area.extent.height), .minDepth = 0.0f, .maxDepth = 1.0f}; + cmdbuf.SetViewport(0, viewport); + cmdbuf.SetScissor(0, draw.area); + cmdbuf.PushConstants(layout, VK_SHADER_STAGE_FRAGMENT_BIT, draw.push); + cmdbuf.Draw(3, 1, 0, 0); + } + cmdbuf.EndRenderPass(); + const VkImageMemoryBarrier post_barrier{ + .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = aspect_info.post_src_access, + .dstAccessMask = aspect_info.post_dst_access, + .oldLayout = VK_IMAGE_LAYOUT_GENERAL, + .newLayout = VK_IMAGE_LAYOUT_GENERAL, + .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, + .image = dst, + .subresourceRange = dst_barrier_range, + }; + cmdbuf.PipelineBarrier(aspect_info.post_src_stages, aspect_info.post_dst_stages, 0, + post_barrier); + }); + begin = end; + } + scheduler.InvalidateState(); +} + +void BlitImageHelper::CopyToMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, + u32 num_samples, + std::span copies) { + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + const VkSampleCountFlagBits samples = SampleCountFlag(num_samples); + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.color_formats[0] = format; + renderpass_key.depth_format = VideoCore::Surface::PixelFormat::Invalid; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .format_class = FormatClass(format), + }; + const MSAACopyAspectInfo aspect_info{ + .src_view_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .attachment_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .src_barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .dst_barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .pre_src_access = VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_dst_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT, + .pre_dst_dst_access = + VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .pre_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .pre_dst_stages = + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .post_src_access = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT, + .post_src_stages = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .post_dst_stages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT, + }; + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, format).format; + CopyToMSAAImpl(renderpass, FindOrEmplaceMSAACopyPipeline(key), *msaa_copy_pipeline_layout, + dst_image, vk_format, src_image, vk_format, scale_x, scale_y, copies, + aspect_info, false); +} + +void BlitImageHelper::CopyToMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, + u32 num_samples, + std::span copies, + bool copy_stencil) { + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + const VkSampleCountFlagBits samples = SampleCountFlag(num_samples); + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.depth_format = format; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .format_class = MSAACopyFormatClass::Float, + }; + VkImageAspectFlags attachment_aspect = VK_IMAGE_ASPECT_DEPTH_BIT; + if (VideoCore::Surface::GetFormatType(format) == + VideoCore::Surface::SurfaceType::DepthStencil) { + attachment_aspect |= VK_IMAGE_ASPECT_STENCIL_BIT; + } + const VkPipelineLayout layout = copy_stencil ? *msaa_copy_depth_stencil_pipeline_layout + : *msaa_copy_pipeline_layout; + const MSAACopyAspectInfo aspect_info{ + .src_view_aspect = VK_IMAGE_ASPECT_DEPTH_BIT, + .attachment_aspect = attachment_aspect, + .src_barrier_aspect = attachment_aspect, + .dst_barrier_aspect = attachment_aspect, + .pre_src_access = VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT, + .pre_dst_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .pre_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .pre_dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .post_src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .post_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .post_dst_stages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + }; + const VkFormat vk_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, format).format; + CopyToMSAAImpl(renderpass, FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil), layout, + dst_image, vk_format, src_image, vk_format, scale_x, scale_y, copies, + aspect_info, copy_stencil); +} + +void BlitImageHelper::CopyPackedDepthStencilToMSAA( + RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, VkFormat src_format, + u32 num_samples, std::span copies, s32 packed_format, + bool copy_stencil) { + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(static_cast(num_samples)); + const s32 scale_x = 1 << samples_x; + const s32 scale_y = 1 << samples_y; + const VkSampleCountFlagBits samples = SampleCountFlag(num_samples); + RenderPassKey renderpass_key{}; + renderpass_key.color_formats.fill(VideoCore::Surface::PixelFormat::Invalid); + renderpass_key.depth_format = format; + renderpass_key.samples = samples; + const VkRenderPass renderpass = render_pass_cache.Get(renderpass_key); + const MSAACopyPipelineKey key{ + .renderpass = renderpass, + .samples = samples, + .format_class = MSAACopyFormatClass::Float, + .packed_depth_stencil = true, + }; + const MSAACopyAspectInfo aspect_info{ + .src_view_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .attachment_aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, + .src_barrier_aspect = VK_IMAGE_ASPECT_COLOR_BIT, + .dst_barrier_aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT, + .pre_src_access = VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | + VK_ACCESS_SHADER_WRITE_BIT | VK_ACCESS_TRANSFER_WRITE_BIT, + .pre_src_dst_access = VK_ACCESS_SHADER_READ_BIT, + .pre_dst_dst_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .pre_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, + .pre_dst_stages = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .post_src_access = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, + .post_dst_access = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_TRANSFER_READ_BIT | + VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT, + .post_src_stages = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + .post_dst_stages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | + VK_PIPELINE_STAGE_TRANSFER_BIT | + VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | + VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, + }; + const VkFormat dst_format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, format).format; + CopyToMSAAImpl(renderpass, FindOrEmplaceMSAACopyDepthPipeline(key, copy_stencil), + *msaa_copy_pipeline_layout, dst_image, dst_format, src_image, src_format, + scale_x, scale_y, copies, aspect_info, false, packed_format); +} + void BlitImageHelper::Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view) { const VkPipelineLayout layout = *one_texture_pipeline_layout; @@ -987,6 +1786,260 @@ VkPipeline BlitImageHelper::FindOrEmplaceClearStencilPipeline( return *clear_stencil_pipelines.back(); } +VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key) { + const size_t index = FindCachedKey(msaa_copy_keys, key, msaa_copy_last); + if (index != msaa_copy_keys.size()) + { + return *msaa_copy_pipelines[index]; + } + msaa_copy_keys.push_back(key); + VkShaderModule frag_module = *convert_non_msaa_to_msaa_frag; + if (key.format_class == MSAACopyFormatClass::SignedInteger) { + frag_module = *convert_non_msaa_to_msaa_sint_frag; + } else if (key.format_class == MSAACopyFormatClass::UnsignedInteger) { + frag_module = *convert_non_msaa_to_msaa_uint_frag; + } + const std::array stages = MakeStages(*clear_color_vert, frag_module); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + msaa_copy_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *msaa_copy_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *msaa_copy_pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key, + bool copy_stencil) { + auto& keys = copy_stencil ? msaa_copy_depth_stencil_keys : msaa_copy_depth_keys; + auto& pipelines = copy_stencil ? msaa_copy_depth_stencil_pipelines : msaa_copy_depth_pipelines; + auto & last = copy_stencil ? msaa_copy_depth_stencil_last : msaa_copy_depth_last; + const size_t index = FindCachedKey(keys, key, last); + if (index != keys.size()) + { + return *pipelines[index]; + } + keys.push_back(key); + const bool writes_stencil = copy_stencil; + const VkShaderModule frag_module = + key.packed_depth_stencil + ? (copy_stencil ? *convert_packed_depth_stencil_to_msaa_frag + : *convert_packed_depth_to_msaa_frag) + : (copy_stencil ? *convert_non_msaa_to_msaa_depth_stencil_frag + : *convert_non_msaa_to_msaa_depth_frag); + const std::array stages = MakeStages(*clear_color_vert, frag_module); + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = VK_TRUE, + .minSampleShading = 1.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + static constexpr VkStencilOpState REPLACE_STENCIL_OP{ + .failOp = VK_STENCIL_OP_REPLACE, + .passOp = VK_STENCIL_OP_REPLACE, + .depthFailOp = VK_STENCIL_OP_REPLACE, + .compareOp = VK_COMPARE_OP_ALWAYS, + .compareMask = 0xFF, + .writeMask = 0xFF, + .reference = 0, + }; + const VkPipelineDepthStencilStateCreateInfo depth_stencil_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .depthTestEnable = VK_TRUE, + .depthWriteEnable = VK_TRUE, + .depthCompareOp = VK_COMPARE_OP_ALWAYS, + .depthBoundsTestEnable = VK_FALSE, + .stencilTestEnable = writes_stencil ? VK_TRUE : VK_FALSE, + .front = writes_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{}, + .back = writes_stencil ? REPLACE_STENCIL_OP : VkStencilOpState{}, + .minDepthBounds = 0.0f, + .maxDepthBounds = 0.0f, + }; + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = &depth_stencil_ci, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = copy_stencil && !key.packed_depth_stencil + ? *msaa_copy_depth_stencil_pipeline_layout + : *msaa_copy_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key) { + const size_t index = FindCachedKey(blit_msaa_color_keys, key, blit_msaa_color_last); + if (index != blit_msaa_color_keys.size()) + { + return *blit_msaa_color_pipelines[index]; + } + blit_msaa_color_keys.push_back(key); + VkShaderModule frag_module = *blit_color_msaa_frag; + if (key.format_class == MSAACopyFormatClass::SignedInteger) { + frag_module = *blit_color_msaa_sint_frag; + } else if (key.format_class == MSAACopyFormatClass::UnsignedInteger) { + frag_module = *blit_color_msaa_uint_frag; + } + auto stages = MakeStages(*full_screen_vert, frag_module); + const std::array specialization_values{ + static_cast(key.samples), static_cast(key.source_samples), + static_cast(key.linear_filter)}; + const std::array sample_entries{{ + {0, 0, sizeof(s32)}, {2, sizeof(s32), sizeof(s32)}, + {1, 2 * sizeof(s32), sizeof(s32)}, + }}; + const VkSpecializationInfo sample_info{ + .mapEntryCount = key.format_class == MSAACopyFormatClass::Float ? 3u : 2u, + .pMapEntries = sample_entries.data(), + .dataSize = sizeof(specialization_values), + .pData = specialization_values.data(), + }; + stages[1].pSpecializationInfo = &sample_info; + const bool is_multisampled = key.samples != VK_SAMPLE_COUNT_1_BIT; + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = is_multisampled ? VK_TRUE : VK_FALSE, + .minSampleShading = is_multisampled ? 1.0f : 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + blit_msaa_color_pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = nullptr, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *blit_msaa_color_pipelines.back(); +} + +VkPipeline BlitImageHelper::FindOrEmplaceBlitDepthStencilMSAAPipeline( + const BlitMSAAPipelineKey& key, bool blit_stencil) { + auto& keys = blit_stencil ? blit_msaa_depth_stencil_keys : blit_msaa_depth_keys; + auto& pipelines = blit_stencil ? blit_msaa_depth_stencil_pipelines : blit_msaa_depth_pipelines; + auto & last = blit_stencil ? blit_msaa_depth_stencil_last : blit_msaa_depth_last; + const size_t index = FindCachedKey(keys, key, last); + if (index != keys.size()) + { + return *pipelines[index]; + } + keys.push_back(key); + auto stages = MakeStages(*full_screen_vert, blit_stencil ? *blit_depth_stencil_msaa_frag + : *blit_depth_msaa_frag); + const std::array sample_values{static_cast(key.samples), + static_cast(key.source_samples)}; + const std::array sample_entries{{ + {0, 0, sizeof(s32)}, {2, sizeof(s32), sizeof(s32)}, + }}; + const VkSpecializationInfo sample_info{ + .mapEntryCount = static_cast(sample_entries.size()), + .pMapEntries = sample_entries.data(), + .dataSize = sizeof(sample_values), + .pData = sample_values.data(), + }; + stages[1].pSpecializationInfo = &sample_info; + const bool is_multisampled = key.samples != VK_SAMPLE_COUNT_1_BIT; + const VkPipelineMultisampleStateCreateInfo multisample_ci{ + .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .rasterizationSamples = key.samples, + .sampleShadingEnable = is_multisampled ? VK_TRUE : VK_FALSE, + .minSampleShading = is_multisampled ? 1.0f : 0.0f, + .pSampleMask = nullptr, + .alphaToCoverageEnable = VK_FALSE, + .alphaToOneEnable = VK_FALSE, + }; + pipelines.push_back(device.GetLogical().CreateGraphicsPipeline({ + .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, + .pNext = nullptr, + .flags = 0, + .stageCount = static_cast(stages.size()), + .pStages = stages.data(), + .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, + .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, + .pTessellationState = nullptr, + .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, + .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, + .pMultisampleState = &multisample_ci, + .pDepthStencilState = blit_stencil ? &PIPELINE_DEPTH_STENCIL_EXPORT_STATE_CREATE_INFO + : &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, + .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO, + .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, + .layout = blit_stencil ? *two_textures_pipeline_layout : *one_texture_pipeline_layout, + .renderPass = key.renderpass, + .subpass = 0, + .basePipelineHandle = VK_NULL_HANDLE, + .basePipelineIndex = 0, + })); + return *pipelines.back(); +} + void BlitImageHelper::ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth) { if (pipeline) { @@ -1029,7 +2082,7 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, vk::ShaderModule& module, bool single_texture, - bool is_target_depth) { + bool is_target_depth, bool write_stencil) { if (pipeline) { return; } @@ -1046,8 +2099,12 @@ void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass ren .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, - .pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr, - .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, + .pDepthStencilState = write_stencil + ? &PIPELINE_DEPTH_STENCIL_EXPORT_STATE_CREATE_INFO + : (is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO + : nullptr), + .pColorBlendState = is_target_depth ? &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO + : &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout, .renderPass = renderpass, @@ -1067,4 +2124,12 @@ void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRen ConvertPipelineEx(pipeline, renderpass, module, true, true); } +void BlitImageHelper::ConvertPipelineDepthStencilTargetEx(vk::Pipeline& pipeline, + VkRenderPass renderpass, + vk::ShaderModule& module) { + // The stencil can only be written with shader stencil export. + ConvertPipelineEx(pipeline, renderpass, module, true, true, + device.IsExtShaderStencilExportSupported()); +} + } // namespace Vulkan diff --git a/src/yuzu_video_core/renderer_vulkan/blit_image.h b/src/yuzu_video_core/renderer_vulkan/blit_image.h index 4ac3244e..0bfe6655 100644 --- a/src/yuzu_video_core/renderer_vulkan/blit_image.h +++ b/src/yuzu_video_core/renderer_vulkan/blit_image.h @@ -3,6 +3,9 @@ #pragma once +#include +#include + #include "yuzu_video_core/engines/fermi_2d.h" #include "yuzu_video_core/renderer_vulkan/vk_descriptor_pool.h" #include "yuzu_video_core/surface.h" @@ -18,6 +21,7 @@ using VideoCommon::Region2D; class Device; class Framebuffer; class ImageView; +class RenderPassCache; class StateTracker; class Scheduler; @@ -45,12 +49,39 @@ struct BlitDepthStencilPipelineKey { VkSampleCountFlagBits samples{VK_SAMPLE_COUNT_1_BIT}; }; +enum class MSAACopyFormatClass : u32 { + Float, + SignedInteger, + UnsignedInteger, +}; + +struct MSAACopyPipelineKey { + constexpr auto operator<=>(const MSAACopyPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; + MSAACopyFormatClass format_class; + bool packed_depth_stencil{}; +}; + +struct BlitMSAAPipelineKey { + constexpr auto operator<=>(const BlitMSAAPipelineKey&) const noexcept = default; + + VkRenderPass renderpass; + VkSampleCountFlagBits samples; + MSAACopyFormatClass format_class; + bool linear_filter{}; + VkSampleCountFlagBits source_samples{}; +}; + class BlitImageHelper { public: explicit BlitImageHelper(const Device& device, Scheduler& scheduler, StateTracker& state_tracker, DescriptorPool& descriptor_pool); ~BlitImageHelper(); + void TickFrame(); + void BlitColor(const Framebuffer* dst_framebuffer, VkImageView src_image_view, const Region2D& dst_region, const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, @@ -65,6 +96,17 @@ public: const Region2D& src_region, Tegra::Engines::Fermi2D::Filter filter, Tegra::Engines::Fermi2D::Operation operation); + /// Blits a multisampled color image with a shader. The destination can have any sample count. + void BlitColorMSAA(const Framebuffer* dst_framebuffer, const ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region, + Tegra::Engines::Fermi2D::Filter filter); + + /// Blits a multisampled depth image with a shader. Stencil needs shader stencil export. + void BlitDepthStencilMSAA(const Framebuffer* dst_framebuffer, ImageView& src_image_view, + const Region2D& dst_region, const Region2D& src_region); + + bool TryResolveDepthStencilMSAA(ImageView & dst, ImageView & src, const Region2D & region); + void ConvertD32ToR32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); void ConvertR32ToD32(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -75,6 +117,8 @@ public: void ConvertABGR8ToD24S8(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + void ConvertABGR8ToS8D24(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); + void ConvertABGR8ToD32F(const Framebuffer* dst_framebuffer, const ImageView& src_image_view); void ConvertD32FToABGR8(const Framebuffer* dst_framebuffer, ImageView& src_image_view); @@ -94,7 +138,45 @@ public: u8 stencil_mask, u32 stencil_ref, u32 stencil_compare_mask, const Region2D& dst_region); + void CopyToMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, u32 num_samples, + std::span copies); + + void CopyToMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, u32 num_samples, + std::span copies, bool copy_stencil); + + void CopyPackedDepthStencilToMSAA(RenderPassCache& render_pass_cache, VkImage dst_image, + VideoCore::Surface::PixelFormat format, VkImage src_image, + VkFormat src_format, u32 num_samples, + std::span copies, + s32 packed_format, bool copy_stencil); + private: + struct MSAACopyAspectInfo { + VkImageAspectFlags src_view_aspect; + VkImageAspectFlags attachment_aspect; + VkImageAspectFlags src_barrier_aspect; + VkImageAspectFlags dst_barrier_aspect; + VkAccessFlags pre_src_access; + VkAccessFlags pre_dst_access; + VkAccessFlags pre_src_dst_access; + VkAccessFlags pre_dst_dst_access; + VkPipelineStageFlags pre_src_stages; + VkPipelineStageFlags pre_dst_stages; + VkAccessFlags post_src_access; + VkAccessFlags post_dst_access; + VkPipelineStageFlags post_src_stages; + VkPipelineStageFlags post_dst_stages; + }; + + void CopyToMSAAImpl(VkRenderPass renderpass, VkPipeline pipeline, VkPipelineLayout layout, + VkImage dst_image, VkFormat dst_format, VkImage src_image, + VkFormat src_format, s32 scale_x, s32 scale_y, + std::span copies, + const MSAACopyAspectInfo& aspect_info, bool copy_stencil, + s32 packed_format = 0); + void Convert(VkPipeline pipeline, const Framebuffer* dst_framebuffer, const ImageView& src_image_view); @@ -108,6 +190,12 @@ private: [[nodiscard]] VkPipeline FindOrEmplaceClearColorPipeline(const BlitImagePipelineKey& key); [[nodiscard]] VkPipeline FindOrEmplaceClearStencilPipeline( const BlitDepthStencilPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceMSAACopyPipeline(const MSAACopyPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceMSAACopyDepthPipeline(const MSAACopyPipelineKey& key, + bool copy_stencil); + [[nodiscard]] VkPipeline FindOrEmplaceBlitColorMSAAPipeline(const BlitMSAAPipelineKey& key); + [[nodiscard]] VkPipeline FindOrEmplaceBlitDepthStencilMSAAPipeline( + const BlitMSAAPipelineKey& key, bool blit_stencil); void ConvertPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass, bool is_target_depth); @@ -116,7 +204,8 @@ private: void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); void ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass, - vk::ShaderModule& module, bool single_texture, bool is_target_depth); + vk::ShaderModule& module, bool single_texture, bool is_target_depth, + bool write_stencil = false); void ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, vk::ShaderModule& module); @@ -124,6 +213,9 @@ private: void ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, vk::ShaderModule& module); + void ConvertPipelineDepthStencilTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, + vk::ShaderModule& module); + const Device& device; Scheduler& scheduler; StateTracker& state_tracker; @@ -135,8 +227,15 @@ private: vk::PipelineLayout one_texture_pipeline_layout; vk::PipelineLayout two_textures_pipeline_layout; vk::PipelineLayout clear_color_pipeline_layout; + vk::PipelineLayout msaa_copy_pipeline_layout; + vk::PipelineLayout msaa_copy_depth_stencil_pipeline_layout; vk::ShaderModule full_screen_vert; vk::ShaderModule blit_color_to_color_frag; + vk::ShaderModule blit_color_msaa_frag; + vk::ShaderModule blit_color_msaa_sint_frag; + vk::ShaderModule blit_color_msaa_uint_frag; + vk::ShaderModule blit_depth_msaa_frag; + vk::ShaderModule blit_depth_stencil_msaa_frag; vk::ShaderModule blit_depth_stencil_frag; vk::ShaderModule clear_color_vert; vk::ShaderModule clear_color_frag; @@ -146,10 +245,18 @@ private: vk::ShaderModule convert_depth_to_float_frag; vk::ShaderModule convert_float_to_depth_frag; vk::ShaderModule convert_abgr8_to_d24s8_frag; + vk::ShaderModule convert_abgr8_to_s8d24_frag; vk::ShaderModule convert_abgr8_to_d32f_frag; vk::ShaderModule convert_d32f_to_abgr8_frag; vk::ShaderModule convert_d24s8_to_abgr8_frag; vk::ShaderModule convert_s8d24_to_abgr8_frag; + vk::ShaderModule convert_non_msaa_to_msaa_frag; + vk::ShaderModule convert_non_msaa_to_msaa_sint_frag; + vk::ShaderModule convert_non_msaa_to_msaa_uint_frag; + vk::ShaderModule convert_non_msaa_to_msaa_depth_frag; + vk::ShaderModule convert_non_msaa_to_msaa_depth_stencil_frag; + vk::ShaderModule convert_packed_depth_to_msaa_frag; + vk::ShaderModule convert_packed_depth_stencil_to_msaa_frag; vk::Sampler linear_sampler; vk::Sampler nearest_sampler; @@ -161,11 +268,38 @@ private: std::vector clear_color_pipelines; std::vector clear_stencil_keys; std::vector clear_stencil_pipelines; + std::vector msaa_copy_keys; + std::vector msaa_copy_pipelines; + size_t msaa_copy_last{}; + std::vector msaa_copy_depth_keys; + std::vector msaa_copy_depth_pipelines; + size_t msaa_copy_depth_last{}; + std::vector msaa_copy_depth_stencil_keys; + std::vector msaa_copy_depth_stencil_pipelines; + size_t msaa_copy_depth_stencil_last{}; + std::vector blit_msaa_color_keys; + std::vector blit_msaa_color_pipelines; + size_t blit_msaa_color_last{}; + std::vector blit_msaa_depth_keys; + std::vector blit_msaa_depth_pipelines; + size_t blit_msaa_depth_last{}; + std::vector blit_msaa_depth_stencil_keys; + std::vector blit_msaa_depth_stencil_pipelines; + size_t blit_msaa_depth_stencil_last{}; + std::vector> native_resolve_passes; + struct MSAACopyResources { + u64 tick; + vk::ImageView src_view; + vk::ImageView dst_view; + vk::Framebuffer framebuffer; + }; + std::deque msaa_copy_resources; vk::Pipeline convert_d32_to_r32_pipeline; vk::Pipeline convert_r32_to_d32_pipeline; vk::Pipeline convert_d16_to_r16_pipeline; vk::Pipeline convert_r16_to_d16_pipeline; vk::Pipeline convert_abgr8_to_d24s8_pipeline; + vk::Pipeline convert_abgr8_to_s8d24_pipeline; vk::Pipeline convert_abgr8_to_d32f_pipeline; vk::Pipeline convert_d32f_to_abgr8_pipeline; vk::Pipeline convert_d24s8_to_abgr8_pipeline; diff --git a/src/yuzu_video_core/renderer_vulkan/fixed_pipeline_state.h b/src/yuzu_video_core/renderer_vulkan/fixed_pipeline_state.h index da293253..29f8c8d2 100644 --- a/src/yuzu_video_core/renderer_vulkan/fixed_pipeline_state.h +++ b/src/yuzu_video_core/renderer_vulkan/fixed_pipeline_state.h @@ -238,6 +238,7 @@ struct FixedPipelineState { VideoCommon::TransformFeedbackState xfb_state; + void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features); size_t Hash() const noexcept; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.cpp index 442659cd..18554f41 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.cpp @@ -43,6 +43,9 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel } std::copy_n(info.constant_buffer_used_sizes.begin(), uniform_buffer_sizes.size(), uniform_buffer_sizes.begin()); + const auto is_written = [](const auto& desc) { return desc.is_written; }; + writes_images = std::ranges::any_of(info.image_descriptors, is_written) || + std::ranges::any_of(info.image_buffer_descriptors, is_written); auto func{[this, &descriptor_pool, shader_notify, pipeline_statistics] { try { diff --git a/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.h b/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.h index f9941401..9b2c04bd 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_compute_pipeline.h @@ -54,11 +54,16 @@ public: return build_failed.load(std::memory_order::relaxed); } + [[nodiscard]] bool WritesImages() const noexcept { + return writes_images; + } + private: const Device& device; vk::PipelineCache& pipeline_cache; GuestDescriptorQueue& guest_descriptor_queue; Shader::Info info; + bool writes_images{}; u32 num_textures{}; size_t num_descriptor_entries{}; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp index 3e77d3ea..b6e63200 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.cpp @@ -45,6 +45,14 @@ using VideoCore::Surface::PixelFormatFromRenderTargetFormat; constexpr size_t NUM_STAGES = Maxwell::MaxShaderStage; constexpr size_t INLINE_IMAGE_ELEMENTS = 64; +constexpr std::array PIPELINE_STAGE_FLAGS{ + VK_PIPELINE_STAGE_VERTEX_SHADER_BIT, + VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT, + VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT, + VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, +}; + DescriptorLayoutBuilder MakeBuilder(const Device& device, std::span infos, u32 num_color_attachments) { static constexpr std::array stages{ @@ -371,6 +379,11 @@ GraphicsPipeline::GraphicsPipeline( continue; } stage_infos[stage] = *info; + const auto is_written = [](const auto& desc) { return desc.is_written; }; + if (std::ranges::any_of(info->image_descriptors, is_written) || + std::ranges::any_of(info->image_buffer_descriptors, is_written)) { + image_write_stages |= PIPELINE_STAGE_FLAGS[stage]; + } enabled_uniform_buffer_masks[stage] = info->constant_buffer_mask; std::ranges::copy(info->constant_buffer_used_sizes, uniform_buffer_sizes[stage].begin()); num_textures += Shader::NumDescriptors(info->texture_descriptors); diff --git a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h index d757404f..77b97a3b 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_graphics_pipeline.h @@ -164,6 +164,10 @@ public: return build_complete.load(std::memory_order_acquire) && !IsBuilt(); } + [[nodiscard]] VkPipelineStageFlags ImageWriteStages() const noexcept { + return image_write_stages; + } + template static auto MakeConfigureSpecFunc() { return [](GraphicsPipeline* pl, bool is_indexed) { pl->ConfigureImpl(is_indexed); }; @@ -220,6 +224,7 @@ private: std::array stage_infos; std::array vertex_attribute_components{}; + VkPipelineStageFlags image_write_stages{}; std::array enabled_uniform_buffer_masks{}; VideoCommon::UniformBufferSizes uniform_buffer_sizes{}; u32 num_textures{}; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp index b371d234..ea5073c4 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -56,7 +56,7 @@ using VideoCommon::FileEnvironment; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 15; +constexpr u32 CACHE_VERSION = 16; constexpr std::array VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'}; template diff --git a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp index 5a5eb29c..be1a018b 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.cpp @@ -218,6 +218,7 @@ void RasterizerVulkan::PrepareDraw(bool is_indexed, Func&& draw_func) { pipeline->SetEngine(maxwell3d, gpu_memory); pipeline->SetVtgInstanceCount(std::exchange(vtg_instance_count, 0)); pipeline->Configure(is_indexed); + image_write_stages |= pipeline->ImageWriteStages(); UpdateDynamicStates(); @@ -574,6 +575,9 @@ void RasterizerVulkan::DispatchCompute() { } std::scoped_lock lock{texture_cache.mutex, buffer_cache.mutex}; pipeline->Configure(*kepler_compute, *gpu_memory, scheduler, buffer_cache, texture_cache); + if (pipeline->WritesImages()) { + image_write_stages |= VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + } const auto& qmd{kepler_compute->launch_description}; auto indirect_address = kepler_compute->GetIndirectComputeAddress(); @@ -845,8 +849,29 @@ void RasterizerVulkan::WaitForIdle() { } void RasterizerVulkan::FragmentBarrier() { - // We already put barriers when a render pass finishes + // Ending the render pass already makes color/depth attachment writes visible. scheduler.RequestOutsideRenderPassOperationContext(); + + if (image_write_stages == 0) { + return; + } + + // Naming the tessellation and geometry stages needs their device features, so use the + // generic graphics stage for the readers. + static constexpr VkPipelineStageFlags SHADER_STAGES = + VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + static constexpr VkMemoryBarrier IMAGE_WRITE_BARRIER{ + .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER, + .pNext = nullptr, + .srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, + }; + + const VkPipelineStageFlags src_stages = image_write_stages; + image_write_stages = 0; + scheduler.Record([src_stages](vk::CommandBuffer cmdbuf) { + cmdbuf.PipelineBarrier(src_stages, SHADER_STAGES, 0, IMAGE_WRITE_BARRIER); + }); } void RasterizerVulkan::TiledCacheBarrier() { @@ -854,6 +879,11 @@ void RasterizerVulkan::TiledCacheBarrier() { if (device.GetDriverID() == VK_DRIVER_ID_MOLTENVK) { scheduler.RequestOutsideRenderPassOperationContext(); } + // As in Ryubing, apply the texture barrier when shaders wrote to images. Ending the render + // pass on every call would split render passes for no reason on other drivers. + if (image_write_stages != 0) { + FragmentBarrier(); + } } void RasterizerVulkan::FlushCommands() { diff --git a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h index e52ef0c7..3309eb86 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_rasterizer.h @@ -217,6 +217,7 @@ private: std::array image_view_ids; boost::container::static_vector sampler_handles; + VkPipelineStageFlags image_write_stages{}; u32 draw_counter = 0; }; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.cpp b/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.cpp index 9cc1aab3..a2f8129d 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include #include @@ -158,6 +159,18 @@ constexpr VkBorderColor ConvertBorderColor(const std::array& color) { }; } +[[nodiscard]] VkImageCreateInfo MakeMsaaScratchImageCreateInfo(const Device& device, + const ImageInfo& info, + VkImageUsageFlags usage) { + ImageInfo scratch_info = info; + scratch_info.num_samples = 1; + VkImageCreateInfo image_ci = MakeImageCreateInfo(device, scratch_info); + image_ci.format = + MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, info.format).format; + image_ci.usage = usage; + return image_ci; +} + [[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator, const ImageInfo& info, std::span view_formats) { if (info.type == ImageType::Buffer) { @@ -598,6 +611,41 @@ void CopyBufferToImage(vk::CommandBuffer cmdbuf, VkBuffer src_buffer, VkImage im }; } +/// Returns true when both regions have the same size and neither is flipped or empty. +[[nodiscard]] bool CanDirectCopy(const Region2D& dst_region, const Region2D& src_region) { + const s32 width = src_region.end.x - src_region.start.x; + const s32 height = src_region.end.y - src_region.start.y; + return width > 0 && height > 0 && dst_region.end.x - dst_region.start.x == width && + dst_region.end.y - dst_region.start.y == height; +} + +[[nodiscard]] VkImageCopy MakeImageCopy(const Region2D& dst_region, const Region2D& src_region, + const VkImageSubresourceLayers& dst_layers, + const VkImageSubresourceLayers& src_layers) { + return VkImageCopy{ + .srcSubresource = src_layers, + .srcOffset = + { + .x = src_region.start.x, + .y = src_region.start.y, + .z = 0, + }, + .dstSubresource = dst_layers, + .dstOffset = + { + .x = dst_region.start.x, + .y = dst_region.start.y, + .z = 0, + }, + .extent = + { + .width = static_cast(src_region.end.x - src_region.start.x), + .height = static_cast(src_region.end.y - src_region.start.y), + .depth = 1, + }, + }; +} + [[nodiscard]] VkImageResolve MakeImageResolve(const Region2D& dst_region, const Region2D& src_region, const VkImageSubresourceLayers& dst_layers, @@ -1082,7 +1130,47 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst return; } ASSERT(src.format == dst.format); - if (aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { + const bool is_resolve = is_src_msaa && !is_dst_msaa; + const bool is_msaa_to_msaa = is_src_msaa && is_dst_msaa; + const bool same_extent = CanDirectCopy(dst_region, src_region); + // vkCmdBlitImage does not accept multisampled images. A copy keeps every sample. + const bool direct_msaa_copy = + is_msaa_to_msaa && src.Samples() == dst.Samples() && same_extent; + if (is_src_msaa || is_dst_msaa) { + if (operation != Fermi2D::Operation::SrcCopy) { + UNIMPLEMENTED_MSG("MSAA blit with operation {}", static_cast(operation)); + return; + } + if (!is_src_msaa) { + UNIMPLEMENTED_MSG("Blit from a single-sample image to an MSAA image"); + return; + } + if ((aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) == 0 && !direct_msaa_copy) { + if (is_resolve && same_extent && + dst_region.start.x == src_region.start.x && dst_region.start.y == src_region.start.y && + dst_region.end.x == src_region.end.x && dst_region.end.y == src_region.end.y && + blit_image_helper.TryResolveDepthStencilMSAA(dst, src, dst_region)) + { + return; + } + // Scaling and flips still use the shader fallback. + + if ((aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) == 0) { + UNIMPLEMENTED_MSG("Stencil-only MSAA blit"); + return; + } + blit_image_helper.BlitDepthStencilMSAA(dst_framebuffer, src, dst_region, src_region); + return; + } + if (aspect_mask == VK_IMAGE_ASPECT_COLOR_BIT && !direct_msaa_copy && + (is_msaa_to_msaa || !same_extent)) { + // vkCmdResolveImage cannot scale or flip. + blit_image_helper.BlitColorMSAA(dst_framebuffer, src, dst_region, src_region, filter); + return; + } + } + if (!direct_msaa_copy && + aspect_mask == (VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT)) { const auto format = src.format; const auto can_blit_depth_stencil = [this, format] { switch (format) { @@ -1096,23 +1184,20 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst } }(); if (!can_blit_depth_stencil) { - UNIMPLEMENTED_IF(is_src_msaa || is_dst_msaa); blit_image_helper.BlitDepthStencil(dst_framebuffer, src.DepthView(), src.StencilView(), dst_region, src_region, filter, operation); return; } } - ASSERT(!(is_dst_msaa && !is_src_msaa)); ASSERT(operation == Fermi2D::Operation::SrcCopy); const VkImage dst_image = dst.ImageHandle(); const VkImage src_image = src.ImageHandle(); const VkImageSubresourceLayers dst_layers = MakeSubresourceLayers(&dst); const VkImageSubresourceLayers src_layers = MakeSubresourceLayers(&src); - const bool is_resolve = is_src_msaa && !is_dst_msaa; scheduler.RequestOutsideRenderPassOperationContext(); scheduler.Record([filter, dst_region, src_region, dst_image, src_image, dst_layers, src_layers, - aspect_mask, is_resolve](vk::CommandBuffer cmdbuf) { + aspect_mask, is_resolve, direct_msaa_copy](vk::CommandBuffer cmdbuf) { const std::array read_barriers{ VkImageMemoryBarrier{ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, @@ -1182,6 +1267,10 @@ void TextureCacheRuntime::BlitImage(Framebuffer* dst_framebuffer, ImageView& dst cmdbuf.ResolveImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, MakeImageResolve(dst_region, src_region, dst_layers, src_layers)); + } else if (direct_msaa_copy) { + cmdbuf.CopyImage(src_image, VK_IMAGE_LAYOUT_GENERAL, dst_image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + MakeImageCopy(dst_region, src_region, dst_layers, src_layers)); } else { const bool is_linear = filter == Fermi2D::Filter::Bilinear; const VkFilter vk_filter = is_linear ? VK_FILTER_LINEAR : VK_FILTER_NEAREST; @@ -1238,10 +1327,12 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im } break; case PixelFormat::S8_UINT_D24_UNORM: - if (src_view.format == PixelFormat::A8B8G8R8_UNORM || - src_view.format == PixelFormat::B8G8R8A8_UNORM) { + if (src_view.format == PixelFormat::A8B8G8R8_UNORM) { return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view); } + if (src_view.format == PixelFormat::B8G8R8A8_UNORM) { + return blit_image_helper.ConvertABGR8ToS8D24(dst, src_view); + } break; case PixelFormat::D32_FLOAT: if (src_view.format == PixelFormat::A8B8G8R8_UNORM || @@ -1254,6 +1345,14 @@ void TextureCacheRuntime::ConvertImage(Framebuffer* dst, ImageView& dst_view, Im return blit_image_helper.ConvertR32ToD32(dst, src_view); } break; + case PixelFormat::D24_UNORM_S8_UINT: + if (src_view.format == PixelFormat::A8B8G8R8_UNORM) { + return blit_image_helper.ConvertABGR8ToS8D24(dst, src_view); + } + if (src_view.format == PixelFormat::B8G8R8A8_UNORM) { + return blit_image_helper.ConvertABGR8ToD24S8(dst, src_view); + } + break; default: break; } @@ -1370,7 +1469,78 @@ bool TextureCacheRuntime::CanReportMemoryUsage() const { return device.CanReportMemoryUsage(); } -void TextureCacheRuntime::TickFrame() {} +bool TextureCacheRuntime::CanUploadMSAA(const Image& image) const noexcept { + // Without shader stencil export only the depth of a depth-stencil image is uploaded. + // Stencil-only images are not handled. + const VkImageAspectFlags aspect = image.AspectMask(); + return (aspect & (VK_IMAGE_ASPECT_COLOR_BIT | VK_IMAGE_ASPECT_DEPTH_BIT)) != 0; +} + +VkImage TextureCacheRuntime::AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci) { + const MsaaScratchKey key{ + .format = image_ci.format, + .type = image_ci.imageType, + .width = image_ci.extent.width, + .height = image_ci.extent.height, + .depth = image_ci.extent.depth, + .levels = image_ci.mipLevels, + .layers = image_ci.arrayLayers, + .usage = image_ci.usage, + .flags = image_ci.flags, + }; + for (size_t index = 0; index < msaa_scratch_images.size(); ++index) + { + auto & scratch = msaa_scratch_images[index]; + if (scratch.key != key || !scheduler.IsFree(scratch.tick)) { + continue; + } + scratch.tick = (std::numeric_limits::max)(); + scratch.unused_frames = 0; + msaa_scratch_last = index; + return *scratch.image; + } + MsaaScratchImage scratch{ + .key = key, + .image = memory_allocator.CreateImage(image_ci), + .tick = (std::numeric_limits::max)(), + .unused_frames = 0, + }; + const VkImage handle = *scratch.image; + msaa_scratch_last = msaa_scratch_images.size(); + msaa_scratch_images.push_back(std::move(scratch)); + return handle; +} + +void TextureCacheRuntime::ReleaseMsaaScratchImage(VkImage image) { + if (msaa_scratch_last < msaa_scratch_images.size() && + *msaa_scratch_images[msaa_scratch_last].image == image) + { + msaa_scratch_images[msaa_scratch_last].tick = scheduler.CurrentTick(); + return; + } + + for (MsaaScratchImage& scratch : msaa_scratch_images) { + if (*scratch.image == image) { + scratch.tick = scheduler.CurrentTick(); + return; + } + } + UNREACHABLE(); +} + +void TextureCacheRuntime::TickFrame() { + msaa_scratch_last = static_cast(-1); + // Retire completed views/framebuffers before their scratch images are evicted. + blit_image_helper.TickFrame(); + static constexpr u32 MAX_UNUSED_SCRATCH_FRAMES = 60; + std::erase_if(msaa_scratch_images, [this](MsaaScratchImage& scratch) { + if (!scheduler.IsFree(scratch.tick)) { + scratch.unused_frames = 0; + return false; + } + return ++scratch.unused_frames > MAX_UNUSED_SCRATCH_FRAMES; + }); +} Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, VAddr cpu_addr_) @@ -1426,6 +1596,92 @@ void Image::UploadMemory(VkBuffer buffer, VkDeviceSize offset, if (is_rescaled) { ScaleDown(true); } + + if (info.num_samples > 1) { + const bool is_color = (aspect_mask & VK_IMAGE_ASPECT_COLOR_BIT) != 0; + const bool is_depth = (aspect_mask & VK_IMAGE_ASPECT_DEPTH_BIT) != 0; + const bool is_stencil = (aspect_mask & VK_IMAGE_ASPECT_STENCIL_BIT) != 0; + const bool copy_stencil = is_stencil && runtime->device.IsExtShaderStencilExportSupported(); + ASSERT(is_color || is_depth); + + VkImageCreateInfo scratch_ci = MakeMsaaScratchImageCreateInfo( + runtime->device, info, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT); + VkImageAspectFlags scratch_aspect = aspect_mask; + s32 packed_depth_stencil_format = -1; + if (is_depth && is_stencil) { + scratch_aspect = VK_IMAGE_ASPECT_COLOR_BIT; + switch (info.format) { + case PixelFormat::D24_UNORM_S8_UINT: + scratch_ci.format = VK_FORMAT_R32_UINT; + packed_depth_stencil_format = 0; + break; + case PixelFormat::S8_UINT_D24_UNORM: + scratch_ci.format = VK_FORMAT_R32_UINT; + packed_depth_stencil_format = 1; + break; + case PixelFormat::D32_FLOAT_S8_UINT: + scratch_ci.format = VK_FORMAT_R32G32_UINT; + packed_depth_stencil_format = 2; + break; + default: + UNREACHABLE(); + } + } + + const VkImage scratch_image = runtime->AcquireMsaaScratchImage(scratch_ci); + scheduler->RequestOutsideRenderPassOperationContext(); + auto vk_copies = TransformBufferImageCopies(copies, offset, scratch_aspect); + scheduler->Record([src_buffer = buffer, scratch_image, scratch_aspect, + vk_copies](vk::CommandBuffer cmdbuf) { + CopyBufferToImage(cmdbuf, src_buffer, scratch_image, scratch_aspect, false, vk_copies); + }); + + // The render-pass based copy expects the destination in GENERAL. This also marks the + // image initialized, but only after the runtime has committed to a real MSAA upload. + runtime->TransitionImageLayout(*this); + + const auto [samples_x, samples_y] = VideoCommon::SamplesLog2(info.num_samples); + boost::container::small_vector image_copies; + image_copies.reserve(copies.size()); + for (const auto& copy : copies) { + image_copies.push_back(VideoCommon::ImageCopy{ + .src_subresource = copy.image_subresource, + .dst_subresource = copy.image_subresource, + .src_offset = copy.image_offset, + .dst_offset = { + copy.image_offset.x >> samples_x, + copy.image_offset.y >> samples_y, + copy.image_offset.z, + }, + .extent = { + copy.image_extent.width >> samples_x, + copy.image_extent.height >> samples_y, + copy.image_extent.depth, + }, + }); + } + + if (packed_depth_stencil_format >= 0) { + runtime->blit_image_helper.CopyPackedDepthStencilToMSAA( + runtime->render_pass_cache, Handle(), info.format, scratch_image, + scratch_ci.format, info.num_samples, image_copies, packed_depth_stencil_format, + copy_stencil); + } else if (is_depth) { + runtime->blit_image_helper.CopyToMSAADepth(runtime->render_pass_cache, Handle(), + info.format, scratch_image, info.num_samples, + image_copies, false); + } else { + runtime->blit_image_helper.CopyToMSAA(runtime->render_pass_cache, Handle(), info.format, + scratch_image, info.num_samples, image_copies); + } + runtime->ReleaseMsaaScratchImage(scratch_image); + + if (is_rescaled) { + ScaleUp(); + } + return; + } + scheduler->RequestOutsideRenderPassOperationContext(); auto vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask); const VkBuffer src_buffer = buffer; diff --git a/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.h b/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.h index 49c2e6fc..a4130e47 100644 --- a/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/yuzu_video_core/renderer_vulkan/vk_texture_cache.h @@ -3,7 +3,9 @@ #pragma once +#include #include +#include #include "yuzu_video_core/texture_cache/texture_cache_base.h" @@ -83,10 +85,11 @@ public: return false; } - bool CanUploadMSAA() const noexcept { - // TODO: Implement buffer to MSAA uploads - return false; - } + bool CanUploadMSAA(const Image& image) const noexcept; + + [[nodiscard]] VkImage AcquireMsaaScratchImage(const VkImageCreateInfo& image_ci); + + void ReleaseMsaaScratchImage(VkImage image); void AccelerateImageUpload(Image&, const StagingBufferRef&, std::span); @@ -124,6 +127,30 @@ public: const Settings::ResolutionScalingInfo& resolution; std::array, VideoCore::Surface::MaxPixelFormat> view_formats; + struct MsaaScratchKey { + VkFormat format{}; + VkImageType type{}; + u32 width{}; + u32 height{}; + u32 depth{}; + u32 levels{}; + u32 layers{}; + VkImageUsageFlags usage{}; + VkImageCreateFlags flags{}; + + auto operator<=>(const MsaaScratchKey&) const = default; + }; + + struct MsaaScratchImage { + MsaaScratchKey key; + vk::Image image; + u64 tick{}; + u32 unused_frames{}; + }; + + std::vector msaa_scratch_images; + size_t msaa_scratch_last = static_cast(-1); + static constexpr size_t indexing_slots = 8 * sizeof(size_t); std::array buffers{}; }; diff --git a/src/yuzu_video_core/texture_cache/texture_cache.h b/src/yuzu_video_core/texture_cache/texture_cache.h index 3ea23e01..9a8dd8bc 100644 --- a/src/yuzu_video_core/texture_cache/texture_cache.h +++ b/src/yuzu_video_core/texture_cache/texture_cache.h @@ -1128,8 +1128,9 @@ void TextureCache

::RefreshContents(Image& image, ImageId image_id) { image.flags &= ~ImageFlagBits::CpuModified; TrackImage(image, image_id); - if (image.info.num_samples > 1 && !runtime.CanUploadMSAA()) { - LOG_WARNING(HW_GPU, "MSAA image uploads are not implemented"); + if (image.info.num_samples > 1 && !runtime.CanUploadMSAA(image)) { + LOG_WARNING(HW_GPU, "MSAA image upload is not supported for format {}", + static_cast(image.info.format)); runtime.TransitionImageLayout(image); return; } diff --git a/src/yuzu_video_core/vulkan_common/vulkan_device.cpp b/src/yuzu_video_core/vulkan_common/vulkan_device.cpp index 69f2ec60..8ca3c00c 100644 --- a/src/yuzu_video_core/vulkan_common/vulkan_device.cpp +++ b/src/yuzu_video_core/vulkan_common/vulkan_device.cpp @@ -1194,6 +1194,11 @@ bool Device::GetSuitability(bool requires_swapchain) SetNext(next, properties.transform_feedback); } + if (instance_version >= VK_API_VERSION_1_2) + { + properties.depth_stencil_resolve.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_STENCIL_RESOLVE_PROPERTIES; + SetNext(next, properties.depth_stencil_resolve); + } // Perform the property fetch. physical.GetProperties2(properties2); properties.properties = properties2.properties; diff --git a/src/yuzu_video_core/vulkan_common/vulkan_device.h b/src/yuzu_video_core/vulkan_common/vulkan_device.h index 1b36eef9..4cb00cb3 100644 --- a/src/yuzu_video_core/vulkan_common/vulkan_device.h +++ b/src/yuzu_video_core/vulkan_common/vulkan_device.h @@ -266,6 +266,15 @@ public: return properties.properties.apiVersion; } + bool SupportsDepthStencilResolve(VkImageAspectFlags aspect) const + { + const auto & caps = properties.depth_stencil_resolve; + return instance_version >= VK_API_VERSION_1_2 && dld.vkCreateRenderPass2 && + ((aspect & VK_IMAGE_ASPECT_DEPTH_BIT) == 0 || + (caps.supportedDepthResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)) && + ((aspect & VK_IMAGE_ASPECT_STENCIL_BIT) == 0 || + (caps.supportedStencilResolveModes & VK_RESOLVE_MODE_SAMPLE_ZERO_BIT)); + } /// Returns the current driver version provided in Vulkan-formatted version numbers. u32 GetDriverVersion() const { return properties.properties.driverVersion; @@ -857,6 +866,7 @@ private: }; struct Properties { + VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{}; VkPhysicalDeviceDriverProperties driver{}; VkPhysicalDeviceSubgroupProperties subgroup_properties{}; VkPhysicalDeviceFloatControlsProperties float_controls{}; diff --git a/src/yuzu_video_core/vulkan_common/vulkan_wrapper.cpp b/src/yuzu_video_core/vulkan_common/vulkan_wrapper.cpp index 4e5f931e..2573f01b 100644 --- a/src/yuzu_video_core/vulkan_common/vulkan_wrapper.cpp +++ b/src/yuzu_video_core/vulkan_common/vulkan_wrapper.cpp @@ -174,6 +174,7 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept { X(vkCreatePipelineLayout); X(vkCreateQueryPool); X(vkCreateRenderPass); + X(vkCreateRenderPass2); X(vkCreateSampler); X(vkCreateSemaphore); X(vkCreateShaderModule); @@ -701,6 +702,13 @@ RenderPass Device::CreateRenderPass(const VkRenderPassCreateInfo& ci) const { return RenderPass(object, handle, *dld); } +RenderPass Device::CreateRenderPass2(const VkRenderPassCreateInfo2 & ci) const +{ + VkRenderPass object; + Check(dld->vkCreateRenderPass2(handle, &ci, nullptr, &object)); + return RenderPass(object, handle, *dld); +} + DescriptorSetLayout Device::CreateDescriptorSetLayout( const VkDescriptorSetLayoutCreateInfo& ci) const { VkDescriptorSetLayout object; diff --git a/src/yuzu_video_core/vulkan_common/vulkan_wrapper.h b/src/yuzu_video_core/vulkan_common/vulkan_wrapper.h index f0cabe81..10b13650 100644 --- a/src/yuzu_video_core/vulkan_common/vulkan_wrapper.h +++ b/src/yuzu_video_core/vulkan_common/vulkan_wrapper.h @@ -272,6 +272,7 @@ struct DeviceDispatch : InstanceDispatch { PFN_vkCreatePipelineLayout vkCreatePipelineLayout{}; PFN_vkCreateQueryPool vkCreateQueryPool{}; PFN_vkCreateRenderPass vkCreateRenderPass{}; + PFN_vkCreateRenderPass2 vkCreateRenderPass2{}; PFN_vkCreateSampler vkCreateSampler{}; PFN_vkCreateSemaphore vkCreateSemaphore{}; PFN_vkCreateShaderModule vkCreateShaderModule{}; @@ -968,6 +969,7 @@ public: DescriptorPool CreateDescriptorPool(const VkDescriptorPoolCreateInfo& ci) const; RenderPass CreateRenderPass(const VkRenderPassCreateInfo& ci) const; + RenderPass CreateRenderPass2(const VkRenderPassCreateInfo2 & ci) const; DescriptorSetLayout CreateDescriptorSetLayout(const VkDescriptorSetLayoutCreateInfo& ci) const;