0037 - Refined cbuffer Contexts
Status | Under Consideration |
---|---|
Authors |
- Planned Version: 202x
- Issues: DXC #4514
Introduction
This proposal aligns cbuffer
declarations more closely with syntactic and
semantic behaviors that are common in C and C++ to align more closely with user
expectation and reduce the need for special case handling in the compiler.
Motivation
The HLSL cbuffer
declaration is a source of irregularity within the language
semantics, and the interaction between cbuffer
declarations and their
contained declarations can be different and surprising.
In DXC, declarations inside a cbuffer
are always hoisted out to translation
unit scope, which effectively ignores the namespace nesting, however the
declaration context hierarchy within a cbuffer
is preserved except again in
the case of a nested cbuffer
which pops back out to translation unit scope.
This behavior in DXC is clearly a bug, and FXC behaves more reasonably. As an example given the following cbuffer declarations:
namespace ns {
cbuffer CB {
float a;
namespace ns2 {
cbuffer CB2 {
float b;
} // cbuffer CB2
} // namespace ns2
} // cbuffer CB
} // namespace ns
FXC @ ShaderPlayground DXC @ Compiler Explorer
In DXC, a
and b
are referenced directly with no namespace qualifications,
while in FXC they are ns::a
and ns::ns2::b
respectively (unless the
namespace contains other variable declarations, which is clearly a bug:
https://godbolt.org/z/Ph57WYexr).
In FXC, cbuffer
declarations behave more like a source range declaration that
groups global declarations into a single constant buffer rather than a semantic
declaration grouping. As such, one could replace the cbuffer
syntax in FXC
with a preprocessor pragma
with push|pop
semantics that specified which
buffer and binding to group declarations into.
This design does not follow any pattern common in C or C++, and thus runs counter to the principle of least astonishment.
Proposed solution
To simplify HLSL’s language semantics and the compiler implementation a new grammar formation is adopted for cbuffers:
\begin{grammar}
\define{cbuffer-declaration-group}\br
\terminal{cbuffer} identifier \opt{resource-binding} \terminal{\{}
\opt{cbuffer-declaration-seq} \terminal {\}}\br
\define{cbuffer-declaration-seq}\br
cbuffer-declaration\br
cbuffer-declaration-seq cbuffer-declaration\br
\define{cbuffer-declaration}\br
variable-declaration\br
empty-declaration\br
\end{grammar}
This simplified grammar disallows members of cbuffer
declarations that do not
have semantic meaning, and allows a simplification of cbuffer
scoping rules.
A cbuffer
may only be declared at translation unit or namespace scope. A
cbuffer
may only contain non-static non-constexpr variable (or empty)
declarations. All declarations within a cbuffer
declare names in the immediate
enclosing scope.
A cbuffer
is a colection of constant values provided to a shader at runtime.
As such, it should only contain constant variable declarations backed by
read-only storage in device memory that persists across the life of the
dispatch.
Notably a cbuffer
cannot contain function declarations, type declarations
(classes, enumerations, typedefs), namespace declarations, constexpr or static
variable declarations, cbuffer
declarations, or any other declaration type not
explicitly listed as allowed.