Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 103 additions & 45 deletions proposals/017-shader-record.md
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
SP #017: Disambiguate `uniform` via `[[push_constant]]` and `[[shader_record]]`
=======================================================================
SP #017: Disambiguate `uniform` via `Payload<T>`, `HitAttribute<T>`, and `ShaderRecord<T>`
============================================================================================

This document proposes a first step toward resolving ambiguities and bugs that
arise from the current usage of `uniform` (and to a lesser extent, `varying`)
in Slang, particularly in ray tracing pipelines.

<!-- It suggests introducing new types such as `PushConstant<T>` and
`ShaderRecord<T>`, which can be used to unify compile-time mappings across APIs
like Direct3D and Vulkan. These new types act as clearer, more consistent
markers of data binding points, compared to the over-loaded semantics
of `uniform` parameters today. -->
This document proposes two concrete shifts to remove ambiguity around `uniform` and ray-tracing
entry point parameters:

It suggests promoting Vulkan's `[[push_constant]] uniform T` and
- It proposes to make `uniform` universally mean "push constants" at entry points across all backends
and pipeline stages with Slang version 2026. Where push constants are not first-class, Slang maps
them to the closest equivalent (e.g., D3D root constants or a small constant buffer; OptiX launch parameters).
- It suggests introducing new types such as `Payload<T>`, `ShaderRecord<T>`, and `HitAttribute<T>`,
which can be used to unify compile-time mappings across APIs like Direct3D and Vulkan. These new types act
as clearer, more consistent markers of data binding points, compared to the over-loaded semantics
of `uniform` parameters today.

Together, these changes provide clear, explicit markers of data binding points without overloading
`uniform`, `in`, `out`, and `inout` semantics on ray tracing entry points.

<!-- Previously, we were considering [[annotations]], but with some discussion, Type<T> would enable future
language improvements like pipeline interfaces. -->

<!-- It suggests promoting Vulkan's `[[push_constant]] uniform T` and
`[[shader_record]] uniform T` global annotations to more universal `[[push_constant]] T` and
`[[shader_record]] T` entrypoint parameter annotations. These annotations can then be used
to unify compile-time mappings across APIs like Direct3D, Vulkan, OptiX, etc. These
new annotations act as clearer, more consistent markers of data binding points,
compared to the over-loaded semantics of `uniform` parameters today.
compared to the over-loaded semantics of `uniform` parameters today. -->

Additionally, this document proposes annotations to more clearly distinguish between
<!-- Additionally, this document proposes annotations to more clearly distinguish between
payload and hit attribute entry point parameters, extending the use of `[[payload]] T` and
by introducing `[[hit_attribute]] T` respectively. This would allow users to more explicitly
mark payload parameters as being constant, eg `void anyhit([[payload]] in MyConstPayload p)`,
where current usage, `void anyhit(in MyConstPayload p)`, would otherwise incorrectly map
`p` to hit attribute, and `void anyhit(MyConstPayload p)` would map `p` to the shader record.
`p` to hit attribute, and `void anyhit(MyConstPayload p)` would map `p` to the shader record. -->

Status
------
Expand Down Expand Up @@ -140,13 +151,37 @@ in complexity.
Global attributed uniform buffers prevent more localized and reduced usage of
per-dispatch constant values.

The following attempts to disambiguate the two overloaded uses of `uniform` that
Slang users face today.
The following attempts to disambiguate the overloaded uses of `uniform`, `in`, `out`, and
`inout` that Slang users face today.

Proposed Approach
-----------------

<!-- 1. **Introduce `PushConstant<T>`** -->
1. Define `uniform` as push constants everywhere
- At entry points, `uniform` parameters always map to push-constant-like storage.
Where the target lacks native push constants, Slang lowers to the closest equivalent
(e.g., a small `ConstantBuffer<T>` with root constants on D3D, or OptiX launch parameters).
- This removes stage- and backend-dependent ambiguity. Developers keep using `uniform` for
per-dispatch/draw constants with identical semantics across ray tracing, compute, and graphics.

2. Introduce `ShaderRecord<T>` for per-shader-record data
- `ShaderRecord<T>` is a first-class generic type valid only on ray-tracing entry points.
- Entry point parameters typed as `ShaderRecord<T>` designate data that lives in the
per-shader record (Vulkan/OptiX SBT; D3D local root signature). Slang reflects this intent so
applications can bind appropriately on each backend.
- Parameters of type `ShaderRecord<T>` are input-only. Using `out`/`inout` is illegal.

3. Introduce `HitAttribute<T>` for hit attributes
- `HitAttribute<T>` is a first-class generic type valid only on ray-tracing entry points that
maps to hit-attribute registers provided by built-in or user geometry intersectors.
- Parameters of type `HitAttribute<T>` are input-only. Using `out`/`inout` is illegal.

