diff options
Diffstat (limited to 'source/slang')
| -rw-r--r-- | source/slang/emit.cpp | 24 | ||||
| -rw-r--r-- | source/slang/ir-entry-point-uniforms.cpp | 2 | ||||
| -rw-r--r-- | source/slang/ir-legalize-types.cpp | 696 | ||||
| -rw-r--r-- | source/slang/ir-specialize-resources.cpp | 4 | ||||
| -rw-r--r-- | source/slang/legalize-types.cpp | 79 | ||||
| -rw-r--r-- | source/slang/legalize-types.h | 151 | ||||
| -rw-r--r-- | source/slang/options.cpp | 1 | ||||
| -rw-r--r-- | source/slang/parameter-binding.cpp | 97 | ||||
| -rw-r--r-- | source/slang/type-layout.cpp | 1009 | ||||
| -rw-r--r-- | source/slang/type-layout.h | 141 |
10 files changed, 1721 insertions, 483 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 |
