Skip to content

Commit 32135c5

Browse files
author
Tim Foley
authored
First steps toward supporting interface-type parameters on shaders (shader-slang#852)
* First steps toward supporting interface-type parameters on shaders What's New ---------- From the perspective of a user, the main thing this change adds is the ability to declare top-level shader parameters (either at global scope, or in an entry-point parameter list) with interface types. For example, the following becomes possible: ```hlsl // Define an interface to modify values interface IModifier { float4 modify(float4 val); } // Define some concrete implementations struct Doubler : IModifier { float4 modify(float4 val) { return val + val; } } struct Squarer : IModifier { ... } // Define a global shader parameter of interface type IModifier gGlobalModifier; // Define an entry point with an interface-type `uniform` parameter void myShader( unifrom IModifier entryPointModifier, float4 inColor : COLOR, out float4 outColor : SV_Target) { // Use the interface-type parameters to compute things float4 color = inColor; color = gGlobalModifier.modify(color); color = entryPointModifier.modify(color); outColor = color; } ``` The user can specialize that shader by specifying the concrete types to use for global and entry-point parameters of interface types (e.g., plugging in `Doubler` for `gGlobalModifier` and `Squarer` for `entryPointModifier`). The "plugging in" process is done in terms of a concept of both global and local "existential slots" which are a new `LayoutResourceKind` that represents the holes where concrete types need to be plugged in for existential/interface types. In simple cases like the above, each interface-type parameter will yield a single existential slot in either the global or entry-point parameter layout. Users can query the start slot and number of slots for each shader parameter, just like they would for any other resource that a parameter can consume. Before generating specialized code, the user plugs in the name of the concrete type they would like to use for each slot using `spSetTypeNameForGlobalExistentialSlot` and/or `spSetTypeNameForEntryPointExistentialSlot`. There are some major limitations to the implementation in this first change: * Parameters must be of interface type (e.g., `IFoo`) and not an array (`IFoo[3]`), or buffer (`ConstantBuffer<IFoo>`) over an interface type. Similarly, `struct` types with interface-type fields still don't work. * The work on interface-type function parameters still doesn't include support for `out` or `inout` parameters, nor for functions that return interface types (that isn't technically related to this change, but affects its usefullness). * No work is being done to correctly lay out shader parameters once the concrete types for existential slots are known, so that this change really only works when the concrete type that gets plugged in is empty. These limitations are severe enough that this feature isn't really usable as implemented in this change, and this merely represents a stepping stone toward a more complete implementation. Implementation -------------- The API side of thing largely mirrors what was already done to support passing strings for the type names to use for global/entry-point generic arguments, so there should be no major surprises there. The logic in `check.cpp` computes the list of existential slots when creating unspecialized `Program`s and `EntryPoint`s (this is logically the "front end" of the compiler), and then checks the supplied argument types against what is expected in each slot when creating specialized `Program`s and `EntryPoint`s. This again mirrors how generic arguments are handled. Type layout was extended to compute the number of existential slots that a type consumes, and will thus automatically assign ranges of slots to top-level and entry-point shader parameters in the same way it already allocates `register`s and `binding`s. The big missing feature is the ability to specialize a layout to account for the concrete types plugged into the existential-type slots. IR generation for specialized programs and entry points was slightly extended so that it attaches information about the concrete types plugged into the existential slots, and the witness tables that show how they conform to the interface for that slot. The linking step needed some small tweaks to make sure that information gets copied over to the target-specific program when we start code generation. The meat of the IR-level work is in `ir-bind-existentials.cpp`, which takes the information that was placed in the IR module by the generation/linking steps and uses it to rewrite shader parameters. For example, if there is a shader parameter `p` of type `IModifier`, and the corresponding existential slot has the type `Doubler` in it, we will rewrite the parameter to have type `Doubler`, and rewrite any uses of `p` to instead use `makeExistential(p, /*witness that Doubler conforms to IModifier*/)`. Once the replacement is done on the parameters, the existing work for specializing existential-based code when the input type(s) are known kicks in and does the rest. Testing ------- A single compute test is added to validate that this feature works. It is narrowly tailored to not require any of the features not supported by the initial implementation (e.g., all of the concrete types used have no members). The test case *does* include use of an associated type through one of these existential-type parameters, which has exposed a subtle bug in how "opening" of existential values is implemented in the front-end. Rather than fix the underlying problem, I cleaned up the code in the front-end to special case when the existential value being opened is a variable bound with `let`, to directly use a reference to that variable rather than introduce a temporary. Similarly, in the IR generation step, I added an optimization to make variables declared with `let` skip introducing an IR-level variable and just use the SSA value of their initializer directly instead. * fixup: missing files * fixup: incorrect type for unreachable return * fixup: actually comment ir-bind-existentials.cpp
1 parent a3fd4e2 commit 32135c5