4. Introduce `Payload<T>` for ray and callable payloads
- `Payload<T>` is a first-class generic type valid only on callable and ray-tracing entry points
that maps to user-controlled payload registers.
- `in`, `out`, and `inout` are all okay and legal on `Payload<T>` parameters. The default is `inout`.

<!--
1. **Extend the use of `[[push_constant]] T` to entry point parameters**
We would extend the use of `[[push_constant]]` to become semantically equivalent to:
```
Expand Down Expand Up @@ -182,7 +217,7 @@ Proposed Approach
behavior across targets. The annotation being universal would then signal that behavior is well defined across
all possible backends, rather than specifically to Vulkan.

<!-- 2. **Introduce `ShaderRecord<T>`** -->
2. **Introduce `ShaderRecord<T>`**
2. **Extend the use of `[[shader_record]] T`**
Likewise, we would extend the use of `[[shader_record]]` to become systematically equivalent to:
```
Expand Down Expand Up @@ -249,20 +284,29 @@ Proposed Approach
`inout` being the default.

This would resolve the ambiguity regarding `in` and implicit `uniform` incorrectly mapping to hit attributes
and shader records.
and shader records. -->


Detailed Explanation
--------------------
1. **Language-Level Model**
<!-- 1. **Language-Level Model**
In a future ideal version of Slang, we might introduce explicit syntax for
specifying data "rates," e.g. `[[thread_group]]`, `[[wave]]`, `[[lane]]`, or more.
This is consistent with advanced GPU programming models that differentiate per-thread,
per-wavefront, per-group, per-dispatch, etc. However, this proposal focuses on
disambiguating push constant uniforms from shader record uniforms, as these are the most
pressing distinction.

2. **Entry-Point Parameter Rules**
pressing distinction. -->

1. **Entry-Point Parameter Rules**
* Parameters declared `uniform` at entry points always map to push-constant-like storage
(push constants, root constants, or a small constant buffer) regardless of stage/backend.
* Parameters typed as `ShaderRecord<T>` map to per-shader-record data (Vulkan/OptiX SBT;
D3D local root signature).
* Parameters typed as `Payload<T>` map to payload registers. `in`, `out`, and `inout` apply,
Copy link
Contributor

Choose a reason for hiding this comment

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

My understanding of these wrapper types is that they should be treated as if they are pointer types. So Payload<T> is effectively a T*. It is not meaningful to have inout T* or out T* because that just means the pointer itself is mutable. Instead, we probably want to have RWPayload<T>, WPayload<T> to represent payload pointers with read/write or write-only access.

Copy link
Contributor

Choose a reason for hiding this comment

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

We should also just make it invalid to use in inout or out on ray tracing entrypoint parameters.

Copy link
Author

Choose a reason for hiding this comment

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

Agreed. I was running into this today with the refactor. I can make it invalid too use in/out/inout on RT entrypoint parameters.

Copy link
Author

Choose a reason for hiding this comment

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

I’m not sure I’m aligned on the RWPayload though. With PAQs, users annotate individual fields within the structure passed as T as being read, write, or read write. It generally never makes sense to make the entire payload read-only.

My thought was to instead look at the T being passed into the payload type and assume that that’s where PAQs information comes from.

Then otherwise drop in/out semantics entirely

with `inout` as the default.
* Parameters typed as `HitAttribute<T>` map to hit-attribute registers and are input-only.

<!-- 2. **Entry-Point Parameter Rules**
* If an entry point parameter is annotated with `[[push_constant]]`, Slang recognizes it as
data bound as push constants (Vulkan), launch parameters (OptiX), or root constants (D3D).
Any additional `uniform` attribute following `[[push_constant]]` is optional, as `uniform` is
Expand All @@ -275,20 +319,31 @@ Detailed Explanation
above explicit types, the compiler may insert an "implicit" `[[push_constant]]` to optimize
performance based on the target language.
* Default functionality of `uniform` will be to map to `[[push_constant]]` for all entry point
types.
types. -->

3. **IR and Reflection Impact**
* For Vulkan, the compiler can remap annotations to the necessary `[[vk::push_constant]]` or
`[[vk::shader_record]]` attributes under the hood.
2. **IR and Reflection Impact**
* For D3D, the compiler would still produce a `ConstantBuffer<T>` or resource
Copy link
Author

Choose a reason for hiding this comment

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

This section needs to be updated. I'm not sure of what changes need to be made to slang's reflection. What's here now was originally just placeholder text, from before the proposal was moved over to the spec repo.

binding in reflection metadata, but with an additional
`slang::TypeReflection::Kind::PushConstant` or `slang::TypeReflection::Kind::ShaderRecord` hint.
This allows app code to unify or alias these buffers with local or global root
signatures.
<!--- I'm not very knowledgeable of Slang's reflection capabilities. This probably
needs a second look... --->

