-
Notifications
You must be signed in to change notification settings - Fork 353
Replace [[block]] attribute by [[layout(...)]] #1361
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
Conversation
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 like this proposal more than #1393 because of its simplicity. "You want a layout? You got your layout. Easy peasy."
What happens if I want to use the same struct in a storage buffer and a uniform buffer? What if my fields would just happen to have the same alignments/sizes in both contexts? Can I assign from an instance of the storage struct to an instance of the uniform struct?
(If the answer is "you can't," that's not a dealbreaker, or even necessarily bad; I'm just trying to understand the proposal.)
| type RTArr = [[stride(16)]] array<vec4<f32>>; | ||
| [[block]] struct S { | ||
| [[layout(storage)]] struct S { | ||
| [[offset(0)]] a : f32; |
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.
Would you still need these [[offset(n)]] annotations?
What if the author wanted to specify them?
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.
We wouldn't need them, but we can discuss separately if we still want them to stay.
|
(This proposal was posted long before #1393, but) In my opinion, this proposal produces considerably less readable code than #1393. With #1393, reading code requires only that you know the layout algorithm (very simple) and the default alignments/sizes (not trivial, but pretty concise). The alignments/sizes only ever diverge from the defaults when explicitly written. This proposal requires the reader to know (up to) one layout algorithm per layout, and one table of alignments/sizes per layout. I think this proposal also makes it much harder to match existing layouts (such as an engine's existing C struct that is set up to interoperate with GLSL and HLSL code) unless our rules match those of GLSL and HLSL (which are complex and difficult). |
Then you just use the
With this proposal, there is no "context" to derive the alignment/sizes from, other than the
Since it's the same type, you can assign things of this type :)
The fact that you see (or write) the alignment/sizes/offset/spans/whatever does not mean you don't need to know what your buffer is used for, which means, you have to know the layout requirements. So I don't clearly see how #1393 makes it cleaner. You still have to know the exact uniform layout rules (std140) in order to answer a question of whether this structure is going to be compatible with uniform buffers. That is, perhaps, the core of my larger point: we can't pretend layouts don't exist. We have those defined in requirements for uniform/storage structures, and the choice is either: Either way, in order to use a structure in host-shareable way, they need to know those (2-4?) layouts you mentioned.
I also believe this point about matching is exaggerated. It's trivial to write a tool that would spell out the offsets for you, given a struct definition (i.e. we can get this into Naga/Tint/shader playground/you name it). It's a task for the machine, not a human. The reality is that most structs will just work as is, and that's also mentioned in #1393. |
I don't think a code-reader needs to know that - I assume they would know if their shader doesn't compile. OTOH, it's much harder to know that your data is getting fed into the shader correctly, for every field. Therefore it's more important a code-reader be able to understand the latter (the data is being fed in correctly) than the former (the shader will compile).
OK, I buy that. It's not core to my opinion though. |
I think I'm starting to understand your point better. Somehow, the discussion on #1393 wasn't clicking the main selling point of the proposal for me (sorry @ben-clayton ! I was asking the wrong questions, possibly). So here is what I see. Both #1393 and this PR could benefit from tooling that tells you the exact offsets of the members. The difference, however, is how difficult it is for the reader to compute them in their head. In #1393, they'd only need to know the "default layout", and any changes have to be explicitly stated with Consider me interested :) Just a bit concerned about the |
Oh, that's awesome! I missed that when reading the proposal. So Would there be any benefit to having a 3rd keyword, |
I believe uniform buffers should be strictly more constraining than storage buffers. |
Yes. That's the point. Earlier on:
My trouble with this is this PR would contemplate a bunch of named layouts.
This is a bug, not a feature. What #1393 gives is a default that "just works" both clearly and performantly, but gives you simple tools to dive into the details and do things exactly as you like, if the rules allow. Separating the layout algorithm from the rules is really important (thanks @kainino0x for emphasizing that). In future, if hardware and driver stacks evolve to support more things like scalar layout, then we can turn on a switch to relax the rules, but the algorithm for layout is the same. |
After sleeping on this I realized that the layout algorithm is already separated out (see https://gpuweb.github.io/gpuweb/wgsl.html#memory-layout-intent, just need to become normative), and it's going to stay separated no matter what path we take (even the offsets, fwiw). So the algorithm separation is not an advantage of #1393, and is not specific to it. Here is some perverted pseudo-code for #1393 const DEFAULT_LAYOUT: Layout = ...;
const STORAGE_RULE: Table = ...; // validation rules for storage buffers
const UNIFORM_RULE: Table = ...; // validation rules for uniform buffers
let custom_decorations; //`[[size]]` and `[[align]]`
let struct_layout = layout_algorithm(struct_declaration, DEFAULT_LAYOUT, custom_decorations);
validate(struct_layout, rule);And here is one for this PR: const STORAGE_LAYOUT: Table = ...;
const UNIFORM_LAYOUT: Table = ...;
let layout_decoration; //`[[layout(general)]]` or `[[layout(uniform)]]`
let struct_layout = layout_algorithm(struct_declaration, layout_decoration);
// no validation needed!This is meant to show how similar the approaches really are:
The spec complexity in case of #1393 is a tiny bit higher (for the size/align decorations), and the users would have more friction to get the uniform buffers going, but the end result may be less error-prone, since tricky parts will have to be annotated with those decorations. |
|
There's a key difference in who is paying for the complexity of understanding the rule set, at what stage of development. Premise: Programmers read code many times more often than they write code. With #1393, when a programmer looks at their struct, and can quickly read off where their data should be, and do it with high confidence. They only remember one set of rules. (With current spec requiring offsets everywhere, they remember zero rules, but that has worse writability.) With named layouts, the programmer has to remember multiple sets of rules (that confuse everybody). They only get feedback when they get the data in the wrong place when integrating with the host side. This has poor readability and a bad feedback channel. Separate point: The memory-layout-intent section is really imprecise, because of ambiguities about how statements reference other statements and how far they recurse, and priority rules. It's not suitable as normative spec language. |
|
@dneto0 that's a very reasonable line of thought!
Generally speaking, it's the same feedback as with #1393, i.e. you have something in your head, and you can run a tool to verify your assumptions. So you are talking about more of a "easier to get right" or "easier to reason about" point here, since following the default layout + explicit decorations makes it easier for the reader than just knowing the layout name. |
|
Superseded by #1393. |
This CL adds unimplemented stubs for the `dpdyCoarse` builtin. Issue: gpuweb#1253
Fixes #621
Related to #1339
Motivation
For the purposes of WebGPU V1 we are considering 2 layouts: one suitable for all the buffers, and one for storage buffers only (better packed). We have the developers computing the layouts in their heads and putting up the numbers into WGSL today as
[[offset(xx)]]attributes on structure fields. The offset of a field depends on:This is problematic for the following reasons. First, it's just tedious and mechanical. Computing the offsets should be a job for the machine, not a human. It's not a problem for SPIR-V since it's not written by humans, but it is a problem for us. Refactoring becomes painful: changing the type affects all following fields, reordering is hard, etc.
Secondly, it's not obvious, by looking at a structure, where it can be used. We have the
[[block]]saying it is used in storage or uniform buffers, but we can't say whether it's valid to be used in a uniform structure.I believe there is a solution that addresses both of these concerns, by renaming the
[[block]]and adding an explicit layout to it. We can call them by proper names, i.e. std140 becomes the "general" layout, since it can be used for any host-shareable structures, and std430 becomes the "storage" layout.The
[[offset]]attribute can be removed if this PR lands.Edit: here is a code example
Issues
(1) It becomes more difficult to match the CPU code layout with GPU code layout.
I believe this can be addressed with simple tooling. The famous shader playground could just annotate the fields online, for example. Moreover, the CPU side structures, at least in C++ and Rust, don't have explicit offsets either. So some manual computation or tooling is needed for this side anyway, before anything can be compared.
(2) The list of layouts can grow, with things like "relaxed" and "scalar" on the radar.
My understanding is that exposing those layout explicitly will make it more clear to the users. If they aren't spelled out, we'd still need the actual specification in WGSL about how the offsets or spans are restricted, based on the layout. So we are going to need to define it, and it becomes a question of whether the layout is explicit or implicit.
(3) No ability to force the large gaps between members
Users can do it manually with padding, and the use case is fairly small, so I don't think we should cater to this.