diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-03-26 12:49:02 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-03-26 12:49:02 -0700 |
| commit | 88859d413e53e4228ae3b832d8bbd2711ccce84a (patch) | |
| tree | e9a3547dbdae359b6a448af4bdaff6170ff9aec6 /source/slang/parameter-binding.cpp | |
| parent | bedcb920045dd68f522e29d90fc3804f84bc08d0 (diff) | |
Allow plugging in types with resources for interface parameters (#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
Diffstat (limited to 'source/slang/parameter-binding.cpp')
| -rw-r--r-- | source/slang/parameter-binding.cpp | 97 |
1 files changed, 95 insertions, 2 deletions
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp index e5057828f..caab5ce6e 100644 --- a/source/slang/parameter-binding.cpp +++ b/source/slang/parameter-binding.cpp @@ -1278,6 +1278,27 @@ static void completeBindingsForParameter( applyBindingInfoToParameter(varLayout, bindingInfos); } + /// Allocate binding location for any "pending" data in a shader parameter. + /// + /// When a parameter contains interface-type fields (recursively), we might + /// not have included them in the base layout for the parameter, and instead + /// need to allocate space for them after all other shader parameters have + /// been laid out. + /// + /// This function should be called on the `pendingVarLayout` field of an + /// existing `VarLayout` to ensure that its pending data has been properly + /// assigned storage. It handles the case where the `pendingVarLayout` + /// field is null. + /// +static void _allocateBindingsForPendingData( + ParameterBindingContext* context, + RefPtr<VarLayout> pendingVarLayout) +{ + if(!pendingVarLayout) return; + + completeBindingsForParameter(context, pendingVarLayout); +} + struct SimpleSemanticInfo { String name; @@ -1806,6 +1827,13 @@ struct ScopeLayoutBuilder RefPtr<StructTypeLayout> m_structLayout; UniformLayoutInfo m_structLayoutInfo; + // We need to compute a layout for any "pending" data inside + // of the parameters being added to the scope, to facilitate + // later allocating space for all the pending parameters after + // the primary shader parameters. + // + StructTypeLayoutBuilder m_pendingDataTypeLayoutBuilder; + void beginLayout( ParameterBindingContext* context) { @@ -1870,9 +1898,24 @@ struct ScopeLayoutBuilder // `struct` layout logic in `type-layout.cpp`. If this gets any // more complicated we should see if there is a way to share it. // - for( auto pendingItem : firstVarLayout->typeLayout->pendingItems ) + if( auto fieldPendingDataTypeLayout = firstVarLayout->typeLayout->pendingDataTypeLayout ) { - m_structLayout->pendingItems.Add(pendingItem); + m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); + auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(firstVarLayout->varDecl, fieldPendingDataTypeLayout); + + m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); + + if( parameterInfo ) + { + for( auto& varLayout : parameterInfo->varLayouts ) + { + varLayout->pendingVarLayout = fieldPendingDataVarLayout; + } + } + else + { + firstVarLayout->pendingVarLayout = fieldPendingDataVarLayout; + } } } @@ -1896,6 +1939,7 @@ struct ScopeLayoutBuilder // Finish computing the layout for the ordindary data (if any). // m_rules->EndStructLayout(&m_structLayoutInfo); + m_pendingDataTypeLayoutBuilder.endLayout(); // Copy the final layout information computed for ordinary data // over to the struct type layout for the scope. @@ -1917,6 +1961,14 @@ struct ScopeLayoutBuilder // record into a suitable object that represents the scope RefPtr<VarLayout> scopeVarLayout = new VarLayout(); scopeVarLayout->typeLayout = scopeTypeLayout; + + if( auto pendingTypeLayout = scopeTypeLayout->pendingDataTypeLayout ) + { + RefPtr<VarLayout> pendingVarLayout = new VarLayout(); + pendingVarLayout->typeLayout = pendingTypeLayout; + scopeVarLayout->pendingVarLayout = pendingVarLayout; + } + return scopeVarLayout; } }; @@ -2451,6 +2503,47 @@ RefPtr<ProgramLayout> generateParameterBindings( cbInfo->space = globalConstantBufferBinding.space; cbInfo->index = globalConstantBufferBinding.index; } + + // After we have laid out all the ordinary parameters, + // we need to go through the global scope plus each entry point, + // and "flush" out any pending data that was associated with + // those scopes as part of dealing with interface-type parameters. + // + _allocateBindingsForPendingData(&context, globalScopeVarLayout->pendingVarLayout); + for( auto entryPoint : sharedContext.programLayout->entryPoints ) + { + _allocateBindingsForPendingData(&context, entryPoint->parametersLayout->pendingVarLayout); + } + + + // HACK: we want global parameters to not have to deal with offsetting + // by the `VarLayout` stored in `globalScopeVarLayout`, so we will scan + // through and for any global parameter that used "pending" data, we will manually + // offset all of its resource infos to account for where the global pending data + // got placed. + // + // TODO: A more appropriate solution would be to pass the `globalScopeVarLayout` + // down into the pass that puts layout information onto global parameters in + // the IR, and apply the offsetting there. + // + for( auto& parameterInfo : sharedContext.parameters ) + { + for( auto varLayout : parameterInfo->varLayouts ) + { + auto pendingVarLayout = varLayout->pendingVarLayout; + if(!pendingVarLayout) continue; + + for( auto& resInfo : pendingVarLayout->resourceInfos ) + { + if( auto globalResInfo = globalScopeVarLayout->pendingVarLayout->FindResourceInfo(resInfo.kind) ) + { + resInfo.index += globalResInfo->index; + resInfo.space += globalResInfo->space; + } + } + } + } + programLayout->parametersLayout = globalScopeVarLayout; { |
