Merge pull request #433 from KhronosGroup/fix-428
Support returning arrays from functions in GLSL/MSL, refactor array copies in MSL.
This commit is contained in:
Коммит
f4bce688d5
|
@ -0,0 +1,31 @@
|
||||||
|
static const float4 _20[2] = { 10.0f.xxxx, 20.0f.xxxx };
|
||||||
|
|
||||||
|
static float4 gl_Position;
|
||||||
|
static float4 vInput0;
|
||||||
|
static float4 vInput1;
|
||||||
|
|
||||||
|
struct SPIRV_Cross_Input
|
||||||
|
{
|
||||||
|
float4 vInput0 : TEXCOORD0;
|
||||||
|
float4 vInput1 : TEXCOORD1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPIRV_Cross_Output
|
||||||
|
{
|
||||||
|
float4 gl_Position : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
void vert_main()
|
||||||
|
{
|
||||||
|
gl_Position = 10.0f.xxxx + vInput1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
|
||||||
|
{
|
||||||
|
vInput0 = stage_input.vInput0;
|
||||||
|
vInput1 = stage_input.vInput1;
|
||||||
|
vert_main();
|
||||||
|
SPIRV_Cross_Output stage_output;
|
||||||
|
stage_output.gl_Position = gl_Position;
|
||||||
|
return stage_output;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||||
|
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
@ -9,6 +11,12 @@ struct Foobar
|
||||||
float b;
|
float b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constant float4 _37[3] = {float4(1.0), float4(2.0), float4(3.0)};
|
||||||
|
constant float4 _49[2] = {float4(1.0), float4(2.0)};
|
||||||
|
constant float4 _54[2] = {float4(8.0), float4(10.0)};
|
||||||
|
constant float4 _55[2][2] = {{float4(1.0), float4(2.0)}, {float4(8.0), float4(10.0)}};
|
||||||
|
constant Foobar _75[2] = {{10.0, 40.0}, {90.0, 70.0}};
|
||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
int index [[user(locn0)]];
|
int index [[user(locn0)]];
|
||||||
|
@ -19,6 +27,20 @@ struct main0_out
|
||||||
float4 FragColor [[color(0)]];
|
float4 FragColor [[color(0)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
fragment main0_out main0(main0_in in [[stage_in]])
|
fragment main0_out main0(main0_in in [[stage_in]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||||
|
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
@ -9,6 +11,9 @@ struct Foo
|
||||||
float b;
|
float b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constant float _16[4] = {1.0, 4.0, 3.0, 2.0};
|
||||||
|
constant Foo _28[2] = {{10.0, 20.0}, {30.0, 40.0}};
|
||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
int line [[user(locn0)]];
|
int line [[user(locn0)]];
|
||||||
|
@ -19,6 +24,20 @@ struct main0_out
|
||||||
float4 FragColor [[color(0)]];
|
float4 FragColor [[color(0)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
fragment main0_out main0(main0_in in [[stage_in]])
|
fragment main0_out main0(main0_in in [[stage_in]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
constant float4 _20[2] = {float4(10.0), float4(20.0)};
|
||||||
|
|
||||||
|
struct main0_in
|
||||||
|
{
|
||||||
|
float4 vInput1 [[attribute(1)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct main0_out
|
||||||
|
{
|
||||||
|
float4 gl_Position [[position]];
|
||||||
|
};
|
||||||
|
|
||||||
|
vertex main0_out main0(main0_in in [[stage_in]])
|
||||||
|
{
|
||||||
|
main0_out out = {};
|
||||||
|
out.gl_Position = float4(10.0) + in.vInput1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(location = 1) in vec4 vInput1;
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = vec4(10.0) + vInput1;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
static const float4 _20[2] = { 10.0f.xxxx, 20.0f.xxxx };
|
||||||
|
|
||||||
|
static float4 gl_Position;
|
||||||
|
static float4 vInput0;
|
||||||
|
static float4 vInput1;
|
||||||
|
|
||||||
|
struct SPIRV_Cross_Input
|
||||||
|
{
|
||||||
|
float4 vInput0 : TEXCOORD0;
|
||||||
|
float4 vInput1 : TEXCOORD1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SPIRV_Cross_Output
|
||||||
|
{
|
||||||
|
float4 gl_Position : SV_Position;
|
||||||
|
};
|
||||||
|
|
||||||
|
void test(out float4 SPIRV_Cross_return_value[2])
|
||||||
|
{
|
||||||
|
SPIRV_Cross_return_value = _20;
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2(out float4 SPIRV_Cross_return_value[2])
|
||||||
|
{
|
||||||
|
float4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
SPIRV_Cross_return_value = foobar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vert_main()
|
||||||
|
{
|
||||||
|
float4 _42[2];
|
||||||
|
test(_42);
|
||||||
|
float4 _44[2];
|
||||||
|
test2(_44);
|
||||||
|
gl_Position = _42[0] + _44[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
SPIRV_Cross_Output main(SPIRV_Cross_Input stage_input)
|
||||||
|
{
|
||||||
|
vInput0 = stage_input.vInput0;
|
||||||
|
vInput1 = stage_input.vInput1;
|
||||||
|
vert_main();
|
||||||
|
SPIRV_Cross_Output stage_output;
|
||||||
|
stage_output.gl_Position = gl_Position;
|
||||||
|
return stage_output;
|
||||||
|
}
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||||
|
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
@ -9,11 +11,27 @@ struct D
|
||||||
float b;
|
float b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constant float4 _14[4] = {float4(0.0), float4(0.0), float4(0.0), float4(0.0)};
|
||||||
|
|
||||||
struct main0_out
|
struct main0_out
|
||||||
{
|
{
|
||||||
float FragColor [[color(0)]];
|
float FragColor [[color(0)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
fragment main0_out main0()
|
fragment main0_out main0()
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
|
|
|
@ -11,6 +11,12 @@ struct Foobar
|
||||||
float b;
|
float b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constant float4 _37[3] = {float4(1.0), float4(2.0), float4(3.0)};
|
||||||
|
constant float4 _49[2] = {float4(1.0), float4(2.0)};
|
||||||
|
constant float4 _54[2] = {float4(8.0), float4(10.0)};
|
||||||
|
constant float4 _55[2][2] = {{float4(1.0), float4(2.0)}, {float4(8.0), float4(10.0)}};
|
||||||
|
constant Foobar _75[2] = {{10.0, 40.0}, {90.0, 70.0}};
|
||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
int index [[user(locn0)]];
|
int index [[user(locn0)]];
|
||||||
|
@ -21,6 +27,20 @@ struct main0_out
|
||||||
float4 FragColor [[color(0)]];
|
float4 FragColor [[color(0)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
float4 resolve(thread const Foobar& f)
|
float4 resolve(thread const Foobar& f)
|
||||||
{
|
{
|
||||||
return float4(f.a + f.b);
|
return float4(f.a + f.b);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||||
|
|
||||||
#include <metal_stdlib>
|
#include <metal_stdlib>
|
||||||
#include <simd/simd.h>
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
@ -9,6 +11,9 @@ struct Foo
|
||||||
float b;
|
float b;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constant float _16[4] = {1.0, 4.0, 3.0, 2.0};
|
||||||
|
constant Foo _28[2] = {{10.0, 20.0}, {30.0, 40.0}};
|
||||||
|
|
||||||
struct main0_in
|
struct main0_in
|
||||||
{
|
{
|
||||||
int line [[user(locn0)]];
|
int line [[user(locn0)]];
|
||||||
|
@ -19,6 +24,20 @@ struct main0_out
|
||||||
float4 FragColor [[color(0)]];
|
float4 FragColor [[color(0)]];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
fragment main0_out main0(main0_in in [[stage_in]])
|
fragment main0_out main0(main0_in in [[stage_in]])
|
||||||
{
|
{
|
||||||
main0_out out = {};
|
main0_out out = {};
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
#pragma clang diagnostic ignored "-Wmissing-prototypes"
|
||||||
|
|
||||||
|
#include <metal_stdlib>
|
||||||
|
#include <simd/simd.h>
|
||||||
|
|
||||||
|
using namespace metal;
|
||||||
|
|
||||||
|
constant float4 _20[2] = {float4(10.0), float4(20.0)};
|
||||||
|
|
||||||
|
struct main0_in
|
||||||
|
{
|
||||||
|
float4 vInput1 [[attribute(1)]];
|
||||||
|
float4 vInput0 [[attribute(0)]];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct main0_out
|
||||||
|
{
|
||||||
|
float4 gl_Position [[position]];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implementation of an array copy function to cover GLSL's ability to copy an array via assignment.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
// An overload for constant arrays.
|
||||||
|
template<typename T, uint N>
|
||||||
|
void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])
|
||||||
|
{
|
||||||
|
for (uint i = 0; i < N; dst[i] = src[i], i++);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test(thread float4 (&SPIRV_Cross_return_value)[2])
|
||||||
|
{
|
||||||
|
spvArrayCopyConstant(SPIRV_Cross_return_value, _20);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test2(thread float4 (&SPIRV_Cross_return_value)[2], thread float4& vInput0, thread float4& vInput1)
|
||||||
|
{
|
||||||
|
float4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
spvArrayCopy(SPIRV_Cross_return_value, foobar);
|
||||||
|
}
|
||||||
|
|
||||||
|
vertex main0_out main0(main0_in in [[stage_in]])
|
||||||
|
{
|
||||||
|
main0_out out = {};
|
||||||
|
float4 _42[2];
|
||||||
|
test(_42);
|
||||||
|
float4 _44[2];
|
||||||
|
test2(_44, in.vInput0, in.vInput1);
|
||||||
|
out.gl_Position = _42[0] + _44[1];
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 vInput0;
|
||||||
|
layout(location = 1) in vec4 vInput1;
|
||||||
|
|
||||||
|
vec4[2] test()
|
||||||
|
{
|
||||||
|
return vec4[](vec4(10.0), vec4(20.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4[2] test2()
|
||||||
|
{
|
||||||
|
vec4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
return foobar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = test()[0] + test2()[1];
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 vInput0;
|
||||||
|
layout(location = 1) in vec4 vInput1;
|
||||||
|
|
||||||
|
vec4[2] test()
|
||||||
|
{
|
||||||
|
return vec4[](vec4(10.0), vec4(20.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4[2] test2()
|
||||||
|
{
|
||||||
|
vec4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
return foobar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = test()[0] + test2()[1];
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 vInput0;
|
||||||
|
layout(location = 1) in vec4 vInput1;
|
||||||
|
|
||||||
|
vec4[2] test()
|
||||||
|
{
|
||||||
|
return vec4[](vec4(10.0), vec4(20.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4[2] test2()
|
||||||
|
{
|
||||||
|
vec4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
return foobar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = test()[0] + test2()[1];
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
#version 310 es
|
||||||
|
|
||||||
|
layout(location = 0) in vec4 vInput0;
|
||||||
|
layout(location = 1) in vec4 vInput1;
|
||||||
|
|
||||||
|
vec4[2] test()
|
||||||
|
{
|
||||||
|
return vec4[](vec4(10.0), vec4(20.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4[2] test2()
|
||||||
|
{
|
||||||
|
vec4 foobar[2];
|
||||||
|
foobar[0] = vInput0;
|
||||||
|
foobar[1] = vInput1;
|
||||||
|
return foobar;
|
||||||
|
}
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
gl_Position = test()[0] + test2()[1];
|
||||||
|
}
|
|
@ -5367,9 +5367,11 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||||
length -= 3;
|
length -= 3;
|
||||||
|
|
||||||
auto &callee = get<SPIRFunction>(func);
|
auto &callee = get<SPIRFunction>(func);
|
||||||
|
auto &return_type = get<SPIRType>(callee.return_type);
|
||||||
bool pure = function_is_pure(callee);
|
bool pure = function_is_pure(callee);
|
||||||
|
|
||||||
bool callee_has_out_variables = false;
|
bool callee_has_out_variables = false;
|
||||||
|
bool emit_return_value_as_argument = false;
|
||||||
|
|
||||||
// Invalidate out variables passed to functions since they can be OpStore'd to.
|
// Invalidate out variables passed to functions since they can be OpStore'd to.
|
||||||
for (uint32_t i = 0; i < length; i++)
|
for (uint32_t i = 0; i < length; i++)
|
||||||
|
@ -5383,12 +5385,25 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||||
flush_variable_declaration(arg[i]);
|
flush_variable_declaration(arg[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!return_type.array.empty() && !backend.can_return_array)
|
||||||
|
{
|
||||||
|
callee_has_out_variables = true;
|
||||||
|
emit_return_value_as_argument = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!pure)
|
if (!pure)
|
||||||
register_impure_function_call();
|
register_impure_function_call();
|
||||||
|
|
||||||
string funexpr;
|
string funexpr;
|
||||||
vector<string> arglist;
|
vector<string> arglist;
|
||||||
funexpr += to_name(func) + "(";
|
funexpr += to_name(func) + "(";
|
||||||
|
|
||||||
|
if (emit_return_value_as_argument)
|
||||||
|
{
|
||||||
|
statement(type_to_glsl(return_type), " ", to_name(id), type_to_array_glsl(return_type), ";");
|
||||||
|
arglist.push_back(to_name(id));
|
||||||
|
}
|
||||||
|
|
||||||
for (uint32_t i = 0; i < length; i++)
|
for (uint32_t i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
// Do not pass in separate images or samplers if we're remapping
|
// Do not pass in separate images or samplers if we're remapping
|
||||||
|
@ -5423,7 +5438,7 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||||
// Check for function call constraints.
|
// Check for function call constraints.
|
||||||
check_function_call_constraints(arg, length);
|
check_function_call_constraints(arg, length);
|
||||||
|
|
||||||
if (get<SPIRType>(result_type).basetype != SPIRType::Void)
|
if (return_type.basetype != SPIRType::Void)
|
||||||
{
|
{
|
||||||
// If the function actually writes to an out variable,
|
// If the function actually writes to an out variable,
|
||||||
// take the conservative route and do not forward.
|
// take the conservative route and do not forward.
|
||||||
|
@ -5435,7 +5450,13 @@ void CompilerGLSL::emit_instruction(const Instruction &instruction)
|
||||||
bool forward = args_will_forward(id, arg, length, pure) && !callee_has_out_variables && pure &&
|
bool forward = args_will_forward(id, arg, length, pure) && !callee_has_out_variables && pure &&
|
||||||
(forced_temporaries.find(id) == end(forced_temporaries));
|
(forced_temporaries.find(id) == end(forced_temporaries));
|
||||||
|
|
||||||
emit_op(result_type, id, funexpr, forward);
|
if (emit_return_value_as_argument)
|
||||||
|
{
|
||||||
|
statement(funexpr, ";");
|
||||||
|
set<SPIRExpression>(id, to_name(id), result_type, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
emit_op(result_type, id, funexpr, forward);
|
||||||
|
|
||||||
// Function calls are implicit loads from all variables in question.
|
// Function calls are implicit loads from all variables in question.
|
||||||
// Set dependencies for them.
|
// Set dependencies for them.
|
||||||
|
@ -7645,6 +7666,7 @@ void CompilerGLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
|
||||||
auto &type = get<SPIRType>(func.return_type);
|
auto &type = get<SPIRType>(func.return_type);
|
||||||
decl += flags_to_precision_qualifiers_glsl(type, return_flags);
|
decl += flags_to_precision_qualifiers_glsl(type, return_flags);
|
||||||
decl += type_to_glsl(type);
|
decl += type_to_glsl(type);
|
||||||
|
decl += type_to_array_glsl(type);
|
||||||
decl += " ";
|
decl += " ";
|
||||||
|
|
||||||
if (func.self == entry_point)
|
if (func.self == entry_point)
|
||||||
|
@ -8453,9 +8475,26 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
|
||||||
|
|
||||||
if (block.return_value)
|
if (block.return_value)
|
||||||
{
|
{
|
||||||
// OpReturnValue can return Undef, so don't emit anything for this case.
|
auto &type = expression_type(block.return_value);
|
||||||
if (ids.at(block.return_value).get_type() != TypeUndef)
|
if (!type.array.empty() && !backend.can_return_array)
|
||||||
statement("return ", to_expression(block.return_value), ";");
|
{
|
||||||
|
// If we cannot return arrays, we will have a special out argument we can write to instead.
|
||||||
|
// The backend is responsible for setting this up, and redirection the return values as appropriate.
|
||||||
|
if (ids.at(block.return_value).get_type() != TypeUndef)
|
||||||
|
emit_array_copy("SPIRV_Cross_return_value", block.return_value);
|
||||||
|
|
||||||
|
if (!block_is_outside_flow_control_from_block(get<SPIRBlock>(current_function->entry_block), block) ||
|
||||||
|
block.loop_dominator != SPIRBlock::NoDominator)
|
||||||
|
{
|
||||||
|
statement("return;");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// OpReturnValue can return Undef, so don't emit anything for this case.
|
||||||
|
if (ids.at(block.return_value).get_type() != TypeUndef)
|
||||||
|
statement("return ", to_expression(block.return_value), ";");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If this block is the very final block and not called from control flow,
|
// If this block is the very final block and not called from control flow,
|
||||||
// we do not need an explicit return which looks out of place. Just end the function here.
|
// we do not need an explicit return which looks out of place. Just end the function here.
|
||||||
|
@ -8463,7 +8502,9 @@ void CompilerGLSL::emit_block_chain(SPIRBlock &block)
|
||||||
// but we actually need a return here ...
|
// but we actually need a return here ...
|
||||||
else if (!block_is_outside_flow_control_from_block(get<SPIRBlock>(current_function->entry_block), block) ||
|
else if (!block_is_outside_flow_control_from_block(get<SPIRBlock>(current_function->entry_block), block) ||
|
||||||
block.loop_dominator != SPIRBlock::NoDominator)
|
block.loop_dominator != SPIRBlock::NoDominator)
|
||||||
|
{
|
||||||
statement("return;");
|
statement("return;");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case SPIRBlock::Kill:
|
case SPIRBlock::Kill:
|
||||||
|
@ -8581,3 +8622,8 @@ uint32_t CompilerGLSL::mask_relevant_memory_semantics(uint32_t semantics)
|
||||||
MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask |
|
MemorySemanticsWorkgroupMemoryMask | MemorySemanticsUniformMemoryMask |
|
||||||
MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask);
|
MemorySemanticsCrossWorkgroupMemoryMask | MemorySemanticsSubgroupMemoryMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerGLSL::emit_array_copy(const string &lhs, uint32_t rhs_id)
|
||||||
|
{
|
||||||
|
statement(lhs, " = ", to_expression(rhs_id), ";");
|
||||||
|
}
|
||||||
|
|
|
@ -331,6 +331,7 @@ protected:
|
||||||
bool allow_precision_qualifiers = false;
|
bool allow_precision_qualifiers = false;
|
||||||
bool can_swizzle_scalar = false;
|
bool can_swizzle_scalar = false;
|
||||||
bool force_gl_in_out_block = false;
|
bool force_gl_in_out_block = false;
|
||||||
|
bool can_return_array = true;
|
||||||
} backend;
|
} backend;
|
||||||
|
|
||||||
void emit_struct(SPIRType &type);
|
void emit_struct(SPIRType &type);
|
||||||
|
@ -424,6 +425,7 @@ protected:
|
||||||
std::string layout_for_variable(const SPIRVariable &variable);
|
std::string layout_for_variable(const SPIRVariable &variable);
|
||||||
std::string to_combined_image_sampler(uint32_t image_id, uint32_t samp_id);
|
std::string to_combined_image_sampler(uint32_t image_id, uint32_t samp_id);
|
||||||
virtual bool skip_argument(uint32_t id) const;
|
virtual bool skip_argument(uint32_t id) const;
|
||||||
|
virtual void emit_array_copy(const std::string &lhs, uint32_t rhs_id);
|
||||||
|
|
||||||
bool buffer_is_packing_standard(const SPIRType &type, BufferPackingStandard packing, uint32_t start_offset = 0,
|
bool buffer_is_packing_standard(const SPIRType &type, BufferPackingStandard packing, uint32_t start_offset = 0,
|
||||||
uint32_t end_offset = std::numeric_limits<uint32_t>::max());
|
uint32_t end_offset = std::numeric_limits<uint32_t>::max());
|
||||||
|
|
|
@ -1663,9 +1663,17 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
|
||||||
string decl;
|
string decl;
|
||||||
|
|
||||||
auto &type = get<SPIRType>(func.return_type);
|
auto &type = get<SPIRType>(func.return_type);
|
||||||
decl += flags_to_precision_qualifiers_glsl(type, return_flags);
|
if (type.array.empty())
|
||||||
decl += type_to_glsl(type);
|
{
|
||||||
decl += " ";
|
decl += flags_to_precision_qualifiers_glsl(type, return_flags);
|
||||||
|
decl += type_to_glsl(type);
|
||||||
|
decl += " ";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We cannot return arrays in HLSL, so "return" through an out variable.
|
||||||
|
decl = "void ";
|
||||||
|
}
|
||||||
|
|
||||||
if (func.self == entry_point)
|
if (func.self == entry_point)
|
||||||
{
|
{
|
||||||
|
@ -1683,6 +1691,19 @@ void CompilerHLSL::emit_function_prototype(SPIRFunction &func, uint64_t return_f
|
||||||
decl += to_name(func.self);
|
decl += to_name(func.self);
|
||||||
|
|
||||||
decl += "(";
|
decl += "(";
|
||||||
|
|
||||||
|
if (!type.array.empty())
|
||||||
|
{
|
||||||
|
// Fake array returns by writing to an out array instead.
|
||||||
|
decl += "out ";
|
||||||
|
decl += type_to_glsl(type);
|
||||||
|
decl += " ";
|
||||||
|
decl += "SPIRV_Cross_return_value";
|
||||||
|
decl += type_to_array_glsl(type);
|
||||||
|
if (!func.arguments.empty())
|
||||||
|
decl += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
for (auto &arg : func.arguments)
|
for (auto &arg : func.arguments)
|
||||||
{
|
{
|
||||||
// Might change the variable name if it already exists in this function.
|
// Might change the variable name if it already exists in this function.
|
||||||
|
@ -3802,6 +3823,7 @@ string CompilerHLSL::compile()
|
||||||
backend.can_swizzle_scalar = true;
|
backend.can_swizzle_scalar = true;
|
||||||
backend.can_declare_struct_inline = false;
|
backend.can_declare_struct_inline = false;
|
||||||
backend.can_declare_arrays_inline = false;
|
backend.can_declare_arrays_inline = false;
|
||||||
|
backend.can_return_array = false;
|
||||||
|
|
||||||
update_active_builtins();
|
update_active_builtins();
|
||||||
analyze_sampler_comparison_states();
|
analyze_sampler_comparison_states();
|
||||||
|
|
132
spirv_msl.cpp
132
spirv_msl.cpp
|
@ -72,6 +72,8 @@ string CompilerMSL::compile()
|
||||||
backend.use_typed_initializer_list = true;
|
backend.use_typed_initializer_list = true;
|
||||||
backend.native_row_major_matrix = false;
|
backend.native_row_major_matrix = false;
|
||||||
backend.flexible_member_array_supported = false;
|
backend.flexible_member_array_supported = false;
|
||||||
|
backend.can_declare_arrays_inline = false;
|
||||||
|
backend.can_return_array = false;
|
||||||
|
|
||||||
replace_illegal_names();
|
replace_illegal_names();
|
||||||
|
|
||||||
|
@ -912,11 +914,19 @@ void CompilerMSL::emit_custom_functions()
|
||||||
|
|
||||||
case SPVFuncImplArrayCopy:
|
case SPVFuncImplArrayCopy:
|
||||||
statement("// Implementation of an array copy function to cover GLSL's ability to copy an array via "
|
statement("// Implementation of an array copy function to cover GLSL's ability to copy an array via "
|
||||||
"assignment. ");
|
"assignment.");
|
||||||
statement("template<typename T>");
|
statement("template<typename T, uint N>");
|
||||||
statement("void spvArrayCopy(thread T* dst, thread const T* src, uint count)");
|
statement("void spvArrayCopy(thread T (&dst)[N], thread const T (&src)[N])");
|
||||||
begin_scope();
|
begin_scope();
|
||||||
statement("for (uint i = 0; i < count; *dst++ = *src++, i++);");
|
statement("for (uint i = 0; i < N; dst[i] = src[i], i++);");
|
||||||
|
end_scope();
|
||||||
|
statement("");
|
||||||
|
|
||||||
|
statement("// An overload for constant arrays.");
|
||||||
|
statement("template<typename T, uint N>");
|
||||||
|
statement("void spvArrayCopyConstant(thread T (&dst)[N], constant T (&src)[N])");
|
||||||
|
begin_scope();
|
||||||
|
statement("for (uint i = 0; i < N; dst[i] = src[i], i++);");
|
||||||
end_scope();
|
end_scope();
|
||||||
statement("");
|
statement("");
|
||||||
break;
|
break;
|
||||||
|
@ -1134,6 +1144,34 @@ void CompilerMSL::declare_undefined_values()
|
||||||
statement("");
|
statement("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerMSL::declare_constant_arrays()
|
||||||
|
{
|
||||||
|
// MSL cannot declare arrays inline (except when declaring a variable), so we must move them out to
|
||||||
|
// global constants directly, so we are able to use constants as variable expressions.
|
||||||
|
bool emitted = false;
|
||||||
|
|
||||||
|
for (auto &id : ids)
|
||||||
|
{
|
||||||
|
if (id.get_type() == TypeConstant)
|
||||||
|
{
|
||||||
|
auto &c = id.get<SPIRConstant>();
|
||||||
|
if (c.specialization)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto &type = get<SPIRType>(c.constant_type);
|
||||||
|
if (!type.array.empty())
|
||||||
|
{
|
||||||
|
auto name = to_name(c.self);
|
||||||
|
statement("constant ", variable_decl(type, name), " = ", constant_expression(c), ";");
|
||||||
|
emitted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (emitted)
|
||||||
|
statement("");
|
||||||
|
}
|
||||||
|
|
||||||
void CompilerMSL::emit_resources()
|
void CompilerMSL::emit_resources()
|
||||||
{
|
{
|
||||||
// Output non-interface structs. These include local function structs
|
// Output non-interface structs. These include local function structs
|
||||||
|
@ -1170,6 +1208,7 @@ void CompilerMSL::emit_resources()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare_constant_arrays();
|
||||||
declare_undefined_values();
|
declare_undefined_values();
|
||||||
|
|
||||||
// Output interface structs.
|
// Output interface structs.
|
||||||
|
@ -1743,27 +1782,44 @@ bool CompilerMSL::maybe_emit_input_struct_assignment(uint32_t id_lhs, uint32_t i
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CompilerMSL::emit_array_copy(const string &lhs, uint32_t rhs_id)
|
||||||
|
{
|
||||||
|
// Assignment from an array initializer is fine.
|
||||||
|
if (ids[rhs_id].get_type() == TypeConstant)
|
||||||
|
statement("spvArrayCopyConstant(", lhs, ", ", to_expression(rhs_id), ");");
|
||||||
|
else
|
||||||
|
statement("spvArrayCopy(", lhs, ", ", to_expression(rhs_id), ");");
|
||||||
|
}
|
||||||
|
|
||||||
// Since MSL does not allow arrays to be copied via simple variable assignment,
|
// Since MSL does not allow arrays to be copied via simple variable assignment,
|
||||||
// if the LHS and RHS represent an assignment of an entire array, it must be
|
// if the LHS and RHS represent an assignment of an entire array, it must be
|
||||||
// implemented by calling an array copy function.
|
// implemented by calling an array copy function.
|
||||||
// Returns whether the struct assignment was emitted.
|
// Returns whether the struct assignment was emitted.
|
||||||
bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs)
|
bool CompilerMSL::maybe_emit_array_assignment(uint32_t id_lhs, uint32_t id_rhs)
|
||||||
{
|
{
|
||||||
// Assignment from an array initializer is fine.
|
|
||||||
if (ids[id_rhs].get_type() == TypeConstant)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
// We only care about assignments of an entire array
|
// We only care about assignments of an entire array
|
||||||
auto &type = expression_type(id_rhs);
|
auto &type = expression_type(id_rhs);
|
||||||
if (type.array.size() == 0)
|
if (type.array.size() == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
auto *var = maybe_get<SPIRVariable>(id_lhs);
|
||||||
|
if (ids[id_rhs].get_type() == TypeConstant && var && var->deferred_declaration)
|
||||||
|
{
|
||||||
|
// Special case, if we end up declaring a variable when assigning the constant array,
|
||||||
|
// we can avoid the copy by directly assigning the constant expression.
|
||||||
|
// This is likely necessary to be able to use a variable as a true look-up table, as it is unlikely
|
||||||
|
// the compiler will be able to optimize the spvArrayCopy() into a constant LUT.
|
||||||
|
// After a variable has been declared, we can no longer assign constant arrays in MSL unfortunately.
|
||||||
|
statement(to_expression(id_lhs), " = ", constant_expression(get<SPIRConstant>(id_rhs)), ";");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the LHS variable has been declared
|
// Ensure the LHS variable has been declared
|
||||||
auto *p_v_lhs = maybe_get_backing_variable(id_lhs);
|
auto *p_v_lhs = maybe_get_backing_variable(id_lhs);
|
||||||
if (p_v_lhs)
|
if (p_v_lhs)
|
||||||
flush_variable_declaration(p_v_lhs->self);
|
flush_variable_declaration(p_v_lhs->self);
|
||||||
|
|
||||||
statement("spvArrayCopy(", to_expression(id_lhs), ", ", to_expression(id_rhs), ", ", to_array_size(type, 0), ");");
|
emit_array_copy(to_expression(id_lhs), id_rhs);
|
||||||
register_write(id_lhs);
|
register_write(id_lhs);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -1949,12 +2005,32 @@ void CompilerMSL::emit_function_prototype(SPIRFunction &func, uint64_t)
|
||||||
processing_entry_point = (func.self == entry_point);
|
processing_entry_point = (func.self == entry_point);
|
||||||
|
|
||||||
auto &type = get<SPIRType>(func.return_type);
|
auto &type = get<SPIRType>(func.return_type);
|
||||||
decl += func_type_decl(type);
|
|
||||||
|
if (type.array.empty())
|
||||||
|
{
|
||||||
|
decl += func_type_decl(type);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We cannot return arrays in MSL, so "return" through an out variable.
|
||||||
|
decl = "void";
|
||||||
|
}
|
||||||
|
|
||||||
decl += " ";
|
decl += " ";
|
||||||
decl += to_name(func.self);
|
decl += to_name(func.self);
|
||||||
|
|
||||||
decl += "(";
|
decl += "(";
|
||||||
|
|
||||||
|
if (!type.array.empty())
|
||||||
|
{
|
||||||
|
// Fake arrays returns by writing to an out array instead.
|
||||||
|
decl += "thread ";
|
||||||
|
decl += type_to_glsl(type);
|
||||||
|
decl += " (&SPIRV_Cross_return_value)";
|
||||||
|
decl += type_to_array_glsl(type);
|
||||||
|
if (!func.arguments.empty())
|
||||||
|
decl += ", ";
|
||||||
|
}
|
||||||
|
|
||||||
if (processing_entry_point)
|
if (processing_entry_point)
|
||||||
{
|
{
|
||||||
decl += entry_point_args(!func.arguments.empty());
|
decl += entry_point_args(!func.arguments.empty());
|
||||||
|
@ -2595,7 +2671,7 @@ string CompilerMSL::func_type_decl(SPIRType &type)
|
||||||
{
|
{
|
||||||
auto &execution = get_entry_point();
|
auto &execution = get_entry_point();
|
||||||
// The regular function return type. If not processing the entry point function, that's all we need
|
// The regular function return type. If not processing the entry point function, that's all we need
|
||||||
string return_type = type_to_glsl(type);
|
string return_type = type_to_glsl(type) + type_to_array_glsl(type);
|
||||||
if (!processing_entry_point)
|
if (!processing_entry_point)
|
||||||
return return_type;
|
return return_type;
|
||||||
|
|
||||||
|
@ -2604,7 +2680,7 @@ string CompilerMSL::func_type_decl(SPIRType &type)
|
||||||
{
|
{
|
||||||
auto &so_var = get<SPIRVariable>(stage_out_var_id);
|
auto &so_var = get<SPIRVariable>(stage_out_var_id);
|
||||||
auto &so_type = get<SPIRType>(so_var.basetype);
|
auto &so_type = get<SPIRType>(so_var.basetype);
|
||||||
return_type = type_to_glsl(so_type);
|
return_type = type_to_glsl(so_type) + type_to_array_glsl(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepend a entry type, based on the execution model
|
// Prepend a entry type, based on the execution model
|
||||||
|
@ -3522,16 +3598,38 @@ CompilerMSL::SPVFuncImpl CompilerMSL::OpCodePreprocessor::get_spv_func_impl(Op o
|
||||||
case OpFMod:
|
case OpFMod:
|
||||||
return SPVFuncImplMod;
|
return SPVFuncImplMod;
|
||||||
|
|
||||||
|
case OpFunctionCall:
|
||||||
|
{
|
||||||
|
auto &return_type = compiler.get<SPIRType>(args[0]);
|
||||||
|
if (!return_type.array.empty())
|
||||||
|
return SPVFuncImplArrayCopy;
|
||||||
|
else
|
||||||
|
return SPVFuncImplNone;
|
||||||
|
}
|
||||||
|
|
||||||
case OpStore:
|
case OpStore:
|
||||||
{
|
{
|
||||||
// Get the result type of the RHS. Since this is run as a pre-processing stage,
|
// Get the result type of the RHS. Since this is run as a pre-processing stage,
|
||||||
// we must extract the result type directly from the Instruction, rather than the ID.
|
// we must extract the result type directly from the Instruction, rather than the ID.
|
||||||
uint32_t id_rhs = args[1];
|
uint32_t id_rhs = args[1];
|
||||||
uint32_t type_id_rhs = result_types[id_rhs];
|
|
||||||
if ((compiler.ids[id_rhs].get_type() != TypeConstant) && type_id_rhs &&
|
|
||||||
compiler.is_array(compiler.get<SPIRType>(type_id_rhs)))
|
|
||||||
return SPVFuncImplArrayCopy;
|
|
||||||
|
|
||||||
|
const SPIRType *type = nullptr;
|
||||||
|
if (compiler.ids[id_rhs].get_type() != TypeNone)
|
||||||
|
{
|
||||||
|
// Could be a constant, or similar.
|
||||||
|
type = &compiler.expression_type(id_rhs);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Or ... an expression.
|
||||||
|
if (result_types[id_rhs] != 0)
|
||||||
|
type = &compiler.get<SPIRType>(result_types[id_rhs]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type && compiler.is_array(*type))
|
||||||
|
return SPVFuncImplArrayCopy;
|
||||||
|
else
|
||||||
|
return SPVFuncImplNone;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -208,6 +208,7 @@ protected:
|
||||||
std::string to_qualifiers_glsl(uint32_t id) override;
|
std::string to_qualifiers_glsl(uint32_t id) override;
|
||||||
void replace_illegal_names() override;
|
void replace_illegal_names() override;
|
||||||
void declare_undefined_values() override;
|
void declare_undefined_values() override;
|
||||||
|
void declare_constant_arrays();
|
||||||
bool is_non_native_row_major_matrix(uint32_t id) override;
|
bool is_non_native_row_major_matrix(uint32_t id) override;
|
||||||
bool member_is_non_native_row_major_matrix(const SPIRType &type, uint32_t index) override;
|
bool member_is_non_native_row_major_matrix(const SPIRType &type, uint32_t index) override;
|
||||||
std::string convert_row_major_matrix(std::string exp_str, const SPIRType &exp_type) override;
|
std::string convert_row_major_matrix(std::string exp_str, const SPIRType &exp_type) override;
|
||||||
|
@ -265,6 +266,7 @@ protected:
|
||||||
const char *get_memory_order(uint32_t spv_mem_sem);
|
const char *get_memory_order(uint32_t spv_mem_sem);
|
||||||
void add_pragma_line(const std::string &line);
|
void add_pragma_line(const std::string &line);
|
||||||
void emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem);
|
void emit_barrier(uint32_t id_exe_scope, uint32_t id_mem_scope, uint32_t id_mem_sem);
|
||||||
|
void emit_array_copy(const std::string &lhs, uint32_t rhs_id) override;
|
||||||
|
|
||||||
Options options;
|
Options options;
|
||||||
std::set<SPVFuncImpl> spv_function_implementations;
|
std::set<SPVFuncImpl> spv_function_implementations;
|
||||||
|
|
Загрузка…
Ссылка в новой задаче