4. **Migration Strategy**
* Existing Slang code using `[[vk::push_constant]] ConstantBuffer<T>` remains valid but is
encouraged to migrate to `uniform` entry point parameters for clarity and portability.
* Existing code using `[[vk::shader_record]] ConstantBuffer<T>` remains valid; `ShaderRecord<T>`
entry point parameters are the recommended form.
* If either `[[vk::shader_record]]` or `[[vk::push_constant]]` or `[[shader_record]]` or `[[push_constant]]`
appear on entrypoint parameters, these will become "syntax sugar" which is transformed by slang into
the appropriate wrapper type. `[[hit_attribute]]` might be considered for addition to allow for consistency.
* Legacy usage of bare (non-`uniform`) parameters in ray tracing entry points should result in a warning, telling
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be an error after slang 2026.

the user to explicitly mark the parameter with one of: `uniform`, `ShaderRecord<T>`, `Payload<T>`, or `HitAttribute<T>`.
* Default behavior of `uniform` will occur with Slang version 2026. Otherwise, a compiler flag will be added to
enable switching default uniform behavior for RT entrypoints.

<!-- 4. **Migration Strategy**
* Existing Slang code using `[[vk::push_constant]] ConstantBuffer<T>` or
`[[vk::shader_record]] ConstantBuffer<T>` remains valid; it just becomes a more
verbose variant of the promoted annotations.
Expand All @@ -297,7 +352,7 @@ Detailed Explanation
be respected.)
* Legacy usage of bare uniform parameters in ray tracing entry points is rare. Still,
we might want to emit a warning and guidance on migration to more rate-specific annotations
while the change is new.
while the change is new. -->

5. **Example**
Rather than this:
Expand All @@ -311,27 +366,24 @@ void myAnyHitShader(
) {...}
```

We would now support the following:
We support the following, with explicit types and unambiguous `uniform`:

```
[shader("anyhit")]
void myAnyHitShader(
[[hit_attribute]] T1 a,
[[payload]] inout T2 b,
[[push_constant]] T3 c,
[[shader_record]] T4 d
HitAttribute<T1> a,
Payload<T2> b,
uniform T3 c,
ShaderRecord<T4> d
) {...}
```

* On Vulkan, constant "uniform" data is now compiled to two distinct binding regions:
* `T3` in the push constant region
* `T4` in the shader binding table record
* On D3D, the same code would reflect as two `ConstantBuffer<T>` regions, with
reflection metadata marking them as "intended for push constant" vs. "intended
for shader record."
* `[[payload]]` and `[[hit_attribute]]` distinguish which registers map to `T1` and `T2`
* Any remaining unannotated parameters would default to `[[push_constant]]`, which would match
current behavior with other entry point types.
* On Vulkan, `uniform T3` maps to the push-constant region; `ShaderRecord<T4>` maps to the SBT.
* On D3D, reflection shows `T3` as push-constant-like and `T4` as shader-record-like data so apps
can bind via root signatures accordingly.
* `Payload<T2>` and `HitAttribute<T1>` explicitly select payload and hit-attribute registers.
* Any remaining unannotated parameters should be made explicit; `uniform` is preferred for
per-dispatch constants.

It would also now be possible to specify different push constant structures in a mixed ray tracing
entrypoint setup.
Expand Down Expand Up @@ -364,8 +416,14 @@ Alternatives Considered
Still, extending `[[push_constant]]` might be a more natural approach, seeing as we already have
this annotation.

By extending `[[push_constant]]` and `[[shader_record]]` annotations, we offer a clearer
language-level model that reduces the ambiguity around uniform and bridges the gap
across GPU backends and pipeline stages. This proposal is a stepping stone toward a
more comprehensive "rates" system in Slang, while providing immediate, practical
improvements to developers.
4. `[[shader_record]]`, `[[payload]]`, `[[push_constant]]`, and ``[[hit_attribute]]`
* Early versions of this language proposal suggested to leverage Slang's attribute annotations
to disambiguate `uniform`, `in`, `out`, etc on RT entrypoints. After some internal discussion,
we decided that a `WrapperType<T>` would be a better fit, as this would open the door for
pipeline-scale use of Slang's interfaces. For example, intersection entrypoints conforming to
an LSS interface might be required to accept a standardized payload structure type.

By redefining `uniform` as push constants and introducing `Payload<T>`, `HitAttribute<T>`, and
`ShaderRecord<T>`, we offer a clearer language model that removes ambiguity and
bridges the gap across GPU backends and pipeline stages. This is a stepping stone toward a more
comprehensive "rates" system in Slang, while providing immediate, practical improvements.