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:
parent
73c2d3500e
commit
3178a7f5ca
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)};
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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)))) {
|
||||
|
|
|
|||
|
|
@ -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)))) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -238,6 +238,7 @@ struct FixedPipelineState {
|
|||
|
||||
VideoCommon::TransformFeedbackState xfb_state;
|
||||
|
||||
|
||||
void Refresh(Tegra::Engines::Maxwell3D& maxwell3d, DynamicFeatures& features);
|
||||
|
||||
size_t Hash() const noexcept;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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{};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue