diff --git a/reference/shaders-hlsl/frag/bit-conversions.frag b/reference/shaders-hlsl/frag/bit-conversions.frag new file mode 100644 index 00000000..2ed359bf --- /dev/null +++ b/reference/shaders-hlsl/frag/bit-conversions.frag @@ -0,0 +1,27 @@ +static float2 value; +static float4 FragColor; + +struct SPIRV_Cross_Input +{ + float2 value : TEXCOORD0; +}; + +struct SPIRV_Cross_Output +{ + float4 FragColor : SV_Target0; +}; + +void frag_main() +{ + int i = asint(value.x); + FragColor = float4(1.0f, 0.0f, asfloat(i), 1.0f); +} + +SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) +{ + value = stage_input.value; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.FragColor = FragColor; + return stage_output; +} diff --git a/reference/shaders-hlsl/frag/builtins.frag b/reference/shaders-hlsl/frag/builtins.frag index 6932a499..922eca7c 100644 --- a/reference/shaders-hlsl/frag/builtins.frag +++ b/reference/shaders-hlsl/frag/builtins.frag @@ -5,14 +5,14 @@ static float4 vColor; struct SPIRV_Cross_Input { - float4 gl_FragCoord : SV_Position; float4 vColor : TEXCOORD0; + float4 gl_FragCoord : SV_Position; }; struct SPIRV_Cross_Output { - float gl_FragDepth : SV_Depth; float4 FragColor : SV_Target0; + float gl_FragDepth : SV_Depth; }; void frag_main() diff --git a/reference/shaders-hlsl/frag/bvec-operations.frag b/reference/shaders-hlsl/frag/bvec-operations.frag new file mode 100644 index 00000000..2398d8c6 --- /dev/null +++ b/reference/shaders-hlsl/frag/bvec-operations.frag @@ -0,0 +1,29 @@ +static float2 value; +static float4 FragColor; + +struct SPIRV_Cross_Input +{ + float2 value : TEXCOORD0; +}; + +struct SPIRV_Cross_Output +{ + float4 FragColor : SV_Target0; +}; + +void frag_main() +{ + bool2 _25 = bool2(value.x == 0.0f, value.y == 0.0f); + bool2 bools1 = bool2(!_25.x, !_25.y); + bool2 bools2 = bool2(value.x <= float2(1.5f, 0.5f).x, value.y <= float2(1.5f, 0.5f).y); + FragColor = float4(1.0f, 0.0f, float(bools1.x), float(bools2.x)); +} + +SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) +{ + value = stage_input.value; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.FragColor = FragColor; + return stage_output; +} diff --git a/reference/shaders-hlsl/frag/various-glsl-ops.frag b/reference/shaders-hlsl/frag/various-glsl-ops.frag new file mode 100644 index 00000000..81332911 --- /dev/null +++ b/reference/shaders-hlsl/frag/various-glsl-ops.frag @@ -0,0 +1,28 @@ +static float2 interpolant; +static float4 FragColor; + +struct SPIRV_Cross_Input +{ + float2 interpolant : TEXCOORD0; +}; + +struct SPIRV_Cross_Output +{ + float4 FragColor : SV_Target0; +}; + +void frag_main() +{ + float4 color = float4(0.0f, 0.0f, 0.0f, EvaluateAttributeSnapped(interpolant, float2(0.100000001490116119384765625f, 0.100000001490116119384765625f)).x); + color += float4(0.0f, 0.0f, 0.0f, ddx_coarse(interpolant.x)); + FragColor = color; +} + +SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input) +{ + interpolant = stage_input.interpolant; + frag_main(); + SPIRV_Cross_Output stage_output; + stage_output.FragColor = FragColor; + return stage_output; +} diff --git a/reference/shaders-hlsl/vert/basic.vert b/reference/shaders-hlsl/vert/basic.vert index e17e9d29..ff52abf4 100644 --- a/reference/shaders-hlsl/vert/basic.vert +++ b/reference/shaders-hlsl/vert/basic.vert @@ -21,8 +21,8 @@ struct SPIRV_Cross_Input struct SPIRV_Cross_Output { - float4 gl_Position : SV_Position; float3 vNormal : TEXCOORD0; + float4 gl_Position : SV_Position; }; void vert_main() diff --git a/reference/shaders-hlsl/vert/locations.vert b/reference/shaders-hlsl/vert/locations.vert index 3580d0a1..d6576687 100644 --- a/reference/shaders-hlsl/vert/locations.vert +++ b/reference/shaders-hlsl/vert/locations.vert @@ -32,12 +32,12 @@ struct SPIRV_Cross_Input struct SPIRV_Cross_Output { - float4 gl_Position : SV_Position; float vLocation0 : TEXCOORD0; float vLocation1 : TEXCOORD1; float vLocation2[2] : TEXCOORD2; Foo vLocation4 : TEXCOORD4; float vLocation9 : TEXCOORD9; + float4 gl_Position : SV_Position; }; void vert_main() diff --git a/reference/shaders-hlsl/vert/qualifiers.vert b/reference/shaders-hlsl/vert/qualifiers.vert index 16dcbc80..8e86710e 100644 --- a/reference/shaders-hlsl/vert/qualifiers.vert +++ b/reference/shaders-hlsl/vert/qualifiers.vert @@ -16,11 +16,11 @@ static Block vout; struct SPIRV_Cross_Output { - float4 gl_Position : SV_Position; nointerpolation float vFlat : TEXCOORD0; centroid float vCentroid : TEXCOORD1; sample float vSample : TEXCOORD2; noperspective float vNoperspective : TEXCOORD3; + float4 gl_Position : SV_Position; }; void vert_main() diff --git a/shaders-hlsl/frag/bit-conversions.frag b/shaders-hlsl/frag/bit-conversions.frag new file mode 100644 index 00000000..faacdc0f --- /dev/null +++ b/shaders-hlsl/frag/bit-conversions.frag @@ -0,0 +1,12 @@ +#version 310 es +precision mediump float; + +layout(location = 0) in vec2 value; + +layout(location = 0) out vec4 FragColor; + +void main() +{ + int i = floatBitsToInt(value.x); + FragColor = vec4(1.0, 0.0, intBitsToFloat(i), 1.0); +} diff --git a/shaders-hlsl/frag/bvec-operations.frag b/shaders-hlsl/frag/bvec-operations.frag new file mode 100644 index 00000000..7221604d --- /dev/null +++ b/shaders-hlsl/frag/bvec-operations.frag @@ -0,0 +1,13 @@ +#version 310 es +precision mediump float; + +layout(location = 0) in vec2 value; + +layout(location = 0) out vec4 FragColor; + +void main() +{ + bvec2 bools1 = not(bvec2(value.x == 0.0, value.y == 0.0)); + bvec2 bools2 = lessThanEqual(value, vec2(1.5, 0.5)); + FragColor = vec4(1.0, 0.0, bools1.x ? 1.0 : 0.0, bools2.x ? 1.0 : 0.0); +} diff --git a/shaders-hlsl/frag/various-glsl-ops.frag b/shaders-hlsl/frag/various-glsl-ops.frag new file mode 100644 index 00000000..7aa3605b --- /dev/null +++ b/shaders-hlsl/frag/various-glsl-ops.frag @@ -0,0 +1,17 @@ +#version 450 + +in vec2 interpolant; + +out vec4 FragColor; + +void main() +{ + vec4 color = vec4(0.0, 0.0, 0.0, interpolateAtOffset(interpolant, vec2(0.1, 0.1))); + + // glslang's HLSL parser currently fails here + //color += vec4(0.0, 0.0, 0.0, interpolateAtSample(interpolant, gl_SampleID)); + //color += vec4(0.0, 0.0, 0.0, interpolateAtCentroid(interpolant)); + + color += vec4(0.0, 0.0, 0.0, dFdxCoarse(interpolant.x)); + FragColor = color; +} diff --git a/spirv_glsl.cpp b/spirv_glsl.cpp index 5e6b0409..743f358e 100644 --- a/spirv_glsl.cpp +++ b/spirv_glsl.cpp @@ -2268,6 +2268,29 @@ void CompilerGLSL::emit_binary_op(uint32_t result_type, uint32_t result_id, uint inherit_expression_dependencies(result_id, op1); } +void CompilerGLSL::emit_unrolled_unary_op(uint32_t result_type, uint32_t result_id, uint32_t operand, const char *op) +{ + auto &type = get(result_type); + auto expr = type_to_glsl_constructor(type); + expr += '('; + for (uint32_t i = 0; i < type.vecsize; i++) + { + // Make sure to call to_expression multiple times to ensure + // that these expressions are properly flushed to temporaries if needed. + expr += op; + expr += to_enclosed_expression(operand); + expr += '.'; + expr += index_to_swizzle(i); + + if (i + 1 < type.vecsize) + expr += ", "; + } + expr += ')'; + emit_op(result_type, result_id, expr, should_forward(operand)); + + inherit_expression_dependencies(result_id, operand); +} + void CompilerGLSL::emit_unrolled_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op) { @@ -5032,6 +5055,46 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction) require_extension("GL_OES_standard_derivatives"); break; + case OpDPdxFine: + UFOP(dFdxFine); + if (options.es) + { + SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES."); + } + if (options.version < 450) + require_extension("GL_ARB_derivative_control"); + break; + + case OpDPdyFine: + UFOP(dFdyFine); + if (options.es) + { + SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES."); + } + if (options.version < 450) + require_extension("GL_ARB_derivative_control"); + break; + + case OpDPdxCoarse: + if (options.es) + { + SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES."); + } + UFOP(dFdxCoarse); + if (options.version < 450) + require_extension("GL_ARB_derivative_control"); + break; + + case OpDPdyCoarse: + UFOP(dFdyCoarse); + if (options.es) + { + SPIRV_CROSS_THROW("GL_ARB_derivative_control is unavailable in OpenGL ES."); + } + if (options.version < 450) + require_extension("GL_ARB_derivative_control"); + break; + case OpFwidth: UFOP(fwidth); if (is_legacy_es()) diff --git a/spirv_glsl.hpp b/spirv_glsl.hpp index f92afc00..a32eb1df 100644 --- a/spirv_glsl.hpp +++ b/spirv_glsl.hpp @@ -309,6 +309,7 @@ protected: void emit_binary_func_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op, SPIRType::BaseType input_type, bool skip_cast_if_equal_type); void emit_unary_func_op(uint32_t result_type, uint32_t result_id, uint32_t op0, const char *op); + void emit_unrolled_unary_op(uint32_t result_type, uint32_t result_id, uint32_t operand, const char *op); void emit_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op); void emit_unrolled_binary_op(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op); void emit_binary_op_cast(uint32_t result_type, uint32_t result_id, uint32_t op0, uint32_t op1, const char *op, @@ -371,7 +372,7 @@ protected: uint32_t type_to_std430_size(const SPIRType &type, uint64_t flags); std::string bitcast_glsl(const SPIRType &result_type, uint32_t arg); - std::string bitcast_glsl_op(const SPIRType &result_type, const SPIRType &argument_type); + virtual std::string bitcast_glsl_op(const SPIRType &result_type, const SPIRType &argument_type); std::string build_composite_combiner(const uint32_t *elems, uint32_t length); bool remove_duplicate_swizzle(std::string &op); bool remove_unity_swizzle(uint32_t base, std::string &op); diff --git a/spirv_hlsl.cpp b/spirv_hlsl.cpp index 2dd7133b..72ec4d11 100644 --- a/spirv_hlsl.cpp +++ b/spirv_hlsl.cpp @@ -22,6 +22,27 @@ using namespace spv; using namespace spirv_cross; using namespace std; +// Returns true if an arithmetic operation does not change behavior depending on signedness. +static bool opcode_is_sign_invariant(Op opcode) +{ + switch (opcode) + { + case OpIEqual: + case OpINotEqual: + case OpISub: + case OpIAdd: + case OpIMul: + case OpShiftLeftLogical: + case OpBitwiseOr: + case OpBitwiseXor: + case OpBitwiseAnd: + return true; + + default: + return false; + } +} + string CompilerHLSL::type_to_glsl(const SPIRType &type) { // Ignore the pointer type since GLSL doesn't have pointers. @@ -219,6 +240,13 @@ void CompilerHLSL::emit_builtin_inputs_in_struct() semantic = "SV_InstanceID"; break; + case BuiltInSampleId: + if (legacy) + SPIRV_CROSS_THROW("Sample ID not supported in SM 3.0 or lower."); + type = "uint"; + semantic = "SV_SampleIndex"; + break; + default: SPIRV_CROSS_THROW("Unsupported builtin in HLSL."); break; @@ -395,6 +423,7 @@ void CompilerHLSL::emit_builtin_variables() case BuiltInVertexIndex: case BuiltInInstanceIndex: + case BuiltInSampleId: type = "int"; break; @@ -608,9 +637,9 @@ void CompilerHLSL::emit_resources() begin_scope(); sort(input_variables.begin(), input_variables.end(), variable_compare); - emit_builtin_inputs_in_struct(); for (auto var : input_variables) emit_interface_block_in_struct(*var, active_inputs); + emit_builtin_inputs_in_struct(); end_scope_decl(); statement(""); } @@ -623,9 +652,9 @@ void CompilerHLSL::emit_resources() begin_scope(); // FIXME: Use locations properly if they exist. sort(output_variables.begin(), output_variables.end(), variable_compare); - emit_builtin_outputs_in_struct(); for (auto var : output_variables) emit_interface_block_in_struct(*var, active_outputs); + emit_builtin_outputs_in_struct(); end_scope_decl(); statement(""); } @@ -1213,6 +1242,36 @@ void CompilerHLSL::emit_uniform(const SPIRVariable &var) statement(variable_decl(var), ";"); } +string CompilerHLSL::bitcast_glsl_op(const SPIRType &out_type, const SPIRType &in_type) +{ + if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::Int) + return type_to_glsl(out_type); + else if (out_type.basetype == SPIRType::UInt64 && in_type.basetype == SPIRType::Int64) + return type_to_glsl(out_type); + else if (out_type.basetype == SPIRType::UInt && in_type.basetype == SPIRType::Float) + return "asuint"; + else if (out_type.basetype == SPIRType::Int && in_type.basetype == SPIRType::UInt) + return type_to_glsl(out_type); + else if (out_type.basetype == SPIRType::Int64 && in_type.basetype == SPIRType::UInt64) + return type_to_glsl(out_type); + else if (out_type.basetype == SPIRType::Int && in_type.basetype == SPIRType::Float) + return "asint"; + else if (out_type.basetype == SPIRType::Float && in_type.basetype == SPIRType::UInt) + return "asfloat"; + else if (out_type.basetype == SPIRType::Float && in_type.basetype == SPIRType::Int) + return "asfloat"; + else if (out_type.basetype == SPIRType::Int64 && in_type.basetype == SPIRType::Double) + SPIRV_CROSS_THROW("Double to Int64 is not supported in HLSL."); + else if (out_type.basetype == SPIRType::UInt64 && in_type.basetype == SPIRType::Double) + SPIRV_CROSS_THROW("Double to UInt64 is not supported in HLSL."); + else if (out_type.basetype == SPIRType::Double && in_type.basetype == SPIRType::Int64) + return "asdouble"; + else if (out_type.basetype == SPIRType::Double && in_type.basetype == SPIRType::UInt64) + return "asdouble"; + else + return ""; +} + void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, const uint32_t *args, uint32_t count) { GLSLstd450 op = static_cast(eop); @@ -1220,26 +1279,36 @@ void CompilerHLSL::emit_glsl_op(uint32_t result_type, uint32_t id, uint32_t eop, switch (op) { case GLSLstd450InverseSqrt: - { emit_unary_func_op(result_type, id, args[0], "rsqrt"); break; - } + case GLSLstd450Fract: - { emit_unary_func_op(result_type, id, args[0], "frac"); break; - } + case GLSLstd450FMix: case GLSLstd450IMix: - { emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "lerp"); break; - } + case GLSLstd450Atan2: - { emit_binary_func_op(result_type, id, args[1], args[0], "atan2"); break; - } + + case GLSLstd450Fma: + emit_trinary_func_op(result_type, id, args[0], args[1], args[2], "mad"); + break; + + case GLSLstd450InterpolateAtCentroid: + emit_unary_func_op(result_type, id, args[0], "EvaluateAttributeAtCentroid"); + break; + case GLSLstd450InterpolateAtSample: + emit_binary_func_op(result_type, id, args[0], args[1], "EvaluateAttributeAtSample"); + break; + case GLSLstd450InterpolateAtOffset: + emit_binary_func_op(result_type, id, args[0], args[1], "EvaluateAttributeSnapped"); + break; + default: CompilerGLSL::emit_glsl_op(result_type, id, eop, args, count); break; @@ -1252,12 +1321,14 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) auto opcode = static_cast(instruction.op); #define BOP(op) emit_binary_op(ops[0], ops[1], ops[2], ops[3], #op) -#define BOP_CAST(op, type, skip_cast) emit_binary_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, skip_cast) +#define BOP_CAST(op, type) \ + emit_binary_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, opcode_is_sign_invariant(opcode)) #define UOP(op) emit_unary_op(ops[0], ops[1], ops[2], #op) #define QFOP(op) emit_quaternary_func_op(ops[0], ops[1], ops[2], ops[3], ops[4], ops[5], #op) #define TFOP(op) emit_trinary_func_op(ops[0], ops[1], ops[2], ops[3], ops[4], #op) #define BFOP(op) emit_binary_func_op(ops[0], ops[1], ops[2], ops[3], #op) -#define BFOP_CAST(op, type, skip_cast) emit_binary_func_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, skip_cast) +#define BFOP_CAST(op, type) \ + emit_binary_func_op_cast(ops[0], ops[1], ops[2], ops[3], #op, type, opcode_is_sign_invariant(opcode)) #define BFOP(op) emit_binary_func_op(ops[0], ops[1], ops[2], ops[3], #op) #define UFOP(op) emit_unary_func_op(ops[0], ops[1], ops[2], #op) @@ -1268,22 +1339,217 @@ void CompilerHLSL::emit_instruction(const Instruction &instruction) emit_binary_func_op(ops[0], ops[1], ops[3], ops[2], "mul"); break; } + case OpVectorTimesMatrix: { emit_binary_func_op(ops[0], ops[1], ops[3], ops[2], "mul"); break; } + case OpMatrixTimesMatrix: { emit_binary_func_op(ops[0], ops[1], ops[3], ops[2], "mul"); break; } + case OpFMod: { requires_op_fmod = true; CompilerGLSL::emit_instruction(instruction); break; } + + case OpDPdx: + UFOP(ddx); + break; + + case OpDPdy: + UFOP(ddy); + break; + + case OpDPdxFine: + UFOP(ddx_fine); + break; + + case OpDPdyFine: + UFOP(ddy_fine); + break; + + case OpDPdxCoarse: + UFOP(ddx_coarse); + break; + + case OpDPdyCoarse: + UFOP(ddy_coarse); + break; + + case OpLogicalNot: + { + auto result_type = ops[0]; + auto id = ops[1]; + auto &type = get(result_type); + + if (type.vecsize > 1) + emit_unrolled_unary_op(result_type, id, ops[2], "!"); + else + UOP(!); + break; + } + + case OpIEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "=="); + else + BOP_CAST(== , SPIRType::Int); + break; + } + + case OpLogicalEqual: + case OpFOrdEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "=="); + else + BOP(== ); + break; + } + + case OpINotEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "!="); + else + BOP_CAST(!= , SPIRType::Int); + break; + } + + case OpLogicalNotEqual: + case OpFOrdNotEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "!="); + else + BOP(!= ); + break; + } + + case OpUGreaterThan: + case OpSGreaterThan: + { + auto result_type = ops[0]; + auto id = ops[1]; + auto type = opcode == OpUGreaterThan ? SPIRType::UInt : SPIRType::Int; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], ">"); + else + BOP_CAST(>, type); + break; + } + + case OpFOrdGreaterThan: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], ">"); + else + BOP(>); + break; + } + + case OpUGreaterThanEqual: + case OpSGreaterThanEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + auto type = opcode == OpUGreaterThanEqual ? SPIRType::UInt : SPIRType::Int; + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], ">="); + else + BOP_CAST(>= , type); + break; + } + + case OpFOrdGreaterThanEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], ">="); + else + BOP(>= ); + break; + } + + case OpULessThan: + case OpSLessThan: + { + auto result_type = ops[0]; + auto id = ops[1]; + + auto type = opcode == OpULessThan ? SPIRType::UInt : SPIRType::Int; + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "<"); + else + BOP_CAST(<, type); + break; + } + + case OpFOrdLessThan: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "<"); + else + BOP(<); + break; + } + + case OpULessThanEqual: + case OpSLessThanEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + auto type = opcode == OpULessThanEqual ? SPIRType::UInt : SPIRType::Int; + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "<="); + else + BOP_CAST(<= , type); + break; + } + + case OpFOrdLessThanEqual: + { + auto result_type = ops[0]; + auto id = ops[1]; + + if (expression_type(ops[2]).vecsize > 1) + emit_unrolled_binary_op(result_type, id, ops[2], ops[3], "<="); + else + BOP(<= ); + break; + } + default: CompilerGLSL::emit_instruction(instruction); break; diff --git a/spirv_hlsl.hpp b/spirv_hlsl.hpp index 2d4961ca..1b535504 100644 --- a/spirv_hlsl.hpp +++ b/spirv_hlsl.hpp @@ -69,6 +69,7 @@ private: void emit_uniform(const SPIRVariable &var) override; std::string layout_for_member(const SPIRType &type, uint32_t index) override; std::string to_interpolation_qualifiers(uint64_t flags) override; + std::string bitcast_glsl_op(const SPIRType &result_type, const SPIRType &argument_type) override; const char *to_storage_qualifiers_glsl(const SPIRVariable &var) override;