MSL: Manually update BuiltInHelperInvocation
when a fragment is discarded.
Some Metal devices have a bug where `simd_is_helper_thread()` won't return true after a fragment has been discarded. We can work around this by manually setting `gl_HelperInvocation` upon discarding a fragment. This is fairly unintrusive, so it is enabled by default. I've made it an option so that, when the bug is fixed, we can disable it.
This commit is contained in:
parent
edd66a2fc9
commit
c7ce92a95b
@ -332,7 +332,7 @@ if (SPIRV_CROSS_STATIC)
|
||||
endif()
|
||||
|
||||
set(spirv-cross-abi-major 0)
|
||||
set(spirv-cross-abi-minor 51)
|
||||
set(spirv-cross-abi-minor 52)
|
||||
set(spirv-cross-abi-patch 0)
|
||||
|
||||
if (SPIRV_CROSS_SHARED)
|
||||
|
7
main.cpp
7
main.cpp
@ -672,6 +672,7 @@ struct CLIArguments
|
||||
bool msl_emulate_subgroups = false;
|
||||
uint32_t msl_fixed_subgroup_size = 0;
|
||||
bool msl_force_sample_rate_shading = false;
|
||||
bool msl_manual_helper_invocation_updates = true;
|
||||
const char *msl_combined_sampler_suffix = nullptr;
|
||||
bool glsl_emit_push_constant_as_ubo = false;
|
||||
bool glsl_emit_ubo_as_plain_uniforms = false;
|
||||
@ -934,6 +935,9 @@ static void print_help_msl()
|
||||
"\t\tIf 0, assume variable subgroup size as actually exposed by Metal.\n"
|
||||
"\t[--msl-force-sample-rate-shading]:\n\t\tForce fragment shaders to run per sample.\n"
|
||||
"\t\tThis adds a [[sample_id]] parameter if none is already present.\n"
|
||||
"\t[--msl-no-manual-helper-invocation-updates]:\n\t\tDo not manually update the HelperInvocation builtin when a fragment is discarded.\n"
|
||||
"\t\tSome Metal devices have a bug where simd_is_helper_thread() does not return true\n"
|
||||
"\t\tafter the fragment is discarded. This behavior is required by Vulkan and SPIR-V, however.\n"
|
||||
"\t[--msl-combined-sampler-suffix <suffix>]:\n\t\tUses a custom suffix for combined samplers.\n");
|
||||
// clang-format on
|
||||
}
|
||||
@ -1205,6 +1209,7 @@ static string compile_iteration(const CLIArguments &args, std::vector<uint32_t>
|
||||
msl_opts.emulate_subgroups = args.msl_emulate_subgroups;
|
||||
msl_opts.fixed_subgroup_size = args.msl_fixed_subgroup_size;
|
||||
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.ios_support_base_vertex_instance = true;
|
||||
msl_comp->set_msl_options(msl_opts);
|
||||
for (auto &v : args.msl_discrete_descriptor_sets)
|
||||
@ -1751,6 +1756,8 @@ static int main_inner(int argc, char *argv[])
|
||||
cbs.add("--msl-fixed-subgroup-size",
|
||||
[&args](CLIParser &parser) { args.msl_fixed_subgroup_size = parser.next_uint(); });
|
||||
cbs.add("--msl-force-sample-rate-shading", [&args](CLIParser &) { args.msl_force_sample_rate_shading = true; });
|
||||
cbs.add("--msl-no-manual-helper-invocation-updates",
|
||||
[&args](CLIParser &) { args.msl_manual_helper_invocation_updates = false; });
|
||||
cbs.add("--msl-combined-sampler-suffix", [&args](CLIParser &parser) {
|
||||
args.msl_combined_sampler_suffix = parser.next_string();
|
||||
});
|
||||
|
@ -11,8 +11,10 @@ struct main0_out
|
||||
fragment main0_out main0()
|
||||
{
|
||||
main0_out out = {};
|
||||
bool _15 = simd_is_helper_thread();
|
||||
discard_fragment();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
bool _15 = gl_HelperInvocation;
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
if (!_15)
|
||||
{
|
||||
out.FragColor = float4(1.0, 0.0, 0.0, 1.0);
|
||||
|
@ -5,7 +5,9 @@ using namespace metal;
|
||||
|
||||
fragment void main0()
|
||||
{
|
||||
discard_fragment();
|
||||
bool _9 = simd_is_helper_thread();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
bool _19 = gl_HelperInvocation;
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,9 @@ using namespace metal;
|
||||
|
||||
fragment void main0()
|
||||
{
|
||||
discard_fragment();
|
||||
bool _9 = simd_is_helper_thread();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
bool _9 = gl_HelperInvocation;
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,13 @@ struct main0_out
|
||||
fragment main0_out main0()
|
||||
{
|
||||
main0_out out = {};
|
||||
bool _12 = simd_is_helper_thread();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
bool _12 = gl_HelperInvocation;
|
||||
float _15 = float(_12);
|
||||
out.FragColor = _15;
|
||||
discard_fragment();
|
||||
bool _16 = simd_is_helper_thread();
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
bool _16 = gl_HelperInvocation;
|
||||
float _17 = float(_16);
|
||||
out.FragColor = _17;
|
||||
return out;
|
||||
|
@ -11,8 +11,10 @@ struct main0_out
|
||||
fragment main0_out main0()
|
||||
{
|
||||
main0_out out = {};
|
||||
bool _15 = simd_is_helper_thread();
|
||||
discard_fragment();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
bool _15 = gl_HelperInvocation;
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
if (!_15)
|
||||
{
|
||||
out.FragColor = float4(1.0, 0.0, 0.0, 1.0);
|
||||
|
@ -1,12 +1,28 @@
|
||||
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||
|
||||
#include <metal_stdlib>
|
||||
#include <simd/simd.h>
|
||||
|
||||
using namespace metal;
|
||||
|
||||
fragment void main0()
|
||||
static inline __attribute__((always_inline))
|
||||
void foo(thread bool& gl_HelperInvocation)
|
||||
{
|
||||
discard_fragment();
|
||||
bool _9 = simd_is_helper_thread();
|
||||
bool helper = _9;
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
}
|
||||
|
||||
static inline __attribute__((always_inline))
|
||||
void bar(thread bool& gl_HelperInvocation)
|
||||
{
|
||||
bool _13 = gl_HelperInvocation;
|
||||
bool helper = _13;
|
||||
}
|
||||
|
||||
fragment void main0()
|
||||
{
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
foo(gl_HelperInvocation);
|
||||
bar(gl_HelperInvocation);
|
||||
}
|
||||
|
||||
|
@ -5,8 +5,10 @@ using namespace metal;
|
||||
|
||||
fragment void main0()
|
||||
{
|
||||
discard_fragment();
|
||||
bool _9 = simd_is_helper_thread();
|
||||
bool gl_HelperInvocation = {};
|
||||
gl_HelperInvocation = simd_is_helper_thread();
|
||||
gl_HelperInvocation = true, discard_fragment();
|
||||
bool _9 = gl_HelperInvocation;
|
||||
bool helper = _9;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,18 @@
|
||||
#version 450
|
||||
#extension GL_EXT_demote_to_helper_invocation : require
|
||||
|
||||
void main()
|
||||
void foo()
|
||||
{
|
||||
demote;
|
||||
}
|
||||
|
||||
void bar()
|
||||
{
|
||||
bool helper = helperInvocationEXT();
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
foo();
|
||||
bar();
|
||||
}
|
||||
|
@ -723,6 +723,10 @@ spvc_result spvc_compiler_options_set_uint(spvc_compiler_options options, spvc_c
|
||||
case SPVC_COMPILER_OPTION_MSL_SHADER_PATCH_INPUT_BUFFER_INDEX:
|
||||
options->msl.shader_patch_input_buffer_index = value;
|
||||
break;
|
||||
|
||||
case SPVC_COMPILER_OPTION_MSL_MANUAL_HELPER_INVOCATION_UPDATES:
|
||||
options->msl.manual_helper_invocation_updates = value != 0;
|
||||
break;
|
||||
#endif
|
||||
|
||||
default:
|
||||
|
@ -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 51
|
||||
#define SPVC_C_API_VERSION_MINOR 52
|
||||
/* Bumped if internal implementation details change. */
|
||||
#define SPVC_C_API_VERSION_PATCH 0
|
||||
|
||||
@ -718,6 +718,7 @@ typedef enum spvc_compiler_option
|
||||
|
||||
SPVC_COMPILER_OPTION_MSL_RAW_BUFFER_TESE_INPUT = 79 | SPVC_COMPILER_OPTION_MSL_BIT,
|
||||
SPVC_COMPILER_OPTION_MSL_SHADER_PATCH_INPUT_BUFFER_INDEX = 80 | SPVC_COMPILER_OPTION_MSL_BIT,
|
||||
SPVC_COMPILER_OPTION_MSL_MANUAL_HELPER_INVOCATION_UPDATES = 81 | SPVC_COMPILER_OPTION_MSL_BIT,
|
||||
|
||||
SPVC_COMPILER_OPTION_INT_MAX = 0x7fffffff
|
||||
} spvc_compiler_option;
|
||||
|
108
spirv_msl.cpp
108
spirv_msl.cpp
@ -259,8 +259,8 @@ void CompilerMSL::build_implicit_builtins()
|
||||
|
||||
if (need_subpass_input || need_sample_pos || need_subgroup_mask || need_vertex_params || need_tesc_params ||
|
||||
need_tese_params || need_multiview || need_dispatch_base || need_vertex_base_params || need_grid_params ||
|
||||
needs_sample_id || needs_subgroup_invocation_id || needs_subgroup_size || has_additional_fixed_sample_mask() ||
|
||||
need_local_invocation_index || need_workgroup_size)
|
||||
needs_sample_id || needs_subgroup_invocation_id || needs_subgroup_size || needs_helper_invocation ||
|
||||
has_additional_fixed_sample_mask() || need_local_invocation_index || need_workgroup_size)
|
||||
{
|
||||
bool has_frag_coord = false;
|
||||
bool has_sample_id = false;
|
||||
@ -274,6 +274,7 @@ void CompilerMSL::build_implicit_builtins()
|
||||
bool has_subgroup_size = false;
|
||||
bool has_view_idx = false;
|
||||
bool has_layer = false;
|
||||
bool has_helper_invocation = false;
|
||||
bool has_local_invocation_index = false;
|
||||
bool has_workgroup_size = false;
|
||||
uint32_t workgroup_id_type = 0;
|
||||
@ -430,6 +431,13 @@ void CompilerMSL::build_implicit_builtins()
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_helper_invocation && builtin == BuiltInHelperInvocation)
|
||||
{
|
||||
builtin_helper_invocation_id = var.self;
|
||||
mark_implicit_builtin(StorageClassInput, BuiltInHelperInvocation, var.self);
|
||||
has_helper_invocation = true;
|
||||
}
|
||||
|
||||
if (need_local_invocation_index && builtin == BuiltInLocalInvocationIndex)
|
||||
{
|
||||
builtin_local_invocation_index_id = var.self;
|
||||
@ -806,6 +814,35 @@ void CompilerMSL::build_implicit_builtins()
|
||||
mark_implicit_builtin(StorageClassOutput, BuiltInSampleMask, var_id);
|
||||
}
|
||||
|
||||
if (!has_helper_invocation && needs_helper_invocation)
|
||||
{
|
||||
uint32_t offset = ir.increase_bound_by(3);
|
||||
uint32_t type_id = offset;
|
||||
uint32_t type_ptr_id = offset + 1;
|
||||
uint32_t var_id = offset + 2;
|
||||
|
||||
// Create gl_HelperInvocation.
|
||||
SPIRType bool_type;
|
||||
bool_type.basetype = SPIRType::Boolean;
|
||||
bool_type.width = 8;
|
||||
bool_type.vecsize = 1;
|
||||
set<SPIRType>(type_id, bool_type);
|
||||
|
||||
SPIRType bool_type_ptr_in;
|
||||
bool_type_ptr_in = bool_type;
|
||||
bool_type_ptr_in.pointer = true;
|
||||
bool_type_ptr_in.pointer_depth++;
|
||||
bool_type_ptr_in.parent_type = type_id;
|
||||
bool_type_ptr_in.storage = StorageClassInput;
|
||||
|
||||
auto &ptr_in_type = set<SPIRType>(type_ptr_id, bool_type_ptr_in);
|
||||
ptr_in_type.self = type_id;
|
||||
set<SPIRVariable>(var_id, type_ptr_id, StorageClassInput);
|
||||
set_decoration(var_id, DecorationBuiltIn, BuiltInHelperInvocation);
|
||||
builtin_helper_invocation_id = var_id;
|
||||
mark_implicit_builtin(StorageClassInput, BuiltInHelperInvocation, var_id);
|
||||
}
|
||||
|
||||
if (need_local_invocation_index && !has_local_invocation_index)
|
||||
{
|
||||
uint32_t offset = ir.increase_bound_by(2);
|
||||
@ -1415,8 +1452,6 @@ string CompilerMSL::compile()
|
||||
backend.basic_uint8_type = "uchar";
|
||||
backend.basic_int16_type = "short";
|
||||
backend.basic_uint16_type = "ushort";
|
||||
backend.discard_literal = "discard_fragment()";
|
||||
backend.demote_literal = "discard_fragment()";
|
||||
backend.boolean_mix_function = "select";
|
||||
backend.swizzle_is_function = false;
|
||||
backend.shared_is_implied = false;
|
||||
@ -1461,6 +1496,20 @@ string CompilerMSL::compile()
|
||||
preprocess_op_codes();
|
||||
build_implicit_builtins();
|
||||
|
||||
if (needs_manual_helper_invocation_updates() &&
|
||||
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
|
||||
{
|
||||
string discard_expr =
|
||||
join(builtin_to_glsl(BuiltInHelperInvocation, StorageClassInput), " = true, discard_fragment()");
|
||||
backend.discard_literal = discard_expr;
|
||||
backend.demote_literal = discard_expr;
|
||||
}
|
||||
else
|
||||
{
|
||||
backend.discard_literal = "discard_fragment()";
|
||||
backend.demote_literal = "discard_fragment()";
|
||||
}
|
||||
|
||||
fixup_image_load_store_access();
|
||||
|
||||
set_enabled_interface_variables(get_active_interface_variables());
|
||||
@ -1587,6 +1636,8 @@ void CompilerMSL::preprocess_op_codes()
|
||||
(is_sample_rate() && (active_input_builtins.get(BuiltInFragCoord) ||
|
||||
(need_subpass_input_ms && !msl_options.use_framebuffer_fetch_subpasses))))
|
||||
needs_sample_id = true;
|
||||
if (preproc.needs_helper_invocation)
|
||||
needs_helper_invocation = true;
|
||||
|
||||
if (is_intersection_query())
|
||||
{
|
||||
@ -1627,10 +1678,26 @@ void CompilerMSL::extract_global_variables_from_functions()
|
||||
ir.for_each_typed_id<SPIRVariable>([&](uint32_t, SPIRVariable &var) {
|
||||
// Some builtins resolve directly to a function call which does not need any declared variables.
|
||||
// Skip these.
|
||||
if (var.storage == StorageClassInput && has_decoration(var.self, DecorationBuiltIn) &&
|
||||
BuiltIn(get_decoration(var.self, DecorationBuiltIn)) == BuiltInHelperInvocation)
|
||||
if (var.storage == StorageClassInput && has_decoration(var.self, DecorationBuiltIn))
|
||||
{
|
||||
return;
|
||||
auto bi_type = BuiltIn(get_decoration(var.self, DecorationBuiltIn));
|
||||
if (bi_type == BuiltInHelperInvocation && !needs_manual_helper_invocation_updates())
|
||||
return;
|
||||
if (bi_type == BuiltInHelperInvocation && needs_manual_helper_invocation_updates())
|
||||
{
|
||||
if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3))
|
||||
SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.3 on iOS.");
|
||||
else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1))
|
||||
SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.1 on macOS.");
|
||||
// Make sure this is declared and initialized.
|
||||
// Force this to have the proper name.
|
||||
set_name(var.self, builtin_to_glsl(BuiltInHelperInvocation, StorageClassInput));
|
||||
auto &entry_func = get<SPIRFunction>(ir.default_entry_point);
|
||||
entry_func.add_local_variable(var.self);
|
||||
vars_needing_early_declaration.push_back(var.self);
|
||||
entry_func.fixup_hooks_in.push_back([this, &var]()
|
||||
{ statement(to_name(var.self), " = simd_is_helper_thread();"); });
|
||||
}
|
||||
}
|
||||
|
||||
if (var.storage == StorageClassInput || var.storage == StorageClassOutput ||
|
||||
@ -1841,6 +1908,17 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std::
|
||||
break;
|
||||
}
|
||||
|
||||
case OpDemoteToHelperInvocation:
|
||||
if (needs_manual_helper_invocation_updates() &&
|
||||
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
|
||||
added_arg_ids.insert(builtin_helper_invocation_id);
|
||||
break;
|
||||
|
||||
case OpIsHelperInvocationEXT:
|
||||
if (needs_manual_helper_invocation_updates())
|
||||
added_arg_ids.insert(builtin_helper_invocation_id);
|
||||
break;
|
||||
|
||||
case OpRayQueryInitializeKHR:
|
||||
case OpRayQueryProceedKHR:
|
||||
case OpRayQueryTerminateKHR:
|
||||
@ -1884,6 +1962,10 @@ void CompilerMSL::extract_global_variables_from_function(uint32_t func_id, std::
|
||||
break;
|
||||
}
|
||||
|
||||
if (needs_manual_helper_invocation_updates() && b.terminator == SPIRBlock::Kill &&
|
||||
(active_input_builtins.get(BuiltInHelperInvocation) || needs_helper_invocation))
|
||||
added_arg_ids.insert(builtin_helper_invocation_id);
|
||||
|
||||
// TODO: Add all other operations which can affect memory.
|
||||
// We should consider a more unified system here to reduce boiler-plate.
|
||||
// This kind of analysis is done in several places ...
|
||||
@ -9027,7 +9109,10 @@ void CompilerMSL::emit_instruction(const Instruction &instruction)
|
||||
SPIRV_CROSS_THROW("simd_is_helper_thread() requires MSL 2.3 on iOS.");
|
||||
else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1))
|
||||
SPIRV_CROSS_THROW("simd_is_helper_thread() requires MSL 2.1 on macOS.");
|
||||
emit_op(ops[0], ops[1], "simd_is_helper_thread()", false);
|
||||
emit_op(ops[0], ops[1],
|
||||
needs_manual_helper_invocation_updates() ? builtin_to_glsl(BuiltInHelperInvocation, StorageClassInput) :
|
||||
"simd_is_helper_thread()",
|
||||
false);
|
||||
break;
|
||||
|
||||
case OpBeginInvocationInterlockEXT:
|
||||
@ -15267,6 +15352,8 @@ string CompilerMSL::builtin_to_glsl(BuiltIn builtin, StorageClass storage)
|
||||
break;
|
||||
|
||||
case BuiltInHelperInvocation:
|
||||
if (needs_manual_helper_invocation_updates())
|
||||
break;
|
||||
if (msl_options.is_ios() && !msl_options.supports_msl_version(2, 3))
|
||||
SPIRV_CROSS_THROW("simd_is_helper_thread() requires version 2.3 on iOS.");
|
||||
else if (msl_options.is_macos() && !msl_options.supports_msl_version(2, 1))
|
||||
@ -16134,6 +16221,11 @@ bool CompilerMSL::OpCodePreprocessor::handle(Op opcode, const uint32_t *args, ui
|
||||
break;
|
||||
}
|
||||
|
||||
case OpIsHelperInvocationEXT:
|
||||
if (compiler.needs_manual_helper_invocation_updates())
|
||||
needs_helper_invocation = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -458,6 +458,12 @@ public:
|
||||
// the extra threads away.
|
||||
bool force_sample_rate_shading = false;
|
||||
|
||||
// If set, gl_HelperInvocation will be set manually whenever a fragment is discarded.
|
||||
// Some Metal devices have a bug where simd_is_helper_thread() does not return true
|
||||
// after a fragment has been discarded. This is a workaround that is only expected to be needed
|
||||
// until the bug is fixed in Metal; it is provided as an option to allow disabling it when that occurs.
|
||||
bool manual_helper_invocation_updates = true;
|
||||
|
||||
bool is_ios() const
|
||||
{
|
||||
return platform == iOS;
|
||||
@ -1005,6 +1011,7 @@ protected:
|
||||
uint32_t builtin_frag_coord_id = 0;
|
||||
uint32_t builtin_sample_id_id = 0;
|
||||
uint32_t builtin_sample_mask_id = 0;
|
||||
uint32_t builtin_helper_invocation_id = 0;
|
||||
uint32_t builtin_vertex_idx_id = 0;
|
||||
uint32_t builtin_base_vertex_id = 0;
|
||||
uint32_t builtin_instance_idx_id = 0;
|
||||
@ -1113,6 +1120,7 @@ protected:
|
||||
bool needs_subgroup_invocation_id = false;
|
||||
bool needs_subgroup_size = false;
|
||||
bool needs_sample_id = false;
|
||||
bool needs_helper_invocation = false;
|
||||
std::string qual_pos_var_name;
|
||||
std::string stage_in_var_name = "in";
|
||||
std::string stage_out_var_name = "out";
|
||||
@ -1180,6 +1188,11 @@ protected:
|
||||
|
||||
bool variable_storage_requires_stage_io(spv::StorageClass storage) const;
|
||||
|
||||
bool needs_manual_helper_invocation_updates() const
|
||||
{
|
||||
return msl_options.manual_helper_invocation_updates && msl_options.supports_msl_version(2, 3);
|
||||
}
|
||||
|
||||
bool has_additional_fixed_sample_mask() const { return msl_options.additional_fixed_sample_mask != 0xffffffff; }
|
||||
std::string additional_fixed_sample_mask_str() const;
|
||||
|
||||
@ -1204,6 +1217,7 @@ protected:
|
||||
bool needs_subgroup_invocation_id = false;
|
||||
bool needs_subgroup_size = false;
|
||||
bool needs_sample_id = false;
|
||||
bool needs_helper_invocation = false;
|
||||
};
|
||||
|
||||
// OpcodeHandler that scans for uses of sampled images
|
||||
|
Loading…
Reference in New Issue
Block a user