summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2019-03-26 12:49:02 -0700
committerGitHub <noreply@github.com>2019-03-26 12:49:02 -0700
commit88859d413e53e4228ae3b832d8bbd2711ccce84a (patch)
treee9a3547dbdae359b6a448af4bdaff6170ff9aec6
parentbedcb920045dd68f522e29d90fc3804f84bc08d0 (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
-rw-r--r--source/slang/emit.cpp24
-rw-r--r--source/slang/ir-entry-point-uniforms.cpp2
-rw-r--r--source/slang/ir-legalize-types.cpp696
-rw-r--r--source/slang/ir-specialize-resources.cpp4
-rw-r--r--source/slang/legalize-types.cpp79
-rw-r--r--source/slang/legalize-types.h151
-rw-r--r--source/slang/options.cpp1
-rw-r--r--source/slang/parameter-binding.cpp97
-rw-r--r--source/slang/type-layout.cpp1009
-rw-r--r--source/slang/type-layout.h141
-rw-r--r--tests/bugs/vk-structured-buffer-load.hlsl.glsl2
-rw-r--r--tests/compute/interface-shader-param3.slang37
-rw-r--r--tests/compute/interface-shader-param4.slang132
-rw-r--r--tests/compute/interface-shader-param4.slang.expected.txt4
-rw-r--r--tests/reflection/parameter-block-explicit-space.slang.expected10
15 files changed, 1887 insertions, 502 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index f17b13762..5a2a93a53 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -6758,25 +6758,19 @@ StructTypeLayout* getScopeStructLayout(
ScopeLayout* scopeLayout)
{
auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;
- if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
+
+ if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
{
- return structTypeLayout;
+ scopeTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
}
- else if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
- {
- auto elementTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
- auto elementTypeStructLayout = as<StructTypeLayout>(elementTypeLayout);
- // We expect all constant buffers to contain `struct` types for now
- SLANG_RELEASE_ASSERT(elementTypeStructLayout);
-
- return elementTypeStructLayout;
- }
- else
+ if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
{
- SLANG_UNEXPECTED("uhandled global-scope binding layout");
- return nullptr;
+ return structTypeLayout;
}
+
+ SLANG_UNEXPECTED("uhandled global-scope binding layout");
+ return nullptr;
}
/// Given a layout computed for a program, get the layout to use when lookup up variables.
@@ -7005,6 +6999,7 @@ String emitEntryPoint(
legalizeExistentialTypeLayout(
irModule,
sink);
+ eliminateDeadCode(compileRequest, irModule);
#if 0
dumpIRIfEnabled(compileRequest, irModule, "EXISTENTIALS LEGALIZED");
@@ -7025,6 +7020,7 @@ String emitEntryPoint(
legalizeResourceTypes(
irModule,
sink);
+ eliminateDeadCode(compileRequest, irModule);
// Debugging output of legalization
#if 0
diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp
index 7bbf58810..501af56cf 100644
--- a/source/slang/ir-entry-point-uniforms.cpp
+++ b/source/slang/ir-entry-point-uniforms.cpp
@@ -68,7 +68,7 @@ namespace Slang
// return t.Sample(s, uv);
// }
//
-// In this case the output of the transformation shold be:
+// In this case the output of the transformation should be:
//
// struct Params
// {
diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp
index bc26f9afc..7b6c4d79d 100644
--- a/source/slang/ir-legalize-types.cpp
+++ b/source/slang/ir-legalize-types.cpp
@@ -74,6 +74,20 @@ LegalVal LegalVal::getImplicitDeref()
return as<ImplicitDerefVal>(obj)->val;
}
+LegalVal LegalVal::wrappedBuffer(
+ LegalVal const& baseVal,
+ LegalElementWrapping const& elementInfo)
+{
+ RefPtr<WrappedBufferPseudoVal> obj = new WrappedBufferPseudoVal();
+ obj->base = baseVal;
+ obj->elementInfo = elementInfo;
+
+ LegalVal result;
+ result.flavor = LegalVal::Flavor::wrappedBuffer;
+ result.obj = obj;
+ return result;
+}
+
//
IRTypeLegalizationContext::IRTypeLegalizationContext(
@@ -109,10 +123,55 @@ static LegalVal declareVars(
IROp op,
LegalType type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo);
+ /// Unwrap a value with flavor `wrappedBuffer`
+ ///
+ /// The original `legalPtrOperand` has a wrapped-buffer type
+ /// which encodes the way that, e.g., a `ConstantBuffer<Foo>`
+ /// where `Foo` includes interface types, got legalized
+ /// into a buffer that stores a `Foo` value plus addition
+ /// fields for the concrete types that got plugged in.
+ ///
+ /// The `elementInfo` is the layout information for the
+ /// modified ("wrapped") buffer type, and specifies how
+ /// the logical element type was expanded into actual fields.
+ ///
+ /// This function returns a new value that undoes all of
+ /// the wrapping and produces a new `LegalVal` that matches
+ /// the nominal type of the original buffer.
+ ///
+static LegalVal unwrapBufferValue(
+ IRTypeLegalizationContext* context,
+ LegalVal legalPtrOperand,
+ LegalElementWrapping const& elementInfo);
+
+ /// Perform any actions required to materialize `val` into a usable value.
+ ///
+ /// Certain case of `LegalVal` (currently just the `wrappedBuffer` case) are
+ /// suitable for use to represent a variable, but cannot be used directly
+ /// in computations, because their structured needs to be "unwrapped."
+ ///
+ /// This function unwraps any `val` that needs it, which may involve
+ /// emitting additional IR instructions, and returns the unmodified
+ /// `val` otherwise.
+ ///
+static LegalVal maybeMaterializeWrappedValue(
+ IRTypeLegalizationContext* context,
+ LegalVal val)
+{
+ if(val.flavor != LegalVal::Flavor::wrappedBuffer)
+ return val;
+
+ auto wrappedBufferVal = val.getWrappedBuffer();
+ return unwrapBufferValue(
+ context,
+ wrappedBufferVal->base,
+ wrappedBufferVal->elementInfo);
+}
+
// Take a value that is being used as an operand,
// and turn it into the equivalent legalized value.
static LegalVal legalizeOperand(
@@ -120,8 +179,10 @@ static LegalVal legalizeOperand(
IRInst* irValue)
{
LegalVal legalVal;
- if (context->mapValToLegalVal.TryGetValue(irValue, legalVal))
- return legalVal;
+ if( context->mapValToLegalVal.TryGetValue(irValue, legalVal) )
+ {
+ return maybeMaterializeWrappedValue(context, legalVal);
+ }
// For now, assume that anything not covered
// by the mapping is legal as-is.
@@ -455,8 +516,8 @@ static LegalVal legalizeFieldExtract(
(IRStructKey*) fieldKey);
}
- /// Take a value of some buffer/pointer type and wrap it according to provided info.
-static LegalVal wrapBufferValue(
+ /// Take a value of some buffer/pointer type and unwrap it according to provided info.
+static LegalVal unwrapBufferValue(
IRTypeLegalizationContext* context,
LegalVal legalPtrOperand,
LegalElementWrapping const& elementInfo)
@@ -495,7 +556,7 @@ static LegalVal wrapBufferValue(
auto simpleElementInfo = elementInfo.getSimple();
auto valPtr = builder->emitFieldAddress(
- simpleElementInfo->type,
+ builder->getPtrType(simpleElementInfo->type),
legalPtrOperand.getSimple(),
simpleElementInfo->key);
@@ -510,7 +571,7 @@ static LegalVal wrapBufferValue(
// wrap them up in an `implicitDeref` value.
//
auto derefField = elementInfo.getImplicitDeref();
- auto baseVal = wrapBufferValue(context, legalPtrOperand, derefField->field);
+ auto baseVal = unwrapBufferValue(context, legalPtrOperand, derefField->field);
return LegalVal::implicitDeref(baseVal);
}
@@ -524,8 +585,8 @@ static LegalVal wrapBufferValue(
auto pairField = elementInfo.getPair();
auto pairInfo = pairField->pairInfo;
- auto ordinaryVal = wrapBufferValue(context, legalPtrOperand, pairField->ordinary);
- auto specialVal = wrapBufferValue(context, legalPtrOperand, pairField->special);
+ auto ordinaryVal = unwrapBufferValue(context, legalPtrOperand, pairField->ordinary);
+ auto specialVal = unwrapBufferValue(context, legalPtrOperand, pairField->special);
return LegalVal::pair(ordinaryVal, specialVal, pairInfo);
}
@@ -541,14 +602,14 @@ static LegalVal wrapBufferValue(
RefPtr<TuplePseudoVal> obj = new TuplePseudoVal();
for( auto ee : tupleField->elements )
{
- auto elementVal = wrapBufferValue(
+ auto elementVal = unwrapBufferValue(
context,
legalPtrOperand,
ee.field);
TuplePseudoVal::Element element;
element.key = ee.key;
- element.val = wrapBufferValue(
+ element.val = unwrapBufferValue(
context,
legalPtrOperand,
ee.field);
@@ -1244,16 +1305,10 @@ static LegalVal legalizeLocalVar(
context->insertBeforeLocalVar = irLocalVar;
- LegalVarChain* varChain = nullptr;
- LegalVarChain varChainStorage;
- if (varLayout)
- {
- varChainStorage.next = nullptr;
- varChainStorage.varLayout = varLayout;
- varChain = &varChainStorage;
- }
+ LegalVarChainLink varChain(LegalVarChain(), varLayout);
UnownedStringSlice nameHint = findNameHint(irLocalVar);
+ context->builder->setInsertBefore(irLocalVar);
LegalVal newVal = declareVars(context, kIROp_Var, legalValueType, typeLayout, varChain, nameHint, nullptr);
// Remove the old local var.
@@ -1286,7 +1341,9 @@ static LegalVal legalizeParam(
context->insertBeforeParam = originalParam;
UnownedStringSlice nameHint = findNameHint(originalParam);
- auto newVal = declareVars(context, kIROp_Param, legalParamType, nullptr, nullptr, nameHint, nullptr);
+
+ context->builder->setInsertBefore(originalParam);
+ auto newVal = declareVars(context, kIROp_Param, legalParamType, nullptr, LegalVarChain(), nameHint, nullptr);
originalParam->removeFromParent();
context->replacedInstructions.Add(originalParam);
@@ -1314,6 +1371,12 @@ static LegalVal legalizeInst(
IRTypeLegalizationContext* context,
IRInst* inst)
{
+ // Any additional instructions we need to emit
+ // in the process of legalizing `inst` should
+ // by default be insertied right before `inst`.
+ //
+ context->builder->setInsertBefore(inst);
+
// Special-case certain operations
switch (inst->op)
{
@@ -1343,9 +1406,15 @@ static LegalVal legalizeInst(
break;
}
- // Need to legalize all the operands.
+ // We will iterate over all the operands, extract the legalized
+ // value of each, and collect them in an array for subsequent use.
+ //
auto argCount = inst->getOperandCount();
List<LegalVal> legalArgs;
+ //
+ // Along the way we will also note whether there were any operands
+ // with non-simple legalized values.
+ //
bool anyComplex = false;
for (UInt aa = 0; aa < argCount; ++aa)
{
@@ -1357,14 +1426,19 @@ static LegalVal legalizeInst(
anyComplex = true;
}
- // Also legalize the type of the instruction
+ // We must also legalize the type of the instruction, since that
+ // is implicitly one of its operands.
+ //
LegalType legalType = legalizeType(context, inst->getFullType());
+ // If there was nothing interesting that occured for the operands
+ // then we can re-use this instruction as-is.
+ //
if (!anyComplex && legalType.flavor == LegalType::Flavor::simple)
{
- // Nothing interesting happened to the operands,
- // so we seem to be okay, right?
-
+ // While the operands are all "simple," they might not necessarily
+ // be equal to the operands we started with.
+ //
for (UInt aa = 0; aa < argCount; ++aa)
{
auto legalArg = legalArgs[aa];
@@ -1510,7 +1584,7 @@ static LegalVal declareSimpleVar(
IROp op,
IRType* type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo)
{
@@ -1518,11 +1592,7 @@ static LegalVal declareSimpleVar(
RefPtr<VarLayout> varLayout = createVarLayout(varChain, typeLayout);
- DeclRef<VarDeclBase> varDeclRef;
- if (varChain)
- {
- varDeclRef = varChain->varLayout->varDecl;
- }
+ DeclRef<VarDeclBase> varDeclRef = varChain.getLeafVarDeclRef();
IRBuilder* builder = context->builder;
@@ -1611,12 +1681,526 @@ static LegalVal declareSimpleVar(
return legalVarVal;
}
+ /// Add layout information for the fields of a wrapped buffer type.
+ ///
+ /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
+ /// where `Foo` might have interface-type fields that have been
+ /// specialized to a concrete type. E.g.:
+ ///
+ /// struct Car { IDriver driver; int mph; };
+ /// ConstantBuffer<Car> machOne;
+ ///
+ /// In a case where the `machOne.driver` field has been specialized
+ /// to the type `SpeedRacer`, we need to generate a legalized
+ /// buffer layout something like:
+ ///
+ /// struct Car_0 { int mph; }
+ /// struct Wrapped { Car_0 car; SpeedRacer card_d; }
+ /// ConstantBuffer<Wrapped> machOne;
+ ///
+ /// The layout information for the existing `machOne` clearly
+ /// can't apply because we have a new element type with new fields.
+ ///
+ /// This function is used to recursively fill in the layout for
+ /// the fields of the `Wrapped` type, using information recorded
+ /// when the legal wrapped buffer type was created.
+ ///
+static void _addFieldsToWrappedBufferElementTypeLayout(
+ TypeLayout* elementTypeLayout, // layout of the original field type
+ StructTypeLayout* newTypeLayout, // layout we are filling in
+ LegalElementWrapping const& elementInfo, // information on how the original type got wrapped
+ LegalVarChain const& varChain, // chain of variables that is leading to this field
+ bool isSpecial) // should we assume a leaf field is a special (interface) type?
+{
+ // The way we handle things depends primary on the
+ // `elementInfo`, because that tells us how things
+ // were wrapped up when the type was legalized.
+
+ switch( elementInfo.flavor )
+ {
+ case LegalElementWrapping::Flavor::none:
+ // A leaf `none` value meant there was nothing
+ // to encode for a particular field (probably
+ // had a `void` or empty structure type).
+ break;
+
+ case LegalElementWrapping::Flavor::simple:
+ {
+ auto simpleInfo = elementInfo.getSimple();
+
+ // A `simple` wrapping means we hit a leaf
+ // field that can be encoded directly.
+ // What we do here depends on whether we've
+ // reached an ordinary field of the original
+ // data type, or if we've reached a leaf
+ // field of interface type.
+ //
+ // We've been tracking a `varChain` that
+ // remembers all the parent `struct` fields
+ // we've navigated through to get here, and
+ // that information has been tracking two
+ // different pieces of layout:
+ //
+ // * The "primary" layout represents the storage
+ // of the buffer element type as we usually
+ // think of its (e.g., the bytes starting at offset zero).
+ //
+ // * The "pending" layout tells us where all the
+ // fields representing concrete types plugged in
+ // for interface-type slots got placed.
+ //
+ // We have tunneled down info to tell us which case
+ // we should use (`isSpecial`).
+ //
+ // Most of the logic is the same between the two
+ // cases. We will be computing layout information
+ // for a field of the new/wrapped buffer element type.
+ //
+ RefPtr<VarLayout> newFieldLayout;
+ if(isSpecial)
+ {
+ // In the special case, that field will be laid out
+ // based on the "pending" var chain, and the type
+ // of the pending data for the element.
+ //
+ newFieldLayout = createSimpleVarLayout(varChain.pendingChain, elementTypeLayout->pendingDataTypeLayout);
+ }
+ else
+ {
+ // The ordinary case just uses the primary layout
+ // information and the primary/nominal type of
+ // the field.
+ //
+ newFieldLayout = createSimpleVarLayout(varChain.primaryChain, elementTypeLayout);
+ }
+
+ // Either way, we add the new field to the struct type
+ // layout we are building, and also update the mapping
+ // information so that we can find the field layout
+ // based on the IR key for the struct field.
+ //
+ newTypeLayout->fields.Add(newFieldLayout);
+ newTypeLayout->mapKeyToLayout.Add(simpleInfo->key, newFieldLayout);
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::implicitDeref:
+ {
+ // This is the case where a field in the element type
+ // has been legalized from `SomePtrLikeType<T>` to
+ // `T`, so there is a different in levels of indirection.
+ //
+ // We need to recurse and see how the type `T`
+ // got laid out to know what field(s) it might comprise.
+ //
+ auto implicitDerefInfo = elementInfo.getImplicitDeref();
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ implicitDerefInfo->field,
+ varChain,
+ isSpecial);
+ return;
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::pair:
+ {
+ // The pair case is the first main workhorse where
+ // if we had a type that mixed ordinary and interface-type
+ // fields, it would get split into an ordinary part
+ // and a "special" part, each of which might comprise
+ // zero or more fields.
+ //
+ // Here we recurse on both the ordinary and special
+ // sides, and the only interesting tidbit is that
+ // we pass along appropriate values for the `isSpecial`
+ // flag so that we act appropriately upon running
+ // into a leaf field.
+ //
+ auto pairElementInfo = elementInfo.getPair();
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ pairElementInfo->ordinary,
+ varChain,
+ false);
+ _addFieldsToWrappedBufferElementTypeLayout(
+ elementTypeLayout,
+ newTypeLayout,
+ pairElementInfo->special,
+ varChain,
+ true);
+ }
+ break;
+
+ case LegalElementWrapping::Flavor::tuple:
+ {
+ // A tuple comes up when we've turned an aggregate
+ // with one or more interface-type fields into
+ // distinct fields at the top level.
+ //
+ // For the most part we just recurse on each field,
+ // but note that we set the `isSpecial` flag on
+ // the recursive calls, since we never use tuples
+ // to store anything that isn't special.
+
+ auto tupleInfo = elementInfo.getTuple();
+ for( auto ee : tupleInfo->elements )
+ {
+ auto oldFieldLayout = getFieldLayout(elementTypeLayout, ee.key);
+ SLANG_ASSERT(oldFieldLayout);
+
+ LegalVarChainLink fieldChain(varChain, oldFieldLayout);
+
+ _addFieldsToWrappedBufferElementTypeLayout(
+ oldFieldLayout->typeLayout,
+ newTypeLayout,
+ ee.field,
+ fieldChain,
+ true);
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unhandled element wrapping flavor");
+ break;
+ }
+}
+
+ /// Add offset information for `kind` to `resultVarLayout`,
+ /// if it doesn't already exist, and adjust the offset so
+ /// that it will represent an offset relative to the
+ /// "primary" data for the surrounding type, rather than
+ /// being relative to the "pending" data.
+ ///
+static void _addOffsetVarLayoutEntry(
+ VarLayout* resultVarLayout,
+ LegalVarChain const& varChain,
+ LayoutResourceKind kind)
+{
+ // If the target already has an offset for this kind, bail out.
+ //
+ if(resultVarLayout->FindResourceInfo(kind))
+ return;
+
+ // Add the `ResourceInfo` that will represent the offset for
+ // this resource kind (it will be initialized to zero by default)
+ //
+ auto resultResInfo = resultVarLayout->findOrAddResourceInfo(kind);
+
+ // Add in any contributions from the "pending" var chain, since
+ // that chain of offsets will accumulate to get the leaf offset
+ // within the pending data, which in this case we assume amounts
+ // to an *absolute* offset.
+ //
+ for(auto vv = varChain.pendingChain; vv; vv = vv->next )
+ {
+ if( auto chainResInfo = vv->varLayout->FindResourceInfo(kind) )
+ {
+ resultResInfo->index += chainResInfo->index;
+ resultResInfo->space += chainResInfo->space;
+ }
+ }
+
+ // Subtract any contributions from the primary var chain, since
+ // we want the resulting offset to be relative to the same
+ // base as that chain.
+ //
+ for(auto vv = varChain.primaryChain; vv; vv = vv->next )
+ {
+ if( auto chainResInfo = vv->varLayout->FindResourceInfo(kind) )
+ {
+ resultResInfo->index -= chainResInfo->index;
+ resultResInfo->space -= chainResInfo->space;
+ }
+ }
+}
+
+ /// Create a variable layout for an field with "pending" type.
+ ///
+ /// The given `typeLayout` should represent the type of a field
+ /// that is being stored in "pending" data, but that now needs
+ /// to be made relative to the "primary" data, because we are
+ /// legalizing the pending data out of the code.
+ ///
+static RefPtr<VarLayout> _createOffsetVarLayout(
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout)
+{
+ RefPtr<VarLayout> resultVarLayout = new VarLayout();
+
+ // For every resource kind the type consumes, we will
+ // compute an adjusted offset for the variable that
+ // encodes the (absolute) offset of the pending data
+ // in `varChain` relative to its primary data.
+ //
+ for( auto resInfo : typeLayout->resourceInfos )
+ {
+ _addOffsetVarLayoutEntry(resultVarLayout, varChain, resInfo.kind);
+ }
+
+ return resultVarLayout;
+}
+
+ /// Place offset information from `srcResInfo` onto `dstLayout`,
+ /// offset by whatever is in `offsetVarLayout`
+static void addOffsetResInfo(
+ VarLayout* dstLayout,
+ VarLayout::ResourceInfo const& srcResInfo,
+ VarLayout* offsetVarLayout)
+{
+ auto kind = srcResInfo.kind;
+ auto dstResInfo = dstLayout->findOrAddResourceInfo(kind);
+
+ dstResInfo->index = srcResInfo.index;
+ dstResInfo->space = srcResInfo.space;
+
+ if( auto offsetResInfo = offsetVarLayout->findOrAddResourceInfo(kind) )
+ {
+ dstResInfo->index += offsetResInfo->index;
+ dstResInfo->space += offsetResInfo->space;
+ }
+}
+
+ /// Create layout information for a wrapped buffer type.
+ ///
+ /// A wrapped buffer type encodes a buffer like `ConstantBuffer<Foo>`
+ /// where `Foo` might have interface-type fields that have been
+ /// specialized to a concrete type.
+ ///
+ /// Consider:
+ ///
+ /// struct Car { IDriver driver; int mph; };
+ /// ConstantBuffer<Car> machOne;
+ ///
+ /// In a case where the `machOne.driver` field has been specialized
+ /// to the type `SpeedRacer`, we need to generate a legalized
+ /// buffer layout something like:
+ ///
+ /// struct Car_0 { int mph; }
+ /// struct Wrapped { Car_0 car; SpeedRacer card_d; }
+ /// ConstantBuffer<Wrapped> machOne;
+ ///
+ /// The layout information for the existing `machOne` clearly
+ /// can't apply because we have a new element type with new fields.
+ ///
+ /// This function is used to create a layout for a legalized
+ /// buffer type that requires wrapping, based on the original
+ /// type layout information and the variable layout information
+ /// of the surrounding context (e.g., the global shader parameter
+ /// that has this type).
+ ///
+static RefPtr<TypeLayout> _createWrappedBufferTypeLayout(
+ TypeLayout* oldTypeLayout,
+ WrappedBufferPseudoType* wrappedBufferTypeInfo,
+ LegalVarChain const& outerVarChain)
+{
+ // We shouldn't get invoked unless there was a parameter group type,
+ // so we will sanity check for that just to be sure.
+ //
+ auto oldParameterGroupTypeLayout = as<ParameterGroupTypeLayout>(oldTypeLayout);
+ SLANG_ASSERT(oldParameterGroupTypeLayout);
+ if(!oldParameterGroupTypeLayout)
+ return oldTypeLayout;
+
+ // The original type must have been split between the direct/primary
+ // data and some amount of "pending" data to deal with interface-type
+ // data in the element type of the parameter group.
+ //
+ // The legalization step will have already flattened the data inside of
+ // the group to a single `struct` type, which places the primary data first,
+ // and then any pending data into additional fields.
+ //
+ // Our job is to compute a type layout that we can apply to that new
+ // element type, and to a parameter group surrounding it, that will
+ // re-create the original intention of the split layout (both primary
+ // and pending data) for a type that now only has the "primary" data.
+ //
+ RefPtr<ParameterGroupTypeLayout> newTypeLayout = new ParameterGroupTypeLayout();
+ newTypeLayout->type = oldTypeLayout->type;
+ newTypeLayout->rules = oldTypeLayout->rules;
+ newTypeLayout->uniformAlignment = oldTypeLayout->uniformAlignment;
+ for(auto resInfo : oldTypeLayout->resourceInfos)
+ newTypeLayout->addResourceUsage(resInfo);
+
+ // Any fields in the "pending" data will have offset information
+ // that is relative to the pending data for their parent, and so on.
+ // We need to compute layout information that only includes primary
+ // data, so any offset information that is relative to the pending data
+ // needs to instead be relative to the primary data. That amounts to
+ // computing the absolute offset of each pending field, and then
+ // subtracting off the absolute offset of the primary data.
+ //
+ // We will compute the offset that needs to be added up front,
+ // and store it in the form of a `VarLayout`. The offsets we need
+ // can be computed from the `outerVarChain`, and we only need to
+ // store offset information for resource kinds actually consumed
+ // by the pending data type for the buffer as a whole (e.g., we
+ // don't need to apply offsetting to uniform bytes, because
+ // those don't show up in the resource usage of a constant buffer
+ // itself, and so the offsets already *are* relative to the start
+ // of the buffer).
+ //
+ auto offsetVarLayout = _createOffsetVarLayout(outerVarChain, oldTypeLayout->pendingDataTypeLayout);
+ LegalVarChainLink offsetVarChain(LegalVarChain(), offsetVarLayout);
+
+ // We will start our construction of the pieces of the output
+ // type layout by looking at the "container" type/variable.
+ //
+ // A parameter block or constant buffer in Slang needs to
+ // distinguish between the resource usage of the thing in
+ // the block/buffer, vs. the resource usage of the block/buffer
+ // itself. Consider:
+ //
+ // struct Material { float4 color; Texture2D tex; }
+ // ConstantBuffer<Material> gMat;
+ //
+ // When compiling for Vulkan, the `gMat` constant buffer needs
+ // a `binding`, and the `tex` field does too, so the overall
+ // resource usage of `gMat` is two bindings, but we need a
+ // way to encode which of those bindings goes to `gMat.tex`
+ // and which to the constant buffer for `gMat` itself.
+ //
+ {
+ // We will start by extracting the "primary" part of the old
+ // container type/var layout, and constructing new objects
+ // that will represent the layout for our wrapped buffer.
+ //
+ auto oldPrimaryContainerVarLayout = oldParameterGroupTypeLayout->containerVarLayout;
+ auto oldPrimaryContainerTypeLayout = oldPrimaryContainerVarLayout->typeLayout;
+
+ RefPtr<TypeLayout> newContainerTypeLayout = new TypeLayout();
+ newContainerTypeLayout->type = oldPrimaryContainerTypeLayout->type;
+
+ RefPtr<VarLayout> newContainerVarLayout = new VarLayout();
+ newContainerVarLayout->typeLayout = newContainerTypeLayout;
+
+ newTypeLayout->containerVarLayout = newContainerVarLayout;
+
+ // Whatever got allocated for the primary container should get copied
+ // over to the new layout (e.g., if we allocated a constant buffer
+ // for `gMat` then we need to retain that information).
+ //
+ newContainerTypeLayout->addResourceUsageFrom(oldPrimaryContainerTypeLayout);
+ for( auto resInfo : oldPrimaryContainerVarLayout->resourceInfos )
+ {
+ auto newResInfo = newContainerVarLayout->findOrAddResourceInfo(resInfo.kind);
+ newResInfo->index = resInfo.index;
+ newResInfo->space = resInfo.space;
+ }
+
+ // It is possible that a constant buffer and/or space didn't get
+ // allocated for the "primary" data, but ended up being required for
+ // the "pending" data (this would happen if, e.g., a constant buffer
+ // didn't appear to have any uniform data in it, but then once we
+ // plugged in concrete types for interface fields it did...), so
+ // we need to account for that case and copy over the relevant
+ // resource usage from the pending data, if there is any.
+ //
+ if( auto oldPendingContainerVarLayout = oldPrimaryContainerVarLayout->pendingVarLayout )
+ {
+ // Whatever resources were allocated for the pending data type,
+ // our new combined container type needs to account for them
+ // (e.g., if we didn't have a constant buffer in the primary
+ // data, but one got allocated in the pending data, we need
+ // to end up with type layout information that includes a
+ // constnat buffer).
+ //
+ auto oldPendingContainerTypeLayout = oldPendingContainerVarLayout->typeLayout;
+ newContainerTypeLayout->addResourceUsageFrom(oldPendingContainerTypeLayout);
+
+ // We also need to add offset information based on the "pending"
+ // var layout, but we need to deal with the fact that this information
+ // is currently stored relative to the pending var layout for the surrounding
+ // context (passed in as `outerVarChain.pendingChain`), but we need it to be
+ // relative to the primary layout for the surrounding context (`outerVarChain.primaryChain`).
+ // This is where the `offsetVarLayout` we computed above comes
+ // in handy, because it represents the value(s) we need to
+ // add to each of the per-resource-kind offsets.
+ //
+ for( auto resInfo : oldPendingContainerVarLayout->resourceInfos )
+ {
+ addOffsetResInfo(newContainerVarLayout, resInfo, offsetVarLayout);
+ }
+ }
+ }
+
+ // Now that we've dealt with the container variable, we can turn
+ // our attention to the element type. This is the part that
+ // actually got legalized and required us to create a "wrapped"
+ // buffer type in the first place, so we know that it will
+ // have both primary and "pending" parts.
+ //
+ // Let's start by extracting the fields we care about from
+ // the original element type/var layout, and constructing
+ // the objects we'll use to represent the type/var layout for
+ // the new element type.
+ //
+ auto oldElementVarLayout = oldParameterGroupTypeLayout->elementVarLayout;
+ auto oldElementTypeLayout = oldElementVarLayout->typeLayout;
+
+ // Now matter what, the element type of a wrapped buffer
+ // will always have a structure type.
+ //
+ RefPtr<StructTypeLayout> newElementTypeLayout = new StructTypeLayout();
+ newElementTypeLayout->type = oldElementTypeLayout->type;
+
+ // The `wrappedBufferTypeInfo` that was passed in tells
+ // us how the fields of the original type got turned into
+ // zero or more fields in the new element type, so we
+ // need to follow its recursive structure to build
+ // layout information for each of the new fields.
+ //
+ // We will track a "chain" of parent variables that
+ // determines how we got to each leaf field, and is
+ // used to add up the offsets that will be stored
+ // in the new `VarLayout`s that get created.
+ // We know we need to add in some offsets (usually
+ // negative) to any fields that were pending data,
+ // so we will account for that in the initial
+ // chain of outer variables that we pass in.
+ //
+ LegalVarChain varChainForElementType;
+ varChainForElementType.primaryChain = nullptr;
+ varChainForElementType.pendingChain = offsetVarChain.primaryChain;
+
+ _addFieldsToWrappedBufferElementTypeLayout(
+ oldElementTypeLayout,
+ newElementTypeLayout,
+ wrappedBufferTypeInfo->elementInfo,
+ varChainForElementType,
+ true);
+
+ // A parameter group type layout holds a `VarLayout` for the element type,
+ // which encodes the offset of the element type with respect to the
+ // start of the parameter group as a whole (e.g., to handle the case
+ // where a constant buffer needs a `binding`, and so does its
+ // element type, so the offset to the first `binding` for the element
+ // type is one, not zero.
+ //
+ LegalVarChainLink elementVarChain(LegalVarChain(), oldParameterGroupTypeLayout->elementVarLayout);
+ auto newElementVarLayout = createVarLayout(elementVarChain, newElementTypeLayout);
+ newTypeLayout->elementVarLayout = newElementVarLayout;
+
+ // For legacy/API reasons, we also need to compute a version of the
+ // element type where the offset stored in the `elementVarLayout`
+ // gets "baked in" to the fields of the element type.
+ //
+ newTypeLayout->offsetElementTypeLayout = applyOffsetToTypeLayout(
+ newElementTypeLayout,
+ newElementVarLayout);
+
+ return newTypeLayout;
+}
+
static LegalVal declareVars(
IRTypeLegalizationContext* context,
IROp op,
LegalType type,
TypeLayout* typeLayout,
- LegalVarChain* varChain,
+ LegalVarChain const& varChain,
UnownedStringSlice nameHint,
IRGlobalNameInfo* globalNameInfo)
{
@@ -1663,27 +2247,18 @@ static LegalVal declareVars(
for (auto ee : tupleType->elements)
{
- // Fields are currently required to have linkage, since we use
- // their mangled name to look up field layout information.
- //
- auto fieldLinkage = ee.key->findDecoration<IRLinkageDecoration>();
- SLANG_ASSERT(fieldLinkage);
-
- auto fieldLayout = getFieldLayout(typeLayout, fieldLinkage->getMangledName());
+ auto fieldLayout = getFieldLayout(typeLayout, ee.key);
RefPtr<TypeLayout> fieldTypeLayout = fieldLayout ? fieldLayout->typeLayout : nullptr;
+ // If we have a type layout coming in, we really expect to have a layout for each field.
+ SLANG_ASSERT(fieldLayout || !typeLayout);
+
// If we are processing layout information, then
// we need to create a new link in the chain
// of variables that will determine offsets
// for the eventual leaf fields...
- LegalVarChain newVarChainStorage;
- LegalVarChain* newVarChain = varChain;
- if (fieldLayout)
- {
- newVarChainStorage.next = varChain;
- newVarChainStorage.varLayout = fieldLayout;
- newVarChain = &newVarChainStorage;
- }
+ //
+ LegalVarChainLink newVarChain(varChain, fieldLayout);
UnownedStringSlice fieldNameHint;
String joinedNameHintStorage;
@@ -1723,21 +2298,18 @@ static LegalVal declareVars(
{
auto wrappedBuffer = type.getWrappedBuffer();
+ auto wrappedTypeLayout = _createWrappedBufferTypeLayout(typeLayout, wrappedBuffer, varChain);
+
auto innerVal = declareSimpleVar(
context,
op,
wrappedBuffer->simpleType,
- typeLayout,
+ wrappedTypeLayout,
varChain,
nameHint,
globalNameInfo);
- auto wrappedVal = wrapBufferValue(
- context,
- innerVal,
- wrappedBuffer->elementInfo);
-
- return wrappedVal;
+ return LegalVal::wrappedBuffer(innerVal, wrappedBuffer->elementInfo);
}
default:
@@ -1776,7 +2348,8 @@ static LegalVal legalizeGlobalVar(
globalNameInfo.counter = 0;
UnownedStringSlice nameHint = findNameHint(irGlobalVar);
- LegalVal newVal = declareVars(context, kIROp_GlobalVar, legalValueType, nullptr, nullptr, nameHint, &globalNameInfo);
+ context->builder->setInsertBefore(irGlobalVar);
+ LegalVal newVal = declareVars(context, kIROp_GlobalVar, legalValueType, nullptr, LegalVarChain(), nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
registerLegalizedValue(context, irGlobalVar, newVal);
@@ -1819,7 +2392,8 @@ static LegalVal legalizeGlobalConstant(
// TODO: need to handle initializer here!
UnownedStringSlice nameHint = findNameHint(irGlobalConstant);
- LegalVal newVal = declareVars(context, kIROp_GlobalConstant, legalValueType, nullptr, nullptr, nameHint, &globalNameInfo);
+ context->builder->setInsertBefore(irGlobalConstant);
+ LegalVal newVal = declareVars(context, kIROp_GlobalConstant, legalValueType, nullptr, LegalVarChain(), nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
registerLegalizedValue(context, irGlobalConstant, newVal);
@@ -1858,14 +2432,7 @@ static LegalVal legalizeGlobalParam(
{
context->insertBeforeGlobal = irGlobalParam->getNextInst();
- LegalVarChain* varChain = nullptr;
- LegalVarChain varChainStorage;
- if (varLayout)
- {
- varChainStorage.next = nullptr;
- varChainStorage.varLayout = varLayout;
- varChain = &varChainStorage;
- }
+ LegalVarChainLink varChain(LegalVarChain(), varLayout);
IRGlobalNameInfo globalNameInfo;
globalNameInfo.globalVar = irGlobalParam;
@@ -1874,6 +2441,7 @@ static LegalVal legalizeGlobalParam(
// TODO: need to handle initializer here!
UnownedStringSlice nameHint = findNameHint(irGlobalParam);
+ context->builder->setInsertBefore(irGlobalParam);
LegalVal newVal = declareVars(context, kIROp_GlobalParam, legalValueType, typeLayout, varChain, nameHint, &globalNameInfo);
// Register the new value as the replacement for the old
diff --git a/source/slang/ir-specialize-resources.cpp b/source/slang/ir-specialize-resources.cpp
index e974ffdb7..d25284927 100644
--- a/source/slang/ir-specialize-resources.cpp
+++ b/source/slang/ir-specialize-resources.cpp
@@ -424,9 +424,9 @@ struct ResourceParameterSpecializationContext
// An easy case is when P is a parameter that doesn't need
// specialization. In that case:
//
- // * The existing argument A shold be used as an argument in
+ // * The existing argument A should be used as an argument in
// the specialized call.
- // * A clone P' of the existing parameter P shold be used as a
+ // * A clone P' of the existing parameter P should be used as a
// parameter of the specialized callee.
// * No additional instructions are needed in the body of
// the callee; the cloned parameter P' should stand in for P.
diff --git a/source/slang/legalize-types.cpp b/source/slang/legalize-types.cpp
index 6e459ecf1..995b797e4 100644
--- a/source/slang/legalize-types.cpp
+++ b/source/slang/legalize-types.cpp
@@ -591,6 +591,18 @@ LegalType createLegalUniformBufferTypeForResources(
{
switch (legalElementType.flavor)
{
+ case LegalType::Flavor::simple:
+ {
+ // Seeing a simple type here means that it must be a
+ // "special" type (a resource type or array thereof)
+ // because otherwise the catch-all behavior in
+ // `createLegalUniformBufferType()` would have handled it.
+ //
+ // This case is the same as what we do for tuple elements below.
+ //
+ return LegalType::implicitDeref(legalElementType);
+ }
+
case LegalType::Flavor::pair:
{
auto pairType = legalElementType.getPair();
@@ -1080,6 +1092,10 @@ LegalType legalizeTypeImpl(
TypeLegalizationContext* context,
IRType* type)
{
+ if(!type)
+ return LegalType::simple(nullptr);
+
+ context->builder->setInsertBefore(type);
if (auto uniformBufferType = as<IRUniformParameterGroupType>(type))
{
@@ -1097,9 +1113,27 @@ LegalType legalizeTypeImpl(
// we'll want to completely eliminate the uniform/ordinary
// part.
+ auto originalElementType = uniformBufferType->getElementType();
+
// Legalize the element type to see what we are working with.
auto legalElementType = legalizeType(context,
- uniformBufferType->getElementType());
+ originalElementType);
+
+ // As a bit of a corner case, if the user requested something
+ // like `ConstantBuffer<Texture2D>` the element type would
+ // legalize to a "simple" type, and that would be interpreted
+ // as an *ordinary* type, but we really need to notice the
+ // case when the element type is simple, but *special*.
+ //
+ if( context->isSpecialType(originalElementType) )
+ {
+ // Anything that has a special element type needs to
+ // be handled by the pass-specific logic in the context.
+ //
+ return context->createLegalUniformBufferType(
+ uniformBufferType->op,
+ legalElementType);
+ }
// Note that even when legalElementType.flavor == Simple
// we still need to create a new uniform buffer type
@@ -1270,7 +1304,7 @@ RefPtr<TypeLayout> getDerefTypeLayout(
RefPtr<VarLayout> getFieldLayout(
TypeLayout* typeLayout,
- String const& mangledFieldName)
+ IRInst* fieldKey)
{
if (!typeLayout)
return nullptr;
@@ -1294,9 +1328,26 @@ RefPtr<VarLayout> getFieldLayout(
if (auto structTypeLayout = as<StructTypeLayout>(typeLayout))
{
+ // First, let's see if the field had a layout registered
+ // directly using its IR key.
+ //
+ RefPtr<VarLayout> fieldLayout;
+ if(structTypeLayout->mapKeyToLayout.TryGetValue(fieldKey, fieldLayout))
+ return fieldLayout;
+
+ // Otherwise, fall back to doing lookup using the linkage
+ // attached to the key, and its mangled name.
+ //
+ auto fieldLinkage = fieldKey->findDecoration<IRLinkageDecoration>();
+ if(!fieldLinkage)
+ return nullptr;
+ auto mangledFieldName = fieldLinkage->getMangledName();
+
+ // In this case we fall back to a linear search over the fields.
+ //
for(auto ff : structTypeLayout->fields)
{
- if(mangledFieldName == getMangledName(ff->varDecl.getDecl()) )
+ if(mangledFieldName == getMangledName(ff->varDecl.getDecl()).getUnownedSlice() )
{
return ff;
}
@@ -1306,9 +1357,9 @@ RefPtr<VarLayout> getFieldLayout(
return nullptr;
}
-RefPtr<VarLayout> createVarLayout(
- LegalVarChain* varChain,
- TypeLayout* typeLayout)
+RefPtr<VarLayout> createSimpleVarLayout(
+ SimpleLegalVarChain* varChain,
+ TypeLayout* typeLayout)
{
if (!typeLayout)
return nullptr;
@@ -1372,7 +1423,23 @@ RefPtr<VarLayout> createVarLayout(
}
}
+ return varLayout;
+}
+
+RefPtr<VarLayout> createVarLayout(
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout)
+{
+ if(!typeLayout)
+ return nullptr;
+
+ auto varLayout = createSimpleVarLayout(varChain.primaryChain, typeLayout);
+
+ if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout)
+ {
+ varLayout->pendingVarLayout = createSimpleVarLayout(varChain.pendingChain, typeLayout);
+ }
return varLayout;
}
diff --git a/source/slang/legalize-types.h b/source/slang/legalize-types.h
index 831d3538d..b35ed9d3c 100644
--- a/source/slang/legalize-types.h
+++ b/source/slang/legalize-types.h
@@ -328,20 +328,139 @@ RefPtr<TypeLayout> getDerefTypeLayout(
RefPtr<VarLayout> getFieldLayout(
TypeLayout* typeLayout,
- String const& mangledFieldName);
+ IRInst* fieldKey);
-// Represents the "chain" of declarations that
-// were followed to get to a variable that we
-// are now declaring as a leaf variable.
+ /// Represents a "chain" of variables leading to some leaf field.
+ ///
+ /// Consider code like:
+ ///
+ /// struct Branch { int leaf; }
+ /// struct Tree { Branch left; Branch right; }
+ /// cbuffer Forest
+ /// {
+ /// int maxTreeHeight;
+ /// Tree tree;
+ /// }
+ ///
+ /// If we ask "what is the offset of `leaf`" the simple answer is zero,
+ /// but sometimes we are talking about `Forest.tree.right.leaf` which
+ /// will have a very different offset. In Slang parameters can consume
+ /// various (and multiple) resource kinds, so a single offset can't
+ /// be tunneled down through most recursive procedures.
+ ///
+ /// Instead we use a "chain" that works up through the stack, and
+ /// records the path from leaf field like `leaf` up to whatever
+ /// variable is the root for the curent operation.
+ ///
+ /// Operations like computing an offset can then be encoded by
+ /// starting with zero and then walking up the chain and adding in
+ /// offsets as encountered.
+ ///
+struct SimpleLegalVarChain
+{
+ // The next link up the chain, or null if this is the end.
+ SimpleLegalVarChain* next = nullptr;
+
+ // The layout for the variable at this link in thain.
+ VarLayout* varLayout = nullptr;
+};
+
+ /// A "chain" of variable declarations that can handle both primary and "pending" data.
+ ///
+ /// In the presence of interface-type fields, a single variable may
+ /// have data that sits in two distinct allocations, and may have
+ /// `VarLayout`s that represent offseting into each of those
+ /// allocations.
+ ///
+ /// A `LegalVarChain` tracks two distinct `SimpleVarChain`s: one for
+ /// the primary/ordinary data allocation, and one for any pending
+ /// data.
+ ///
+ /// It is okay if the primary/pending chains have different numbers
+ /// of links in them.
+ ///
+ /// Offsets for particular resource kinds in the primary or pending
+ /// data allocation can be queried on the appropriate sub-chain.
+ ///
struct LegalVarChain
{
- LegalVarChain* next;
- VarLayout* varLayout;
+ // The chain of variables that represents the primary allocation.
+ SimpleLegalVarChain* primaryChain = nullptr;
+
+ // The chain of variables that represents the pending allocation.
+ SimpleLegalVarChain* pendingChain = nullptr;
+
+ // If the primary chain is non-empty, gets the variable at the leaf.
+ DeclRef<VarDeclBase> getLeafVarDeclRef() const
+ {
+ if(!primaryChain)
+ return DeclRef<VarDeclBase>();
+
+ return primaryChain->varLayout->varDecl;
+ }
+};
+
+ /// RAII type for adding a link to a `LegalVarChain` as needed.
+ ///
+ /// This type handles the bookkeeping for creating a `LegalVarChain`
+ /// that links in one more variable. It will add a link to each of
+ /// the primary and pending sub-chains if and only if there is non-null
+ /// layout information for the primary/pending case.
+ ///
+ /// Typical usage in a recursive function is:
+ ///
+ /// void someRecursiveFunc(LegalVarChain const& outerChain, ...)
+ /// {
+ /// if(auto subVar = needToRecurse(...))
+ /// {
+ /// LegalVarChainLink subChain(outerChain, subVar);
+ /// someRecursiveFunc(subChain, ...);
+ /// }
+ /// ...
+ /// }
+ ///
+struct LegalVarChainLink : LegalVarChain
+{
+ /// Default constructor: yields an empty chain.
+ LegalVarChainLink()
+ {
+ }
+
+ /// Copy constructor: yields a copy of the `parent` chain.
+ LegalVarChainLink(LegalVarChain const& parent)
+ : LegalVarChain(parent)
+ {}
+
+ /// Construct a chain that extends `parent` with `varLayout`, if it is non-null.
+ LegalVarChainLink(LegalVarChain const& parent, VarLayout* varLayout)
+ : LegalVarChain(parent)
+ {
+ if( varLayout )
+ {
+ primaryLink.next = parent.primaryChain;
+ primaryLink.varLayout = varLayout;
+ primaryChain = &primaryLink;
+
+ if( auto pendingVarLayout = varLayout->pendingVarLayout )
+ {
+ pendingLink.next = parent.pendingChain;
+ pendingLink.varLayout = pendingVarLayout;
+ pendingChain = &pendingLink;
+ }
+ }
+ }
+
+ SimpleLegalVarChain primaryLink;
+ SimpleLegalVarChain pendingLink;
};
RefPtr<VarLayout> createVarLayout(
- LegalVarChain* varChain,
- TypeLayout* typeLayout);
+ LegalVarChain const& varChain,
+ TypeLayout* typeLayout);
+
+RefPtr<VarLayout> createSimpleVarLayout(
+ SimpleLegalVarChain* varChain,
+ TypeLayout* typeLayout);
//
// The result of legalizing an IR value will be
@@ -409,6 +528,16 @@ struct LegalVal
SLANG_ASSERT(flavor == Flavor::pair);
return obj.as<PairPseudoVal>();
}
+
+ static LegalVal wrappedBuffer(
+ LegalVal const& baseVal,
+ LegalElementWrapping const& elementInfo);
+
+ RefPtr<WrappedBufferPseudoVal> getWrappedBuffer() const
+ {
+ SLANG_ASSERT(flavor == Flavor::wrappedBuffer);
+ return obj.as<WrappedBufferPseudoVal>();
+ }
};
struct TuplePseudoVal : LegalValImpl
@@ -437,6 +566,12 @@ struct ImplicitDerefVal : LegalValImpl
LegalVal val;
};
+struct WrappedBufferPseudoVal : LegalValImpl
+{
+ LegalVal base;
+ LegalElementWrapping elementInfo;
+};
+
//
/// Context that drives type legalization
diff --git a/source/slang/options.cpp b/source/slang/options.cpp
index a7334f13b..dc37873a4 100644
--- a/source/slang/options.cpp
+++ b/source/slang/options.cpp
@@ -468,6 +468,7 @@ struct OptionsParser
else if(argStr == "-validate-ir" )
{
requestImpl->getFrontEndReq()->shouldValidateIR = true;
+ requestImpl->getBackEndReq()->shouldValidateIR = true;
}
else if(argStr == "-skip-codegen" )
{
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;
{
diff --git a/source/slang/type-layout.cpp b/source/slang/type-layout.cpp
index 77588036d..ce767fc8b 100644
--- a/source/slang/type-layout.cpp
+++ b/source/slang/type-layout.cpp
@@ -878,30 +878,6 @@ bool IsResourceKind(LayoutResourceKind kind)
}
- /// A custom tuple to capture the outputs of type layout
-struct TypeLayoutResult
-{
- /// The actual heap-allocated layout object with all the details
- RefPtr<TypeLayout> layout;
-
- /// A simplified representation of layout information.
- ///
- /// This information is suitable for the case where a type only
- /// consumes a single resource.
- ///
- SimpleLayoutInfo info;
-
- /// Default constructor.
- TypeLayoutResult()
- {}
-
- /// Construct a result from the given layout object and simple layout info.
- TypeLayoutResult(RefPtr<TypeLayout> inLayout, SimpleLayoutInfo const& inInfo)
- : layout(inLayout)
- , info(inInfo)
- {}
-};
-
/// Create a type layout for a type that has simple layout needs.
///
/// This handles any type that can express its layout in `SimpleLayoutInfo`,
@@ -1090,18 +1066,17 @@ static bool shouldAllocateRegisterSpaceForParameterBlock(
}
// Given an existing type layout `oldTypeLayout`, apply offsets
-// to any contained fields based on the resource infos in `offsetTypeLayout`
-// *and* the usage implied by `offsetLayout`
+// to any contained fields based on the resource infos in `offsetVarLayout`.
RefPtr<TypeLayout> applyOffsetToTypeLayout(
RefPtr<TypeLayout> oldTypeLayout,
- RefPtr<TypeLayout> offsetTypeLayout)
+ RefPtr<VarLayout> offsetVarLayout)
{
// There is no need to apply offsets if the old type and the offset
// don't share any resource infos in common.
bool anyHit = false;
for (auto oldResInfo : oldTypeLayout->resourceInfos)
{
- if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind))
+ if (auto offsetResInfo = offsetVarLayout->FindResourceInfo(oldResInfo.kind))
{
anyHit = true;
break;
@@ -1138,12 +1113,9 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout(
auto newResInfo = newField->findOrAddResourceInfo(oldResInfo.kind);
newResInfo->index = oldResInfo.index;
newResInfo->space = oldResInfo.space;
- if (auto offsetResInfo = offsetTypeLayout->FindResourceInfo(oldResInfo.kind))
+ if (auto offsetResInfo = offsetVarLayout->FindResourceInfo(oldResInfo.kind))
{
- // We should not be trying to offset things by an infinite amount,
- // since that would leave all the indices undefined.
- SLANG_RELEASE_ASSERT(offsetResInfo->count.isFinite());
- newResInfo->index += offsetResInfo->count.getFiniteValue();
+ newResInfo->index += offsetResInfo->index;
}
}
@@ -1180,273 +1152,466 @@ RefPtr<TypeLayout> applyOffsetToTypeLayout(
return newTypeLayout;
}
- /// Take a type layout that might include pending items and fold them into the layout.
-static RefPtr<TypeLayout> flushPendingItems(
- TypeLayoutContext const& context,
- RefPtr<TypeLayout> layout)
+static bool _usesResourceKind(RefPtr<TypeLayout> typeLayout, LayoutResourceKind kind)
{
- SLANG_UNUSED(context);
+ auto resInfo = typeLayout->FindResourceInfo(kind);
+ return resInfo && resInfo->count != 0;
+}
- // If there are no pending items on the layout,
- // then there is nothing to be done.
- //
- if(layout->pendingItems.Count() == 0)
- return layout;
+static bool _usesOrdinaryData(RefPtr<TypeLayout> typeLayout)
+{
+ return _usesResourceKind(typeLayout, LayoutResourceKind::Uniform);
+}
- // We need to compute a new type layout that reflects
- // the resource usage of the provided `layout`, plus
- // any resource usage for the pending items.
- //
- // TODO: To be correct we should construct a new `TypeLayout`
- // of the same class, but that would take more work, so
- // we'll re-use the one we already have... kind of gross...
- //
- for( auto pendingItem : layout->pendingItems )
+ /// Add resource usage from `srcTypeLayout` to `dstTypeLayout` unless it would be "masked."
+ ///
+ /// This function is appropriate for applying resource usage from an element type
+ /// to the resource usage of a container like a `ConstantBuffer<X>` or
+ /// `ParameterBlock<X>`.
+ ///
+static void _addUnmaskedResourceUsage(
+ TypeLayout* dstTypeLayout,
+ TypeLayout* srcTypeLayout,
+ bool haveFullRegisterSpaceOrSet)
+{
+ for( auto resInfo : srcTypeLayout->resourceInfos )
{
- auto itemTypeLayout = pendingItem.getTypeLayout();
-
- // Any resources used by a pending item should be
- // billed against the flushed layout we are computing.
- //
- // TODO: We need to make this handlde ordinary/uniform
- // data carefully, so that it respects alignment and
- // other layout rules for the target.
- //
- // TODO: We should only be adding in resource usage
- // that can be "hidden" by the type of parameter block
- // being built (e.g., only a `ParameterBlock` that allocates
- // full `set`s/`space`s can hide the `register`s/`binding`s
- // used by resource fields).
- //
- // TODO: we need to write something back to the item,
- // which should have a `VarLayout` or something like
- // that attached to it!
- //
- for( auto resInfo : itemTypeLayout->resourceInfos )
+ switch( resInfo.kind )
{
- layout->addResourceUsage(resInfo);
+ case LayoutResourceKind::Uniform:
+ // Ordinary/uniform resource usage will always be masked.
+ break;
+
+ case LayoutResourceKind::RegisterSpace:
+ case LayoutResourceKind::ExistentialTypeParam:
+ // A parameter group will always pay for full registers
+ // spaces consumed by its element type.
+ //
+ // The same is true for existential type parameters,
+ // since these need to be exposed up through the API.
+ //
+ dstTypeLayout->addResourceUsage(resInfo);
+ break;
+
+ default:
+ // For all other resource kinds, a parameter group
+ // will be able to mask them if and only if it
+ // has a full space/set allocated to it.
+ //
+ // Otherwise, the resource usage of the group must
+ // include the resource usage of the element.
+ //
+ if( !haveFullRegisterSpaceOrSet )
+ {
+ dstTypeLayout->addResourceUsage(resInfo);
+ }
+ break;
}
}
- layout->pendingItems.Clear();
-
- return layout;
}
-static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
+static RefPtr<TypeLayout> _createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType,
- SimpleLayoutInfo parameterGroupInfo,
RefPtr<TypeLayout> rawElementTypeLayout)
{
- // If there are any "pending" items that need to be laid out in
- // the element type of the parameter group, then we want to flush
- // them here.
+ // We are being asked to create a layout for a parameter group,
+ // which is curently either a `ParameterBlock<T>` or a `ConstantBuffer<T>`
//
- // TODO: We might need to make this only flush *parts* of the pending
- // items, based on what the parameter group can absorb, and leave
- // other parts still pending in the type layout we return...
- //
- rawElementTypeLayout = flushPendingItems(
- context.with(context.getRulesFamily()->getConstantBufferRules()),
- rawElementTypeLayout);
-
auto parameterGroupRules = context.rules;
-
RefPtr<ParameterGroupTypeLayout> typeLayout = new ParameterGroupTypeLayout();
typeLayout->type = parameterGroupType;
typeLayout->rules = parameterGroupRules;
+ // Computing the layout is made tricky by several factors.
+ //
+ // A parameter group has to draw a distinction between the element type,
+ // and the resources it consumes, and the "container," which main
+ // consume other resources. The type of resource consumed by
+ // the two can overlap.
+ //
+ // Consider:
+ //
+ // struct MyMaterial { float2 uvScale; Texture2D albedoMap; }
+ // ParameterBlock<MyMaterial> gMaterial;
+ //
+ // In this example, `gMaterial` will need both a constant buffer
+ // binding (to hold the data for `uvScale`) and a texture binding
+ // (for `albedoMap`). On Vulkan, those two things require the *same*
+ // `LayoutResourceKind` (representing a GLSL `binding`). We will
+ // thus track the resource usage of the "container" type and
+ // element type separately, and then combine these to form
+ // the overall layout for the parameter group.
+
RefPtr<TypeLayout> containerTypeLayout = new TypeLayout();
containerTypeLayout->type = parameterGroupType;
containerTypeLayout->rules = parameterGroupRules;
- // The layout of the constant buffer if it gets stored
- // in another constant buffer is just what we computed
- // originally (which should be a single binding "slot"
- // and hence no uniform data).
- //
- SLANG_RELEASE_ASSERT(parameterGroupInfo.kind != LayoutResourceKind::Uniform);
- typeLayout->uniformAlignment = 1;
- containerTypeLayout->uniformAlignment = 1;
-
- // TODO(tfoley): There is a subtle question here of whether
- // a constant buffer declaration that then contains zero
- // bytes of uniform data should actually allocate a CB
- // binding slot. For now I'm going to try to ignore it,
- // but handling this robustly could let other code
- // simply handle the "global scope" as a giant outer
- // CB declaration...
-
- // Make sure that we allocate resource usage for the
- // parameter block itself.
- if( parameterGroupInfo.size != 0 )
- {
- containerTypeLayout->addResourceUsage(
- parameterGroupInfo.kind,
- parameterGroupInfo.size);
- }
-
- // There are several different cases that need to be handled here,
- // depending on whether we have a `ParameterBlock`, a `ConstantBuffer`,
- // or some other kind of parameter group. Furthermore, in the
- // `ParameterBlock` case, we need to deal with different layout
- // rules depending on whether a block should map to a register `space`
- // in HLSL or not.
-
- // Check if we are working with a parameter block...
+ // Because the container and element types will each be situated
+ // at some offset relative to the initial register/binding for
+ // the group as a whole, we allocate a `VarLayout` for both
+ // the container and the element type, to store that offset
+ // information (think of `TypeLayout`s as holding size information,
+ // while `VarLayout`s hold offset information).
+
+ RefPtr<VarLayout> containerVarLayout = new VarLayout();
+ containerVarLayout->typeLayout = containerTypeLayout;
+ typeLayout->containerVarLayout = containerVarLayout;
+
+ RefPtr<VarLayout> elementVarLayout = new VarLayout();
+ elementVarLayout->typeLayout = rawElementTypeLayout;
+ typeLayout->elementVarLayout = elementVarLayout;
+
+ // It is possible to have a `ConstantBuffer<T>` that doesn't
+ // actually need a constant buffer register/binding allocated to it,
+ // because the type `T` doesn't actually contain any ordinary/uniform
+ // data that needs to go into the constant buffer. For example:
+ //
+ // struct MyMaterial { Texture2D t; SamplerState s; };
+ // ConstantBuffer<MyMaterial> gMaterial;
+ //
+ // In this example, the `gMaterial` parameter doesn't actually need
+ // a constant buffer allocated for it. This isn't something that
+ // comes up often for `ConstantBuffer`, but can happen a lot for
+ // `ParameterBlock`.
+ //
+ // To determine if we actually need a constant-buffer binding,
+ // we will inspect the element type and see if it contains
+ // any ordinary/uniform data.
+ //
+ bool wantConstantBuffer = _usesOrdinaryData(rawElementTypeLayout);
+ if( wantConstantBuffer )
+ {
+ // If there is any ordinary data, then we'll need to
+ // allocate a constant buffer regiser/binding into
+ // the overall layout, to account for it.
+ //
+ auto cbUsage = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
+ containerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
+ }
+
+ // Similarly to how we only need a constant buffer to be allocated
+ // if the contents of the group actually had ordinary/uniform data,
+ // we also only want to allocate a `space` or `set` if that is really
+ // required.
+ //
+ //
+ bool canUseSpaceOrSet = false;
+ //
+ // We will only allocate a `space` or `set` if the type is `ParameterBlock<T>`
+ // and not just `ConstantBuffer<T>`.
+ //
+ // Note: `parameterGroupType` is allowed to be null here, if we are allocating
+ // an anonymous constant buffer for global or entry-point parameters, but that
+ // is fine because the case will just return null in that case anyway.
+ //
auto parameterBlockType = as<ParameterBlockType>(parameterGroupType);
-
- // Check if we have a parameter block *and* it should be
- // allocated into its own register space(s)
- bool ownRegisterSpace = false;
- if (parameterBlockType)
+ if( parameterBlockType )
{
- // Should we allocate this block its own register space?
+ // We also can't allocate a `space` or `set` unless the compilation
+ // target actually supports them.
+ //
if( shouldAllocateRegisterSpaceForParameterBlock(context) )
{
- ownRegisterSpace = true;
+ canUseSpaceOrSet = true;
}
+ }
- // If we need a register space, then maybe allocate one.
- if( ownRegisterSpace )
- {
- // The basic logic here is that if the parameter block only
- // contains other parameter blocks (which themselves have
- // their own register spaces), then we don't need to
- // allocate *yet another* register space for the block.
-
- bool needsARegisterSpace = false;
- for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
- {
- if(elementResourceInfo.kind != LayoutResourceKind::RegisterSpace)
- {
- needsARegisterSpace = true;
- break;
- }
- }
-
- // If we determine that a register space is needed, then add one here.
- if( needsARegisterSpace )
- {
- typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
- }
- }
+ // Just knowing that we *can* use a `space` or `set` doesn't tell
+ // us if we would *like* to.
+ //
+ // The basic rule here is that if the element type of the parameter
+ // block contains anything that isn't itself consuming a full
+ // register `space` or `set`, then we'll want an umbrella `space`/`set`
+ // for all such data.
+ //
+ bool wantSpaceOrSet = false;
+ if( canUseSpaceOrSet )
+ {
+ // Note that if we are allocating a constant buffer to hold
+ // some ordinary/uniform data then we definitely want a space/set,
+ // but we don't need to special-case that because the loop
+ // here will also detect the `LayoutResourceKind::Uniform` usage.
- // Next, we check if the parameter block has any uniform data, since
- // that means we need to allocate a constant-buffer binding for it.
- bool anyUniformData = false;
- if(auto elementUniformInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
{
- if( elementUniformInfo->count != 0 )
+ if(elementResourceInfo.kind != LayoutResourceKind::RegisterSpace)
{
- // We have a non-zero number of bytes of uniform data here.
- anyUniformData = true;
+ wantSpaceOrSet = true;
+ break;
}
}
- if( anyUniformData )
- {
- // We need to ensure that the block itself consumes at least one "register" for its
- // constant buffer part.
- auto cbUsage = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
- containerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
- }
}
- // The layout for the element type was computed without any knowledge
- // of what resources the parent type was going to consume; we now
- // need to go through and offset that any starting locations (e.g.,
- // in nested `StructTypeLayout`s) based on what we allocated to
- // the parent.
+ // If after all that we determine that we want a register space/set,
+ // then we allocate one as part of the overall resource usage for
+ // the parameter group type.
//
- // Note: at the moment, constant buffers apply their own offsetting
- // logic elsewhere, so we need to only do this logic for parameter blocks
- RefPtr<TypeLayout> offsetTypeLayout = applyOffsetToTypeLayout(rawElementTypeLayout, containerTypeLayout);
- typeLayout->offsetElementTypeLayout = offsetTypeLayout;
-
+ if( wantSpaceOrSet )
+ {
+ containerTypeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
+ }
- RefPtr<VarLayout> containerVarLayout = new VarLayout();
- containerVarLayout->typeLayout = containerTypeLayout;
+ // Now that we've computed basic resource requirements for the container
+ // part of things (i.e., does it require a constant buffer or not?),
+ // let's go ahead and assign the container variable a relative offset
+ // of zero for each of the kinds of resources that it consumes.
+ //
for( auto typeResInfo : containerTypeLayout->resourceInfos )
{
containerVarLayout->findOrAddResourceInfo(typeResInfo.kind);
}
- typeLayout->containerVarLayout = containerVarLayout;
- // We will construct a dummy variable layout to represent the offsettting
- // that needs to be applied to the element type to put it after the
- // container.
- RefPtr<VarLayout> elementVarLayout = new VarLayout();
- elementVarLayout->typeLayout = rawElementTypeLayout;
+ // Because the container's resource allocation is logically coming
+ // first in the overall group, the element needs to have a layout
+ // such that it comes *after* the container in the relative order.
+ //
for( auto elementTypeResInfo : rawElementTypeLayout->resourceInfos )
{
auto kind = elementTypeResInfo.kind;
auto elementVarResInfo = elementVarLayout->findOrAddResourceInfo(kind);
+
+ // If the container part of things is using the same resource kind
+ // as the element type, then the element needs to start at an offset
+ // after the container.
+ //
if( auto containerTypeResInfo = containerTypeLayout->FindResourceInfo(kind) )
{
SLANG_RELEASE_ASSERT(containerTypeResInfo->count.isFinite());
elementVarResInfo->index += containerTypeResInfo->count.getFiniteValue();
}
}
- typeLayout->elementVarLayout = elementVarLayout;
- if (ownRegisterSpace)
+ // The existing Slang reflection API was created before we really
+ // understood the wrinkle that the "container" and elements parts
+ // of a parameter group could collide on some resource kinds,
+ // so the API doesn't currently expose the nice `VarLayout`s we've
+ // just computed.
+ //
+ // Instead, the API allows the user to query the element type layout
+ // for the group, and the user just assumes that the offsetting
+ // is magically applied there. To go back to the earlier example:
+ //
+ // struct MyMaterial { Texture2D t; SamplerState s; };
+ // ConstantBuffer<MyMaterial> gMaterial;
+ //
+ // A user of the existing reflection API expects to be able to
+ // query the `binding` of `gMaterial` and get back zero, then
+ // query the `binding` of the `t` field of the element type
+ // and get *one*. It is clear that in the abstract, the
+ // `MyMaterial::t` field should have an offset of zero (as
+ // the first field in a `struct`), so to meet the user's
+ // expectations, some cleverness is needed.
+ //
+ // We will use a subroutine `applyOffsetToTypeLayout`
+ // that tries to recursively walk an existing `TypeLayout`
+ // and apply an offset to its fields. This is currently
+ // quite ad hoc, but that doesn't matter much as it
+ // handles `struct` types which are the 99% case for
+ // parameter blocks.
+ //
+ typeLayout->offsetElementTypeLayout = applyOffsetToTypeLayout(rawElementTypeLayout, elementVarLayout);
+
+ // Next, resource usage from the container and element
+ // types may need to "bleed through" to the overall
+ // parameter group type.
+ //
+ // If the parameter group is a `ConstantBuffer<Foo>` then
+ // any ordinary/uniform bytes consumed by `Foo` are masked,
+ // but any other resources it consumes (e.g. `binding`s) need
+ // to bleed through and be accounted for in the overall
+ // layout of the type.
+ //
+ // If we have a `ParameterBlock<Foo>` then any ordinary/uniform
+ // bytes are masked. Furthermore, *if* a whole `space`/`set`
+ // was allocated to the block, then any `register`s or
+ // `binding`s consumed by `Foo` (and by the "container" constant
+ // buffer if we allocated one) are also masked. Any whole
+ // spaces/sets consumed by `Foo` need to bleed through.
+ //
+ // We can start with the easier case of the container type,
+ // since it will either be empty or consume a single constant
+ // buffer. Its resource usage will only bleed through if we
+ // didn't allocate a full `space` or `set`.
+ //
+ _addUnmaskedResourceUsage(typeLayout, containerTypeLayout, wantSpaceOrSet);
+
+ // next we turn to the element type, where the cases are slightly
+ // more involved (technically we could use this same logic for
+ // the container, as it is more general, but it was simpler to
+ // just special-case the container).
+ //
+
+ _addUnmaskedResourceUsage(typeLayout, rawElementTypeLayout, wantSpaceOrSet);
+
+ // At this point we have handled all the complexities that
+ // arise for a parameter group that doesn't include interface-type
+ // fields, or that doesn't include specialization for those fields.
+ //
+ // The remaining complexity all arises if we have interface-type
+ // data in the parameter group, and we are specializing it to
+ // concrete types, that will have their own layout requirements.
+ // In those cases there will be "pending data" on the element
+ // type layout that need to get placed somwhere, but wasn't
+ // included in the layout computed so far.
+ //
+ // All of this is extra work we only have to do if there is
+ // "pending" data in the element type layout.
+ //
+ if( auto pendingElementTypeLayout = rawElementTypeLayout->pendingDataTypeLayout )
{
- // A parameter block type that gets its own register space will only
- // include resource usage from the element type when it itself consumes
- // whole register spaces.
- if (auto elementResInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace))
+ auto rules = rawElementTypeLayout->rules;
+
+ // One really annoying complication we need to deal with here
+ // its that it is possible that the original parameter group
+ // declaration didn't need a constant buffer or `space`/`set`
+ // to be allocated, but once we consider the "pending" data
+ // we need to have a constant buffer and/or space.
+ //
+ // We will compute whether the pending data create a demand
+ // for a constant buffer and/or a space/set, so that we know
+ // if we are in the tricky case.
+ //
+ bool pendingDataWantsConstantBuffer = _usesOrdinaryData(pendingElementTypeLayout);
+ bool pendingDataWantsSpaceOrSet = false;
+ if( canUseSpaceOrSet )
{
- typeLayout->addResourceUsage(*elementResInfo);
+ for( auto resInfo : pendingElementTypeLayout->resourceInfos )
+ {
+ if( resInfo.kind != LayoutResourceKind::RegisterSpace )
+ {
+ pendingDataWantsSpaceOrSet = true;
+ break;
+ }
+ }
}
- }
- else
- {
- // If the parameter block is *not* getting its own regsiter space, then
- // it needs to include the resource usage from the "container" type, plus
- // any relevant resource usage for the element type.
- // We start by accumulating any resource usage from the container.
- for (auto containerResourceInfo : containerTypeLayout->resourceInfos)
+ // We will use a few different variables to track resource
+ // usage for the pending data, with roles similar to the
+ // umbrella type layout, container layout, and element layout
+ // that already came up for the main part of the parameter group type.
+
+
+ RefPtr<TypeLayout> pendingContainerTypeLayout = new TypeLayout();
+ pendingContainerTypeLayout->type = parameterGroupType;
+ pendingContainerTypeLayout->rules = parameterGroupRules;
+
+ containerTypeLayout->pendingDataTypeLayout = pendingContainerTypeLayout;
+
+ RefPtr<VarLayout> pendingContainerVarLayout = new VarLayout();
+ pendingContainerVarLayout->typeLayout = pendingContainerTypeLayout;
+
+ containerVarLayout->pendingVarLayout = pendingContainerVarLayout;
+
+
+ RefPtr<VarLayout> pendingElementVarLayout = new VarLayout();
+ pendingElementVarLayout->typeLayout = pendingElementTypeLayout;
+
+ elementVarLayout->pendingVarLayout = pendingElementVarLayout;
+
+ // If we need a space/set for the pending data, and don't already
+ // have one, then we will allocate it now, as part of the
+ // "full" data type.
+ //
+ if( pendingDataWantsSpaceOrSet && !wantSpaceOrSet )
{
- typeLayout->addResourceUsage(containerResourceInfo);
+ pendingContainerTypeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, 1);
+
+ // From here on, we know we have access to a register space,
+ // and we can mask any registers/bindings appropriately.
+ //
+ wantSpaceOrSet = true;
}
- // Now we will (possibly) accumulate the resources used by the element
- // type into the resources used by the parameter group. The reason
- // this is "possibly" is because, e.g., a `ConstantBuffer<Foo>` should
- // not report itself as consuming `sizeof(Foo)` bytes of uniform data,
- // or else it would mess up layout for any type that contains the
- // constant buffer.
- for( auto elementResourceInfo : rawElementTypeLayout->resourceInfos )
+ // If we need a constant buffer for laying out ordinary
+ // data, and didn't have one allocated before, we will create
+ // one.
+ //
+ if( pendingDataWantsConstantBuffer && !wantConstantBuffer )
{
- switch( elementResourceInfo.kind )
- {
- case LayoutResourceKind::Uniform:
- // Uniform resource usages will always be hidden.
- break;
+ auto cbUsage = rules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
+ pendingContainerTypeLayout->addResourceUsage(cbUsage.kind, cbUsage.size);
- default:
- // All other register types will not be hidden,
- // since we aren't in the case where the parameter group
- // gets its own register space.
- typeLayout->addResourceUsage(elementResourceInfo);
- break;
+ wantConstantBuffer = true;
+ }
+
+ for( auto resInfo : pendingContainerTypeLayout->resourceInfos )
+ {
+ pendingContainerVarLayout->findOrAddResourceInfo(resInfo.kind);
+ }
+
+ // Now that we've added in the resource usage for any CB or set/space
+ // we needed to allocate just for the pending data, we can safely
+ // lay out the pending data itself.
+ //
+ // The ordinary/uniform part of things wil always be "masked" and
+ // needs to come after any uniform data from the original element type.
+ //
+ // To kick things off we will initialize state for `struct` type layout,
+ // so that we can lay out the pending data as if it were the second
+ // field in a structure type, after the original data.
+ //
+ UniformLayoutInfo uniformLayout = rules->BeginStructLayout();
+ if( auto resInfo = rawElementTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ uniformLayout.alignment = rawElementTypeLayout->uniformAlignment;
+ uniformLayout.size = resInfo->count;
+ }
+
+ // Now we can scan through the resources used by the pending data.
+ //
+ for( auto resInfo : pendingElementTypeLayout->resourceInfos )
+ {
+ if( resInfo.kind == LayoutResourceKind::Uniform )
+ {
+ // For the ordinary/uniform resource kind, we will add the resource
+ // usage as a structure field, and then write the resulting offset
+ // into the variable layout for the pending data.
+ //
+ auto offset = rules->AddStructField(
+ &uniformLayout,
+ UniformLayoutInfo(
+ resInfo.count,
+ pendingElementTypeLayout->uniformAlignment));
+ pendingElementVarLayout->findOrAddResourceInfo(resInfo.kind)->index = offset.getFiniteValue();
+ }
+ else
+ {
+ // For all other resource kinds, we will set the offset in
+ // the variable layout based on the total resources of that
+ // kind seen so far (including the "container" if any),
+ // and then bump the count for total resource usage.
+ //
+ auto elementVarResInfo = pendingElementVarLayout->findOrAddResourceInfo(resInfo.kind);
+ if( auto containerTypeInfo = pendingContainerTypeLayout->FindResourceInfo(resInfo.kind) )
+ {
+ elementVarResInfo->index = containerTypeInfo->count.getFiniteValue();
+ }
}
}
- }
+ rules->EndStructLayout(&uniformLayout);
- return typeLayout;
-}
+ // Okay, now we have a `VarLayout` for the element data, and an overall `TypeLayout`
+ // for all the data that this parameter group needs allocated for pending
+ // data.
+ //
+ // The next major step is to compute the version of that combined resource usage
+ // that will "bleed through" and thus needs to be allocated at the next level
+ // up the hierarchy.
+ //
+ RefPtr<TypeLayout> unmaskedPendingDataTypeLayout = new TypeLayout();
+ _addUnmaskedResourceUsage(unmaskedPendingDataTypeLayout, pendingContainerTypeLayout, wantSpaceOrSet);
+ _addUnmaskedResourceUsage(unmaskedPendingDataTypeLayout, pendingElementTypeLayout, wantSpaceOrSet);
-static bool usesResourceKind(RefPtr<TypeLayout> typeLayout, LayoutResourceKind kind)
-{
- auto resInfo = typeLayout->FindResourceInfo(kind);
- return resInfo && resInfo->count != 0;
-}
+ // TODO: we should probably optimize for the case where there is no unmasked
+ // usage that needs to be reported out, since it should be a common case.
-static bool usesOrdinaryData(RefPtr<TypeLayout> typeLayout)
-{
- return usesResourceKind(typeLayout, LayoutResourceKind::Uniform);
+ // Now we need to update the type layout to what we've done.
+ //
+ typeLayout->pendingDataTypeLayout = unmaskedPendingDataTypeLayout;
+ }
+
+ return typeLayout;
}
/// Do we need to wrap the given element type in a constant buffer layout?
@@ -1454,15 +1619,15 @@ static bool needsConstantBuffer(RefPtr<TypeLayout> elementTypeLayout)
{
// We need a constant buffer if the element type has ordinary/uniform data.
//
- if(usesOrdinaryData(elementTypeLayout))
+ if(_usesOrdinaryData(elementTypeLayout))
return true;
- // We also need a constant buffer if there are any "pending"
- // items that need ordinary/uniform data allocated to them.
+ // We also need a constant buffer if there is any "pending"
+ // data that need ordinary/uniform data allocated to them.
//
- for( auto pendingItem : elementTypeLayout->pendingItems )
+ if(auto pendingDataTypeLayout = elementTypeLayout->pendingDataTypeLayout)
{
- if(usesOrdinaryData(pendingItem.getTypeLayout()))
+ if(_usesOrdinaryData(pendingDataTypeLayout))
return true;
}
@@ -1487,50 +1652,29 @@ RefPtr<TypeLayout> createConstantBufferTypeLayoutIfNeeded(
.with(parameterGroupRules)
.with(context.targetReq->getDefaultMatrixLayoutMode()),
nullptr,
- parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
elementTypeLayout);
}
-static RefPtr<ParameterGroupTypeLayout> _createParameterGroupTypeLayout(
+static RefPtr<TypeLayout> _createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType,
RefPtr<Type> elementType,
LayoutRulesImpl* elementTypeRules)
{
- auto parameterGroupRules = context.rules;
-
- // First compute resource usage of the block itself.
- // For now we assume that the layout of the block can
- // always be described in a `SimpleLayoutInfo` (only
- // a single resource kind consumed).
- SimpleLayoutInfo info;
- if (parameterGroupType)
- {
- info = getParameterGroupLayoutInfo(
- parameterGroupType,
- parameterGroupRules);
- }
- else
- {
- // If there is no concrete type, then it seems like we are
- // being asked to compute layout for the global scope
- info = parameterGroupRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer);
- }
-
- // Now compute a layout for the elements of the parameter block.
- // Note that we need to be careful and deal with the case where
- // the elements of the block use the same resource kind consumed
- // by the block itself.
-
+ // We will first compute a layout for the element type of
+ // the parameter group.
+ //
auto elementTypeLayout = createTypeLayout(
context.with(elementTypeRules),
elementType);
+ // Now we delegate to a routine that does the meat of
+ // the complicated layout logic.
+ //
return _createParameterGroupTypeLayout(
context,
parameterGroupType,
- info,
elementTypeLayout);
}
@@ -1569,7 +1713,7 @@ LayoutRulesImpl* getParameterBufferElementTypeLayoutRules(
}
}
-RefPtr<ParameterGroupTypeLayout> createParameterGroupTypeLayout(
+RefPtr<TypeLayout> createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType)
{
@@ -1962,6 +2106,197 @@ static RefPtr<TypeLayout> maybeAdjustLayoutForArrayElementType(
}
}
+ /// Convert a `TypeLayout` to a `TypeLayoutResult`
+ ///
+ /// A `TypeLayout` holds all the data needed to make a `TypeLayoutResult` in practice,
+ /// but sometimes it is more convenient to have the data split out.
+ ///
+TypeLayoutResult makeTypeLayoutResult(RefPtr<TypeLayout> typeLayout)
+{
+ TypeLayoutResult result;
+ result.layout = typeLayout;
+
+ // If the type only consumes a single kind of non-uniform resource,
+ // we can fill in the `info` field directly.
+ //
+ if( typeLayout->resourceInfos.Count() == 1 )
+ {
+ auto resInfo = typeLayout->resourceInfos[0];
+ if( resInfo.kind != LayoutResourceKind::Uniform )
+ {
+ result.info.kind = resInfo.kind;
+ result.info.size = resInfo.count;
+ return result;
+ }
+ }
+
+ // Otherwise, we will fill out the info based on the uniform
+ // resources consumed, if any.
+ //
+ if( auto resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ result.info.kind = LayoutResourceKind::Uniform;
+ result.info.alignment = typeLayout->uniformAlignment;
+ result.info.size = resInfo->count;
+ }
+
+ // If there was no ordinary/uniform resource usage, then we
+ // will leave the `info` field in its default state (which
+ // shows no resources consumed).
+ //
+ // The type layout might have more detailed information, but
+ // at this point it must contain either zero, or more than one
+ // `ResourceInfo`, so there is nothing unambiguous we can
+ // store into `info`.
+
+ return result;
+}
+
+//
+// StructTypeLayoutBuilder
+//
+
+void StructTypeLayoutBuilder::beginLayout(
+ Type* type,
+ LayoutRulesImpl* rules)
+{
+ m_rules = rules;
+
+ m_typeLayout = new StructTypeLayout();
+ m_typeLayout->type = type;
+ m_typeLayout->rules = m_rules;
+
+ m_info = m_rules->BeginStructLayout();
+}
+
+void StructTypeLayoutBuilder::beginLayoutIfNeeded(
+ Type* type,
+ LayoutRulesImpl* rules)
+{
+ if( !m_typeLayout )
+ {
+ beginLayout(type, rules);
+ }
+}
+
+RefPtr<VarLayout> StructTypeLayoutBuilder::addField(
+ DeclRef<VarDeclBase> field,
+ TypeLayoutResult fieldResult)
+{
+ SLANG_ASSERT(m_typeLayout);
+
+ RefPtr<TypeLayout> fieldTypeLayout = fieldResult.layout;
+ UniformLayoutInfo fieldInfo = fieldResult.info.getUniformLayout();
+
+ // Note: we don't add any zero-size fields
+ // when computing structure layout, just
+ // to avoid having a resource type impact
+ // the final layout.
+ //
+ // This means that the code to generate final
+ // declarations needs to *also* eliminate zero-size
+ // fields to be safe...
+ //
+ LayoutSize uniformOffset = m_info.size;
+ if(fieldInfo.size != 0)
+ {
+ uniformOffset = m_rules->AddStructField(&m_info, fieldInfo);
+ }
+
+
+ // We need to create variable layouts
+ // for each field of the structure.
+ RefPtr<VarLayout> fieldLayout = new VarLayout();
+ fieldLayout->varDecl = field;
+ fieldLayout->typeLayout = fieldTypeLayout;
+ m_typeLayout->fields.Add(fieldLayout);
+ m_typeLayout->mapVarToLayout.Add(field.getDecl(), fieldLayout);
+
+ // Set up uniform offset information, if there is any uniform data in the field
+ if( fieldTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
+ }
+
+ // Add offset information for any other resource kinds
+ for( auto fieldTypeResourceInfo : fieldTypeLayout->resourceInfos )
+ {
+ // Uniforms were dealt with above
+ if(fieldTypeResourceInfo.kind == LayoutResourceKind::Uniform)
+ continue;
+
+ // We should not have already processed this resource type
+ SLANG_RELEASE_ASSERT(!fieldLayout->FindResourceInfo(fieldTypeResourceInfo.kind));
+
+ // The field will need offset information for this kind
+ auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind);
+
+ // It is possible for a `struct` field to use an unbounded array
+ // type, and in the D3D case that would consume an unbounded number
+ // of registers. What is more, a single `struct` could have multiple
+ // such fields, or ordinary resource fields after an unbounded field.
+ //
+ // We handle this case by allocating a distinct register space for
+ // any field that consumes an unbounded amount of registers.
+ //
+ if( fieldTypeResourceInfo.count.isInfinite() )
+ {
+ // We need to add one register space to own the storage for this field.
+ //
+ auto structTypeSpaceResourceInfo = m_typeLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace);
+ auto spaceOffset = structTypeSpaceResourceInfo->count;
+ structTypeSpaceResourceInfo->count += 1;
+
+ // The field itself will record itself as having a zero offset into
+ // the chosen space.
+ //
+ fieldResourceInfo->space = spaceOffset.getFiniteValue();
+ fieldResourceInfo->index = 0;
+ }
+ else
+ {
+ // In the case where the field consumes a finite number of slots, we
+ // can simply set its offset/index to the number of such slots consumed
+ // so far, and then increment the number of slots consumed by the
+ // `struct` type itself.
+ //
+ auto structTypeResourceInfo = m_typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind);
+ fieldResourceInfo->index = structTypeResourceInfo->count.getFiniteValue();
+ structTypeResourceInfo->count += fieldTypeResourceInfo.count;
+ }
+ }
+
+ return fieldLayout;
+}
+
+RefPtr<VarLayout> StructTypeLayoutBuilder::addField(
+ DeclRef<VarDeclBase> field,
+ RefPtr<TypeLayout> fieldTypeLayout)
+{
+ TypeLayoutResult fieldResult = makeTypeLayoutResult(fieldTypeLayout);
+ return addField(field, fieldResult);
+}
+
+void StructTypeLayoutBuilder::endLayout()
+{
+ if(!m_typeLayout) return;
+
+ m_rules->EndStructLayout(&m_info);
+
+ m_typeLayout->uniformAlignment = m_info.alignment;
+ m_typeLayout->addResourceUsage(LayoutResourceKind::Uniform, m_info.size);
+}
+
+RefPtr<StructTypeLayout> StructTypeLayoutBuilder::getTypeLayout()
+{
+ return m_typeLayout;
+}
+
+TypeLayoutResult StructTypeLayoutBuilder::getTypeLayoutResult()
+{
+ return TypeLayoutResult(m_typeLayout, m_info);
+}
+
static TypeLayoutResult _createTypeLayout(
TypeLayoutContext const& context,
Type* type)
@@ -2365,20 +2700,11 @@ static TypeLayoutResult _createTypeLayout(
if (auto structDeclRef = declRef.as<StructDecl>())
{
- RefPtr<StructTypeLayout> typeLayout = new StructTypeLayout();
- typeLayout->type = type;
- typeLayout->rules = rules;
-
- // The layout of a `struct` type is computed in the somewhat
- // obvious fashion by keeping a running counter of the resource
- // usage for each kind of resource, and then for a field that
- // uses a given resource, assigning it the current offset and
- // then bumping the offset by the field size. In the case of
- // uniform data we also need to deal with alignment and other
- // detailed layout rules.
-
- UniformLayoutInfo info = rules->BeginStructLayout();
+ StructTypeLayoutBuilder typeLayoutBuilder;
+ StructTypeLayoutBuilder pendingDataTypeLayoutBuilder;
+ typeLayoutBuilder.beginLayout(type, rules);
+ auto typeLayout = typeLayoutBuilder.getTypeLayout();
for (auto field : GetFields(structDeclRef))
{
// Static fields shouldn't take part in layout.
@@ -2409,105 +2735,37 @@ static TypeLayoutResult _createTypeLayout(
fieldLayoutContext,
GetType(field).Ptr(),
field.getDecl());
- RefPtr<TypeLayout> fieldTypeLayout = fieldResult.layout;
- UniformLayoutInfo fieldInfo = fieldResult.info.getUniformLayout();
-
- // Note: we don't add any zero-size fields
- // when computing structure layout, just
- // to avoid having a resource type impact
- // the final layout.
- //
- // This means that the code to generate final
- // declarations needs to *also* eliminate zero-size
- // fields to be safe...
- LayoutSize uniformOffset = info.size;
- if(fieldInfo.size != 0)
- {
- uniformOffset = rules->AddStructField(&info, fieldInfo);
- }
-
- // We need to create variable layouts
- // for each field of the structure.
- RefPtr<VarLayout> fieldLayout = new VarLayout();
- fieldLayout->varDecl = field;
- fieldLayout->typeLayout = fieldTypeLayout;
- typeLayout->fields.Add(fieldLayout);
- typeLayout->mapVarToLayout.Add(field.getDecl(), fieldLayout);
-
- // Set up uniform offset information, if there is any uniform data in the field
- if( fieldTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
- {
- fieldLayout->AddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
- }
-
- // Add offset information for any other resource kinds
- for( auto fieldTypeResourceInfo : fieldTypeLayout->resourceInfos )
- {
- // Uniforms were dealt with above
- if(fieldTypeResourceInfo.kind == LayoutResourceKind::Uniform)
- continue;
-
- // We should not have already processed this resource type
- SLANG_RELEASE_ASSERT(!fieldLayout->FindResourceInfo(fieldTypeResourceInfo.kind));
-
- // The field will need offset information for this kind
- auto fieldResourceInfo = fieldLayout->AddResourceInfo(fieldTypeResourceInfo.kind);
-
- // It is possible for a `struct` field to use an unbounded array
- // type, and in the D3D case that would consume an unbounded number
- // of registers. What is more, a single `struct` could have multiple
- // such fields, or ordinary resource fields after an unbounded field.
- //
- // We handle this case by allocating a distinct register space for
- // any field that consumes an unbounded amount of registers.
- //
- if( fieldTypeResourceInfo.count.isInfinite() )
- {
- // We need to add one register space to own the storage for this field.
- //
- auto structTypeSpaceResourceInfo = typeLayout->findOrAddResourceInfo(LayoutResourceKind::RegisterSpace);
- auto spaceOffset = structTypeSpaceResourceInfo->count;
- structTypeSpaceResourceInfo->count += 1;
+ auto fieldTypeLayout = fieldResult.layout;
- // The field itself will record itself as having a zero offset into
- // the chosen space.
- //
- fieldResourceInfo->space = spaceOffset.getFiniteValue();
- fieldResourceInfo->index = 0;
- }
- else
- {
- // In the case where the field consumes a finite number of slots, we
- // can simply set its offset/index to the number of such slots consumed
- // so far, and then increment the number of slots consumed by the
- // `struct` type itself.
- //
- auto structTypeResourceInfo = typeLayout->findOrAddResourceInfo(fieldTypeResourceInfo.kind);
- fieldResourceInfo->index = structTypeResourceInfo->count.getFiniteValue();
- structTypeResourceInfo->count += fieldTypeResourceInfo.count;
- }
- }
+ auto fieldVarLayout = typeLayoutBuilder.addField(field, fieldResult);
- // Fields of a structure type may have existential/interface type,
- // or nested existential/interface-type fields. When doing layout
- // for a specialized program, these will show up as "pending" types
- // that need to be laid out at the end of the surrounding block/container.
- //
- // Any pending types on fields of a structure become pending types
- // on the structure itself.
+ // If any of the fields of the `struct` type had existential/interface
+ // type, then we need to compute a second `StructTypeLayout` that
+ // represents the layout and resource using for the "pending data"
+ // that this type needs to have stored somewhere, but which can't
+ // be laid out in the layout of the type itself.
//
- for( auto pendingItem : fieldTypeLayout->pendingItems )
+ if(auto fieldPendingDataTypeLayout = fieldTypeLayout->pendingDataTypeLayout)
{
- typeLayout->pendingItems.Add(pendingItem);
+ // We only create this secondary layout on-demand, so that
+ // we don't end up with a bunch of empty structure type layouts
+ // created for no reason.
+ //
+ pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(type, rules);
+ auto fieldPendingVarLayout = pendingDataTypeLayoutBuilder.addField(field, fieldPendingDataTypeLayout);
+ fieldVarLayout->pendingVarLayout = fieldPendingVarLayout;
}
}
- rules->EndStructLayout(&info);
+ typeLayoutBuilder.endLayout();
+ pendingDataTypeLayoutBuilder.endLayout();
- typeLayout->uniformAlignment = info.alignment;
- typeLayout->addResourceUsage(LayoutResourceKind::Uniform, info.size);
+ if( auto pendingDataTypeLayout = pendingDataTypeLayoutBuilder.getTypeLayout() )
+ {
+ typeLayout->pendingDataTypeLayout = pendingDataTypeLayout;
+ }
- return TypeLayoutResult(typeLayout, info);
+ return typeLayoutBuilder.getTypeLayoutResult();
}
else if (auto globalGenParam = declRef.as<GlobalGenericParamDecl>())
{
@@ -2582,12 +2840,10 @@ static TypeLayoutResult _createTypeLayout(
// Layout for this specialized interface type then results
// in a type layout that tracks both the resource usage of the
// interface type itself (just the type + value slots introduced
- // above), plus a "pending" type that represents the value
+ // above), plus a "pending data" type that represents the value
// conceptually pointed to by the interface-type field/variable at runtime.
//
- TypeLayout::PendingItem pendingItem;
- pendingItem.typeLayout = concreteTypeLayout;
- typeLayout->pendingItems.Add(pendingItem);
+ typeLayout->pendingDataTypeLayout = concreteTypeLayout;
}
return TypeLayoutResult(typeLayout, SimpleLayoutInfo());
@@ -2854,6 +3110,13 @@ RefPtr<TypeLayout> createTypeLayout(
return _createTypeLayout(context, type).layout;
}
+void TypeLayout::addResourceUsageFrom(TypeLayout* otherTypeLayout)
+{
+ for(auto resInfo : otherTypeLayout->resourceInfos)
+ addResourceUsage(resInfo);
+}
+
+
RefPtr<TypeLayout> TypeLayout::unwrapArray()
{
TypeLayout* typeLayout = this;
diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h
index cdeadbb16..5ac9229b4 100644
--- a/source/slang/type-layout.h
+++ b/source/slang/type-layout.h
@@ -328,23 +328,20 @@ public:
// the space storing it in the above array
UInt uniformAlignment = 1;
- /// An item that is conceptually owned by this type, but is pending layout.
+
+ /// The layout for data that is conceptually owned by this type, but which is pending layout.
///
/// When a type contains interface/existential fields (recursively), the
/// actual data referenced by these fields needs to get allocated somewhere,
/// but it cannot go inline at the point where the interface/existential
- /// type appears, or else
+ /// type appears, or else the layout of a composite object would change
+ /// when the concrete type(s) we plug in change.
///
- struct PendingItem
- {
- RefPtr<TypeLayout> typeLayout;
-
- RefPtr<TypeLayout> getTypeLayout() const { return typeLayout; }
- RefPtr<Type> getType() const { return typeLayout->type; }
- };
-
- /// The pending items owned by this type, but which are pending layout.
- List<PendingItem> pendingItems;
+ /// We solve this problem by tracking this data that is "pending" layout,
+ /// and then "flushing" the pending data at appropriate places during
+ /// the layout process.
+ ///
+ RefPtr<TypeLayout> pendingDataTypeLayout;
ResourceInfo* FindResourceInfo(LayoutResourceKind kind)
{
@@ -383,6 +380,8 @@ public:
addResourceUsage(info);
}
+ void addResourceUsageFrom(TypeLayout* otherTypeLayout);
+
/// "Unwrap" any layers of array-ness from this type layout.
///
/// If this is an `ArrayTypeLayout`, returns the result of unwrapping the element type layout.
@@ -475,6 +474,8 @@ public:
return AddResourceInfo(kind);
}
+
+ RefPtr<VarLayout> pendingVarLayout;
};
// type layout for a variable that has a constant-buffer type
@@ -501,6 +502,16 @@ public:
// so that any fields (if the element type is a `struct`)
// will be offset by the resource usage of the container.
RefPtr<TypeLayout> offsetElementTypeLayout;
+
+ // If the element type layout had any "pending" data, then
+ // as much of that data as possible will be flushed to
+ // fit into the overall layout of the parameter group.
+ //
+ // This field stores the offset information for where
+ // the pending data got stored relative to the start of
+ // the group.
+ //
+// RefPtr<VarLayout> flushedDataVarLayout;
};
// type layout for a variable that has a constant-buffer type
@@ -585,6 +596,12 @@ public:
// in the array above, rather than to the actual pointer,
// so that we
Dictionary<Decl*, RefPtr<VarLayout>> mapVarToLayout;
+
+ // As an accellerator for type layouts created at the
+ // IR layer, we include a second map that use IR "key"
+ // instructions to map to fields.
+ //
+ Dictionary<IRInst*, RefPtr<VarLayout>> mapKeyToLayout;
};
class GenericParamTypeLayout : public TypeLayout
@@ -924,6 +941,98 @@ struct TypeLayoutContext
}
};
+//
+
+ /// A custom tuple to capture the outputs of type layout
+struct TypeLayoutResult
+{
+ /// The actual heap-allocated layout object with all the details
+ RefPtr<TypeLayout> layout;
+
+ /// A simplified representation of layout information.
+ ///
+ /// This information is suitable for the case where a type only
+ /// consumes a single resource.
+ ///
+ SimpleLayoutInfo info;
+
+ /// Default constructor.
+ TypeLayoutResult()
+ {}
+
+ /// Construct a result from the given layout object and simple layout info.
+ TypeLayoutResult(RefPtr<TypeLayout> inLayout, SimpleLayoutInfo const& inInfo)
+ : layout(inLayout)
+ , info(inInfo)
+ {}
+};
+
+ /// Helper type for building `struct` type layouts
+struct StructTypeLayoutBuilder
+{
+public:
+ /// Begin the layout process for `type`, using `rules`
+ void beginLayout(
+ Type* type,
+ LayoutRulesImpl* rules);
+
+ /// Begin the layout process for `type`, using `rules`, if it hasn't already been begun.
+ ///
+ /// This functions allows for a `StructTypeLayoutBuilder` to be use lazily,
+ /// only allocating a type layout object if it is actaully needed.
+ ///
+ void beginLayoutIfNeeded(
+ Type* type,
+ LayoutRulesImpl* rules);
+
+ /// Add a field to the struct type layout.
+ ///
+ /// One of the `beginLayout*()` functions must have been called previously.
+ ///
+ RefPtr<VarLayout> addField(
+ DeclRef<VarDeclBase> field,
+ TypeLayoutResult fieldResult);
+
+ /// Add a field to the struct type layout.
+ ///
+ /// One of the `beginLayout*()` functions must have been called previously.
+ ///
+ RefPtr<VarLayout> addField(
+ DeclRef<VarDeclBase> field,
+ RefPtr<TypeLayout> fieldTypeLayout);
+
+ /// Complete layout.
+ ///
+ /// If layout was begun, ensures that the result of `getTypeLayout()` is usable.
+ /// If layout was never begin, does nothing.
+ ///
+ void endLayout();
+
+ /// Get the type layout.
+ ///
+ /// This can be called any time after `beginLayout*()`.
+ /// In particular, it can be called before `endLayout`.
+ ///
+ RefPtr<StructTypeLayout> getTypeLayout();
+
+ /// The the type layout result.
+ ///
+ /// This is primarily useful for implementation code in `_createTypeLayout`.
+ ///
+ TypeLayoutResult getTypeLayoutResult();
+
+private:
+ /// The layout rules being used, if layout has begun.
+ LayoutRulesImpl* m_rules = nullptr;
+
+ /// The type layout being computed, if layout has begun.
+ RefPtr<StructTypeLayout> m_typeLayout;
+
+ /// Uniform offset/alignment statte used when computing offset for uniform fields.
+ UniformLayoutInfo m_info;
+};
+
+//
// Get an appropriate set of layout rules (packaged up
// as a `TypeLayoutContext`) to perform type layout
@@ -963,7 +1072,7 @@ RefPtr<TypeLayout> createTypeLayout(
//
/// Create a layout for a parameter-group type (a `ConstantBuffer` or `ParameterBlock`).
-RefPtr<ParameterGroupTypeLayout> createParameterGroupTypeLayout(
+RefPtr<TypeLayout> createParameterGroupTypeLayout(
TypeLayoutContext const& context,
RefPtr<ParameterGroupType> parameterGroupType);
@@ -993,6 +1102,12 @@ createStructuredBufferTypeLayout(
int findGenericParam(List<RefPtr<GenericParamLayout>> & genericParameters, GlobalGenericParamDecl * decl);
//
+// Given an existing type layout `oldTypeLayout`, apply offsets
+// to any contained fields based on the resource infos in `offsetVarLayout`.
+RefPtr<TypeLayout> applyOffsetToTypeLayout(
+ RefPtr<TypeLayout> oldTypeLayout,
+ RefPtr<VarLayout> offsetVarLayout);
+
}
#endif
diff --git a/tests/bugs/vk-structured-buffer-load.hlsl.glsl b/tests/bugs/vk-structured-buffer-load.hlsl.glsl
index 1cf0b00a7..206a25ef0 100644
--- a/tests/bugs/vk-structured-buffer-load.hlsl.glsl
+++ b/tests/bugs/vk-structured-buffer-load.hlsl.glsl
@@ -6,7 +6,7 @@ layout(row_major) uniform;
layout(row_major) buffer;
#extension GL_NV_ray_tracing : require
-layout(std430, binding = 2) readonly buffer _S1 {
+layout(std430, binding = 1) readonly buffer _S1 {
float _data[];
} gParamBlock_sbuf_0;
diff --git a/tests/compute/interface-shader-param3.slang b/tests/compute/interface-shader-param3.slang
index 3a9debaa0..3c8b24be1 100644
--- a/tests/compute/interface-shader-param3.slang
+++ b/tests/compute/interface-shader-param3.slang
@@ -4,20 +4,6 @@
// interface types at more complicated places in the overall layout.
//
-// NOTE TO SELF:
-//
-// First issue is that the constant buffer layouts aren't being
-// computed correctly for `gStrategy` (even in the previous test),
-// so that it doesn't get a `b` register bound.
-//
-// Second issue is that the type legalization logic is now overzealous,
-// and moves the `modifier` member of the entry-point constant buffer
-// out to global scope, when it should allocate it inside the constant
-// buffer.
-//
-
-
-
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12
//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
@@ -59,7 +45,15 @@ int test(
//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
RWStructuredBuffer<int> gOutputBuffer;
-//TEST_INPUT:cbuffer(data=[1 0 0 0], stride=4):dxbinding(0),glbinding(1)
+// Note: while we declare `gStrategy` here, there is no matching
+// line providing input data at this point. That is partly to
+// work around an apparent bug in the test runner, but it is also
+// to reflect the fact that this declaration does not cause a
+// constant buffer binding to be allocated, because just from
+// the declaration of `gStrategy` we cannot tell whether a
+// constant buffer binding is even needed (there might be no
+// uniform/ordinary data).
+//
ConstantBuffer<IRandomNumberGenerationStrategy> gStrategy;
[numthreads(4, 1, 1)]
@@ -87,7 +81,7 @@ void computeMain(
//
// Here's the incantation to make the test runner fill in the constant buffer:
//
-//TEST_INPUT:cbuffer(data=[256 0 0 0 16 0 0 0], stride=4):dxbinding(1),glbinding(2)
+//TEST_INPUT:cbuffer(data=[256 0 0 0 16 0 0 0], stride=4):dxbinding(0),glbinding(1)
//
// So, the value `256` will be used for `extra` and the value `16`
// will be written to the first four bytes of the concrete value
@@ -142,3 +136,14 @@ struct MyModifier : IModifier
//TEST_INPUT: globalExistentialType MyStrategy
//TEST_INPUT: entryPointExistentialType MyModifier
+
+// Once the concrete types are plugged in, the compiler can
+// see that `gStrategy` needs a constant buffer register/binding.
+// It will alocate the location for `gStrategy` after all other
+// shader parameters, to ensure that different specializations
+// of the same shader will agree on the locations for all the
+// non-interface-type parameters.
+//
+//TEST_INPUT:cbuffer(data=[1 0 0 0], stride=4):dxbinding(1),glbinding(2)
+
+
diff --git a/tests/compute/interface-shader-param4.slang b/tests/compute/interface-shader-param4.slang
new file mode 100644
index 000000000..07c6951e0
--- /dev/null
+++ b/tests/compute/interface-shader-param4.slang
@@ -0,0 +1,132 @@
+// interface-shader-param3.slang
+
+// This test builds on `interface-shader-param3.slang` by putting
+// resources into the concrete types that satisfy interface-type
+// shader parameters.
+//
+
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12
+//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
+
+// A lot of the setup is the same as for `interface-shader-param`,
+// so look there if you want the comments.
+
+interface IRandomNumberGenerator
+{
+ [mutating]
+ int randomInt();
+}
+
+interface IRandomNumberGenerationStrategy
+{
+ associatedtype Generator : IRandomNumberGenerator;
+ Generator makeGenerator(int seed);
+}
+
+interface IModifier
+{
+ int modify(int val);
+}
+
+int test(
+ int seed,
+ IRandomNumberGenerationStrategy inStrategy,
+ IModifier modifier)
+{
+ let strategy = inStrategy;
+ var generator = strategy.makeGenerator(seed);
+ let unused = generator.randomInt();
+ let val = generator.randomInt();
+ let modifiedVal = modifier.modify(val);
+ return modifiedVal;
+}
+
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<int> gOutputBuffer;
+
+ConstantBuffer<IRandomNumberGenerationStrategy> gStrategy;
+
+[numthreads(4, 1, 1)]
+void computeMain(
+
+// Similarly to the previous test, we are declaring two `uniform`
+// paameters on the entry point, where one will be plugged in
+// with a concrete type, and thus get laid out second.
+//
+ uniform IModifier modifier,
+ uniform int extra,
+//
+// The uniform/ordinary data for these two parameters will end
+// up in the constant buffers, so let's declare that. Unlike
+// the previous test, the concrete type plugged in for `modifier`
+// has no uniform/ordinary data, so we don't need to fill it in.
+//
+//TEST_INPUT:cbuffer(data=[256]):dxbinding(0),glbinding(2)
+
+ uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ let tid = dispatchThreadID.x;
+
+ let inputVal : int = tid;
+ let outputVal = test(inputVal, gStrategy, modifier)
+ + extra*extra;
+
+ gOutputBuffer[tid] = outputVal;
+}
+
+// Okay, now we get to the part that is unique starting
+// in this test: we add data to the concrete types
+// that we will use as parameters.
+
+struct MyStrategy : IRandomNumberGenerationStrategy
+{
+ RWStructuredBuffer<int> globalSeeds;
+
+ struct Generator : IRandomNumberGenerator
+ {
+ int state;
+
+ [mutating]
+ int randomInt()
+ {
+ return state++;
+ }
+ }
+
+ Generator makeGenerator(int seed)
+ {
+ Generator generator = { globalSeeds[seed] };
+ return generator;
+ }
+}
+
+struct MyModifier : IModifier
+{
+ RWStructuredBuffer<int> localModifiers;
+
+ int modify(int val)
+ {
+ return val ^ localModifiers[val & 3];
+ }
+}
+
+//TEST_INPUT: globalExistentialType MyStrategy
+//TEST_INPUT: entryPointExistentialType MyModifier
+
+// The concrete types we plug in for `gStrategy` and `modifier`
+// have buffer resources in them, so we need to assign them
+// data. The registers/bindings for these parameters will
+// always come after all other shader parameters, and their
+// relative order will match the relative order of their
+// declarations in the global order that Slang uses for
+// assigning bindings (all globals before all entry point parameters).
+//
+// Here's the data for `gStrategy`:
+//
+//TEST_INPUT:ubuffer(data=[1 2 4 8], stride=4):dxbinding(1),glbinding(1)
+//
+// Here's the data for `modifier`:
+//
+//TEST_INPUT:ubuffer(data=[16 32 64 128], stride=4):dxbinding(2),glbinding(3)
diff --git a/tests/compute/interface-shader-param4.slang.expected.txt b/tests/compute/interface-shader-param4.slang.expected.txt
new file mode 100644
index 000000000..e962124e1
--- /dev/null
+++ b/tests/compute/interface-shader-param4.slang.expected.txt
@@ -0,0 +1,4 @@
+10042
+10083
+10025
+10029
diff --git a/tests/reflection/parameter-block-explicit-space.slang.expected b/tests/reflection/parameter-block-explicit-space.slang.expected
index e683a641f..04093f4ca 100644
--- a/tests/reflection/parameter-block-explicit-space.slang.expected
+++ b/tests/reflection/parameter-block-explicit-space.slang.expected
@@ -6,7 +6,10 @@ standard output = {
"parameters": [
{
"name": "a",
- "binding": {"kind": "constantBuffer", "space": 2, "index": 0, "count": 0},
+ "bindings": [
+ {"kind": "constantBuffer", "space": 2, "index": 0, "count": 0},
+ {"kind": "registerSpace", "index": 2}
+ ],
"type": {
"kind": "parameterBlock",
"elementType": {
@@ -54,7 +57,10 @@ standard output = {
},
{
"name": "b",
- "binding": {"kind": "constantBuffer", "space": 3, "index": 0, "count": 0},
+ "bindings": [
+ {"kind": "constantBuffer", "space": 3, "index": 0, "count": 0},
+ {"kind": "registerSpace", "index": 3}
+ ],
"type": {
"kind": "parameterBlock",
"elementType": {