Improve multisample rendering paths (#395)

Handle multisample image conversions and resolves consistently. Reuse temporary resources and reduce repeated pipeline lookups.
This commit is contained in:
Bruno 2026-10-10 12:13:13 +02:00 committed by GitHub
parent 73c2d3500e
commit 3178a7f5ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
42 changed files with 2183 additions and 39 deletions

View File

@ -57,6 +57,24 @@
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_d24s8_depth.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_s8d24_depth.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_s8d24.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_d32f.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
@ -99,6 +117,78 @@
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_comp.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_comp_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa_sint.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa_uint.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_depth_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_depth_stencil_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_sint.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_uint.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_depth.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_depth_stencil.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_packed_depth_to_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_packed_depth_stencil_to_msaa.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>
<Command>call "$(SolutionDir)src\script\generate_shader_spc_header.cmd" "$(OutDir)" "%(FullPath)"</Command>
<Outputs>$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag.h;$(SolutionDir)src\yuzu_video_core\host_shaders\%(Filename)_frag_spv.h</Outputs>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_s8d24_to_abgr8.frag">
<FileType>Document</FileType>
<Message>Compiling %(Filename)%(Extension)</Message>

View File

@ -16,6 +16,15 @@
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_d24s8.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_d24s8_depth.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_s8d24_depth.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_s8d24.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_abgr8_to_d32f.frag">
<Filter>frag</Filter>
</CustomBuild>
@ -37,6 +46,42 @@
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa.comp">
<Filter>comp</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa_sint.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_color_msaa_uint.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_depth_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\blit_depth_stencil_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_sint.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_uint.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_depth.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_non_msaa_to_msaa_depth_stencil.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_packed_depth_to_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_packed_depth_stencil_to_msaa.frag">
<Filter>frag</Filter>
</CustomBuild>
<CustomBuild Include="..\yuzu_video_core\host_shaders\convert_s8d24_to_abgr8.frag">
<Filter>frag</Filter>
</CustomBuild>

View File

@ -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);

View File

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

View File

@ -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);

View File

@ -114,7 +114,7 @@ struct RuntimeInfo {
// Float remains the default for other backends.
std::array<AttributeType, 8> 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{};
};

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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;
}

View File

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

View File

@ -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;
}

View File

@ -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)))) {

View File

@ -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)))) {

View File

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

View File

@ -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;
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

@ -93,7 +93,7 @@ public:
return true;
}
bool CanUploadMSAA() const noexcept {
bool CanUploadMSAA(const Image&) const noexcept {
return true;
}

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,9 @@
#pragma once
#include <deque>
#include <span>
#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<const VideoCommon::ImageCopy> copies);
void CopyToMSAADepth(RenderPassCache& render_pass_cache, VkImage dst_image,
VideoCore::Surface::PixelFormat format, VkImage src_image, u32 num_samples,
std::span<const VideoCommon::ImageCopy> 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<const VideoCommon::ImageCopy> 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<const VideoCommon::ImageCopy> 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<vk::Pipeline> clear_color_pipelines;
std::vector<BlitDepthStencilPipelineKey> clear_stencil_keys;
std::vector<vk::Pipeline> clear_stencil_pipelines;
std::vector<MSAACopyPipelineKey> msaa_copy_keys;
std::vector<vk::Pipeline> msaa_copy_pipelines;
size_t msaa_copy_last{};
std::vector<MSAACopyPipelineKey> msaa_copy_depth_keys;
std::vector<vk::Pipeline> msaa_copy_depth_pipelines;
size_t msaa_copy_depth_last{};
std::vector<MSAACopyPipelineKey> msaa_copy_depth_stencil_keys;
std::vector<vk::Pipeline> msaa_copy_depth_stencil_pipelines;
size_t msaa_copy_depth_stencil_last{};
std::vector<BlitMSAAPipelineKey> blit_msaa_color_keys;
std::vector<vk::Pipeline> blit_msaa_color_pipelines;
size_t blit_msaa_color_last{};
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_keys;
std::vector<vk::Pipeline> blit_msaa_depth_pipelines;
size_t blit_msaa_depth_last{};
std::vector<BlitMSAAPipelineKey> blit_msaa_depth_stencil_keys;
std::vector<vk::Pipeline> blit_msaa_depth_stencil_pipelines;
size_t blit_msaa_depth_stencil_last{};
std::vector<std::pair<u64, vk::RenderPass>> native_resolve_passes;
struct MSAACopyResources {
u64 tick;
vk::ImageView src_view;
vk::ImageView dst_view;
vk::Framebuffer framebuffer;
};
std::deque<MSAACopyResources> 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;

View File

@ -238,6 +238,7 @@ struct FixedPipelineState {
VideoCommon::TransformFeedbackState xfb_state;
void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features);
size_t Hash() const noexcept;

View File

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

View File

@ -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{};

View File

@ -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<VkPipelineStageFlagBits, NUM_STAGES> 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<const Shader::Info> 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);

View File

@ -164,6 +164,10 @@ public:
return build_complete.load(std::memory_order_acquire) && !IsBuilt();
}
[[nodiscard]] VkPipelineStageFlags ImageWriteStages() const noexcept {
return image_write_stages;
}
template <typename Spec>
static auto MakeConfigureSpecFunc() {
return [](GraphicsPipeline* pl, bool is_indexed) { pl->ConfigureImpl<Spec>(is_indexed); };
@ -220,6 +224,7 @@ private:
std::array<Shader::Info, NUM_STAGES> stage_infos;
std::array<u8, Maxwell::NumVertexAttributes> vertex_attribute_components{};
VkPipelineStageFlags image_write_stages{};
std::array<u32, 5> enabled_uniform_buffer_masks{};
VideoCommon::UniformBufferSizes uniform_buffer_sizes{};
u32 num_textures{};

View File

@ -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<char, 8> VULKAN_CACHE_MAGIC_NUMBER{'y', 'u', 'z', 'u', 'v', 'k', 'c', 'h'};
template <typename Container>

View File

@ -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() {

View File

@ -217,6 +217,7 @@ private:
std::array<VideoCommon::ImageViewId, MAX_IMAGE_VIEWS> image_view_ids;
boost::container::static_vector<VkSampler, MAX_TEXTURES> sampler_handles;
VkPipelineStageFlags image_write_stages{};
u32 draw_counter = 0;
};

View File

@ -3,6 +3,7 @@
#include <algorithm>
#include <array>
#include <limits>
#include <memory>
#include <span>
#include <vector>
@ -158,6 +159,18 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& 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<const VkFormat> 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<u32>(src_region.end.x - src_region.start.x),
.height = static_cast<u32>(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<u32>(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<u64>::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<u64>::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<size_t>(-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<VideoCommon::ImageCopy, 16> 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;

View File

@ -3,7 +3,9 @@
#pragma once
#include <compare>
#include <span>
#include <vector>
#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<const VideoCommon::SwizzleParameters>);
@ -124,6 +127,30 @@ public:
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, 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<MsaaScratchImage> msaa_scratch_images;
size_t msaa_scratch_last = static_cast<size_t>(-1);
static constexpr size_t indexing_slots = 8 * sizeof(size_t);
std::array<vk::Buffer, indexing_slots> buffers{};
};

View File

@ -1128,8 +1128,9 @@ void TextureCache<P>::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<u32>(image.info.format));
runtime.TransitionImageLayout(image);
return;
}

View File

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

View File

@ -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{};

View File

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

View File

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