diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2019-08-08 11:22:32 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-08 11:22:32 -0700 |
| commit | 2552217b76c0bd83e18fceba1d35a367bf569eca (patch) | |
| tree | 0651175e4601af75bc18687c853068f013e6c1b9 /source/slang/slang-parameter-binding.cpp | |
| parent | 81ce78d08a7e3fbe74f2fd41c5a258ea4b078245 (diff) | |
Revise new COM-lite API (#1007)
* Revise new COM-lite API
This change revises the "COM-lite" API that was recently introduced to try to streamline it and introduce some missing central/base concepts.
The central new abstraction in the API is the notion of a "component type," which is a unit of shader code composition. A component type can have:
* IR code for some number of functions/types/etc.
* Zero or more global shader parameters
* Zero or more "entry point" functions at which execution can start
* Zero or more "specialization" parameters (types or values that must be filled in before kernel code can be generated)
* Zero or more "requirements" (dependencies on other component types that must be satisfied before kernel code can be generated)
Both individual compiled modules, and validated entry points are then examples of component types, and we additionally define a few services that apply to all component types:
* We can take N component types and compose them to create a new component type that combines their code, shader parameters, entry points, and specialization parameters. A composed component type may also include requirements from the sub-component types, but it is also possible that by composing thing we satisfy requirements (if `A` requires `B`, and we compose `A` and `B`, then the requirement is now satisfied, and doesn't appear on the composite).
* We can take a component type with N specialization parameters, and specialize it by giving N compatible specialization arguments. The result of specialization is a new component type with zero specialization parameters. Under the right circumstances the specialzed component type will be layout compatible with the unspecialized one.
* One more example that isn't exposed in the public API today is that we can take a component with requirements and "complete" it by automatically composing it with component types that satisfy those requirements. This can be seen as a kind of linking step that pulls together the transitive closure of dependencies.
* We can query the layout for the shader parameters and entry points of a component type, for a specific target.
* We can query compiled kernel code for an entry point in a component type (for a specific target). This only works for component types with zero specialization parameters and zero requirements.
The idea is that by giving users a fairly general algebra of operations on component types, they can compose final programs in ways that meet their requirements. For example, it becomes possible to incrementally "grow" a component type to represent the global root signature for ray tracing shaders as new entry points are added, in such a way that it always stays layout-compatible with kernels that have already been compiled.
Much of the implementation work here is in implementing the unifying component type abstraction, and in particular re-writing code that used to assume a program consisted of a flat list of modules and entry points to work with a hierarchical representation that reflects the underlying algebra (e.g., with types to represent composite and specialized component types).
There's also a hidden "legacy" case of a component type to deal with some legacy compiler behaviors that can't be directly modeled on top of the simple algebra with modules and entry points.
This API is by no means feature-complete or fully developed. It is expected that we will flesh it out more when bringing up application code (e.g., Falcor) on top of the revamped API.
One notable thing that went away in this change is explicit support for "entry point groups" and notions of local root signatures (especially the Falcor-specific handling of the `shared` keyword, which a previous change turned into an explicitly supported feature). With the new "building blocks" approach, it should be possible for a DXR application to deal with local root signatures as a matter of policy (on top of the API we provide). If/when we need to provide some kind of emulation of local root signatures for Vulkan (and/or if Vulkan is extended with an explicit notion of local root signatures), we might need to revisit this choice.
* Fix debug build
There was invalid code inside an `assert()`, so the release build didn't catch it.
* fixup: warnings
* fixup: more warnings-as-errors
* fixup: review notes
* fixup: use component type visitors in place of dynamic casting
Diffstat (limited to 'source/slang/slang-parameter-binding.cpp')
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 1043 |
1 files changed, 765 insertions, 278 deletions
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp index dc99f55e2..722725af7 100644 --- a/source/slang/slang-parameter-binding.cpp +++ b/source/slang/slang-parameter-binding.cpp @@ -674,16 +674,22 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( EntryPointParameterState const& state, RefPtr<VarLayout> varLayout); -// Collect a single declaration into our set of parameters -static void collectGlobalGenericParameter( - ParameterBindingContext* context, - RefPtr<GlobalGenericParamDecl> paramDecl) +static RefPtr<VarLayout> _createVarLayout( + TypeLayout* typeLayout, + DeclRef<VarDeclBase> varDeclRef) { - RefPtr<GenericParamLayout> layout = new GenericParamLayout(); - layout->decl = paramDecl; - layout->index = (int)context->shared->programLayout->globalGenericParams.getCount(); - context->shared->programLayout->globalGenericParams.add(layout); - context->shared->programLayout->globalGenericParamsMap[layout->decl->getName()->text] = layout.Ptr(); + RefPtr<VarLayout> varLayout = new VarLayout(); + varLayout->typeLayout = typeLayout; + varLayout->varDecl = varDeclRef; + + if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout) + { + RefPtr<VarLayout> pendingVarLayout = new VarLayout(); + pendingVarLayout->typeLayout = pendingDataTypeLayout; + varLayout->pendingVarLayout = pendingVarLayout; + } + + return varLayout; } // Collect a single declaration into our set of parameters @@ -712,9 +718,7 @@ static void collectGlobalScopeParameter( return; // Now create a variable layout that we can use - RefPtr<VarLayout> varLayout = new VarLayout(); - varLayout->typeLayout = typeLayout; - varLayout->varDecl = varDeclRef; + RefPtr<VarLayout> varLayout = _createVarLayout(typeLayout, varDeclRef); // The logic in `check.cpp` that created the `GlobalShaderParamInfo` // will have identified any cases where there might be multiple @@ -748,6 +752,7 @@ static void collectGlobalScopeParameter( RefPtr<VarLayout> additionalVarLayout = new VarLayout(); additionalVarLayout->typeLayout = typeLayout; additionalVarLayout->varDecl = additionalVarDeclRef; + additionalVarLayout->pendingVarLayout = varLayout->pendingVarLayout; parameterInfo->varLayouts.add(additionalVarLayout); } @@ -1770,15 +1775,40 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( return structLayout; } - else if (auto globalGenericParam = declRef.as<GlobalGenericParamDecl>()) + else if (auto globalGenericParamDecl = declRef.as<GlobalGenericParamDecl>()) { - auto genParamTypeLayout = new GenericParamTypeLayout(); - // we should have already populated ProgramLayout::genericEntryPointParams list at this point, - // so we can find the index of this generic param decl in the list - genParamTypeLayout->type = type; - genParamTypeLayout->paramIndex = findGenericParam(context->shared->programLayout->globalGenericParams, globalGenericParam.getDecl()); - genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count += 1; - return genParamTypeLayout; + auto& layoutContext = context->layoutContext; + + if( auto concreteType = findGlobalGenericSpecializationArg( + layoutContext, + globalGenericParamDecl) ) + { + // If we know what concrete type has been used to specialize + // the global generic type parameter, then we should use + // the concrete type instead. + // + // Note: it should be illegal for the user to use a generic + // type parameter in a varying parameter list without giving + // it an explicit user-defined semantic. Otherwise, it would be possible + // that the concrete type that gets plugged in is a user-defined + // `struct` that uses some `SV_` semantics in its definition, + // so that any static information about what system values + // the entry point uses would be incorrect. + // + return processEntryPointVaryingParameter(context, concreteType, state, varLayout); + } + else + { + // If we don't know a concrete type, then we aren't generating final + // code, so the reflection information should show the generic + // type parameter. + // + // We don't make any attempt to assign varying parameter resources + // to the generic type, since we can't know how many "slots" + // of varying input/output it would consume. + // + return createTypeLayoutForGlobalGenericTypeParam(layoutContext, type, globalGenericParamDecl); + } } else if (auto associatedTypeParam = declRef.as<AssocTypeDecl>()) { @@ -1804,15 +1834,12 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter( /// Compute the type layout for a parameter declared directly on an entry point. static RefPtr<TypeLayout> computeEntryPointParameterTypeLayout( ParameterBindingContext* context, - SubstitutionSet typeSubst, DeclRef<VarDeclBase> paramDeclRef, RefPtr<VarLayout> paramVarLayout, EntryPointParameterState& state) { - auto paramDeclRefType = GetType(paramDeclRef); - SLANG_ASSERT(paramDeclRefType); - - auto paramType = paramDeclRefType->Substitute(typeSubst).as<Type>(); + auto paramType = GetType(paramDeclRef); + SLANG_ASSERT(paramType); if( paramDeclRef.getDecl()->HasModifier<HLSLUniformModifier>() ) { @@ -1940,6 +1967,12 @@ struct ScopeLayoutBuilder { m_structLayout->mapVarToLayout.Add(firstVarLayout->varDecl.getDecl(), firstVarLayout); } + } + + void addParameter( + RefPtr<VarLayout> varLayout) + { + _addParameter(varLayout, nullptr); // Any "pending" items on a field type become "pending" items // on the overall `struct` type layout. @@ -1948,42 +1981,58 @@ 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. // - if( auto fieldPendingDataTypeLayout = firstVarLayout->typeLayout->pendingDataTypeLayout ) + if( auto fieldPendingDataTypeLayout = varLayout->typeLayout->pendingDataTypeLayout ) { m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); - auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(firstVarLayout->varDecl, fieldPendingDataTypeLayout); + auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(varLayout->varDecl, fieldPendingDataTypeLayout); m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); - if( parameterInfo ) - { - for( auto& varLayout : parameterInfo->varLayouts ) - { - varLayout->pendingVarLayout = fieldPendingDataVarLayout; - } - } - else - { - firstVarLayout->pendingVarLayout = fieldPendingDataVarLayout; - } + varLayout->pendingVarLayout = fieldPendingDataVarLayout; } } void addParameter( - RefPtr<VarLayout> varLayout) - { - _addParameter(varLayout, nullptr); - } - - void addParameter( ParameterInfo* parameterInfo) { SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.getCount() != 0); auto firstVarLayout = parameterInfo->varLayouts.getFirst(); _addParameter(firstVarLayout, parameterInfo); - } + // Global parameters will have their non-orindary/uniform + // pending data handled by the main parameter binding + // logic, but we still need to construct a layout + // that includes any pending data. + // + if(auto fieldPendingVarLayout = firstVarLayout->pendingVarLayout) + { + auto fieldPendingTypeLayout = fieldPendingVarLayout->typeLayout; + + m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); + m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); + + auto fieldUniformLayoutInfo = fieldPendingTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform); + LayoutSize fieldUniformSize = fieldUniformLayoutInfo ? fieldUniformLayoutInfo->count : 0; + if( fieldUniformSize != 0 ) + { + // Make sure uniform fields get laid out properly... + + UniformLayoutInfo fieldInfo( + fieldUniformSize, + fieldPendingTypeLayout->uniformAlignment); + + LayoutSize uniformOffset = m_rules->AddStructField( + m_pendingDataTypeLayoutBuilder.getStructLayoutInfo(), + fieldInfo); + + fieldPendingVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue(); + } + + m_pendingDataTypeLayoutBuilder.getTypeLayout()->fields.add(fieldPendingVarLayout); + } + + } // Add a "simple" parameter that cannot have any user-defined // register or binding modifiers, so that its layout computation @@ -2088,21 +2137,48 @@ static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding( return info; } + /// Remove resource usage from `typeLayout` that should only be stored per-entry-point. + /// + /// This is used when constructing the overall layout for an entry point, to make sure + /// that certain kinds of resource usage from the entry point don't "leak" into + /// the resource usage of the overall program. + /// +static void removePerEntryPointParameterKinds( + TypeLayout* typeLayout) +{ + typeLayout->removeResourceUsage(LayoutResourceKind::VaryingInput); + typeLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput); + typeLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord); + typeLayout->removeResourceUsage(LayoutResourceKind::HitAttributes); + typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam); + typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam); +} + /// Iterate over the parameters of an entry point to compute its requirements. /// static RefPtr<EntryPointLayout> collectEntryPointParameters( - ParameterBindingContext* context, - EntryPoint* entryPoint, - SubstitutionSet typeSubst) + ParameterBindingContext* context, + EntryPoint* entryPoint, + EntryPoint::EntryPointSpecializationInfo* specializationInfo) { DeclRef<FuncDecl> entryPointFuncDeclRef = entryPoint->getFuncDeclRef(); + // If specialization was applied to the entry point, then the side-band + // information that was generated will have a more specialized reference + // to the entry point with generic parameters filled in. We should + // use that version if it is available. + // + if(specializationInfo) + entryPointFuncDeclRef = specializationInfo->specializedFuncDeclRef; + + auto entryPointType = DeclRefType::Create(context->getLinkage()->getSessionImpl(), entryPointFuncDeclRef); + // We will take responsibility for creating and filling in // the `EntryPointLayout` object here. // RefPtr<EntryPointLayout> entryPointLayout = new EntryPointLayout(); entryPointLayout->profile = entryPoint->getProfile(); - entryPointLayout->entryPoint = entryPointFuncDeclRef.getDecl(); + entryPointLayout->entryPoint = entryPointFuncDeclRef; // The entry point layout must be added to the output // program layout so that it can be accessed by reflection. @@ -2114,19 +2190,6 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // 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 `EntryPoint` and its matching `EntryPointLayout`, - // so we'll use it. - // - for( auto taggedUnionType : entryPoint->getTaggedUnionTypes() ) - { - SLANG_ASSERT(taggedUnionType); - auto substType = taggedUnionType->Substitute(typeSubst).as<Type>(); - auto typeLayout = createTypeLayout(context->layoutContext, substType); - entryPointLayout->taggedUnionTypeLayouts.add(typeLayout); - } - // 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: @@ -2149,22 +2212,33 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( ScopeLayoutBuilder scopeBuilder; scopeBuilder.beginLayout(context); auto paramsStructLayout = scopeBuilder.m_structLayout; + paramsStructLayout->type = entryPointType; for( auto& shaderParamInfo : entryPoint->getShaderParams() ) { auto paramDeclRef = shaderParamInfo.paramDeclRef; + // Any generic specialization applied to the entry-point function + // must also be applied to its parameters. + paramDeclRef.substitutions = entryPointFuncDeclRef.substitutions; + // When computing layout for an entry-point parameter, // we want to make sure that the layout context has access // to the existential type arguments (if any) that were // provided for the entry-point existential type parameters (if any). // - context->layoutContext= context->layoutContext - .withExistentialTypeArgs( - entryPoint->getExistentialTypeArgCount(), - entryPoint->getExistentialTypeArgs()) - .withExistentialTypeSlotsOffsetBy( - shaderParamInfo.firstExistentialTypeSlot); + if(specializationInfo) + { + auto& existentialSpecializationArgs = specializationInfo->existentialSpecializationArgs; + auto genericSpecializationParamCount = entryPoint->getGenericSpecializationParamCount(); + + context->layoutContext = context->layoutContext + .withSpecializationArgs( + existentialSpecializationArgs.getBuffer(), + existentialSpecializationArgs.getCount()) + .withSpecializationArgsOffsetBy( + shaderParamInfo.firstSpecializationParamIndex - genericSpecializationParamCount); + } // Any error messages we emit during the process should // refer to the location of this parameter. @@ -2183,7 +2257,6 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( auto paramTypeLayout = computeEntryPointParameterTypeLayout( context, - typeSubst, paramDeclRef, paramVarLayout, state); @@ -2204,6 +2277,26 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // scopeBuilder.addSimpleParameter(paramVarLayout); } + + // We don't want certain kinds of resource usage within an entry + // point to "leak" into the overall resource usage of the entry + // point and thus lead to offsetting of successive entry points. + // + // For example if we have a vertex and a fragment entry point + // in the some program, and each has one varying input, then + // the both the vertex and fragment varying outputs should have + // a location/index of zero. It would be bad if the fragment + // input (or whichever entry point comes second in the global + // ordering) started at location one, because then it wouldn't + // line up correctly with any vertex stage outputs. + // + // We handle this with a bit of a kludge, by removing the + // particular `LayoutResourceKind`s that are susceptible to + // this problem from the overall resource usage of the entry + // point. + // + removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); + entryPointLayout->parametersLayout = scopeBuilder.endLayout(); // For an entry point with a non-`void` return type, we need to process the @@ -2212,7 +2305,7 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( // TODO: Ideally we should make the layout process more robust to empty/void // types and apply this logic unconditionally. // - auto resultType = GetResultType(entryPointFuncDeclRef)->Substitute(typeSubst).as<Type>(); + auto resultType = GetResultType(entryPointFuncDeclRef); SLANG_ASSERT(resultType); if( !resultType->Equals(resultType->getSession()->getVoidType()) ) @@ -2226,7 +2319,7 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( auto resultTypeLayout = processEntryPointVaryingParameterDecl( context, entryPointFuncDeclRef.getDecl(), - resultType->Substitute(typeSubst).as<Type>(), + resultType, state, resultLayout); @@ -2248,136 +2341,257 @@ static RefPtr<EntryPointLayout> collectEntryPointParameters( return entryPointLayout; } - /// Remove resource usage from `typeLayout` that should only be stored per-entry-point. - /// - /// This is used when constructing the layout for an entry point group, to make sure - /// that certain kinds of resource usage from the entry point don't "leak" into - /// the resource usage of the group. - /// -static void removePerEntryPointParameterKinds( - TypeLayout* typeLayout) + /// Visitor used by `collectGlobalGenericArguments` +struct CollectGlobalGenericArgumentsVisitor : ComponentTypeVisitor { - typeLayout->removeResourceUsage(LayoutResourceKind::VaryingInput); - typeLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput); - typeLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord); - typeLayout->removeResourceUsage(LayoutResourceKind::HitAttributes); - typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam); - typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam); -} + CollectGlobalGenericArgumentsVisitor( + ParameterBindingContext* context) + : m_context(context) + {} -static void collectParameters( - ParameterBindingContext* inContext, - Program* program) -{ - // All of the parameters in translation units directly - // referenced in the compile request are part of one - // logical namespace/"linkage" so that two parameters - // with the same name should represent the same - // parameter, and get the same binding(s) + ParameterBindingContext* m_context; - ParameterBindingContext contextData = *inContext; - auto context = &contextData; - context->stage = Stage::Unknown; + void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(entryPoint); + SLANG_UNUSED(specializationInfo); + } - auto globalGenericSubst = program->getGlobalGenericSubstitution(); + void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(module); - // We will start by looking for any global generic type parameters. + if(!specializationInfo) + return; - for(RefPtr<Module> module : program->getModuleDependencies()) - { - for( auto genParamDecl : module->getModuleDecl()->getMembersOfType<GlobalGenericParamDecl>() ) + for(auto& globalGenericArg : specializationInfo->genericArgs) { - collectGlobalGenericParameter(context, genParamDecl); + if(auto globalGenericTypeParamDecl = as<GlobalGenericParamDecl>(globalGenericArg.paramDecl)) + { + m_context->shared->programLayout->globalGenericArgs.Add(globalGenericTypeParamDecl, globalGenericArg.argVal); + } } } - // Once we have enumerated global generic type parameters, we can - // begin enumerating shader parameters, starting at the global scope. - // - // Because we have already enumerated the global generic type parameters, - // we will be able to look up the index of a global generic type parameter - // when we see it referenced in the type of one of the shader parameters. + void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + visitChildren(composite, specializationInfo); + } - for(auto& globalParamInfo : program->getShaderParams() ) + void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE { - // When computing layout for a global shader parameter, - // we want to make sure that the layout context has access - // to the existential type arguments (if any) that were - // provided for the global existential type parameters (if any). - // - context->layoutContext= context->layoutContext - .withExistentialTypeArgs( - program->getExistentialTypeArgCount(), - program->getExistentialTypeArgs()) - .withExistentialTypeSlotsOffsetBy( - globalParamInfo.firstExistentialTypeSlot); + specialized->getBaseComponentType()->acceptVisitor(this, specialized->getSpecializationInfo()); + } - collectGlobalScopeParameter(context, globalParamInfo, globalGenericSubst); + void visitLegacy(LegacyProgram* legacy, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // TODO: Need to do something in this case... + SLANG_UNUSED(legacy); + SLANG_UNUSED(specializationInfo); } +}; + + /// Collect an ordered list of all the specialization arguments given for global generic specialization parameters in `program`. + /// + /// This information is used to accelerate the process of mapping a global generic type + /// to its definition during type layout. + /// +static void collectGlobalGenericArguments( + ParameterBindingContext* context, + ComponentType* program) +{ + CollectGlobalGenericArgumentsVisitor visitor(context); + program->acceptVisitor(&visitor, nullptr); +} - // Next consider parameters for entry points - for( auto entryPointGroup : program->getEntryPointGroups() ) + /// Collect information about the (unspecialized) specialization parameters of `program` into `context`. + /// + /// This function computes the reflection/layout for for the specialization parameters, so + /// that they can be exposed to the API user. + /// +static void collectSpecializationParams( + ParameterBindingContext* context, + ComponentType* program) +{ + auto specializationParamCount = program->getSpecializationParamCount(); + for(Index ii = 0; ii < specializationParamCount; ++ii) { - RefPtr<EntryPointGroupLayout> entryPointGroupLayout = new EntryPointGroupLayout(); - entryPointGroupLayout->group = entryPointGroup; + auto specializationParam = program->getSpecializationParam(ii); + switch(specializationParam.flavor) + { + case SpecializationParam::Flavor::GenericType: + case SpecializationParam::Flavor::GenericValue: + { + RefPtr<GenericSpecializationParamLayout> paramLayout = new GenericSpecializationParamLayout(); + paramLayout->decl = specializationParam.object.as<Decl>(); + context->shared->programLayout->specializationParams.add(paramLayout); + } + break; - context->shared->programLayout->entryPointGroups.add(entryPointGroupLayout); + case SpecializationParam::Flavor::ExistentialType: + case SpecializationParam::Flavor::ExistentialValue: + { + RefPtr<ExistentialSpecializationParamLayout> paramLayout = new ExistentialSpecializationParamLayout(); + paramLayout->type = specializationParam.object.as<Type>(); + context->shared->programLayout->specializationParams.add(paramLayout); + } + break; + default: + SLANG_UNEXPECTED("unhandled specialization parameter flavor"); + break; + } + } +} + + /// Visitor used by `collectParameters()` +struct CollectParametersVisitor : ComponentTypeVisitor +{ + CollectParametersVisitor( + ParameterBindingContext* context) + : m_context(context) + {} - ScopeLayoutBuilder scopeBuilder; - scopeBuilder.beginLayout(context); - auto entryPointGroupParamsStructLayout = scopeBuilder.m_structLayout; + ParameterBindingContext* m_context; - // First lay out any shader parameters that belong to the group - // itself, rather than to its nested entry points. + void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // The parameters of a composite component type can + // be determined by just visiting its children in order. // - // This ensures that looking up one of the parameters of the - // group by index in its parameters truct will Just Work. + visitChildren(composite, specializationInfo); + } + + void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE + { + // The parameters of a specialized component type + // are just those of its base component type, with + // appropriate specialization information passed + // along. // - for( auto groupParam : entryPointGroup->getShaderParams() ) + visitChildren(specialized); + + // While we are at it, we will also make note of any + // tagged-union types that were used as part of the + // specialization arguments, since we need to make + // sure that their layout information is computed + // and made available for IR code generation. + // + // Note: this isn't really the best place for this logic to sit, + // but it is the simplest place where we can collect all the tagged + // union types that get referenced by a program. + // + for( auto taggedUnionType : specialized->getTaggedUnionTypes() ) { - auto paramDeclRef = groupParam.paramDeclRef; - auto paramType = GetType(paramDeclRef); + SLANG_ASSERT(taggedUnionType); + auto substType = taggedUnionType; + auto typeLayout = createTypeLayout(m_context->layoutContext, substType); + m_context->shared->programLayout->taggedUnionTypeLayouts.add(typeLayout); + } + } - RefPtr<VarLayout> paramVarLayout = new VarLayout(); - paramVarLayout->varDecl = paramDeclRef; - auto paramTypeLayout = createTypeLayout( - context->layoutContext.with(context->getRulesFamily()->getConstantBufferRules()), - paramType); - paramVarLayout->typeLayout = paramTypeLayout; + void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // An entry point is a leaf case. + // + // In our current model an entry point does not introduce + // any global shader parameters, but in practice it effectively + // acts a lot like a single global shader parameter named after + // the entry point and with a `struct` type that combines + // all the `uniform` entry point parameters. + // + // Later passes will need to make sure that the entry point + // gets enumerated in the right order relative to any global + // shader parameters. + // - scopeBuilder.addSimpleParameter(paramVarLayout); - } + ParameterBindingContext contextData = *m_context; + auto context = &contextData; + context->stage = entryPoint->getStage(); - for(auto entryPoint : entryPointGroup->getEntryPoints()) - { - // Note: we do not want the entry point group to accumulate - // locations for varying input/output parameters: those - // should be specific to each entry point. - // - // We address this issue by manually removing any - // layout information for the relevant resource kinds - // from the group's layout before adding the parameters - // of any entry point for layout. - // - removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); + collectEntryPointParameters(context, entryPoint, specializationInfo); + } - context->stage = entryPoint->getStage(); - auto entryPointLayout = collectEntryPointParameters(context, entryPoint, globalGenericSubst); + void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // A single module represents a leaf case for layout. + // + // We will enumerate the (global) shader parameters declared + // in the module and add each to our canonical ordering. + // + auto paramCount = module->getShaderParamCount(); - auto entryPointParamsLayout = entryPointLayout->parametersLayout; - auto entryPointParamsTypeLayout = entryPointParamsLayout->typeLayout; + ExpandedSpecializationArg* specializationArgs = specializationInfo + ? specializationInfo->existentialArgs.getBuffer() + : nullptr; - scopeBuilder.addSimpleParameter(entryPointParamsLayout); + for(Index pp = 0; pp < paramCount; ++pp) + { + auto shaderParamInfo = module->getShaderParam(pp); + if(specializationArgs) + { + m_context->layoutContext = m_context->layoutContext.withSpecializationArgs( + specializationArgs, + shaderParamInfo.specializationParamCount); + specializationArgs += shaderParamInfo.specializationParamCount; + } - entryPointGroupLayout->entryPoints.add(entryPointLayout); + collectGlobalScopeParameter(m_context, shaderParamInfo, SubstitutionSet()); } - removePerEntryPointParameterKinds(scopeBuilder.m_structLayout); + } + + + void visitLegacy(LegacyProgram* legacy, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // A legacy program is also a leaf case, and we + // can enumerate its parameters directly. + // + // Note: there is a mismatch here where we really + // ought to be tracking specialization arguments + // for a `LegacyProgram` akin to how they are + // tracked for a `Module`, but right now we try + // to do it like a `CompositeComponentType`. + // As a result we are just ignoring specialization + // information here, which will lead to incorrect + // results if somebody every uses specialization + // together with the "legacy" program case. + // + // TODO: eliminate this problem by getting rid of + // `LegacyProgram`, rather than spend time trying + // to make this corner case actually work. + // + SLANG_UNUSED(specializationInfo); - entryPointGroupLayout->parametersLayout = scopeBuilder.endLayout(); + auto paramCount = legacy->getShaderParamCount(); + for(Index pp = 0; pp < paramCount; ++pp) + { + collectGlobalScopeParameter(m_context, legacy->getShaderParam(pp), SubstitutionSet()); + } } - context->entryPointLayout = nullptr; +}; + + /// Recursively collect the global shader parameters and entry points in `program`. + /// + /// This function is used to establish the global ordering of parameters and + /// entry points used for layout. + /// +static void collectParameters( + ParameterBindingContext* inContext, + ComponentType* program) +{ + // All of the parameters in translation units directly + // referenced in the compile request are part of one + // logical namespace/"linkage" so that two parameters + // with the same name should represent the same + // parameter, and get the same binding(s) + + ParameterBindingContext contextData = *inContext; + auto context = &contextData; + context->stage = Stage::Unknown; + + CollectParametersVisitor visitor(context); + program->acceptVisitor(&visitor, nullptr); } /// Emit a diagnostic about a uniform parameter at global scope. @@ -2418,6 +2632,302 @@ static int _calcTotalNumUsedRegistersForLayoutResourceKind(ParameterBindingConte return numUsed; } + /// Keep track of the running global counter for entry points and global parameters visited. + /// + /// Because of explicit `register` and `[[vk::binding(...)]]` support, parameter binding + /// needs to proceed in multiple passes, and each pass must both visit the things that + /// need layout (parameters and entry points) in the same order in each pass, and must + /// also be able to look up the side-band information that flows between passes. + /// + /// Currently the `ParameterBindingContext` keeps separate arrays for global shader + /// parameters and entry points, but in the global ordering for layout they can be + /// interleaved. There is also no simple tracking structure that relates a global + /// parameter or entry point to its index in those arrays. Instead, we just keep + /// running counters during our passes over the program so that we can easily + /// compute the linear index of each entry point and global parameter as it + /// is encountered. + /// +struct ParameterBindingVisitorCounters +{ + Index entryPointCounter = 0; + Index globalParamCounter = 0; +}; + + /// Recursive routine to "complete" all binding for parameters and entry points in `componentType`. + /// + /// This includes allocation of as-yet-unused register/binding ranges to parameters (which + /// will then affect the ranges of registers/bindings that are available to subsequent + /// parameters), and imporantly *also* includes allocate of space to any "pending" + /// data for interface/existential type parameters/fields. + /// +static void _completeBindings( + ParameterBindingContext* context, + ComponentType* componentType, + ParameterBindingVisitorCounters* ioCounters); + + /// A visitor used by `_completeBindings`. + /// + /// This visitor walks the structure of a `ComponentType` to ensure that + /// any shader parameters (and entry points) it contains that *don't* + /// have explicit bindings on them get allocated registers/bindings + /// as appropriate. + /// + /// The main complication of this visitor is how it handles the + /// `SpecializedComponentType` case, because a specialized component + /// type needs to be handled as an atomic unit that lays out the + /// same in all contexts. + /// +struct CompleteBindingsVisitor : ComponentTypeVisitor +{ + CompleteBindingsVisitor(ParameterBindingContext* context, ParameterBindingVisitorCounters* counters) + : m_context(context) + , m_counters(counters) + {} + + ParameterBindingContext* m_context; + ParameterBindingVisitorCounters* m_counters; + + void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(entryPoint); + SLANG_UNUSED(specializationInfo); + + // We compute the index of the entry point in the global ordering, + // so we can look up the tracking data in our context. As a result + // we don't actually make use of the parameters that were passed in. + // + auto globalEntryPointIndex = m_counters->entryPointCounter++; + auto globalEntryPointInfo = m_context->shared->programLayout->entryPoints[globalEntryPointIndex]; + + + // We mostly treat an entry point like a single shader parameter that + // uses its `parametersLayout`. + // + completeBindingsForParameter(m_context, globalEntryPointInfo->parametersLayout); + } + + void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(specializationInfo); + // A module is a leaf case: we just want to visit each parameter. + visitLeafParams(module); + } + + void visitLegacy(LegacyProgram* legacy, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(specializationInfo); + // A legacy program is a leaf case: we just want to visit each parameter. + visitLeafParams(legacy); + } + + void visitLeafParams(ComponentType* componentType) + { + auto paramCount = componentType->getShaderParamCount(); + for(Index ii = 0; ii < paramCount; ++ii) + { + auto globalParamIndex = m_counters->globalParamCounter++; + auto globalParamInfo = m_context->shared->parameters[globalParamIndex]; + + completeBindingsForParameter(m_context, globalParamInfo); + } + } + + void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + // We just wnat to recurse on the children of the composite in order. + visitChildren(composite, specializationInfo); + } + + void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE + { + // The handling of a specialized component type here is subtle. + // + // We do *not* simply recurse on the base component type. + // Doing so would ensure that the parameters would get + // registers/bindings allocated to them, but it wouldn't + // allocate space for the "pending" data related to + // existential/interface parameters. + // + // Instead, we recursive through `_completeBindings`, + // which has the job of allocating space for the parameters, + // and then for any "pending" data required. + // + // Handling things this way ensures that a particular + // `SpecializedComponentType` gets laid out exactly + // the same wherever it gets used, rather than + // getting laid out differently when it is placed + // into different compositions. + // + auto base = specialized->getBaseComponentType(); + _completeBindings(m_context, base, m_counters); + } +}; + + /// A visitor used by `_completeBindings`. + /// + /// This visitor is used to follow up after the `CompleteBindingsVisitor` + /// any ensure that any "pending" data required by the parameters that + /// got laid out now gets a location. + /// + /// To make a concrete example: + /// + /// Texture2D a; + /// IThing b; + /// Texture2D c; + /// + /// If these parameters were laid out with `b` specialized to a type + /// that contains a single `Texture2D`, then the `CompleteBindingsVisitor` + /// would visit `a`, `b`, and then `c` in order. It would give `a` the + /// first register/binding available (say, `t0`). It would then make + /// a note that due to specialization, `b`, needs a `t` register as well, + /// but it *cannot* be allocated just yet, because doing so would change + /// the location of `c`, so it is marked as "pending." Then `c` would + /// be visited and get `t1`. As a result the registers given to `a` + /// and `c` are independent of how `b` gets specialized. + /// + /// Next, the `FlushPendingDataVisitor` comes through and applies to + /// the parameters again. For `a` there is no pending data, but for + /// `b` there is a pending request for a `t` register, so it gets allocated + /// now (getting `t2`). The `c` parameter then has no pending data, so + /// we are done. + /// + /// *When* the pending data gets flushed is then significant. In general, + /// the order in which modules get composed an specialized is signficaint. + /// The module above (let's call it `M`) has one specialization parameter + /// (for `b`), and if we want to compose it with another module `N` that + /// has no specialization parameters, we could compute either: + /// + /// compose(specialize(M, SomeType), N) + /// + /// or: + /// + /// specialize(compose(M,N), SomeType) + /// + /// In the first case, the "pending" data for `M` gets flushed right after `M`, + /// so that `specialize(M,SomeType)` can have a consistent layout + /// regardless of how it is used. In the second case, the pending data for + /// `M` only gets flushed after `N`'s parameters are allocated, thus guaranteeing + /// that the `compose(M,N)` part has a consistent layout regardless of what + /// type gets plugged in during specialization. + /// + /// There are trade-offs to be made by an application about which approach + /// to prefer, and the compiler supports either policy choice. + /// +struct FlushPendingDataVisitor : ComponentTypeVisitor +{ + FlushPendingDataVisitor(ParameterBindingContext* context, ParameterBindingVisitorCounters* counters) + : m_context(context) + , m_counters(counters) + {} + + ParameterBindingContext* m_context; + ParameterBindingVisitorCounters* m_counters; + + void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(entryPoint); + SLANG_UNUSED(specializationInfo); + + auto globalEntryPointIndex = m_counters->entryPointCounter++; + auto globalEntryPointInfo = m_context->shared->programLayout->entryPoints[globalEntryPointIndex]; + + // We need to allocate space for any "pending" data that + // appeared in the entry-point parameter list. + // + _allocateBindingsForPendingData(m_context, globalEntryPointInfo->parametersLayout->pendingVarLayout); + } + + void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(specializationInfo); + visitLeafParams(module); + } + + void visitLegacy(LegacyProgram* legacy, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + SLANG_UNUSED(specializationInfo); + visitLeafParams(legacy); + } + + void visitLeafParams(ComponentType* componentType) + { + // In the "leaf" case we just allocate space for any + // pending data in the parameters, in order. + // + auto paramCount = componentType->getShaderParamCount(); + for(Index ii = 0; ii < paramCount; ++ii) + { + auto globalParamIndex = m_counters->globalParamCounter++; + auto globalParamInfo = m_context->shared->parameters[globalParamIndex]; + auto firstVarLayout = globalParamInfo->varLayouts[0]; + + _allocateBindingsForPendingData(m_context, firstVarLayout->pendingVarLayout); + } + } + + void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE + { + visitChildren(composite, specializationInfo); + } + + void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE + { + // Because `SpecializedComponentType` was a special case for `CompleteBindingsVisitor`, + // it ends up being a special case here too. + // + // The `CompleteBindings...` pass treated a `SpecializedComponentType` + // as an atomic unit. Any "pending" data that came from its parameters + // will already have been dealt with, so it would be incorrect for + // us to recurse into `specialized`. + // + // Instead, we just need to *skip* `specialized`, since it was + // completely handled already. This isn't quite as simple + // as just doing nothing, because our passes are using + // some global counters to find the absolute/linear index + // of each parameter and entry point as it is encountered. + // We will simply bump those counters by the number of + // parameters and entry points contained under `specialized`, + // which is luckily provided by the `ComponentType` API. + // + m_counters->globalParamCounter += specialized->getShaderParamCount(); + m_counters->entryPointCounter += specialized->getEntryPointCount(); + } + +}; + +static void _completeBindings( + ParameterBindingContext* context, + ComponentType* componentType, + ParameterBindingVisitorCounters* ioCounters) +{ + ParameterBindingVisitorCounters savedCounters = *ioCounters; + + CompleteBindingsVisitor completeBindingsVisitor(context, ioCounters); + componentType->acceptVisitor(&completeBindingsVisitor, nullptr); + + FlushPendingDataVisitor flushVisitor(context, &savedCounters); + componentType->acceptVisitor(&flushVisitor, nullptr); +} + + /// "Complete" binding of parametesr in the given `program`. + /// + /// Completing binding involves both assigning registers/bindings + /// to an parameters that didn't get explicit locations, and then + /// also providing locations to any "pending" data that needed + /// space allocated (used for existential/interface type parameters). + /// +static void _completeBindings( + ParameterBindingContext* context, + ComponentType* program) +{ + // The process of completing binding has a recursive structure, + // so we will immediately delegate to a subroutine that handles + // the recursion. + // + ParameterBindingVisitorCounters counters; + _completeBindings(context, program, &counters); +} + RefPtr<ProgramLayout> generateParameterBindings( TargetProgram* targetProgram, DiagnosticSink* sink) @@ -2450,20 +2960,67 @@ RefPtr<ProgramLayout> generateParameterBindings( context.shared = &sharedContext; context.layoutContext = layoutContext; - // Walk through AST to discover all the parameters + // We want to start by finding out what (if anything) has + // been bound to the global generic parameters of the + // program, since we need to know these types to compute + // layout for parameters that use the generic type parameters. + // + collectGlobalGenericArguments(&context, program); + + // Next we want to collect a full listing of all the shader + // parameters that need to be considered for layout, along + // with all of the entry points, which also need their + // parameters laid out and thus act pretty much like global + // parameters themselves. + // collectParameters(&context, program); - // Now walk through the parameters to generate initial binding information + // We will also collect basic information on the specialization + // parameters exposed by the program. + // + // Whereas `collectGlobalGenericArguments` was collecting the + // concrete types that have been plugged into specialization + // parameters, this step is about collecting the *unspecialized* + // parameters (if any) for the purposes of reflection. + // + collectSpecializationParams(&context, program); + + // Once we have a canonical list of all the shader parameters + // (and entry points) in need of layout, we will walk through + // the parameters that might have explicit binding annotations, + // and "reserve" the registers/bindings/etc. that those parameters + // declare so that subequent automatic layout steps do not try to + // overlap them. + // + // Along the way we will issue diagnostics if there appear to + // be overlapping, conflicting, or inconsistent explicit bindings. + // + // Note that we do *not* support explicit binding annotations + // on entry point parameters, so we only consider global shader + // parameters here. + // + // (Also note that explicit bindings end up being the main + // source of complexity in the layout system, and we could greatly + // simplify this file by eliminating support for explicit + // binding in the future) + // for( auto& parameter : sharedContext.parameters ) { generateParameterBindings(&context, parameter); } - // Determine if there are any global-scope parameters that use `Uniform` - // resources, and thus need to get packaged into a constant buffer. + // Once we have a canonical list of all the parameters, we can + // detect if there are any global-scope parameters that make use + // of `LayoutResourceKind::Uniform`, since such parameters would + // need to be packaged into a "default" constant buffer. + // The fxc/dxc compilers support this step, and in reflection + // refer to the generated constant buffer as `$Globals`. + // + // Note that this logic doesn't account for the existance of + // "legacy" (non-buffer-bound) uniforms in GLSL for OpenGL. + // If we wanted to support legaqcy uniforms we would probably + // want to do so through a different feature. // - // Note: this doesn't account for GLSL's support for "legacy" uniforms - // at global scope, which don't get assigned a CB. bool needDefaultConstantBuffer = false; for( auto& parameterInfo : sharedContext.parameters ) { @@ -2525,6 +3082,32 @@ RefPtr<ProgramLayout> generateParameterBindings( break; } } + + // We also need a default space for any entry-point parameters + // that consume appropriate resource kinds. + // + for(auto& entryPoint : sharedContext.programLayout->entryPoints) + { + auto paramsLayout = entryPoint->parametersLayout; + for(auto resInfo : paramsLayout->resourceInfos ) + { + switch(resInfo.kind) + { + default: + break; + + case LayoutResourceKind::RegisterSpace: + case LayoutResourceKind::VaryingInput: + case LayoutResourceKind::VaryingOutput: + case LayoutResourceKind::HitAttributes: + case LayoutResourceKind::RayPayload: + continue; + } + + needDefaultSpace = true; + break; + } + } } // If we need a space for default bindings, then allocate it here. @@ -2575,12 +3158,14 @@ RefPtr<ProgramLayout> generateParameterBindings( &context, needDefaultConstantBuffer); - // Now walk through again to actually give everything - // ranges of registers... - for( auto& parameter : sharedContext.parameters ) - { - completeBindingsForParameter(&context, parameter); - } + // Now that all of the explicit bindings have been dealt with + // and we've also allocate any space/buffer that is required + // for global-scope parameters, we will go through the + // shader parameters and entry points yet again, in order + // to actually allocate specific bindings/registers to + // parameters and entry points that need them. + // + _completeBindings(&context, program); // Next we need to create a type layout to reflect the information // we have collected, and we will use the `ScopeLayoutBuilder` @@ -2602,104 +3187,6 @@ RefPtr<ProgramLayout> generateParameterBindings( cbInfo->index = globalConstantBufferBinding.index; } - // After we have laid out all the ordinary global parameters, - // we need to "flush" out any pending data that was associated with - // the global scope as part of dealing with interface-type parameters. - // - _allocateBindingsForPendingData(&context, globalScopeVarLayout->pendingVarLayout); - - // After we have allocated registers/bindings to everything - // in the global scope we will process the parameters - // of the entry points. - // - // Note: at the moment we are laying out *all* information related to global-scope - // parameters (including pending data from interface-type parameters) before - // anything pertaining to entry points. This is a crucial design choice to - // get right, and we might want to revisit it based on experience. - - // In some cases, a user will want to ensure that all the - // entry points they compile get non-overlapping - // registers/bindings. E.g., if you have a vertex and fragment - // shader being compiled together for Vulkan, you probably want distinct - // bindings for their entry-point `uniform` parametres, so - // that they can be used together. - // - // In other cases, however, a user probably doesn't want us - // to conservatively allocate non-overlapping bindings. - // E.g., if they have a bunch of compute shaders in a single - // file, then they probably want each compute shader to - // compute its parameter layout "from scratch" as if the - // others don't exist. - // - // The way we handle this is by putting the entry points of a - // `Program` into groups, and ensuring that within each group - // we allocate parameters that don't overlap, but we don't - // worry about overlap across groups. - // - for( auto entryPointGroup : sharedContext.programLayout->entryPointGroups ) - { - // We save off the allocation state as it was before the entry-point - // group, so that we can restore it to this state after each group. - // - // TODO: We probably ought to wrap all the state relevant to allocation - // of registers/bindings into a single struct/field so that we only - // have one thing to save/restore here even if new state gets added. - // - auto savedGlobalSpaceUsedRangeSets = sharedContext.globalSpaceUsedRangeSets; - auto savedUsedSpaces = sharedContext.usedSpaces; - - // The group will have been allocated a layout that combines the - // usage of all of the contained entry-points, so we just need to - // allocate the entry-point group to be placed after all the global-scope - // parameters. - // - auto entryPointGroupParamsLayout = entryPointGroup->parametersLayout; - completeBindingsForParameter(&context, entryPointGroupParamsLayout); - - _allocateBindingsForPendingData(&context, entryPointGroupParamsLayout->pendingVarLayout); - - // TODO: Should we add the offset information from the group to - // the layout information for each entry point (and thence to its parameters)? - // - // This seems important if we want to allow clients to conveniently - // ignore groups when doing their reflection queries. - - // Once we've allocated bindigns for the parameters of entry points - // in the group, we restore the state for tracking what register/bindings - // are used to where it was before. - // - sharedContext.globalSpaceUsedRangeSets = savedGlobalSpaceUsedRangeSets; - sharedContext.usedSpaces = savedUsedSpaces; - } - - // 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; { @@ -2723,7 +3210,7 @@ ProgramLayout* TargetProgram::getOrCreateLayout(DiagnosticSink* sink) } void generateParameterBindings( - Program* program, + ComponentType* program, TargetRequest* targetReq, DiagnosticSink* sink) { |
