From f20c64c348393602ed2a9c873386345cc4b493e8 Mon Sep 17 00:00:00 2001 From: Tim Foley Date: Thu, 31 Jan 2019 11:33:57 -0800 Subject: Initial support for uniform parameters on entry points (#815) * Initial support for uniform parameters on entry points The basic feature this work adds is the ability to define a shader entry point like: ```hlsl [shader("fragment")] float4 main( uniform Texture2D t, uniform SamplerState s, float2 uv : UV) { return t.Sample(s,uv); } ``` In this example, the `uniform` keyword is used to mark that the given entry point parameters are *not* varying input/output flowing through the pipeline, but rather uniform shader parameters that should function as if the shader was declared more like: ```hlsl Texture2D t, SamplerState s, [shader("fragment")] float4 main( float2 uv : UV) { return t.Sample(s,uv); } ``` Allowing `uniform` parameters on entry points makes it easier to define multiple entry points in one file without accidentally polluting the global scope with shader parameters that only certain entry points care about. This feature is also more or less a prerequisite for allowing generic type parameters directly on entry point functions, since the main use case for those type parameters is for determining what goes in various `ConstantBuffer`s or `ParameterBlock`s. There are two main pieces to the implementation. First, we need to be able to compute appropriate layout information for entry points that include `uniform` parameters. Second, we need to transform the entry point function to move any `uniform` parameters to be ordinary global-scope shader parameters, to make sure that all other back-end passes don't need to worry about this special case. The latter piece of the implementation is, relatively speaking, simpler. The pass in `ir-entry-point-uniforms.{h,cpp}` converts entry point parameters that are determined to be uniform (using the already-computed layout information) into fields of a `struct` type and then declares a global shader parameter based on that `struct` type (and applies already-computed layout information to that parameter). After that, the remaining IR passes (notably including type legalization) will handle things just as for any other global shader parameter. The changes to the layout step are more significant, but most of the changes are just cleanups and fixes to enable the feature. The two major changes that enable entry-point `uniform` parameters are: * In `collectEntryPointParameters` we now dispatch out to a new `computeEntryPointParameterTypeLayout` function, which decided whether to compute the type layout for a `uniform` parameter, or for a varying parameter (what used to be the default behavior handled by `processEntryPointParameterDecl`). * The main `generateParameterBindings` routine was extended so that it allocates registers/bindings to the resources required by each entry point (using `completeBindingsForParameter`) after it has allocated registers/binding to all of the global-scope parameters (this addition is mirrored in `specializeProgramLayout`). The effect of these changes is that the `uniform` parameters of any entry points specified in a compile request will be laid out after the global-scope parameters, in the order the entry points were specified in the compile request. A bunch of smaller changes were made around parameter layout that are worth enumerating so that the diffs make some sense: * The `EntryPointLayout` type was changed so that instead of trying to *be* a `StructTypeLayout`, it instead *owns* one, in the same fashion as `ProgramLayout`. This commonality was factored into a base class `ScopeLayout`, and a bunch of edits followed from that change. * Because `uniform` parameters are moved out of the entry point parameter list early in the IR transformations, the logic in `ir-glsl-legalize.cpp` that tried to look up parameter layout information by index would no longer work if the entry point parameter list had been altered. Instead, that logic now looks for the decorations directly on the parameters. * The `UsedRange` type in `parameter-binding.cpp` was tracking the existing parameter associated with a range using a `ParameterInfo*` (which accounts for the possibility of multiple `VarDecl`s mapping to the same logical shader parameter), when just using a `VarLayout*` is sufficient for all current use cases. The overhead of allocating a `ParameterInfo` seems like overkill for entry-point parameters, where there can't possibly be multiple declarations of the "same" parameter, so avoiding these overheads was a focus when trying to deduplicate code between the global and entry-point parameter cases. * A bunch of parameter binding logic that was specific to GLSL input has been deleted completely. There was no way to even execute this code in the compiler today, and there is pretty much zero chance of us needing (or wanting) to deal with GLSL input in the future. This includes custom `UsedRangeSet`s specific to each translation unit, which were only needed for global-scope `in` and `out` varying declarations in GLSL. * A bunch of functions with `EntryPointParameter` in their names were renamed to use `EntryPointVaryingParameter` to help distinguish that they only apply to the varying case, while entry point `uniform` parameters are handled elsewhere. * The `completeBindingsForParameter` function was re-worked into something that can be used for both global-scope shader parameters (where we have a `ParameterInfo` and possibly explicit bindings) and entry-point parameters (where we expect to have neither). This helps unify the (fairly subtle) logic for how we allocate and assign bindings for resources, constant buffers, parameter blocks, etc. * A small change was made so that the entry-point stage is attached directly to top-level parameters of the entry point, and *not* recursively to every field along the way. This could be a breaking change for some applications, but it makes more logical sense (to me); we'll have to check if this affects Falcor. This change produces different output for several of the reflection tests, but the changes are consistent with no longer attaching stage information to sub-fields of varying `struct`-type parameters. * Because there is a bunch of repeated logic in `parameter-binding.cpp` that has to do with computing a `struct` layout for ordinary/uniform data, I tried to factor that into a single `ScopeLayoutBuilder` type, which handles computing the offsets for any parameters with ordinary data, and then also handles wrapping up the layout in a constant buffer layout if there was any ordinary data at the end. * A similar convenience routine `maybeAllocateConstantBufferBinding` was added because I noticed multiple places in `parameter-binding.cpp` that were trying to allocate a constant buffer binding for global uniforms, and they were wildly inconsistent (and in most cases used logic that would only work for D3D). * The main `generateParameterBindings` routine is significantly shortened by using all of these utilities that were introduced. I tried to comment the places that changed to explain the overall flow correctly. * The `specializeProgramLayout` routine (used to take a `ProgramLayout` from `generateParameterBindings` and specialize it based on knowledge of global generic arguments) had basically been rewritten with more explicit commenting/rationale for what happens in each step. It makes use of the same shared utilities as `generateParameterBindings` and `collectEntryPointParameters`. In terms of testing: * I added a test case to specifically test the new behavior, and in particular I made sure to include a mix of both global and entry-point parameters and also to have entry-point parameters of both ordinary and resource/object types. * I tweaked an existing test for global type parameters to use an entry-point `uniform` parameter instead of a global one, in an effort to migrate it toward being able to use an explicitly generic entry point. * fixups from merge --- source/slang/parameter-binding.cpp | 1213 ++++++++++++++++++++---------------- 1 file changed, 679 insertions(+), 534 deletions(-) (limited to 'source/slang/parameter-binding.cpp') diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp index 076b29659..904ec3129 100644 --- a/source/slang/parameter-binding.cpp +++ b/source/slang/parameter-binding.cpp @@ -15,7 +15,7 @@ struct ParameterInfo; struct UsedRange { // What parameter has claimed this range? - ParameterInfo* parameter = nullptr; + VarLayout* parameter; // Begin/end of the range (half-open interval) UInt begin; @@ -69,7 +69,7 @@ struct UsedRanges // then we return that parameter so that the // caller can issue an error. // - ParameterInfo* Add(UsedRange range) + VarLayout* Add(UsedRange range) { // The invariant on entry to this // function is that the `ranges` array @@ -86,8 +86,8 @@ struct UsedRanges // match the parameter on `range`, so that // the compiler can issue useful diagnostics. // - ParameterInfo* newParam = range.parameter; - ParameterInfo* existingParam = nullptr; + VarLayout* newParam = range.parameter; + VarLayout* existingParam = nullptr; // A clever algorithm might use a binary // search to identify the first entry in `ranges` @@ -210,7 +210,7 @@ struct UsedRanges return existingParam; } - ParameterInfo* Add(ParameterInfo* param, UInt begin, UInt end) + VarLayout* Add(VarLayout* param, UInt begin, UInt end) { UsedRange range; range.parameter = param; @@ -219,7 +219,7 @@ struct UsedRanges return Add(range); } - ParameterInfo* Add(ParameterInfo* param, UInt begin, LayoutSize end) + VarLayout* Add(VarLayout* param, UInt begin, LayoutSize end) { UsedRange range; range.parameter = param; @@ -246,7 +246,7 @@ struct UsedRanges // Try to find space for `count` entries - UInt Allocate(ParameterInfo* param, UInt count) + UInt Allocate(VarLayout* param, UInt count) { UInt begin = 0; @@ -279,11 +279,16 @@ struct UsedRanges struct ParameterBindingInfo { - size_t space; - size_t index; + size_t space = 0; + size_t index = 0; LayoutSize count; }; +struct ParameterBindingAndKindInfo : ParameterBindingInfo +{ + LayoutResourceKind kind = LayoutResourceKind::None; +}; + enum { kLayoutResourceKindCount = SLANG_PARAMETER_CATEGORY_COUNT, @@ -353,11 +358,6 @@ struct SharedParameterBindingContext // Dictionary> globalSpaceUsedRangeSets; - // What ranges of resource bindings are claimed for particular translation unit? - // This is only used for varying input/output. - // - Dictionary> translationUnitUsedRangeSets; - // Which register spaces have been claimed so far? UsedRanges usedSpaces; @@ -1098,91 +1098,16 @@ RefPtr tryGetEffectiveTypeForGLSLVaryingOutput( return nullptr; } -RefPtr -getTypeLayoutForGlobalShaderParameter_GLSL( + /// Determine how to lay out a global variable that might be a shader parameter. + /// + /// Returns `nullptr` if the declaration does not represent a shader parameter. +RefPtr getTypeLayoutForGlobalShaderParameter( ParameterBindingContext* context, - VarDeclBase* varDecl) -{ - auto layoutContext = context->layoutContext; - auto rules = layoutContext.getRulesFamily(); - auto type = varDecl->getType(); - - // A GLSL shader parameter will be marked with - // a qualifier to match the boundary it uses - // - // In the case of a parameter block, we will have - // consumed this qualifier as part of parsing, - // so that it won't be present on the declaration - // any more. As such we also inspect the type - // of the variable. - - // We want to check for a constant-buffer type with a `push_constant` layout - // qualifier before we move on to anything else. - if( varDecl->HasModifier() && as(type) ) - { - return CreateTypeLayout( - layoutContext.with(rules->getPushConstantBufferRules()), - type); - } - - // TODO(tfoley): We have multiple variations of - // the `uniform` modifier right now, and that - // needs to get fixed... - if( varDecl->HasModifier() || as(type) ) - { - return CreateTypeLayout( - layoutContext.with(rules->getConstantBufferRules()), - type); - } - - if( varDecl->HasModifier() || as(type) ) - { - return CreateTypeLayout( - layoutContext.with(rules->getShaderStorageBufferRules()), - type); - } - - if (auto effectiveVaryingInputType = tryGetEffectiveTypeForGLSLVaryingInput(context, varDecl)) - { - // We expect to handle these elsewhere - SLANG_DIAGNOSE_UNEXPECTED(getSink(context), varDecl, "GLSL varying input"); - return CreateTypeLayout( - layoutContext.with(rules->getVaryingInputRules()), - effectiveVaryingInputType); - } - - if (auto effectiveVaryingOutputType = tryGetEffectiveTypeForGLSLVaryingOutput(context, varDecl)) - { - // We expect to handle these elsewhere - SLANG_DIAGNOSE_UNEXPECTED(getSink(context), varDecl, "GLSL varying output"); - return CreateTypeLayout( - layoutContext.with(rules->getVaryingOutputRules()), - effectiveVaryingOutputType); - } - - // A `const` global with a `layout(constant_id = ...)` modifier - // is a declaration of a specialization constant. - if( varDecl->HasModifier() ) - { - return CreateTypeLayout( - layoutContext.with(rules->getSpecializationConstantRules()), - type); - } - - // GLSL says that an "ordinary" global variable - // is just a (thread local) global and not a - // parameter - return nullptr; -} - -RefPtr -getTypeLayoutForGlobalShaderParameter_HLSL( - ParameterBindingContext* context, - VarDeclBase* varDecl) + VarDeclBase* varDecl, + Type* type) { auto layoutContext = context->layoutContext; auto rules = layoutContext.getRulesFamily(); - auto type = varDecl->getType(); if( varDecl->HasModifier() && as(type) ) { @@ -1217,32 +1142,13 @@ getTypeLayoutForGlobalShaderParameter_HLSL( type); } -// Determine how to lay out a global variable that might be -// a shader parameter. -// Returns `nullptr` if the declaration does not represent -// a shader parameter. - -RefPtr -getTypeLayoutForGlobalShaderParameter( +RefPtr getTypeLayoutForGlobalShaderParameter( ParameterBindingContext* context, VarDeclBase* varDecl) { - switch( context->sourceLanguage ) - { - case SourceLanguage::Slang: - case SourceLanguage::HLSL: - return getTypeLayoutForGlobalShaderParameter_HLSL(context, varDecl); - - case SourceLanguage::GLSL: - return getTypeLayoutForGlobalShaderParameter_GLSL(context, varDecl); - - default: - SLANG_UNEXPECTED("unhandled source language"); - UNREACHABLE_RETURN(nullptr); - } + return getTypeLayoutForGlobalShaderParameter(context, varDecl, varDecl->getType()); } - // enum EntryPointParameterDirection @@ -1264,44 +1170,12 @@ struct EntryPointParameterState }; -static RefPtr processEntryPointParameter( +static RefPtr processEntryPointVaryingParameter( ParameterBindingContext* context, RefPtr type, EntryPointParameterState const& state, RefPtr varLayout); -static void collectGlobalScopeGLSLVaryingParameter( - ParameterBindingContext* context, - RefPtr varDecl, - RefPtr effectiveType, - EntryPointParameterDirection direction) -{ - int defaultSemanticIndex = 0; - - EntryPointParameterState state; - state.directionMask = direction; - state.ioSemanticIndex = &defaultSemanticIndex; - state.stage = context->stage; - state.loc = varDecl->loc; - - RefPtr varLayout = new VarLayout(); - varLayout->varDecl = makeDeclRef(varDecl.Ptr()); - - varLayout->typeLayout = processEntryPointParameter( - context, - effectiveType, - state, - varLayout); - - // Now add it to our list of reflection parameters, so - // that it can get a location assigned later... - - ParameterInfo* parameterInfo = new ParameterInfo(); - parameterInfo->translationUnit = context->translationUnit; - context->shared->parameters.Add(parameterInfo); - parameterInfo->varLayouts.Add(varLayout); -} - // Collect a single declaration into our set of parameters static void collectGlobalGenericParameter( ParameterBindingContext* context, @@ -1319,25 +1193,6 @@ static void collectGlobalScopeParameter( ParameterBindingContext* context, RefPtr varDecl) { - // HACK: We need to intercept GLSL varying `in` and `out` here, way earlier - // in the process, so that we can avoid all kinds of nastiness that would - // otherwise be applied to them. - if (context->sourceLanguage == SourceLanguage::GLSL) - { - if (auto effectiveVaryingInputType = tryGetEffectiveTypeForGLSLVaryingInput(context, varDecl)) - { - collectGlobalScopeGLSLVaryingParameter(context, varDecl, effectiveVaryingInputType, kEntryPointParameterDirection_Input); - return; - } - - if (auto effectiveVaryingOutputType = tryGetEffectiveTypeForGLSLVaryingOutput(context, varDecl)) - { - collectGlobalScopeGLSLVaryingParameter(context, varDecl, effectiveVaryingOutputType, kEntryPointParameterDirection_Output); - return; - } - } - - // We use a single operation to both check whether the // variable represents a shader parameter, and to compute // the layout for that parameter's type. @@ -1422,22 +1277,6 @@ static UInt allocateUnusedSpaces( return context->shared->usedSpaces.Allocate(nullptr, count); } -static RefPtr findUsedRangeSetForTranslationUnit( - ParameterBindingContext* context, - TranslationUnitRequest* translationUnit) -{ - if (!translationUnit) - return findUsedRangeSetForSpace(context, 0); - - RefPtr usedRangeSet; - if (context->shared->translationUnitUsedRangeSets.TryGetValue(translationUnit, usedRangeSet)) - return usedRangeSet; - - usedRangeSet = new UsedRangeSet(); - context->shared->translationUnitUsedRangeSets.Add(translationUnit, usedRangeSet); - return usedRangeSet; -} - static void addExplicitParameterBinding( ParameterBindingContext* context, RefPtr parameterInfo, @@ -1486,15 +1325,15 @@ static void addExplicitParameterBinding( // need to grab a full space markSpaceUsed(context, semanticInfo.space); } - auto overlappedParameterInfo = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add( - parameterInfo, + auto overlappedVarLayout = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add( + parameterInfo->varLayouts[0], semanticInfo.index, semanticInfo.index + count); - if (overlappedParameterInfo) + if (overlappedVarLayout) { auto paramA = parameterInfo->varLayouts[0]->varDecl.getDecl(); - auto paramB = overlappedParameterInfo->varLayouts[0]->varDecl.getDecl(); + auto paramB = overlappedVarLayout->varDecl.getDecl(); getSink(context)->diagnose(paramA, Diagnostics::parameterBindingsOverlap, getReflectionName(paramA), @@ -1641,22 +1480,6 @@ static void addExplicitParameterBindings_GLSL( semanticInfo.index = attr->set; semanticInfo.space = 0; } - else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::VertexInput)) != nullptr ) - { - // Try to find `location` binding - if(!findLayoutArg(varDecl, &semanticInfo.index)) - return; - - usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit); - } - else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::FragmentOutput)) != nullptr ) - { - // Try to find `location` binding - if(!findLayoutArg(varDecl, &semanticInfo.index)) - return; - - usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit); - } else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::SpecializationConstant)) != nullptr ) { // Try to find `constant_id` binding @@ -1697,21 +1520,16 @@ void generateParameterBindings( } // Generate the binding information for a shader parameter. -static void completeBindingsForParameter( +static void completeBindingsForParameterImpl( ParameterBindingContext* context, + RefPtr firstVarLayout, + ParameterBindingInfo bindingInfos[kLayoutResourceKindCount], RefPtr parameterInfo) { // For any resource kind used by the parameter // we need to update its layout information // to include a binding for that resource kind. // - // We will use the first declaration of the parameter as - // a stand-in for all the declarations, so it is important - // that earlier code has validated that the declarations - // "match". - - SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0); - auto firstVarLayout = parameterInfo->varLayouts.First(); auto firstTypeLayout = firstVarLayout->typeLayout; // We need to deal with allocation of full register spaces first, @@ -1731,7 +1549,7 @@ static void completeBindingsForParameter( // has specified an explicit binding, since those won't // go into our contiguously allocated range. // - auto& bindingInfo = parameterInfo->bindingInfo[(int)kind]; + auto& bindingInfo = bindingInfos[(int)kind]; if( bindingInfo.count != 0 ) { continue; @@ -1798,7 +1616,7 @@ static void completeBindingsForParameter( // Did we already apply some explicit binding information // for this resource kind? auto kind = typeRes.kind; - auto& bindingInfo = parameterInfo->bindingInfo[(int)kind]; + auto& bindingInfo = bindingInfos[(int)kind]; if( bindingInfo.count != 0 ) { // If things have already been bound, our work is done. @@ -1892,26 +1710,53 @@ static void completeBindingsForParameter( // space. UInt space = context->shared->defaultSpace; - - RefPtr usedRangeSet; - switch (kind) - { - default: - usedRangeSet = findUsedRangeSetForSpace(context, space); - break; - - case LayoutResourceKind::VertexInput: - case LayoutResourceKind::FragmentOutput: - usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit); - break; - } + RefPtr usedRangeSet = findUsedRangeSetForSpace(context, space); bindingInfo.count = count; - bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(parameterInfo, count.getFiniteValue()); - + bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(firstVarLayout, count.getFiniteValue()); bindingInfo.space = space; } } +} + +static void applyBindingInfoToParameter( + RefPtr varLayout, + ParameterBindingInfo bindingInfos[kLayoutResourceKindCount]) +{ + for(auto k = 0; k < kLayoutResourceKindCount; ++k) + { + auto kind = LayoutResourceKind(k); + auto& bindingInfo = bindingInfos[k]; + + // skip resources we aren't consuming + if(bindingInfo.count == 0) + continue; + + // Add a record to the variable layout + auto varRes = varLayout->AddResourceInfo(kind); + varRes->space = (int) bindingInfo.space; + varRes->index = (int) bindingInfo.index; + } +} + +// Generate the binding information for a shader parameter. +static void completeBindingsForParameter( + ParameterBindingContext* context, + RefPtr parameterInfo) +{ + // We will use the first declaration of the parameter as + // a stand-in for all the declarations, so it is important + // that earlier code has validated that the declarations + // "match". + + SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0); + auto firstVarLayout = parameterInfo->varLayouts.First(); + + completeBindingsForParameterImpl( + context, + firstVarLayout, + parameterInfo->bindingInfo, + parameterInfo); // At this point we should have explicit binding locations chosen for // all the relevant resource kinds, so we can apply these to the @@ -1919,23 +1764,25 @@ static void completeBindingsForParameter( for(auto& varLayout : parameterInfo->varLayouts) { - for(auto k = 0; k < kLayoutResourceKindCount; ++k) - { - auto kind = LayoutResourceKind(k); - auto& bindingInfo = parameterInfo->bindingInfo[k]; - - // skip resources we aren't consuming - if(bindingInfo.count == 0) - continue; - - // Add a record to the variable layout - auto varRes = varLayout->AddResourceInfo(kind); - varRes->space = (int) bindingInfo.space; - varRes->index = (int) bindingInfo.index; - } + applyBindingInfoToParameter(varLayout, parameterInfo->bindingInfo); } } +static void completeBindingsForParameter( + ParameterBindingContext* context, + RefPtr varLayout) +{ + ParameterBindingInfo bindingInfos[kLayoutResourceKindCount]; + completeBindingsForParameterImpl( + context, + varLayout, + bindingInfos, + nullptr); + applyBindingInfoToParameter(varLayout, bindingInfos); +} + + + static void collectGlobalScopeParameters( ParameterBindingContext* context, ModuleDecl* program) @@ -2121,7 +1968,7 @@ static RefPtr processSimpleEntryPointParameter( return typeLayout; } -static RefPtr processEntryPointParameterDecl( +static RefPtr processEntryPointVaryingParameterDecl( ParameterBindingContext* context, Decl* decl, RefPtr type, @@ -2159,20 +2006,15 @@ static RefPtr processEntryPointParameterDecl( // *or* we couldn't find an explicit semantic to apply on the given // declaration, so we will just recursive with whatever we have at // the moment. - return processEntryPointParameter(context, type, state, varLayout); + return processEntryPointVaryingParameter(context, type, state, varLayout); } -static RefPtr processEntryPointParameter( +static RefPtr processEntryPointVaryingParameter( ParameterBindingContext* context, RefPtr type, EntryPointParameterState const& state, RefPtr varLayout) { - if (varLayout) - { - varLayout->stage = state.stage; - } - // The default handling of varying parameters should not apply // to geometry shader output streams; they have their own special rules. if( auto gsStreamType = as(type) ) @@ -2192,7 +2034,7 @@ static RefPtr processEntryPointParameter( elementState.stage = state.stage; elementState.loc = state.loc; - auto elementTypeLayout = processEntryPointParameter(context, elementType, elementState, nullptr); + auto elementTypeLayout = processEntryPointVaryingParameter(context, elementType, elementState, nullptr); RefPtr typeLayout = new StreamOutputTypeLayout(); typeLayout->type = type; @@ -2316,13 +2158,13 @@ static RefPtr processEntryPointParameter( auto elementCount = (UInt) GetIntVal(arrayType->ArrayLength); // We use the first element to derive the layout for the element type - auto elementTypeLayout = processEntryPointParameter(context, arrayType->baseType, state, varLayout); + auto elementTypeLayout = processEntryPointVaryingParameter(context, arrayType->baseType, state, varLayout); // We still walk over subsequent elements to make sure they consume resources // as needed for( UInt ii = 1; ii < elementCount; ++ii ) { - processEntryPointParameter(context, arrayType->baseType, state, nullptr); + processEntryPointVaryingParameter(context, arrayType->baseType, state, nullptr); } RefPtr arrayTypeLayout = new ArrayTypeLayout(); @@ -2357,7 +2199,7 @@ static RefPtr processEntryPointParameter( RefPtr fieldVarLayout = new VarLayout(); fieldVarLayout->varDecl = field; - auto fieldTypeLayout = processEntryPointParameterDecl( + auto fieldTypeLayout = processEntryPointVaryingParameterDecl( context, field.getDecl(), GetType(field), @@ -2409,6 +2251,210 @@ static RefPtr processEntryPointParameter( UNREACHABLE_RETURN(nullptr); } + /// Compute the type layout for a parameter declared directly on an entry point. +static RefPtr computeEntryPointParameterTypeLayout( + ParameterBindingContext* context, + SubstitutionSet typeSubst, + RefPtr paramDecl, + RefPtr paramVarLayout, + EntryPointParameterState& state) +{ + auto paramType = paramDecl->type.type->Substitute(typeSubst).as(); + + if( paramDecl->HasModifier() ) + { + // An entry-point parameter that is explicitly marked `uniform` represents + // a uniform shader parameter passed via the implicitly-defined + // constant buffer (e.g., the `$Params` constant buffer seen in fxc/dxc output). + // + return CreateTypeLayout( + context->layoutContext.with(context->getRulesFamily()->getConstantBufferRules()), + paramType); + } + else + { + // The default case is a varying shader parameter, which could be used for + // input, output, or both. + // + // The varying case needs to not only compute a layout, but also assocaite + // "semantic" strings/indices with the varying parameters by recursively + // walking their structure. + + state.directionMask = 0; + + // If it appears to be an input, process it as such. + if( paramDecl->HasModifier() || paramDecl->HasModifier() || !paramDecl->HasModifier() ) + { + state.directionMask |= kEntryPointParameterDirection_Input; + } + + // If it appears to be an output, process it as such. + if(paramDecl->HasModifier() || paramDecl->HasModifier()) + { + state.directionMask |= kEntryPointParameterDirection_Output; + } + + return processEntryPointVaryingParameterDecl( + context, + paramDecl.Ptr(), + paramDecl->type.type->Substitute(typeSubst).as(), + state, + paramVarLayout); + } +} + +// There are multiple places where we need to compute the layout +// for a "scope" such as the global scope or an entry point. +// The `ScopeLayoutBuilder` encapsulates the logic around: +// +// * Doing layout for the ordinary/uniform fields, which involves +// using the `struct` layout rules for constant buffers on +// the target. +// +// * Creating a final type/var layout that reflects whether the +// scope needs a constant buffer to be allocated to it. +// +struct ScopeLayoutBuilder +{ + ParameterBindingContext* m_context = nullptr; + LayoutRulesImpl* m_rules = nullptr; + RefPtr m_structLayout; + UniformLayoutInfo m_structLayoutInfo; + bool m_needConstantBuffer = false; + + void beginLayout( + ParameterBindingContext* context) + { + m_context = context; + m_rules = context->getRulesFamily()->getConstantBufferRules(); + m_structLayout = new StructTypeLayout(); + m_structLayout->rules = m_rules; + + m_structLayoutInfo = m_rules->BeginStructLayout(); + } + + void _addParameter( + RefPtr firstVarLayout, + ParameterInfo* parameterInfo) + { + // Does the parameter have any uniform data? + auto layoutInfo = firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform); + LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0; + if( uniformSize != 0 ) + { + m_needConstantBuffer = true; + + // Make sure uniform fields get laid out properly... + + UniformLayoutInfo fieldInfo( + uniformSize, + firstVarLayout->typeLayout->uniformAlignment); + + LayoutSize uniformOffset = m_rules->AddStructField( + &m_structLayoutInfo, + fieldInfo); + + if( parameterInfo ) + { + for( auto& varLayout : parameterInfo->varLayouts ) + { + varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); + } + } + else + { + firstVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); + } + } + + m_structLayout->fields.Add(firstVarLayout); + + if( parameterInfo ) + { + for( auto& varLayout : parameterInfo->varLayouts ) + { + m_structLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout); + } + } + else + { + m_structLayout->mapVarToLayout.Add(firstVarLayout->varDecl.getDecl(), firstVarLayout); + } + } + + void addParameter( + RefPtr varLayout) + { + _addParameter(varLayout, nullptr); + } + + void addParameter( + ParameterInfo* parameterInfo) + { + SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0); + auto firstVarLayout = parameterInfo->varLayouts.First(); + + _addParameter(firstVarLayout, parameterInfo); + } + + RefPtr endLayout() + { + m_rules->EndStructLayout(&m_structLayoutInfo); + + RefPtr scopeTypeLayout = m_structLayout; + + // If the caller decided to allocate a constant buffer for + // the ordinary data, then we need to wrap up the structure + // type (layout) in a constant buffer type (layout). + // + if( m_needConstantBuffer ) + { + auto constantBufferLayout = createParameterGroupTypeLayout( + m_context->layoutContext, + nullptr, + m_rules, + m_rules->GetObjectLayout(ShaderParameterKind::ConstantBuffer), + m_structLayout); + + scopeTypeLayout = constantBufferLayout; + } + + // We now have a bunch of layout information, which we should + // record into a suitable object that represents the scope + RefPtr scopeVarLayout = new VarLayout(); + scopeVarLayout->typeLayout = scopeTypeLayout; + return scopeVarLayout; + } +}; + + /// Helper routine to allocate a constant buffer binding if one is needed. + /// + /// This function primarily exists to encapsulate the logic for allocating + /// the resources required for a constant buffer in the appropriate + /// target-specific fashion. + /// +static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding( + ParameterBindingContext* context, + bool needConstantBuffer) +{ + if( !needConstantBuffer ) return ParameterBindingAndKindInfo(); + + UInt space = context->shared->defaultSpace; + auto usedRangeSet = findUsedRangeSetForSpace(context, space); + + auto layoutInfo = context->getRulesFamily()->getConstantBufferRules()->GetObjectLayout( + ShaderParameterKind::ConstantBuffer); + + ParameterBindingAndKindInfo info; + info.kind = layoutInfo.kind; + info.count = layoutInfo.size; + info.index = usedRangeSet->usedResourceRanges[(int)layoutInfo.kind].Allocate(nullptr, layoutInfo.size.getFiniteValue()); + info.space = space; + return info; +} + + /// Iterate over the parameters of an entry point to compute its requirements. + /// static void collectEntryPointParameters( ParameterBindingContext* context, EntryPointRequest* entryPoint, @@ -2420,17 +2466,27 @@ static void collectEntryPointParameters( // Something must have failed earlier, so that // we didn't find a declaration to match this // entry point request. + // return; } - // Create the layout object here - auto entryPointLayout = new EntryPointLayout(); + // We will take responsibility for creating and filling in + // the `EntryPointLayout` object here. + // + RefPtr entryPointLayout = new EntryPointLayout(); entryPointLayout->profile = entryPoint->profile; entryPointLayout->entryPoint = entryPointFuncDecl; - context->entryPointLayout = entryPointLayout; + // The entry point layout must be added to the output + // program layout so that it can be accessed by reflection. + // context->shared->programLayout->entryPoints.Add(entryPointLayout); + // For the duration of our parameter collection work we will + // establish this entry point as the current one in the context. + // + context->entryPointLayout = entryPointLayout; + // Note: this isn't really the best place for this logic to sit, // but it is the simplest place where we have a direct correspondence // between a single `EntryPointRequest` and its matching `EntryPointLayout`, @@ -2443,77 +2499,105 @@ static void collectEntryPointParameters( entryPointLayout->taggedUnionTypeLayouts.Add(typeLayout); } - // Okay, we seemingly have an entry-point function, and now we need to collect info on its parameters too + // We are going to iterate over the entry-point parameters, + // and while we do so we will go ahead and perform layout/binding + // assignment for two cases: // - // TODO: Long-term we probably want complete information on all inputs/outputs of an entry point, - // but for now we are really just trying to scrape information on fragment outputs, so lets do that: + // First, the varying parameters of the entry point will have + // their semantics and locations assigned, so we set up state + // for tracking that layout. // - // TODO: check whether we should enumerate the parameters before the return type, or vice versa - int defaultSemanticIndex = 0; - EntryPointParameterState state; state.ioSemanticIndex = &defaultSemanticIndex; state.optSemanticName = nullptr; state.semanticSlotCount = 0; state.stage = entryPoint->getStage(); - for( auto m : entryPointFuncDecl->Members ) - { - auto paramDecl = as(m); - if(!paramDecl) - continue; + // Second, we will compute offsets for any "ordinary" data + // in the parameter list (e.g., a `uniform float4x4 mvp` parameter), + // which is what the `ScopeLayoutBuilder` is designed to help with. + // + ScopeLayoutBuilder scopeBuilder; + scopeBuilder.beginLayout(context); + auto paramsStructLayout = scopeBuilder.m_structLayout; - // We have an entry-point parameter, and need to figure out what to do with it. + for( auto paramDecl : entryPointFuncDecl->getMembersOfType() ) + { + // Any error messages we emit during the process should + // refer to the location of this parameter. + // state.loc = paramDecl->loc; - // TODO: need to handle `uniform`-qualified parameters here - if (paramDecl->HasModifier()) - continue; - - state.directionMask = 0; - - // If it appears to be an input, process it as such. - if( paramDecl->HasModifier() || paramDecl->HasModifier() || !paramDecl->HasModifier() ) - { - state.directionMask |= kEntryPointParameterDirection_Input; - } - - // If it appears to be an output, process it as such. - if(paramDecl->HasModifier() || paramDecl->HasModifier()) - { - state.directionMask |= kEntryPointParameterDirection_Output; - } - + // We are going to construct the variable layout for this + // parameter *before* computing the type layout, because + // the type layout computation is also determining the effective + // semantic of the parameter, which needs to be stored + // back onto the `VarLayout`. + // RefPtr paramVarLayout = new VarLayout(); - paramVarLayout->varDecl = makeDeclRef(paramDecl); + paramVarLayout->varDecl = makeDeclRef(paramDecl.Ptr()); + paramVarLayout->stage = state.stage; - auto paramTypeLayout = processEntryPointParameterDecl( + auto paramTypeLayout = computeEntryPointParameterTypeLayout( context, + typeSubst, paramDecl, - paramDecl->type.type->Substitute(typeSubst).dynamicCast(), - state, - paramVarLayout); + paramVarLayout, + state); + paramVarLayout->typeLayout = paramTypeLayout; - // Skip parameters for which we could not compute a layout + // We expect to always be able to compute a layout for + // entry-point parameters, but to be defensive we will + // skip parameters that couldn't have a layout computed + // when assertions are disabled. + // + SLANG_ASSERT(paramTypeLayout); if(!paramTypeLayout) continue; - paramVarLayout->typeLayout = paramTypeLayout; + // Now that we've computed the layout to use for the parameter, + // we need to add its resource usage to that of the entry + // point as a whole. + // + // Any "ordinary" data (e.g., a `float4x4`) needs to be accounted + // for using the `ScopeLayoutBuilder`, since it will handle + // the details of target-specific `struct` type layout. + // + scopeBuilder.addParameter(paramVarLayout); - for (auto rr : paramTypeLayout->resourceInfos) + // All of the other resources types will be handled in a + // simpler loop that just increments the relevant counters. + // + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) { - auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind); - paramVarLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue(); - entryPointRes->count += rr.count; - } + // We need to skip ordinary data because it is being + // handled by the `scopeBuilder`. + // + if(paramTypeResInfo.kind == LayoutResourceKind::Uniform) + continue; + + // Whatever resources the parameter uses, we need to + // assign the parameter's location/register/binding offset to + // be the sum of everything added so far. + // + auto entryPointResInfo = paramsStructLayout->findOrAddResourceInfo(paramTypeResInfo.kind); + paramVarLayout->findOrAddResourceInfo(paramTypeResInfo.kind)->index = entryPointResInfo->count.getFiniteValue(); - entryPointLayout->fields.Add(paramVarLayout); - entryPointLayout->mapVarToLayout.Add(paramDecl, paramVarLayout); + // We then need to add the resources consumed by the parameter + // to those consumed by the entry point. + // + entryPointResInfo->count += paramTypeResInfo.count; + } } + entryPointLayout->parametersLayout = scopeBuilder.endLayout(); - // If we have a non-`void` output type for the entry point, then process it as - // an output parameter. + // For an entry point with a non-`void` return type, we need to process the + // return type as a varying output parameter. + // + // TODO: Ideally we should make the layout process more robust to empty/void + // types and apply this logic unconditionally. + // auto resultType = entryPointFuncDecl->ReturnType.type; if( !resultType->Equals(resultType->getSession()->getVoidType()) ) { @@ -2521,8 +2605,9 @@ static void collectEntryPointParameters( state.directionMask = kEntryPointParameterDirection_Output; RefPtr resultLayout = new VarLayout(); + resultLayout->stage = state.stage; - auto resultTypeLayout = processEntryPointParameterDecl( + auto resultTypeLayout = processEntryPointVaryingParameterDecl( context, entryPointFuncDecl, resultType->Substitute(typeSubst).dynamicCast(), @@ -2535,7 +2620,7 @@ static void collectEntryPointParameters( for (auto rr : resultTypeLayout->resourceInfos) { - auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind); + auto entryPointRes = paramsStructLayout->findOrAddResourceInfo(rr.kind); resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue(); entryPointRes->count += rr.count; } @@ -2687,6 +2772,7 @@ void generateParameterBindings( context.shared = &sharedContext; context.translationUnit = nullptr; context.layoutContext = layoutContext; + // Walk through AST to discover all the parameters collectParameters(&context, compileReq); @@ -2787,24 +2873,10 @@ void generateParameterBindings( // If there are any global-scope uniforms, then we need to // allocate a constant-buffer binding for them here. - ParameterBindingInfo globalConstantBufferBinding; - globalConstantBufferBinding.index = 0; - globalConstantBufferBinding.space = 0; - if( needDefaultConstantBuffer ) - { - // TODO: this logic is only correct for D3D targets, where - // global-scope uniforms get wrapped into a constant buffer. - - UInt space = sharedContext.defaultSpace; - auto usedRangeSet = findUsedRangeSetForSpace(&context, space); - - globalConstantBufferBinding.index = - usedRangeSet->usedResourceRanges[ - (int)LayoutResourceKind::ConstantBuffer].Allocate(nullptr, 1); - - globalConstantBufferBinding.space = space; - } - + // + ParameterBindingAndKindInfo globalConstantBufferBinding = maybeAllocateConstantBufferBinding( + &context, + needDefaultConstantBuffer); // Now walk through again to actually give everything // ranges of registers... @@ -2813,135 +2885,200 @@ void generateParameterBindings( completeBindingsForParameter(&context, parameter); } - // TODO: need to deal with parameters declared inside entry-point - // parameter lists at some point... - - - // Next we need to create a type layout to reflect the information - // we have collected. - - // We will lay out any bare uniforms at the global scope into - // a single constant buffer. This is appropriate for HLSL global-scope - // uniforms, and Vulkan GLSL doesn't allow uniforms at global scope, - // so it should work out. + // After we have allocated registers/bindings to everything + // in the global scope we will process the parameters + // of each entry point in order. // - // For legacy GLSL targets, we'd probably need a distinct resource - // kind and set of rules here, since legacy uniforms are not the - // same as the contents of a constant buffer. - auto globalScopeRules = context.getRulesFamily()->getConstantBufferRules(); - - RefPtr globalScopeStructLayout = new StructTypeLayout(); - globalScopeStructLayout->rules = globalScopeRules; - - UniformLayoutInfo structLayoutInfo = globalScopeRules->BeginStructLayout(); - for( auto& parameterInfo : sharedContext.parameters ) + // Note: the effect of the current implemetnation is to + // allocate non-overlapping registers/bindings between all + // the entry points in the compile request (e.g., if you + // have a vertex and fragment shader being compiled together, + // we will allocate distinct constant buffer registers for + // their uniform parameters). + // + // TODO: We probably need to provide some more nuanced control + // over whether entry points get overlapping or non-overlapping + // bindings. It seems clear that if we were compiling multiple + // compute kernels in one invocation we'd want them to get + // overlapping bindings, because we cannot ever have them bound + // together in a single pipeline state. + // + // Similarly, entry point parameters of DirectX Raytracing (DXR) + // shaders should probably be allowed to overlap by default, + // since those parameters should really go into the "local root signature." + // (Note: there is a bit more subtlety around ray tracing + // shaders that will be assembled into a "hit group") + // + // For now we are just doing the simplest thing, which will be + // appropriate for: + // + // * Compiling a single compute shader in a compile request. + // * Compiling some number of rasterization shader entry points + // in a single request, to be used together. + // * Compiling a single ray-tracing shader in a compile request. + // + for( auto entryPoint : sharedContext.programLayout->entryPoints ) { - SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0); - auto firstVarLayout = parameterInfo->varLayouts.First(); - - // Does the field have any uniform data? - auto layoutInfo = firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform); - LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0; - if( uniformSize != 0 ) - { - // Make sure uniform fields get laid out properly... - - UniformLayoutInfo fieldInfo( - uniformSize, - firstVarLayout->typeLayout->uniformAlignment); - - LayoutSize uniformOffset = globalScopeRules->AddStructField( - &structLayoutInfo, - fieldInfo); - - for( auto& varLayout : parameterInfo->varLayouts ) - { - varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); - } - } - - globalScopeStructLayout->fields.Add(firstVarLayout); - - for( auto& varLayout : parameterInfo->varLayouts ) - { - globalScopeStructLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout); - } + auto entryPointParamsLayout = entryPoint->parametersLayout; + completeBindingsForParameter(&context, entryPointParamsLayout); } - globalScopeRules->EndStructLayout(&structLayoutInfo); - RefPtr globalScopeLayout = globalScopeStructLayout; - - // If there are global-scope uniforms, then we need to wrap - // up a global constant buffer type layout to hold them - if( needDefaultConstantBuffer ) + // Next we need to create a type layout to reflect the information + // we have collected, and we will use the `ScopeLayoutBuilder` + // to encapsulate the logic that can be shared with the entry-point + // case. + // + ScopeLayoutBuilder globalScopeLayoutBuilder; + globalScopeLayoutBuilder.beginLayout(&context); + for( auto& parameterInfo : sharedContext.parameters ) { - auto globalConstantBufferLayout = createParameterGroupTypeLayout( - layoutContext, - nullptr, - globalScopeRules, - globalScopeRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer), - globalScopeStructLayout); - - globalScopeLayout = globalConstantBufferLayout; + globalScopeLayoutBuilder.addParameter(parameterInfo); } - // We now have a bunch of layout information, which we should - // record into a suitable object that represents the program - RefPtr globalVarLayout = new VarLayout(); - globalVarLayout->typeLayout = globalScopeLayout; - if (needDefaultConstantBuffer) + auto globalScopeVarLayout = globalScopeLayoutBuilder.endLayout(); + if( globalConstantBufferBinding.count != 0 ) { - auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer); + auto cbInfo = globalScopeVarLayout->findOrAddResourceInfo(globalConstantBufferBinding.kind); cbInfo->space = globalConstantBufferBinding.space; cbInfo->index = globalConstantBufferBinding.index; } - programLayout->globalScopeLayout = globalVarLayout; + programLayout->parametersLayout = globalScopeVarLayout; } -StructTypeLayout* getGlobalStructLayout( - ProgramLayout* programLayout); - RefPtr specializeProgramLayout( - TargetRequest * targetReq, - ProgramLayout* programLayout, + TargetRequest* targetReq, + ProgramLayout* oldProgramLayout, SubstitutionSet typeSubst) { + // The goal of the layout specialization step is to take an existing `ProgramLayout`, + // and add a layout to any parameter(s) that could not be laid out previously, because + // they had a dependence on generic type parameters that made layout impossible at + // the time. + // + // TODO: It would be far simpler to just "re-do" the entire layout process, just + // with knowledge of what the global type substitution is, but that would mean that + // global parameters that come after a generic-dependent parameter might change + // their location/binding/register depending on what types are plugged in. + // Our current design preserves the layout for any global parameter that was placed during + // the initial layout of a program (before the generic arguments were know). + // It isn't clear that this design choice pays off in practice, since there is lot + // of complexity in this function. + RefPtr newProgramLayout; newProgramLayout = new ProgramLayout(); newProgramLayout->targetRequest = targetReq; - newProgramLayout->globalGenericParams = programLayout->globalGenericParams; - - List> paramTypeLayouts; - auto globalStructLayout = getGlobalStructLayout(programLayout); - SLANG_ASSERT(globalStructLayout); - RefPtr structLayout = new StructTypeLayout(); - RefPtr globalScopeLayout = structLayout; - structLayout->uniformAlignment = globalStructLayout->uniformAlignment; - - // Try to find rules based on the selected code-generation target - auto layoutContext = getInitialLayoutContextForTarget(targetReq); + newProgramLayout->globalGenericParams = oldProgramLayout->globalGenericParams; - // If there was no target, or there are no rules for the target, - // then bail out here. - if (!layoutContext.rules) - return newProgramLayout; + // The basic idea will be to iterate over the parameters in the old layout, + // and "pick up where we left off" in terms of allocating registers to things. + // + // That means we will look at the existing parameters (that were laid out already) + // and mark any registers/bytes/bindings/etc. that they occupy as "used" so + // that the subsequent layout of the generic-dependency parameters will not + // collide with them. + // + // We will use the same kind of context type as the original parameter binding + // step did, so we initialize its state here: + + auto layoutContext = getInitialLayoutContextForTarget(targetReq); + SLANG_ASSERT(layoutContext.rules); - - // we need to initialize a layout context to mark used registers SharedParameterBindingContext sharedContext; sharedContext.compileRequest = targetReq->compileRequest; sharedContext.defaultLayoutRules = layoutContext.getRulesFamily(); sharedContext.programLayout = newProgramLayout; sharedContext.targetRequest = targetReq; - // Create a sub-context to collect parameters that get - // declared into the global scope ParameterBindingContext context; context.shared = &sharedContext; context.translationUnit = nullptr; context.layoutContext = layoutContext; - - + + // We will also need state for laying out any global-scope parameters + // that include ordinary/uniform data. + // + auto oldGlobalStructLayout = getGlobalStructLayout(oldProgramLayout); + SLANG_ASSERT(oldGlobalStructLayout); + + ScopeLayoutBuilder newGlobalScopeLayoutBuilder; + newGlobalScopeLayoutBuilder.beginLayout(&context); + auto& newGlobalStructLayoutInfo = newGlobalScopeLayoutBuilder.m_structLayoutInfo; + auto newGlobalStructLayout = newGlobalScopeLayoutBuilder.m_structLayout; + + // The initial state for uniform layout will be based on whatever + // global-scope ordinary/uniform parameters were laid out before. + // The alignment can be read directly from the old global layout. + // + newGlobalStructLayoutInfo.alignment = oldGlobalStructLayout->uniformAlignment; + newGlobalStructLayoutInfo.size = 0; + + // The remaining information needs to be collected by looking at + // the individual parameters in the existing layout. + // + bool oldAnyUniforms = false; + for(auto oldVarLayout : oldGlobalStructLayout->fields) + { + // If a parameter made use of a global generic parameter, then we would + // have skipped applying layout to it in the original layout process, + // and so we should skip it for the process of recovering the existing + // layout information. + // + if (oldVarLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) + continue; + + // Otherwise, we will "reserve" any resources that the parameter was + // determined to consume. + // + // The easy case is any registers/bindings used for textures/sampler/etc. + // We iterate over the kinds of resources consumed by teh parameter. + // + for( auto varResInfo : oldVarLayout->resourceInfos ) + { + // For each kind of resource consumed the `varResInfo` will tell us + // the start of the consumed range, whle the type will be needed + // to tell us the amount of resources consumed. + // + if( auto typeResInfo = oldVarLayout->typeLayout->FindResourceInfo(varResInfo.kind) ) + { + // We will mark the range of resources consumed by theis parameter + // as "used" so that it cannot be claimed by later parameters. + // + auto usedRangeSet = findUsedRangeSetForSpace(&context, varResInfo.space); + markSpaceUsed(&context, varResInfo.space); + usedRangeSet->usedResourceRanges[(int)varResInfo.kind].Add( + nullptr, // we don't need to track parameter info here + varResInfo.index, + varResInfo.index + typeResInfo->count); + } + } + + // The more subtle case is when the parameter consumes ordinary bytes + // of uniform (constant buffer) memory, because we do not use the + // same "used range" model to allocate space for ordinary data. + // + // Instead, we simply track the highest byte offset covered by any parameter. + // + if (auto varUniformInfo = oldVarLayout->FindResourceInfo(LayoutResourceKind::Uniform)) + { + oldAnyUniforms = true; + + if( auto typeUniformInfo = oldVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) ) + { + newGlobalStructLayoutInfo.size = maximum( + newGlobalStructLayoutInfo.size, + varUniformInfo->index + typeUniformInfo->count); + } + } + } + + // Rather than attempt to re-use the entry-point layout information + // that was collected in the first pass, we will re-collect the + // information for entry points from scratch. + // + // This ensures that when an entry point makes use of a generic type + // parameter, the layout of its parameter list strictly follows + // the declaration order. + // for (auto & translationUnit : targetReq->compileRequest->translationUnits) { for (auto & entryPoint : translationUnit->entryPoints) @@ -2951,137 +3088,145 @@ RefPtr specializeProgramLayout( context.entryPointLayout = nullptr; } - auto constantBufferRules = context.getRulesFamily()->getConstantBufferRules(); - structLayout->rules = constantBufferRules; - structLayout->fields.SetSize(globalStructLayout->fields.Count()); - UniformLayoutInfo structLayoutInfo; - structLayoutInfo.alignment = globalStructLayout->uniformAlignment; - structLayoutInfo.size = 0; - bool anyUniforms = false; - Dictionary, RefPtr> varLayoutMapping; - for (uint32_t varId = 0; varId < globalStructLayout->fields.Count(); varId++) + // Now that we've marked thing as being used, we can make a second + // sweep to compute the requirements of any generic-dependent parameters. + // + // Along the way we will build up the new layout for the global-scope + // structure type, including the offsets of all ordinary/uniform fields. + // + + bool newAnyUniforms = oldAnyUniforms; + List> newVarLayouts; + Dictionary, RefPtr> mapOldLayoutToNew; + for(auto oldVarLayout : oldGlobalStructLayout->fields) { - auto &varLayout = globalStructLayout->fields[varId]; - // To recover layout context, we skip generic resources in the first pass - if (varLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) + // In this pass, the variables that *don't* depend on generic parameters + // are the easy ones to handle. We can just copy them over to the new layout. + // + if(!oldVarLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) + { + newGlobalStructLayout->fields.Add(oldVarLayout); continue; + } - if (auto uniformInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform)) - { - anyUniforms = true; + // In the case where things are generic-dependent, we need to re-do + // the type layout process on the type that results from doing + // substutition with the global generic arguments. + // + RefPtr oldType = oldVarLayout->getTypeLayout()->getType(); + RefPtr newType = oldType->Substitute(typeSubst).as(); - if( auto tUniformInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) ) - { - structLayoutInfo.size = maximum(structLayoutInfo.size, uniformInfo->index + tUniformInfo->count); - } - } - for( auto resInfo : varLayout->resourceInfos ) + RefPtr newTypeLayout = getTypeLayoutForGlobalShaderParameter( + &context, + oldVarLayout->varDecl, + newType); + + RefPtr newVarLayout = new VarLayout(); + newVarLayout->varDecl = oldVarLayout->varDecl; + newVarLayout->stage = oldVarLayout->stage; + newVarLayout->typeLayout = newTypeLayout; + + newGlobalScopeLayoutBuilder.addParameter(newVarLayout); + newVarLayouts.Add(newVarLayout); + mapOldLayoutToNew.Add(oldVarLayout, newVarLayout); + + if(auto uniformInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform)) { - if( auto tresInfo = varLayout->typeLayout->FindResourceInfo(resInfo.kind) ) + if(uniformInfo->count != 0) { - auto usedRangeSet = findUsedRangeSetForSpace(&context, resInfo.space); - markSpaceUsed(&context, resInfo.space); - usedRangeSet->usedResourceRanges[(int)resInfo.kind].Add( - nullptr, // we don't need to track parameter info here - resInfo.index, - resInfo.index + tresInfo->count); + newAnyUniforms = true; + diagnoseGlobalUniform(&sharedContext, newVarLayout->varDecl); } } - structLayout->fields[varId] = varLayout; - varLayoutMapping[varLayout] = varLayout; } - auto originalGlobalCBufferInfo = programLayout->globalScopeLayout->FindResourceInfo(LayoutResourceKind::ConstantBuffer); - VarLayout::ResourceInfo globalCBufferInfo; - globalCBufferInfo.kind = LayoutResourceKind::None; - globalCBufferInfo.space = 0; - globalCBufferInfo.index = 0; - if (originalGlobalCBufferInfo) + auto newGlobalScopeVarLayout = newGlobalScopeLayoutBuilder.endLayout(); + + // We had better have made a copy of every field in the original layout. + // + SLANG_ASSERT(oldGlobalStructLayout->fields.Count() == newGlobalStructLayout->fields.Count()); + + // If there were no global-scope uniforms before, but there + // are now that we've done global substitution, then we + // need to allocate a global constant buffer to hold them. + // + auto newGlobalConstantBufferBinding = maybeAllocateConstantBufferBinding(&context, newAnyUniforms && !oldAnyUniforms); + + // Now we need to "complete" finding for each of the new parameters, + // which is the step that actually allocates resource to them. + // + // Note: we don't support generic-dependent parameters with explicit bindings, + // so we should probably emit an error message about that in the original + // layout step. + // + for(auto newVarLayout : newVarLayouts) { - globalCBufferInfo.kind = LayoutResourceKind::ConstantBuffer; - globalCBufferInfo.space = originalGlobalCBufferInfo->space; - globalCBufferInfo.index = originalGlobalCBufferInfo->index; + completeBindingsForParameter(&context, newVarLayout); } - // we have the context restored, can continue to layout the generic variables now - for (uint32_t varId = 0; varId < globalStructLayout->fields.Count(); varId++) + + // One remaining missing step is that the `StructLayout` type maintains + // a map from variable declarations to their layouts, and in some cases + // multiple declarations will map to the same layout (because, e.g., the + // same `cbuffer` was declared in both a vertex and fragment shader file). + // + // We need to clone that remapping information over from the old program + // layout. This is why we created the `mapOldLayoutToNew` mapping in + // the preceding loop. + // + // TODO: This step would be easier if the `StructLayout::mapVarToLayout` + // dictionary were instead a mapping from variable declaration to the + // *index* of the corresponding layout in the `fields` array. + // + for(auto entry : oldGlobalStructLayout->mapVarToLayout) { - auto &varLayout = globalStructLayout->fields[varId]; - if (varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::GenericResource)) - { - RefPtr newType = varLayout->typeLayout->type->Substitute(typeSubst).dynamicCast(); - RefPtr newTypeLayout = CreateTypeLayout( - layoutContext.with(constantBufferRules), - newType); - auto layoutInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform); - LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0; - if (uniformSize != 0) - { - if (globalCBufferInfo.kind == LayoutResourceKind::None) - { - // user defined a uniform via a global generic type argument - // but we have not reserved a binding for the global uniform buffer - UInt space = 0; - auto usedRangeSet = findUsedRangeSetForSpace(&context, space); - globalCBufferInfo.kind = LayoutResourceKind::ConstantBuffer; - globalCBufferInfo.index = - usedRangeSet->usedResourceRanges[ - (int)LayoutResourceKind::ConstantBuffer].Allocate(nullptr, 1); - globalCBufferInfo.space = space; - } - } - RefPtr newVarLayout = new VarLayout(); - RefPtr paramInfo = new ParameterInfo(); - newVarLayout->varDecl = varLayout->varDecl; - newVarLayout->stage = varLayout->stage; - newVarLayout->typeLayout = newTypeLayout; - paramInfo->varLayouts.Add(newVarLayout); - completeBindingsForParameter(&context, paramInfo); - // update uniform layout - - if (uniformSize != 0) - { - // Make sure uniform fields get laid out properly... - UniformLayoutInfo fieldInfo( - uniformSize, - newTypeLayout->uniformAlignment); - LayoutSize uniformOffset = layoutContext.getRulesFamily()->getConstantBufferRules()->AddStructField( - &structLayoutInfo, - fieldInfo); - newVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); - anyUniforms = true; - - diagnoseGlobalUniform(&sharedContext, varLayout->varDecl); - } - structLayout->fields[varId] = newVarLayout; - varLayoutMapping[varLayout] = newVarLayout; - } + RefPtr varLayout = entry.Value; + mapOldLayoutToNew.TryGetValue(varLayout, varLayout); + newGlobalStructLayout->mapVarToLayout[entry.Key] = varLayout; } - for (auto mapping : globalStructLayout->mapVarToLayout) + + // Just as for the initial computation of layout, we will complete + // binding for entry-point parameters *after* we have laid out + // all the global-scope parameters. + // + // Note that this includes layout of generic-dependent global scope + // parameters, so it is possible for entry point uniform parameters + // to end up with a different register/binding after generic specialization. + // (There really isn't a great way around that) + // + for( auto entryPoint : sharedContext.programLayout->entryPoints ) { - RefPtr updatedVarLayout = mapping.Value; - varLayoutMapping.TryGetValue(updatedVarLayout, updatedVarLayout); - structLayout->mapVarToLayout[mapping.Key] = updatedVarLayout; + auto entryPointParamsLayout = entryPoint->parametersLayout; + completeBindingsForParameter(&context, entryPointParamsLayout); } - // If there are global-scope uniforms, then we need to wrap - // up a global constant buffer type layout to hold them - RefPtr globalVarLayout = new VarLayout(); - if (anyUniforms) + // As a last step we need to set up the binding/offset information + // for the global scope itself. + // + // We will start by copying whatever information was in the old layout. + // { - auto globalConstantBufferLayout = createParameterGroupTypeLayout( - layoutContext, - nullptr, - constantBufferRules, - constantBufferRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer), - structLayout); + auto oldGlobalScopeVarLayout = oldProgramLayout->parametersLayout; + for( auto oldResInfo : oldGlobalScopeVarLayout->resourceInfos ) + { + auto newResInfo = newGlobalScopeVarLayout->findOrAddResourceInfo(oldResInfo.kind); + newResInfo->space = oldResInfo.space; + newResInfo->kind = oldResInfo.kind; + } + } - globalScopeLayout = globalConstantBufferLayout; - auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer); - *cbInfo = globalCBufferInfo; + // If we had to create a constant buffer to house the global-scope + // ordinary/uniform data, then we need to make sure to set that + // information on the global scope. + // + if(newGlobalConstantBufferBinding.kind != LayoutResourceKind::None ) + { + auto resInfo = newGlobalScopeVarLayout->findOrAddResourceInfo(newGlobalConstantBufferBinding.kind); + resInfo->space = newGlobalConstantBufferBinding.space; + resInfo->index = newGlobalConstantBufferBinding.index; } - globalVarLayout->typeLayout = globalScopeLayout; - programLayout->globalScopeLayout = globalVarLayout; - newProgramLayout->globalScopeLayout = globalVarLayout; + + newProgramLayout->parametersLayout = newGlobalScopeVarLayout; return newProgramLayout; } -} + +} // namespace Slang -- cgit v1.2.3