MSL: Don't output depth and stencil values with explicit early fragment tests.

Fragment shaders that require explicit early fragment tests are incompatible
with specifying depth and stencil values within the shader. If explicit early
fragment tests is specified, remove the depth and stencil outputs from the
output structure, and replace them with dummy local variables.

Add CompilerMSL:uses_explicit_early_fragment_test() function to consolidate
testing for whether early fragment tests are required.

Add two unit tests for depth-out with, and without, early fragment tests.
This commit is contained in:
Bill Hollings 2021-11-12 14:17:00 -05:00
parent 401296d3b8
commit 248e9ae9ed
8 changed files with 115 additions and 7 deletions

View File

@ -0,0 +1,19 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 color_out [[color(0)]];
};
[[ early_fragment_tests ]] fragment main0_out main0()
{
float gl_FragDepth;
main0_out out = {};
out.color_out = float4(1.0, 0.0, 0.0, 1.0);
gl_FragDepth = 0.699999988079071044921875;
return out;
}

View File

@ -0,0 +1,19 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 color_out [[color(0)]];
float gl_FragDepth [[depth(less)]];
};
fragment main0_out main0()
{
main0_out out = {};
out.color_out = float4(1.0, 0.0, 0.0, 1.0);
out.gl_FragDepth = 0.699999988079071044921875;
return out;
}

View File

@ -0,0 +1,19 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 color_out [[color(0)]];
};
[[ early_fragment_tests ]] fragment main0_out main0()
{
float gl_FragDepth;
main0_out out = {};
out.color_out = float4(1.0, 0.0, 0.0, 1.0);
gl_FragDepth = 0.699999988079071044921875;
return out;
}

View File

@ -0,0 +1,19 @@
#include <metal_stdlib>
#include <simd/simd.h>
using namespace metal;
struct main0_out
{
float4 color_out [[color(0)]];
float gl_FragDepth [[depth(less)]];
};
fragment main0_out main0()
{
main0_out out = {};
out.color_out = float4(1.0, 0.0, 0.0, 1.0);
out.gl_FragDepth = 0.699999988079071044921875;
return out;
}

View File

@ -0,0 +1,11 @@
#version 430
layout(depth_less) out float gl_FragDepth;
layout(early_fragment_tests) in;
layout(location = 0) out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
gl_FragDepth = 0.699999988079071044921875;
}

View File

@ -0,0 +1,10 @@
#version 430
layout(depth_less) out float gl_FragDepth;
layout(location = 0) out vec4 color_out;
void main()
{
color_out = vec4(1.0, 0.0, 0.0, 1.0);
gl_FragDepth = 0.699999988079071044921875;
}

View File

@ -3364,16 +3364,22 @@ uint32_t CompilerMSL::add_interface_block(StorageClass storage, bool patch)
// It's not enough to simply avoid marking fragment outputs if the pipeline won't
// accept them. We can't put them in the struct at all, or otherwise the compiler
// complains that the outputs weren't explicitly marked.
// Frag depth and stencil outputs are incompatible with explicit early fragment tests.
// In GLSL, depth and stencil outputs are just ignored when explicit early fragment tests are required.
// In Metal, it's a compilation error, so we need to exclude them from the output struct.
if (get_execution_model() == ExecutionModelFragment && storage == StorageClassOutput && !patch &&
((is_builtin && ((bi_type == BuiltInFragDepth && !msl_options.enable_frag_depth_builtin) ||
(bi_type == BuiltInFragStencilRefEXT && !msl_options.enable_frag_stencil_ref_builtin))) ||
((is_builtin && ((bi_type == BuiltInFragDepth && (!msl_options.enable_frag_depth_builtin || uses_explicit_early_fragment_test())) ||
(bi_type == BuiltInFragStencilRefEXT && (!msl_options.enable_frag_stencil_ref_builtin || uses_explicit_early_fragment_test())))) ||
(!is_builtin && !(msl_options.enable_frag_output_mask & (1 << location)))))
{
hidden = true;
disabled_frag_outputs.push_back(var_id);
// If a builtin, force it to have the proper name.
// If a builtin, force it to have the proper name, and mark it as not part of the output struct.
if (is_builtin)
{
set_name(var_id, builtin_to_glsl(bi_type, StorageClassFunction));
mask_stage_output_by_builtin(bi_type);
}
}
// Barycentric inputs must be emitted in stage-in, because they can have interpolation arguments.
@ -11156,10 +11162,7 @@ string CompilerMSL::func_type_decl(SPIRType &type)
execution.output_vertices, ") ]] vertex");
break;
case ExecutionModelFragment:
entry_type = execution.flags.get(ExecutionModeEarlyFragmentTests) ||
execution.flags.get(ExecutionModePostDepthCoverage) ?
"[[ early_fragment_tests ]] fragment" :
"fragment";
entry_type = uses_explicit_early_fragment_test() ? "[[ early_fragment_tests ]] fragment" : "fragment";
break;
case ExecutionModelTessellationControl:
if (!msl_options.supports_msl_version(1, 2))
@ -11179,6 +11182,12 @@ string CompilerMSL::func_type_decl(SPIRType &type)
return entry_type + " " + return_type;
}
bool CompilerMSL::uses_explicit_early_fragment_test()
{
auto &ep_flags = get_entry_point().flags;
return ep_flags.get(ExecutionModeEarlyFragmentTests) || ep_flags.get(ExecutionModePostDepthCoverage);
}
// In MSL, address space qualifiers are required for all pointer or reference variables
string CompilerMSL::get_argument_address_space(const SPIRVariable &argument)
{

View File

@ -932,6 +932,8 @@ protected:
void build_implicit_builtins();
uint32_t build_constant_uint_array_pointer();
void emit_entry_point_declarations() override;
bool uses_explicit_early_fragment_test();
uint32_t builtin_frag_coord_id = 0;
uint32_t builtin_sample_id_id = 0;
uint32_t builtin_sample_mask_id = 0;