26 files changed

+1266
-106
lines changed

slang.h

+45
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,43 @@ extern "C"
12971297
int genericArgCount,
12981298
char const** genericArgs);
12991299

1300+
/** Specify the concrete type to be used for a global "existential slot."
1301+
1302+
Every shader parameter (or leaf field of a `struct`-type shader parameter)
1303+
that has an interface or array-of-interface type introduces an existential
1304+
slot. The number of slots consumed by a shader parameter, and the starting
1305+
slot of each parameter can be queried via the reflection API using
1306+
`SLANG_PARAMETER_CATEGORY_EXISTENTIAL_SLOT`.
1307+
1308+
In order to generate specialized code, a concrete type needs to be specified
1309+
for each existential slot. This function specifies the name of the type
1310+
(or in general a type *expression*) to use for a specific slot at the
1311+
global scope.
1312+
*/
1313+
SLANG_API SlangResult spSetTypeNameForGlobalExistentialSlot(
1314+
SlangCompileRequest* request,
1315+
int slotIndex,
1316+
char const* typeName);
1317+
1318+
/** Specify the concrete type to be used for an entry-point "existential slot."
1319+
1320+
Every shader parameter (or leaf field of a `struct`-type shader parameter)
1321+
that has an interface or array-of-interface type introduces an existential
1322+
slot. The number of slots consumed by a shader parameter, and the starting
1323+
slot of each parameter can be queried via the reflection API using
1324+
`SLANG_PARAMETER_CATEGORY_EXISTENTIAL_SLOT`.
1325+
1326+
In order to generate specialized code, a concrete type needs to be specified
1327+
for each existential slot. This function specifies the name of the type
1328+
(or in general a type *expression*) to use for a specific slot at the
1329+
entry-point scope.
1330+
*/
1331+
SLANG_API SlangResult spSetTypeNameForEntryPointExistentialSlot(
1332+
SlangCompileRequest* request,
1333+
int entryPointIndex,
1334+
int slotIndex,
1335+
char const* typeName);
1336+
13001337
/** Execute the compilation request.
13011338
13021339
@returns SlangResult, SLANG_OK on success. Use SLANG_SUCCEEDED() and SLANG_FAILED() to test SlangResult.
@@ -1517,6 +1554,12 @@ extern "C"
15171554
SLANG_PARAMETER_CATEGORY_CALLABLE_PAYLOAD,
15181555
SLANG_PARAMETER_CATEGORY_SHADER_RECORD,
15191556

1557+
// A parameter of interface or array-of-interface type introduces
1558+
// one existential slot, into which a concrete type must be plugged
1559+
// to enable specialized code generation.
1560+
//
1561+
SLANG_PARAMETER_CATEGORY_EXISTENTIAL_SLOT,
1562+
15201563
//
15211564
SLANG_PARAMETER_CATEGORY_COUNT,
15221565

@@ -1896,6 +1939,8 @@ namespace slang
18961939

18971940
ShaderRecord = SLANG_PARAMETER_CATEGORY_SHADER_RECORD,
18981941

1942+
ExistentialSlot = SLANG_PARAMETER_CATEGORY_EXISTENTIAL_SLOT,
1943+
18991944
// DEPRECATED:
19001945
VertexInput = SLANG_PARAMETER_CATEGORY_VERTEX_INPUT,
19011946
FragmentOutput = SLANG_PARAMETER_CATEGORY_FRAGMENT_OUTPUT,

0 commit comments

Comments
 (0)