diff --git a/CMakeLists.txt b/CMakeLists.txt index ccaedc90..ba678733 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -332,7 +332,7 @@ if (SPIRV_CROSS_STATIC) endif() set(spirv-cross-abi-major 0) -set(spirv-cross-abi-minor 54) +set(spirv-cross-abi-minor 55) set(spirv-cross-abi-patch 0) if (SPIRV_CROSS_SHARED) diff --git a/main.cpp b/main.cpp index 3605a54a..776fcd32 100644 --- a/main.cpp +++ b/main.cpp @@ -675,6 +675,7 @@ struct CLIArguments bool msl_force_sample_rate_shading = false; bool msl_manual_helper_invocation_updates = true; bool msl_check_discarded_frag_stores = false; + bool msl_sample_dref_lod_array_as_grad = false; const char *msl_combined_sampler_suffix = nullptr; bool glsl_emit_push_constant_as_ubo = false; bool glsl_emit_ubo_as_plain_uniforms = false; @@ -947,6 +948,10 @@ static void print_help_msl() "\t\tSome Metal devices have a bug where stores to resources from a fragment shader\n" "\t\tcontinue to execute, even when the fragment is discarded. These checks\n" "\t\tprevent these stores from executing.\n" + "\t[--msl-sample-dref-lod-array-as-grad]:\n\t\tUse a gradient instead of a level argument.\n" + "\t\tSome Metal devices have a bug where the level() argument to\n" + "\t\tdepth2d_array::sample_compare() in a fragment shader is biased by some\n" + "\t\tunknown amount. This prevents the bias from being added.\n" "\t[--msl-combined-sampler-suffix ]:\n\t\tUses a custom suffix for combined samplers.\n"); // clang-format on } @@ -1221,6 +1226,7 @@ static string compile_iteration(const CLIArguments &args, std::vector msl_opts.force_sample_rate_shading = args.msl_force_sample_rate_shading; msl_opts.manual_helper_invocation_updates = args.msl_manual_helper_invocation_updates; msl_opts.check_discarded_frag_stores = args.msl_check_discarded_frag_stores; + msl_opts.sample_dref_lod_array_as_grad = args.msl_sample_dref_lod_array_as_grad; msl_opts.ios_support_base_vertex_instance = true; msl_comp->set_msl_options(msl_opts); for (auto &v : args.msl_discrete_descriptor_sets) @@ -1774,6 +1780,8 @@ static int main_inner(int argc, char *argv[]) cbs.add("--msl-no-manual-helper-invocation-updates", [&args](CLIParser &) { args.msl_manual_helper_invocation_updates = false; }); cbs.add("--msl-check-discarded-frag-stores", [&args](CLIParser &) { args.msl_check_discarded_frag_stores = true; }); + cbs.add("--msl-sample-dref-lod-array-as-grad", + [&args](CLIParser &) { args.msl_sample_dref_lod_array_as_grad = true; }); cbs.add("--msl-combined-sampler-suffix", [&args](CLIParser &parser) { args.msl_combined_sampler_suffix = parser.next_string(); }); diff --git a/reference/opt/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag b/reference/opt/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag new file mode 100644 index 00000000..f6751a42 --- /dev/null +++ b/reference/opt/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag @@ -0,0 +1,23 @@ +#include +#include + +using namespace metal; + +struct main0_out +{ + float4 o_color [[color(0)]]; +}; + +struct main0_in +{ + float3 v_texCoord [[user(locn0)]]; + float v_lodBias [[user(locn1)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], depth2d_array u_sampler [[texture(0)]], sampler u_samplerSmplr [[sampler(0)]]) +{ + main0_out out = {}; + out.o_color = float4(u_sampler.sample_compare(u_samplerSmplr, float2(in.v_texCoord.x, 0.5), uint(rint(in.v_texCoord.y)), in.v_texCoord.z, gradient2d(exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0), exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0))), 0.0, 0.0, 1.0); + return out; +} + diff --git a/reference/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag b/reference/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag new file mode 100644 index 00000000..924ba7ec --- /dev/null +++ b/reference/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag @@ -0,0 +1,33 @@ +#include +#include + +using namespace metal; + +struct buf0 +{ + float4 u_scale; +}; + +struct buf1 +{ + float4 u_bias; +}; + +struct main0_out +{ + float4 o_color [[color(0)]]; +}; + +struct main0_in +{ + float3 v_texCoord [[user(locn0)]]; + float v_lodBias [[user(locn1)]]; +}; + +fragment main0_out main0(main0_in in [[stage_in]], depth2d_array u_sampler [[texture(0)]], sampler u_samplerSmplr [[sampler(0)]]) +{ + main0_out out = {}; + out.o_color = float4(u_sampler.sample_compare(u_samplerSmplr, float2(in.v_texCoord.x, 0.5), uint(rint(in.v_texCoord.y)), in.v_texCoord.z, gradient2d(exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0), exp2(in.v_lodBias - 0.5) / float2(u_sampler.get_width(), 1.0))), 0.0, 0.0, 1.0); + return out; +} + diff --git a/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag b/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag new file mode 100644 index 00000000..8f8c115b --- /dev/null +++ b/shaders-msl/frag/depth-array-texture-lod.lod-as-grad.1d-as-2d.msl23.frag @@ -0,0 +1,12 @@ +#version 450 core +layout(location = 0) out mediump vec4 o_color; +layout(location = 0) in highp vec3 v_texCoord; +layout(location = 1) in highp float v_lodBias; +layout(set = 0, binding = 0) uniform highp sampler1DArrayShadow u_sampler; +layout(set = 0, binding = 1) uniform buf0 { highp vec4 u_scale; }; +layout(set = 0, binding = 2) uniform buf1 { highp vec4 u_bias; }; + +void main() +{ + o_color = vec4(textureLod(u_sampler, v_texCoord, v_lodBias), 0.0, 0.0, 1.0); +} diff --git a/spirv_cross_c.cpp b/spirv_cross_c.cpp index 72614d78..c8ebbe4c 100644 --- a/spirv_cross_c.cpp +++ b/spirv_cross_c.cpp @@ -738,6 +738,10 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c case SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS_TIER: options->msl.argument_buffers_tier = static_cast(value); break; + + case SPVC_COMPILER_OPTION_MSL_SAMPLE_DREF_LOD_ARRAY_AS_GRAD: + options->msl.sample_dref_lod_array_as_grad = value != 0; + break; #endif default: diff --git a/spirv_cross_c.h b/spirv_cross_c.h index 826e25a7..e39ff2c4 100644 --- a/spirv_cross_c.h +++ b/spirv_cross_c.h @@ -40,7 +40,7 @@ extern "C" { /* Bumped if ABI or API breaks backwards compatibility. */ #define SPVC_C_API_VERSION_MAJOR 0 /* Bumped if APIs or enumerations are added in a backwards compatible way. */ -#define SPVC_C_API_VERSION_MINOR 54 +#define SPVC_C_API_VERSION_MINOR 55 /* Bumped if internal implementation details change. */ #define SPVC_C_API_VERSION_PATCH 0 @@ -724,6 +724,7 @@ typedef enum spvc_compiler_option SPVC_COMPILER_OPTION_GLSL_ENABLE_ROW_MAJOR_LOAD_WORKAROUND = 83 | SPVC_COMPILER_OPTION_GLSL_BIT, SPVC_COMPILER_OPTION_MSL_ARGUMENT_BUFFERS_TIER = 84 | SPVC_COMPILER_OPTION_MSL_BIT, + SPVC_COMPILER_OPTION_MSL_SAMPLE_DREF_LOD_ARRAY_AS_GRAD = 85 | SPVC_COMPILER_OPTION_MSL_BIT, SPVC_COMPILER_OPTION_INT_MAX = 0x7fffffff } spvc_compiler_option; diff --git a/spirv_msl.cpp b/spirv_msl.cpp index a886ef48..cb5e2981 100644 --- a/spirv_msl.cpp +++ b/spirv_msl.cpp @@ -10823,7 +10823,8 @@ string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool // We will detect a compile-time constant 0 value for gradient and promote that to level(0) on MSL. bool constant_zero_x = !grad_x || expression_is_constant_null(grad_x); bool constant_zero_y = !grad_y || expression_is_constant_null(grad_y); - if (constant_zero_x && constant_zero_y) + if (constant_zero_x && constant_zero_y && + (!imgtype.image.arrayed || !msl_options.sample_dref_lod_array_as_grad)) { lod = 0; grad_x = 0; @@ -10869,6 +10870,52 @@ string CompilerMSL::to_function_args(const TextureFunctionArguments &args, bool { farg_str += ", " + to_expression(lod); } + else if (msl_options.sample_dref_lod_array_as_grad && args.dref && imgtype.image.arrayed) + { + if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 3)) + SPIRV_CROSS_THROW("Using non-constant 0.0 gradient() qualifier for sample_compare. This is not " + "supported on macOS prior to MSL 2.3."); + // Some Metal devices have a bug where the LoD is erroneously biased upward + // when using a level() argument. Since this doesn't happen as much with gradient2d(), + // if we perform the LoD calculation in reverse, we can pass a gradient + // instead. + // lod = log2(rhoMax/eta) -> exp2(lod) = rhoMax/eta + // If we make all of the scale factors the same, eta will be 1 and + // exp2(lod) = rho. + // rhoX = dP/dx * extent; rhoY = dP/dy * extent + // Therefore, dP/dx = dP/dy = exp2(lod)/extent. + // (Subtracting 0.5 before exponentiation gives better results.) + string grad_opt, extent; + switch (imgtype.image.dim) + { + case Dim1D: + grad_opt = "2d"; + extent = join("float2(", to_expression(img), ".get_width(), 1.0)"); + break; + case Dim2D: + grad_opt = "2d"; + extent = join("float2(", to_expression(img), ".get_width(), ", to_expression(img), ".get_height())"); + break; + case DimCube: + if (imgtype.image.arrayed && msl_options.emulate_cube_array) + { + grad_opt = "2d"; + extent = join("float2(", to_expression(img), ".get_width())"); + } + else + { + grad_opt = "cube"; + extent = join("float3(", to_expression(img), ".get_width())"); + } + break; + default: + grad_opt = "unsupported_gradient_dimension"; + extent = "float3(1.0)"; + break; + } + farg_str += join(", gradient", grad_opt, "(exp2(", to_expression(lod), " - 0.5) / ", extent, ", exp2(", + to_expression(lod), " - 0.5) / ", extent, ")"); + } else { farg_str += ", level(" + to_expression(lod) + ")"; diff --git a/spirv_msl.hpp b/spirv_msl.hpp index 737575d4..3c9dde96 100644 --- a/spirv_msl.hpp +++ b/spirv_msl.hpp @@ -487,6 +487,15 @@ public: // only when the bug is present. bool check_discarded_frag_stores = false; + // If set, Lod operands to OpImageSample*DrefExplicitLod for 1D and 2D array images + // will be implemented using a gradient instead of passing the level operand directly. + // Some Metal devices have a bug where the level() argument to depth2d_array::sample_compare() + // in a fragment shader is biased by some unknown amount, possibly dependent on the + // partial derivatives of the texture coordinates. This is a workaround that is only + // expected to be needed until the bug is fixed in Metal; it is provided as an option + // so it can be enabled only when the bug is present. + bool sample_dref_lod_array_as_grad = false; + bool is_ios() const { return platform == iOS; diff --git a/test_shaders.py b/test_shaders.py index cf329e5e..c26f37a2 100755 --- a/test_shaders.py +++ b/test_shaders.py @@ -344,6 +344,8 @@ def cross_compile_msl(shader, spirv, opt, iterations, paths): msl_args.append('--msl-force-sample-rate-shading') if '.discard-checks.' in shader: msl_args.append('--msl-check-discarded-frag-stores') + if '.lod-as-grad.' in shader: + msl_args.append('--msl-sample-dref-lod-array-as-grad') if '.decoration-binding.' in shader: msl_args.append('--msl-decoration-binding') if '.mask-location-0.' in shader: