Skip to content

Commit 88859d4

Browse files
author
Tim Foley
authored
Allow plugging in types with resources for interface parameters (shader-slang#913)
* Allow plugging in types with resources for interface parameters The key feature enabled by this change is that you can take a shader declared with interface-type parameters: ```hlsl ConstantBuffer<ILight> gLight; float4 myShader(IMaterial material, ...) { ... } ``` and specialize its interface-type parameters to concrete type that can contain resources like textures, samplers, etc. The hard part of doing this layout is that we need to support signatures that include a mix of interface and non-interface types. Imagine this contrived example: ```hlsl float4 myShader( Texture2D diffuseMap, ILight light, Texture2D specularMap) { ... } ``` We end up wanting `diffuseMap` to get `register(t0)` and `specularMap` to get `register(t1)`, so that they have the same location no matter what we plug in for `light`. But if we plug in a concrete type for `light` that needs a texture register, we need to allocate it *somewhere*. We handle this by having the `TypeLayout` for `light` come back with a "primary" type layout that doesn't have any texture registers, but with a "pending" type layout that includes the texture register requirements of whatever concrete type we plug in. This split between "primary" and "pending" layout then needs to work its way up the hierarchy, so that an aggregate `struct` type with a mix of interface and non-interface fields (recursively), needs to compute an aggregate "primary type layout" and an aggregate "pending type layout," and then each field needs to be able to compute its offset in the primary/pending layout of the aggregate. A large chunk of the work in this PR is then just implementing the split between primary and pending data, and ensuring that layouts are computed appropriately. The next catch is that when a "parameter group" (either a parameter block or constant buffer) contains one or more values of interface type, then we can allow the parameter group to "mask" some of the resource usage of the concrete types we plug in, but others "bleed through." For example, if we have: ```hlsl struct MyStuff { float3 color; ILight light; } ConstantBuffer<MyStuff> myStuff; struct SpotLight { float3 position; Texture2D shadowMap; } `` If we plug in the `SpotLight` type for `myStuff.light`, then the `float3` data for the light can be "masked" by the fact that we have a constant buffer (we can just allocate the `float3` `position` right after `color`), but the `Texture2D` needed for `shadowMap` needs to "bleed through" and become "pending" data for the `myStuff` shader parameter. Adding support for that detail more or less required a full rewrite of the logic for allocating parameter group type layouts. The next detail is that when we go to legalize a declaration like the `myStuff` buffer, we will end up with something like: ```hlsl struct MyStuff_stripped { float3 color; } struct Wrapped { MyStuff_stripped primary; SpotLight pending; } ConstantBuffer<Wrapped> myStuff; ``` This "wrapped" version of the buffer type more accurately reflects the layout we need/want for the uniform/ordinary data, but in order to further legalize it and pull out the resource-type fields like `shadowMap` we need to have accurate layout information, and the problem is that layout information for the original buffer can't apply to this new "wrapped" buffer. The last major piece of this change is logic that runs during existential type legalization to compute new layouts for "wrapped" buffers like these that embeds correct offset/binding/register information for any resources nested inside them. A key challenge in that code is that existential legalization needs to erase any "pending" data from the program entirely, so that offset information that used to be relatie to the "pending" part of a surrounding type now needs to be relative to the primary part. The work here may not be 100% complete for all scenarios, but it does well enough on the new and existing tests that I want to checkpoint it. Note that a few other tests have had their output changed, but in all cases I've reviewed the diffs and determined that the change in observable behavior is consistent with what we intened Slang's behavior to be. Note that there is still one major piece of support for interface-type parameters that is missing here, and which might force us to revisit some of the decisions in this code: we don't properly support user-defined `struct` types with interface-type fields. * fixup: typos
1 parent bedcb92 commit 88859d4

15 files changed

+1887
-502
lines changed

source/slang/emit.cpp

+10-14
Original file line numberDiff line numberDiff line change
@@ -6758,25 +6758,19 @@ StructTypeLayout* getScopeStructLayout(
67586758
ScopeLayout* scopeLayout)
67596759
{
67606760
auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;
6761-
if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
6761+
6762+
if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
67626763
{
6763-
return structTypeLayout;
6764+
scopeTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
67646765
}
6765-
else if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
6766-
{
6767-
auto elementTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
6768-
auto elementTypeStructLayout = as<StructTypeLayout>(elementTypeLayout);
67696766

6770-
// We expect all constant buffers to contain `struct` types for now
6771-
SLANG_RELEASE_ASSERT(elementTypeStructLayout);
6772-
6773-
return elementTypeStructLayout;
6774-
}
6775-
else
6767+
if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
67766768
{
6777-
SLANG_UNEXPECTED("uhandled global-scope binding layout");
6778-
return nullptr;
6769+
return structTypeLayout;
67796770
}
6771+
6772+
SLANG_UNEXPECTED("uhandled global-scope binding layout");
6773+
return nullptr;
67806774
}
67816775

67826776
/// Given a layout computed for a program, get the layout to use when lookup up variables.
@@ -7005,6 +6999,7 @@ String emitEntryPoint(
70056999
legalizeExistentialTypeLayout(
70067000
irModule,
70077001
sink);
7002+
eliminateDeadCode(compileRequest, irModule);
70087003

70097004
#if 0
70107005
dumpIRIfEnabled(compileRequest, irModule, "EXISTENTIALS LEGALIZED");
@@ -7025,6 +7020,7 @@ String emitEntryPoint(
70257020
legalizeResourceTypes(
70267021
irModule,
70277022
sink);
7023+
eliminateDeadCode(compileRequest, irModule);
70287024

70297025
// Debugging output of legalization
70307026
#if 0

source/slang/ir-entry-point-uniforms.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ namespace Slang
6868
// return t.Sample(s, uv);
6969
// }
7070
//
71-
// In this case the output of the transformation shold be:
71+
// In this case the output of the transformation should be:
7272
//
7373
// struct Params
7474
// {

0 commit comments

Comments
 (0)