-
Notifications
You must be signed in to change notification settings - Fork 193
[msl-out] wrap arrays in structs so that they can be returned by functions #764
[msl-out] wrap arrays in structs so that they can be returned by functions #764
Conversation
I guess we need some kind of type override system for this? |
Thank you for taking a stab at this!
Are you sure this is the right solution? I think there may be multiple clip distances, technically, so if we implement this workaround, we'll be back at square one with regards to general support for this. |
Yeah I just realised that table 5.3 of https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf states that it can be an array of floats. I'll have a go at using the array type in the output struct so instead of |
To clarify, the reason we can't do is is because a wrapped array doesn't count for
|
I think what I've got here works pretty well now. Just FYI, what template<typename T, size_t Num>
struct spvUnsafeArray
{
T elements[Num ? Num : 1];
thread T& operator [] (size_t pos) thread
{
return elements[pos];
}
constexpr const thread T& operator [] (size_t pos) const thread
{
return elements[pos];
}
device T& operator [] (size_t pos) device
{
return elements[pos];
}
constexpr const device T& operator [] (size_t pos) const device
{
return elements[pos];
}
constexpr const constant T& operator [] (size_t pos) const constant
{
return elements[pos];
}
threadgroup T& operator [] (size_t pos) threadgroup
{
return elements[pos];
}
constexpr const threadgroup T& operator [] (size_t pos) const threadgroup
{
return elements[pos];
}
}; and returns that in functions instead: static inline __attribute__((always_inline))
spvUnsafeArray<float, 1> xyz()
{
spvUnsafeArray<float, 1> y;
return y;
} This means that they don't have to do |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Amazing stuff, I'm excited to see this happening!
I'd prefer to make this work with inner
instead of defining this complex template in the code like SPIRV-Cross does.
Are we all set? |
const auto _tmp = type10 {v_uv, _.gl_Position, _.gl_PointSize, {_.gl_ClipDistance[0]}}; | ||
return main2Output { _tmp.member, _tmp.gl_Position1, _tmp.gl_PointSize1, {_tmp.gl_ClipDistance1[0]} }; | ||
const auto _tmp = type10 {v_uv, _.gl_Position, _.gl_PointSize, _.gl_ClipDistance}; | ||
return main2Output { _tmp.member, _tmp.gl_Position1, _tmp.gl_PointSize1, {_tmp.gl_ClipDistance1.inner[0]} }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wait, so here we can't just do _tmp.gl_ClipDistance1
for the last argument?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nope, unfortunately not. Not gl_ClipDistance1.inner
either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm, I'm concerned now, a little bit. So when we are initializing the I/O struct, we can't just pass _tmp.gl_ClipDistance1
because it's an array. But how would this work in other places? I.e. if you have a user function returning a user struct, then passing { xxx.inner[0], xxx.inner[1] }
would just fail horribly, since the only field of the target struct is inner
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Afaik, only having a single pair of those {
brackets when constructing a struct is okay if it only has one field. I think more testing would be nice, but I haven't found a (trivial) example that this breaks. Something like the following, for example, is fine:
#version 450
vec3[8] hello_world() {
vec3[8] xyz;
return xyz;
}
struct X {
vec3[8] yyy;
};
vec3[8] hmm2() {
X x;
x.yyy = hello_world();
x.yyy[1] = vec3(3.0);
return x.yyy;
}
layout(location = 0) out vec4 colour;
void main() {
vec3[8] xyz = hmm2();
colour = vec4(xyz[7], 1.0);
}
->
#include <metal_stdlib>
#include <simd/simd.h>
struct type3 {
metal::float3 inner[8u];
};
struct X {
type3 yyy;
};
constant metal::float3 const_type1_ = {3.0, 3.0, 3.0};
type3 hello_world(
) {
type3 xyz;
return xyz;
}
type3 hmm2_(
) {
X x;
type3 _e13 = hello_world();
for(int _i=0; _i<8; ++_i) x.yyy.inner[_i] = _e13.inner[_i];
x.yyy.inner[1] = const_type1_;
return x.yyy;
}
void main1(
thread metal::float4& colour
) {
type3 xyz1;
type3 _e13 = hmm2_();
for(int _i=0; _i<8; ++_i) xyz1.inner[_i] = _e13.inner[_i];
metal::float3 _e15 = xyz1.inner[7];
colour = metal::float4(_e15.x, _e15.y, _e15.z, 1.0);
return;
}
struct main2Output {
metal::float4 member [[color(0)]];
};
fragment main2Output main2(
) {
metal::float4 colour = {};
main1(colour);
return main2Output { colour };
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean, specifically for arrays.
struct Foo {
xxx: array<f32; 3>;
};
fn bar() -> Foo {
return Foo(array<f32;3>(1.0, 2.0, 3.0));
}
My understanding is that this PR right now is not going to handle this. Ideally, we'd want it to generate something like
const auto _tmp = Foo { .. };
return { { _tmp.xxx[0], _tmp.xxx[1], _tmp.xxx[2] } };
This will fail because the produced Foo
would be declared with the inner
in it, but the return
code doesn't use it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, I don't understand how type1 {1.0, 2.0, 3.0}
part works :(
type1
is a structure with 1 element. So shouldn't it yell at us for providing 3 elements instead of 1?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I'm not 100% sure either, but what seems to happen is that (as I wrote in #764 (comment)):
Afaik, only having a single pair of those
{
brackets when constructing a struct is okay if it only has one field
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same thing is allowed in C:
struct Arr {
float inner[4];
};
int main() {
struct Arr arr = { 1.0, 1.5, 2.0, 2.5 };
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uh oh, in both MSL and C it lets you skip fields in the constructor though so
Foo bar1(
) {
return Foo {type1 {1.0}, 2};
}
also works?? With no warnings or errors 😟 ??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's going to happen if the array only has one element? How is the compiler going to figure out if the initializer is for this field or for the inner
itself? And initializers don't even have to be provided for all the fields. Ugh, C is weird :(
I think so! I hope this doesn't break any vertex shaders. |
I'm royally confused about how C arrays work, and that is going to make the maintenance of this code to be difficult. |
Assigning arrays by value works fine since all arrays are now wrapped by a struct.
Assigning arrays by value works fine since all arrays are now wrapped by a struct.
Assigning arrays by value works fine since all arrays are now wrapped by a struct. Co-authored-by: teoxoy <[email protected]>
This fixes #741. Unfortunately it currently breaks the
quad-vert.msl
snapshot with this error:This right solution to fix this test would be for
gl_ClipDistance
to be treated as afloat
instead of afloat[1]
inmsl-out
.