-
Notifications
You must be signed in to change notification settings - Fork 304
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WGSL] Proposal: Default Struct Layout #1393
Comments
Thank you for collaborating on this proposal and writing it down in such great detail! First, the table in "Default alignments and sizes" appears to be an application of the rules we already have in the spec (https://gpuweb.github.io/gpuweb/wgsl.html#memory-layout-intent) to all the types. If I understand correctly, it just spells out the values we already have, not proposes any new logic there. Secondly, the proposal has 2 structure layouts provided:
Then, the proposal spells out how the "default" layout can be automatically computed, without the need of However, there is no help in figuring out the "restricted" layout for uniform buffers. It's as implicit as it is today in the spec (i.e. the rules are spelled out, but there is no assistance from the compiler to get them). Thirdly, the proposal introduces new type decorations - the If I got these points right, then my only concern about this is that it makes uniform buffers more complicated for no good reason. It's hard to find a pipeline today without any uniform buffers, they should be easy to construct correctly. The simplest way to solve this would be to make the user to opt into a layout instead of having a default one, like #1361 suggests. |
It quite possibly matches perfectly (I'll try and take a deeper look tomorrow). If there are differences, then we can discuss whether we'd want to update this proposal or the existing spec.
Not sure I entirely follow. I've tried to give examples where the default layout should always work (storage buffers), and examples of where the default layout may give errors for uniform buffer usage, and examples of how you might fix these.
There is help in that if you try to compile an incompatible layout, the compiler can provide assistance with decent compiler error messages by describing which fields are misaligned and/or which structs are incorrectly sized.
Yes - tighter packing if / when we permit such layouts.
I believe for the most part, the default layout rules will allow structs to be used as uniform buffers without significant manual fixups. There are few obviously cases where it would likely fail ( So comparing against #1361, with this proposal:
|
Thank you for the answer!
Sorry, I don't understand this part. How can we not talk about uniform buffer layouts?
I read it as "no help".
If these tighter packing rules aren't in the spec, then I think it only supports the point that
I don't think #1361 puts any more restrictions on this. A |
The spec will specify the requirements for the uniform data, it's just not defined by this proposal. See the very top of the proposal: "If the wider group backs this proposal, next steps would be to agree on the mandatory layout rules for each storage type."
"error: Uniform buffer layout rules require field 'foo' to be 16 byte aligned, but has offset 20." is a lot more help than none. But it isn't an automatic layout, which I think is what you're asking for.
I personally think that the
Okay, fair enough. |
Ok, so there have to be 2 layouts defined anyway. It's just that the proposal only has one but not the other, gotcha!
That kind of error is the minimum we can do. It's what any proposal would have to do anyway. When I asked for "help", I meant what does it offer on top of the minimum.
I don't quite understand this bit. It's no surprise that this proposal doesn't require me to remember 2 layouts, since it totally excludes the uniform layout from the scope, but assumes that it's going to be there. |
There is one implicit default layout. At least one other layout will be defined in the spec for more restrictive storage classes. None of these non-default layouts are implicit, and are purely used for compile time validation of what you've written.
If you can remember the default layout rules, you can read any WGSL structure and know what the field offsets are. Writing a uniform buffer struct correctly first time would also require knowing other layout rules, but the compiler can provide assistance if you don't know the rules off by heart. |
I think this proposal is going in a good direction (full disclosure: I haven't read #1361 yet). A few questions/statements:
I think it would be a mistake to apply this to
Given the distinction between Put another way, I don't see why
In this example, the |
This isn't the way I think about it. There is one layout algorithm defined (now and forever). There is one set of default sizes/alignments which are used by that algorithm if not overridden (now and forever). There are two sets of layout validation rules: those for uniform and those for storage (and more could be added later).
I believe they would not.
Note this just got tweaked to be
This restriction is specific to struct-typed members and comes from underlying layout rules, e.g. Vulkan's Standard Buffer Layout:
|
I find the fact that uniform buffers seem to be demoted to second class citizens (because they need manual author fixup) distasteful. |
Assuming I've done my homework correctly, no, you wouldn't. It might not be the most optimal packing, however.
Maybe. ,Decorations on type aliases isn't a highly needed feature (
This was a decision to try and keep the default layout compatible enough so it wouldn't cause backend translation issues. We might be able to relax it further, but this seemed like a good compromise.
They don't always. I suspect needing to fix them up would be less common than you might expect. |
This was done because the more restricted layout for uniform buffers is a legacy constraint. The devices that require it are diminishing over time. |
(This is in comparison to #1361) |
The future is bright though. The awkward constraints on uniform buffer is a legacy thing. (D3D even calls it legacy cbuffer layout). Technology is moving such that the constraints will age out. At some point future authors will be able to take the more relaxed rules for granted. (subject to how they declare they are opting in, cf. #600) |
Resolved to accept something along these lines. Big open questions from meeting:
|
@dneto0 voiced preference for field-only - examples 2 and 3. If anyone has preferences for size and alignment decorations on structs (example 1) then please voice your opinions now, otherwise I'll start a PR with just the field and array decorations in the next few days. |
I prefer size and alignment on fields because that's where the information will be used. The size and alignment of a struct really only matters when used in context of another struct. The only net benefit I can see of putting alignment and size on the struct type is reuse. But that may be a false reuse because what really matters is how that struct is embedded in something else. In Ben's examples, the size of struct A only really matters in context of embedding it in struct B. |
About array strides. Array stride is a property of the array type, not the structure field at which the array is placed in the struct. Another reason is the array stride is needed for any pointers derived from a stored array.
|
@dneto0 I share the desire to limit the decorations to places where they are required, i.e. have them on fields since this is a more constrained/simple model to implement and specify. However, I'm not so convinced that we should make an exception for the stride. I think we could go either way, in which case making it consistent with Here is the reasoning:
struct InnerArray {
[[stride] data: array<Foo, 5>;
};
struct Bar {
[[stride]] data: array<InnerArray, 10>;
};
|
I'm happy to remove the
I sort of buy this argument, but I also see dneto's argument here - especially regarding multidimensional arrays. On reflection, I actually think |
This wouldn't block multi-dimensional arrays, it would just require an intermediate structure to be declared. Not a big deal.
Good point! I'm thinking if we should just make the |
IMO, it's not an exception:
I think of stride as implying it's repeated more than once (e.g. the stride between footsteps when walking), so I wouldn't agree with that. But if stride were to move out to the member, I'd be fine merging the two terms (saying there's only one "footstep" for non-array members, multiple for arrays). |
It's a matter of interpretation. If you think about arrays similar to structures, like I mentioned in #1447 (comment): // [[stride(S)]] array<T, 3>
struct Array {
[[size(S)]] _element0: T,
[[size(S)]] _element1: T,
[[size(S)]] _element2: T,
}; Then That's why I'm saying "we could go either way". So if we are to pick an interpretation among the two, it seems to me that the one with less exceptions is nicer, isn't it? |
I think anything attached to members is part of the outer type, they just are much more convenient to syntactically put on the members and not outside the type. OTOH with an array there's no other place to put it, and it applies equally to every member. In that sense, both structs and arrays have "properties of the type" (even though syntactically one is inside and one is outside the definition) that only make sense in host-shareable contexts. Anyway, I'm only arguing mental model here, and numerous mental models are valid here. So I'm not really strongly any particular way, just looking for what's both clear and convenient. |
#1447 has been merged. Closing. |
Also removes need for passing additional information into `makeBinaryF32Case` Fixes gpuweb#1391
Internally at Google @dj2, @kainino0x, @dneto0, @alan-baker and I have been discussing struct layout ideas. We have (mostly) converged on this proposal.
If the wider group backs this proposal, next steps would be to agree on the mandatory layout rules for each storage type.
WGSL Struct Alignment Proposal
Design principles
Provides a single default layout for any host-shareable datatype (primitive, structure, array).
The layout is always valid for storage buffers.
The rules are practical - not too wasteful.
The rules are forward-looking - std140 / “extended” is considered legacy.
Structure layout is defined in terms of “size” and “layout” with no additional rules. The offset of any field is always exactly:
roundUp(alignment field, offset previous field + size previous field)
.User defined data types and individual structure fields may diverge from the default layout, by using annotations to override the default alignment, size, and array stride values.
[[align(n), size(n)]]
can be decorated on user-defined types and structure fields.[[stride(n)]]
on array types.In most cases, this is required to make a datatype conform to the restrictions of uniform buffers.
It can also be used to specify layouts denser than the default, e.g. to use with future optional capabilities (e.g. “scalar” layout).
Rules for valid uniform/storage buffer layouts are defined separately from the algorithm that defines the default layout
This proposal intentionally does not define the layout rules for the various storage classes, but does demonstrate how more restrictive storage class layouts are supported.
Proposal
Default alignments and sizes
Every data type has a default alignment and size value. When
[[size(n)]]
or[[align(n)]]
decorations aren’t specified for the type, they’re the same as theAlign(S, storage)
andExtent(V, storage)
in the current WGSL draft spec.Default alignment and size for each WGSL data type:
f32
i32
u32
vec2
vec3
vec4
[[stride(S)]]
array<T, N>
alignof(T)
N * S
[[stride(S)]]
array<T>
alignof(T)
matXxY
(col-major)alignof(vecY)
sizeof(array<vecY, X>)
mat2x2
mat3x2
mat4x2
mat2x3
mat3x3
mat4x3
mat2x4
mat3x4
mat4x4
struct S
roundUp(alignof(S), offsetof(last field) + sizeof(last field))
Structures and type aliases may have their defaults overridden by using the
[[size(n)]]
or[[align(n)]]
decorations on their declaration:Example:
Default array stride
array<T, N>
roundUp(alignof(T), sizeof(T))
array<T>
roundUp(alignof(T), sizeof(T))
Structure Layouts
The rules for aligning a structure field:
[[align(n)]]
and / or[[size(n)]]
decorations, in which case these field decorations take precedence.roundUp(alignment field, offset previous field + size previous field)
.roundUp(alignof(S), offsetof(last field) + sizeof(last field))
Default Structure Layout Example
Example translated to GLSL
Observe that:
struct A
's size is padded to 24 bytes (roundUp(20, 8)
)struct A
's alignment is equal to the field with the largest alignment (w
)c
, packs into end of thevec3
ofb
.g
has the alignment of the array element (A
)The default layout rules will always produce layouts that are valid for storage buffers (without requiring explicit
[[align(n)]]
/[[size(n)]]
decorations).The same may not be true for non-storage buffers.
Conforming to more constrained storage classes
Some storage classes are (at least initially) likely to impose tighter constraints on structure layouts than the default layout.
It is the developer's responsibility to ensure that structure layouts are valid for all storage class uses.
Field offsets and structure sizes that are invalid for a storage class that use that structure will result in a compilation error.
For example, a uniform storage class might have a stricter set of layout rules to the default layout.
If we used the
struct B
definition from the above in a uniform buffer, we may encounter the following compilation errors:The developer can solve these compilation errors in a number of ways:
(1) Using
[[align]]
and[[size]]
on struct(2) Using
[[align]]
and[[size]]
on fields(3) Using padding fields
Related discussions: #1303, #1310, #1349. #1361
The text was updated successfully, but these errors were encountered: