Skip to content
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

Constant buffers (cbuffer) #94

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft

Conversation

hekota
Copy link
Member

@hekota hekota commented Nov 5, 2024

Initial description and design ideas.


These should be lowered to `target("dx.TypedBuffer", ..)`.Detailed design TBD.

### Lowering constant buffer variable access

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to understand how the layout rules for SPIR-V will work. The only real problem is cbuffers. There is a problem because cbuffers have a different layout than other buffer types, and I don't know how that will be represented in llvm-ir.

Here is the example I am thinking about: https://godbolt.org/z/f45r7vEoG.

In llvm-ir, will the load from the cbuffer be a single load of an object of type S with the cbuffer layout? Will the store to the structured buffer store an object of type S with the structured buffer layout? If the answer is yes to both, does there need to be a conversion from one to the other in between the load and store?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have just updated the document. We would like to include the cbuffer layout on the LLVM target type so the backend does not need to know the specific cbuffer layout rules. Alternatively, it could be a type annotation, but we would like to investigate the including in on the target type first. It probably does not answer your question though.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that the conversion from one to the other will look like an address space cast, or something like that. We'll need to make sure we aren't mixing raw memory operations between cbuffer memory and other resource memories, but as long as we have each type of thing in its own address space the boundaries themselves should be clear at least.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@hekota Thanks. That answers part of my question and was in line with what I was thinking. The layout rules are a language feature. This is consistent with what I see in C/C++. The basic types are encoded in the data layout, and then the layout for the structs are set by being explicit with the padding in the struct.

@bogner I'll wait to see what you do. I don't see how the address space cast will work because that works on pointer, but we need to cast after the load. We also want to make sure that whatever we do, it can be easily optimized. In SPIRV there is an OpCopyLogical instruction to do the cast, when it cannot be optimized away.


## Introduction

Shader inputs usually include a number of constants which are stored in one or more buffer resources in memory with specific packing rules. These resources can be organized into two types of buffers: constant buffers and texture buffers. This document describes design decisions related to constant buffers.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we try and format the source md to 80 columns.

- How to encode the cbuffer layout into LLVM target type
- How to implement `ConstantBuffer` member access
- Handling of `$Globals` constant buffer
- Nested `cbuffer` declarations
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put together a bit of an example that illustrates how cbuffer works in various interesting cases here:
https://godbolt.org/z/E4cdx7xj8

Same shader with FXC:
https://shader-playground.timjones.io/39c0683735c0cd79b5970b5116765940

ConstantBuffer<MyConstants> CB;
```

In this case the buffer variables are referenced as if they were members of the `ConstantBuffer` class: `CB.F`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as if they were members of the ConstantBuffer class: CB.F

I think it is more like as if CB was of type MyConstants and the fields are members of that object.


### Parsing `cbuffer` Declaration

In Clang frontend the `cbuffer` declaration will be parsed into a new AST Node called `HLSLConstantBufferDecl`. This class will be based on from `NameDecl` and `DeclContext`.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

be based on from NameDecl and DeclContext.

I think the "from" shouldn't be there.


`ConstantBuffer` definition will be added to the `HLSLExternalSemaSource` the same way as other resource classes. It will have a resource handle with `CBuffer` resource class and the contained type would be the template type argument. It will be handled as other resources classes, for example it can be passed into a function.

At the same time Clang needs to recognize this class represents a constant buffer and the contained type fields are accessed using the `.` operator on `ConstantBuffer` instance. In other words treating `ConstantBuffer<MyConstants> CB;` as if it was declared as `cbuffer __CB { MyConstants CB; }`. The exact way how to do this is TBD.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
At the same time Clang needs to recognize this class represents a constant buffer and the contained type fields are accessed using the `.` operator on `ConstantBuffer` instance. In other words treating `ConstantBuffer<MyConstants> CB;` as if it was declared as `cbuffer __CB { MyConstants CB; }`. The exact way how to do this is TBD.
At the same time, Clang needs to recognize that this class represents a constant buffer and the contained type fields are accessed using the `.` operator on `ConstantBuffer` instance. In other words treating `ConstantBuffer<MyConstants> CB;` as if it was declared as `cbuffer __CB { MyConstants CB; }`. The exact way how to do this is TBD.

Or:

Suggested change
At the same time Clang needs to recognize this class represents a constant buffer and the contained type fields are accessed using the `.` operator on `ConstantBuffer` instance. In other words treating `ConstantBuffer<MyConstants> CB;` as if it was declared as `cbuffer __CB { MyConstants CB; }`. The exact way how to do this is TBD.
Clang needs to recognize that this class represents a constant buffer and the contained type fields are accessed using the `.` operator on `ConstantBuffer` instance. In other words treating `ConstantBuffer<MyConstants> CB;` as if it was declared as `cbuffer __CB { MyConstants CB; }`. The exact way how to do this is TBD.

Comment on lines 115 to 116
- Using type annotations to store cbuffer layout information. Subtypes would have its own layout annotations.
- This is something we can fall back to if encoding the cbuffer layout on LLVM target type turns out to be too unwieldy, especially when it comes to encoding the layout of an array of structures.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be interested to know why the proposed solution was chosen rather than the type annotation one. What's the downside to using type annotations?

Comment on lines 84 to 86
One possibility is compressing the list of offsets into a smaller number of integers - taking advantage of the fact that outside of `packoffset` use the difference between two adjancent offsets is never more than 16. The compression could also include repetition construct that would help with encoding of array offset. But, as with any compressions, there's always the chance of degenerate cases that will end up with the compressed shape being the same size or larger than the original.

> Note: The most tricky part of the layout encoding are probably arrays of structures because the structure-specific layout gets repeated many times and might not be easy to compress. One idea how to solve this could be translating structures embedded in `cbuffer` into separate target types with their own encoded layout and including them in the `cbuffer` target type. It is not clear though if this is possible (probably yes) or if it would actually make things easier or not.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should be encoding the offset of each element of arrays, nor should we be encoding the offsets of inner struct members for each instance of them in the outer struct. I think we need to associate type information, containing member offsets, with struct types, like we use for type annotations in DXIL. This is a lot more efficient, only encoding the offsets we need once. Then we need some way to connect the target type with the annotated type, or the annotation metadata itself. Perhaps we can have cbuffer type metadata which references the target type and the contained type, then lists member info, including these offsets. Other member info will be useful/necessary, such as matrix identification and orientation information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: No status
Development

Successfully merging this pull request may close these issues.

5 participants