diff options
| author | jsmall-nvidia <jsmall@nvidia.com> | 2019-05-31 17:20:37 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-05-31 17:20:37 -0400 |
| commit | 6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch) | |
| tree | 5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-parameter-binding.cpp | |
| parent | b81ff3ef968d1cc4e954b31a1812b3c391d17b02 (diff) | |
Use slang- prefix on slang compiler and core source (#973)
* Prefixing source files in source/slang with slang-
* Prefix source in source/slang with slang- prefix.
* Rename core source files with slang- prefix.
* Update project files.
* Fix problems from automatic merge.
Diffstat (limited to 'source/slang/slang-parameter-binding.cpp')
| -rw-r--r-- | source/slang/slang-parameter-binding.cpp | 2583 |
1 files changed, 2583 insertions, 0 deletions
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp new file mode 100644 index 000000000..5c4fd24b5 --- /dev/null +++ b/source/slang/slang-parameter-binding.cpp @@ -0,0 +1,2583 @@ +// slang-parameter-binding.cpp +#include "slang-parameter-binding.h" + +#include "slang-lookup.h" +#include "slang-compiler.h" +#include "slang-type-layout.h" + +#include "../../slang.h" + +namespace Slang { + +struct ParameterInfo; + +// Information on ranges of registers already claimed/used +struct UsedRange +{ + // What parameter has claimed this range? + VarLayout* parameter; + + // Begin/end of the range (half-open interval) + UInt begin; + UInt end; +}; +bool operator<(UsedRange left, UsedRange right) +{ + if (left.begin != right.begin) + return left.begin < right.begin; + if (left.end != right.end) + return left.end < right.end; + return false; +} + +static bool rangesOverlap(UsedRange const& x, UsedRange const& y) +{ + SLANG_ASSERT(x.begin <= x.end); + SLANG_ASSERT(y.begin <= y.end); + + // If they don't overlap, then one must be earlier than the other, + // and that one must therefore *end* before the other *begins* + + if (x.end <= y.begin) return false; + if (y.end <= x.begin) return false; + + // Otherwise they must overlap + return true; +} + + +struct UsedRanges +{ + // The `ranges` array maintains a sorted list of `UsedRange` + // objects such that the `end` of a range is <= the `begin` + // of any range that comes after it. + // + // The values covered by each `[begin,end)` range are marked + // as used, and anything not in such an interval is implicitly + // free. + // + // TODO: if it ever starts to matter for performance, we + // could encode this information as a tree instead of an array. + // + List<UsedRange> ranges; + + // Add a range to the set, either by extending + // existing range(s), or by adding a new one. + // + // If we find that the new range overlaps with + // an existing range for a *different* parameter + // then we return that parameter so that the + // caller can issue an error. + // + VarLayout* Add(UsedRange range) + { + // The invariant on entry to this + // function is that the `ranges` array + // is sorted and no two entries in the + // array intersect. We must preserve + // that property as a postcondition. + // + // The other postcondition is that the + // interval covered by the input `range` + // must be marked as consumed. + + // We will try track any parameter associated + // with an overlapping range that doesn't + // match the parameter on `range`, so that + // the compiler can issue useful diagnostics. + // + VarLayout* newParam = range.parameter; + VarLayout* existingParam = nullptr; + + // A clever algorithm might use a binary + // search to identify the first entry in `ranges` + // that might overlap `range`, but we are going + // to settle for being less clever for now, in + // the hopes that we can at least be correct. + // + // Note: we are going to iterate over `ranges` + // using indices, because we may actually modify + // the array as we go. + // + Int rangeCount = ranges.getCount(); + for(Int rr = 0; rr < rangeCount; ++rr) + { + auto existingRange = ranges[rr]; + + // The invariant on entry to each loop + // iteration will be that `range` does + // *not* intersect any preceding entry + // in the array. + // + // Note that this invariant might be + // true only because we modified + // `range` along the way. + // + // If `range` does not intertsect `existingRange` + // then our invariant will be trivially + // true for the next iteration. + // + if(!rangesOverlap(existingRange, range)) + { + continue; + } + + // We now know that `range` and `existingRange` + // intersect. The first thing to do + // is to check if we have a parameter + // associated with `existingRange`, so + // that we can use it for emitting diagnostics + // about the overlap: + // + if( existingRange.parameter + && existingRange.parameter != newParam) + { + // There was an overlap with a range that + // had a parameter specified, so we will + // use that parameter in any subsequent + // diagnostics. + // + existingParam = existingRange.parameter; + } + + // Before we can move on in our iteration, + // we need to re-establish our invariant by modifying + // `range` so that it doesn't overlap with `existingRange`. + // Of course we also want to end up with a correct + // result for the overall operation, so we can't just + // throw away intervals. + // + // We first note that if `range` starts before `existingRange`, + // then the interval from `range.begin` to `existingRange.begin` + // needs to be accounted for in the final result. Furthermore, + // the interval `[range.begin, existingRange.begin)` could not + // intersect with any range already in the `ranges` array, + // because it comes strictly before `existingRange`, and our + // invariant says there is no intersection with preceding ranges. + // + if(range.begin < existingRange.begin) + { + UsedRange prefix; + prefix.begin = range.begin; + prefix.end = existingRange.begin; + prefix.parameter = range.parameter; + ranges.add(prefix); + } + // + // Now we know that the interval `[range.begin, existingRange.begin)` + // is claimed, if it exists, and clearly the interval + // `[existingRange.begin, existingRange.end)` is already claimed, + // so the only interval left to consider would be + // `[existingRange.end, range.end)`, if it is non-empty. + // That range might intersect with others in the array, so + // we will need to continue iterating to deal with that + // possibility. + // + range.begin = existingRange.end; + + // If the range would be empty, then of course we have nothing + // left to do. + // + if(range.begin >= range.end) + break; + + // Otherwise, have can be sure that `range` now comes + // strictly *after* `existingRange`, and thus our invariant + // is preserved. + } + + // If we manage to exit the loop, then we have resolved + // an intersection with existing entries - possibly by + // adding some new entries. + // + // If the `range` we are left with is still non-empty, + // then we should go ahead and add it. + // + if(range.begin < range.end) + { + ranges.add(range); + } + + // Any ranges that got added along the way might not + // be in the proper sorted order, so we'll need to + // sort the array to restore our global invariant. + // + ranges.sort(); + + // We end by returning an overlapping parameter that + // we found along the way, if any. + // + return existingParam; + } + + VarLayout* Add(VarLayout* param, UInt begin, UInt end) + { + UsedRange range; + range.parameter = param; + range.begin = begin; + range.end = end; + return Add(range); + } + + VarLayout* Add(VarLayout* param, UInt begin, LayoutSize end) + { + UsedRange range; + range.parameter = param; + range.begin = begin; + range.end = end.isFinite() ? end.getFiniteValue() : UInt(-1); + return Add(range); + } + + bool contains(UInt index) + { + for (auto rr : ranges) + { + if (index < rr.begin) + return false; + + if (index >= rr.end) + continue; + + return true; + } + + return false; + } + + + // Try to find space for `count` entries + UInt Allocate(VarLayout* param, UInt count) + { + UInt begin = 0; + + UInt rangeCount = ranges.getCount(); + for (UInt rr = 0; rr < rangeCount; ++rr) + { + // try to fit in before this range... + + UInt end = ranges[rr].begin; + + // If there is enough space... + if (end >= begin + count) + { + // ... then claim it and be done + Add(param, begin, begin + count); + return begin; + } + + // ... otherwise, we need to look at the + // space between this range and the next + begin = ranges[rr].end; + } + + // We've run out of ranges to check, so we + // can safely go after the last one! + Add(param, begin, begin + count); + return begin; + } +}; + +struct ParameterBindingInfo +{ + size_t space = 0; + size_t index = 0; + LayoutSize count; +}; + +struct ParameterBindingAndKindInfo : ParameterBindingInfo +{ + LayoutResourceKind kind = LayoutResourceKind::None; +}; + +enum +{ + kLayoutResourceKindCount = SLANG_PARAMETER_CATEGORY_COUNT, +}; + +struct UsedRangeSet : RefObject +{ + // Information on what ranges of "registers" have already + // been claimed, for each resource type + UsedRanges usedResourceRanges[kLayoutResourceKindCount]; +}; + +// Information on a single parameter +struct ParameterInfo : RefObject +{ + // Layout info for the concrete variables that will make up this parameter + List<RefPtr<VarLayout>> varLayouts; + + ParameterBindingInfo bindingInfo[kLayoutResourceKindCount]; + + // The translation unit this parameter is specific to, if any + TranslationUnitRequest* translationUnit = nullptr; + + ParameterInfo() + { + // Make sure we aren't claiming any resources yet + for( int ii = 0; ii < kLayoutResourceKindCount; ++ii ) + { + bindingInfo[ii].count = 0; + } + } +}; + +struct EntryPointParameterBindingContext +{ + // What ranges of resources bindings are already claimed for this translation unit + UsedRangeSet usedRangeSet; +}; + +// State that is shared during parameter binding, +// across all translation units +struct SharedParameterBindingContext +{ + SharedParameterBindingContext( + LayoutRulesFamilyImpl* defaultLayoutRules, + ProgramLayout* programLayout, + TargetRequest* targetReq, + DiagnosticSink* sink) + : defaultLayoutRules(defaultLayoutRules) + , programLayout(programLayout) + , targetRequest(targetReq) + , m_sink(sink) + { + } + + DiagnosticSink* m_sink = nullptr; + + // The program that we are laying out +// Program* program = nullptr; + + // The target request that is triggering layout + // + // TODO: We should eventually strip this down to + // just the subset of fields on the target that + // can influence layout decisions. + TargetRequest* targetRequest = nullptr; + + LayoutRulesFamilyImpl* defaultLayoutRules; + + // All shader parameters we've discovered so far, and started to lay out... + List<RefPtr<ParameterInfo>> parameters; + + // The program layout we are trying to construct + RefPtr<ProgramLayout> programLayout; + + // What ranges of resources bindings are already claimed at the global scope? + // We store one of these for each declared binding space/set. + // + Dictionary<UInt, RefPtr<UsedRangeSet>> globalSpaceUsedRangeSets; + + // Which register spaces have been claimed so far? + UsedRanges usedSpaces; + + // The space to use for auto-generated bindings. + UInt defaultSpace = 0; + + TargetRequest* getTargetRequest() { return targetRequest; } + DiagnosticSink* getSink() { return m_sink; } +}; + +static DiagnosticSink* getSink(SharedParameterBindingContext* shared) +{ + return shared->getSink(); +} + +// State that might be specific to a single translation unit +// or event to an entry point. +struct ParameterBindingContext +{ + // All the shared state needs to be available + SharedParameterBindingContext* shared; + + // The type layout context to use when computing + // the resource usage of shader parameters. + TypeLayoutContext layoutContext; + + // What stage (if any) are we compiling for? + Stage stage; + + // The entry point that is being processed right now. + EntryPointLayout* entryPointLayout = nullptr; + + TargetRequest* getTargetRequest() { return shared->getTargetRequest(); } + LayoutRulesFamilyImpl* getRulesFamily() { return layoutContext.getRulesFamily(); } +}; + +static DiagnosticSink* getSink(ParameterBindingContext* context) +{ + return getSink(context->shared); +} + + +struct LayoutSemanticInfo +{ + LayoutResourceKind kind; // the register kind + UInt space; + UInt index; + + // TODO: need to deal with component-granularity binding... +}; + +static bool isDigit(char c) +{ + return (c >= '0') && (c <= '9'); +} + +/// Given a string that specifies a name and index (e.g., `COLOR0`), +/// split it into slices for the name part and the index part. +static void splitNameAndIndex( + UnownedStringSlice const& text, + UnownedStringSlice& outName, + UnownedStringSlice& outDigits) +{ + char const* nameBegin = text.begin(); + char const* digitsEnd = text.end(); + + char const* nameEnd = digitsEnd; + while( nameEnd != nameBegin && isDigit(*(nameEnd - 1)) ) + { + nameEnd--; + } + char const* digitsBegin = nameEnd; + + outName = UnownedStringSlice(nameBegin, nameEnd); + outDigits = UnownedStringSlice(digitsBegin, digitsEnd); +} + +LayoutResourceKind findRegisterClassFromName(UnownedStringSlice const& registerClassName) +{ + switch( registerClassName.size() ) + { + case 1: + switch (*registerClassName.begin()) + { + case 'b': return LayoutResourceKind::ConstantBuffer; + case 't': return LayoutResourceKind::ShaderResource; + case 'u': return LayoutResourceKind::UnorderedAccess; + case 's': return LayoutResourceKind::SamplerState; + + default: + break; + } + break; + + case 5: + if( registerClassName == "space" ) + { + return LayoutResourceKind::RegisterSpace; + } + break; + + default: + break; + } + return LayoutResourceKind::None; +} + +LayoutSemanticInfo ExtractLayoutSemanticInfo( + ParameterBindingContext* context, + HLSLLayoutSemantic* semantic) +{ + LayoutSemanticInfo info; + info.space = 0; + info.index = 0; + info.kind = LayoutResourceKind::None; + + UnownedStringSlice registerName = semantic->registerName.Content; + if (registerName.size() == 0) + return info; + + // The register name is expected to be in the form: + // + // identifier-char+ digit+ + // + // where the identifier characters name a "register class" + // and the digits identify a register index within that class. + // + // We are going to split the string the user gave us + // into these constituent parts: + // + UnownedStringSlice registerClassName; + UnownedStringSlice registerIndexDigits; + splitNameAndIndex(registerName, registerClassName, registerIndexDigits); + + LayoutResourceKind kind = findRegisterClassFromName(registerClassName); + if(kind == LayoutResourceKind::None) + { + getSink(context)->diagnose(semantic->registerName, Diagnostics::unknownRegisterClass, registerClassName); + return info; + } + + // For a `register` semantic, the register index is not optional (unlike + // how it works for varying input/output semantics). + if( registerIndexDigits.size() == 0 ) + { + getSink(context)->diagnose(semantic->registerName, Diagnostics::expectedARegisterIndex, registerClassName); + } + + UInt index = 0; + for(auto c : registerIndexDigits) + { + SLANG_ASSERT(isDigit(c)); + index = index * 10 + (c - '0'); + } + + + UInt space = 0; + if( auto registerSemantic = as<HLSLRegisterSemantic>(semantic) ) + { + auto const& spaceName = registerSemantic->spaceName.Content; + if(spaceName.size() != 0) + { + UnownedStringSlice spaceSpelling; + UnownedStringSlice spaceDigits; + splitNameAndIndex(spaceName, spaceSpelling, spaceDigits); + + if( kind == LayoutResourceKind::RegisterSpace ) + { + getSink(context)->diagnose(registerSemantic->spaceName, Diagnostics::unexpectedSpecifierAfterSpace, spaceName); + } + else if( spaceSpelling != UnownedTerminatedStringSlice("space") ) + { + getSink(context)->diagnose(registerSemantic->spaceName, Diagnostics::expectedSpace, spaceSpelling); + } + else if( spaceDigits.size() == 0 ) + { + getSink(context)->diagnose(registerSemantic->spaceName, Diagnostics::expectedSpaceIndex); + } + else + { + for(auto c : spaceDigits) + { + SLANG_ASSERT(isDigit(c)); + space = space * 10 + (c - '0'); + } + } + } + } + + // TODO: handle component mask part of things... + if( semantic->componentMask.Content.size() != 0 ) + { + getSink(context)->diagnose(semantic->componentMask, Diagnostics::componentMaskNotSupported); + } + + info.kind = kind; + info.index = (int) index; + info.space = space; + return info; +} + + +// + +// Given a GLSL `layout` modifier, we need to be able to check for +// a particular sub-argument and extract its value if present. +template<typename T> +static bool findLayoutArg( + RefPtr<ModifiableSyntaxNode> syntax, + UInt* outVal) +{ + for( auto modifier : syntax->GetModifiersOfType<T>() ) + { + if( modifier ) + { + *outVal = (UInt) strtoull(String(modifier->valToken.Content).getBuffer(), nullptr, 10); + return true; + } + } + return false; +} + +template<typename T> +static bool findLayoutArg( + DeclRef<Decl> declRef, + UInt* outVal) +{ + return findLayoutArg<T>(declRef.getDecl(), outVal); +} + + /// 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<TypeLayout> getTypeLayoutForGlobalShaderParameter( + ParameterBindingContext* context, + VarDeclBase* varDecl, + Type* type) +{ + auto layoutContext = context->layoutContext; + auto rules = layoutContext.getRulesFamily(); + + if( varDecl->HasModifier<ShaderRecordAttribute>() && as<ConstantBufferType>(type) ) + { + return createTypeLayout( + layoutContext.with(rules->getShaderRecordConstantBufferRules()), + type); + } + + + // 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<PushConstantAttribute>() && as<ConstantBufferType>(type)) + { + return createTypeLayout( + layoutContext.with(rules->getPushConstantBufferRules()), + type); + } + + // HLSL `static` modifier indicates "thread local" + if(varDecl->HasModifier<HLSLStaticModifier>()) + return nullptr; + + // HLSL `groupshared` modifier indicates "thread-group local" + if(varDecl->HasModifier<HLSLGroupSharedModifier>()) + return nullptr; + + // TODO(tfoley): there may be other cases that we need to handle here + + // An "ordinary" global variable is implicitly a uniform + // shader parameter. + return createTypeLayout( + layoutContext.with(rules->getConstantBufferRules()), + type); +} + +// + +struct EntryPointParameterState +{ + String* optSemanticName = nullptr; + int* ioSemanticIndex = nullptr; + EntryPointParameterDirectionMask directionMask; + int semanticSlotCount; + Stage stage = Stage::Unknown; + bool isSampleRate = false; + SourceLoc loc; +}; + + +static RefPtr<TypeLayout> processEntryPointVaryingParameter( + ParameterBindingContext* context, + RefPtr<Type> type, + EntryPointParameterState const& state, + RefPtr<VarLayout> varLayout); + +// Collect a single declaration into our set of parameters +static void collectGlobalGenericParameter( + ParameterBindingContext* context, + RefPtr<GlobalGenericParamDecl> paramDecl) +{ + 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(); +} + +// Collect a single declaration into our set of parameters +static void collectGlobalScopeParameter( + ParameterBindingContext* context, + GlobalShaderParamInfo const& shaderParamInfo, + SubstitutionSet globalGenericSubst) +{ + auto varDeclRef = shaderParamInfo.paramDeclRef; + + // We apply any substitutions for global generic parameters here. + auto type = GetType(varDeclRef)->Substitute(globalGenericSubst).as<Type>(); + + // 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. + auto typeLayout = getTypeLayoutForGlobalShaderParameter( + context, + varDeclRef.getDecl(), + type); + + // If we did not find appropriate layout rules, then it + // must mean that this global variable is *not* a shader + // parameter. + if(!typeLayout) + return; + + // Now create a variable layout that we can use + RefPtr<VarLayout> varLayout = new VarLayout(); + varLayout->typeLayout = typeLayout; + varLayout->varDecl = varDeclRef; + + // The logic in `check.cpp` that created the `GlobalShaderParamInfo` + // will have identified any cases where there might be multiple + // global variables that logically represent the same shader parameter. + // + // We will track the same basic information during layout using + // the `ParameterInfo` type. + // + // TODO: `ParameterInfo` should probably become `LayoutParamInfo`. + // + ParameterInfo* parameterInfo = new ParameterInfo(); + context->shared->parameters.add(parameterInfo); + + // Add the first variable declaration to the list of declarations for the parameter + parameterInfo->varLayouts.add(varLayout); + + // Add any additional variables to the list of declarations + for( auto additionalVarDeclRef : shaderParamInfo.additionalParamDeclRefs ) + { + // TODO: We should either eliminate the design choice where different + // declarations of the "same" shade parameter get merged across + // translation units (it is effectively just a compatiblity feature), + // or we should clean things up earlier in the chain so that we can + // re-use a single `VarLayout` across all of the different declarations. + // + // TODO: It would also make sense in these cases to ensure that + // such global shader parameters get the same mangled name across + // all translation units, so that they can automatically be collapsed + // during linking. + + RefPtr<VarLayout> additionalVarLayout = new VarLayout(); + additionalVarLayout->typeLayout = typeLayout; + additionalVarLayout->varDecl = additionalVarDeclRef; + + parameterInfo->varLayouts.add(additionalVarLayout); + } +} + +static RefPtr<UsedRangeSet> findUsedRangeSetForSpace( + ParameterBindingContext* context, + UInt space) +{ + RefPtr<UsedRangeSet> usedRangeSet; + if (context->shared->globalSpaceUsedRangeSets.TryGetValue(space, usedRangeSet)) + return usedRangeSet; + + usedRangeSet = new UsedRangeSet(); + context->shared->globalSpaceUsedRangeSets.Add(space, usedRangeSet); + return usedRangeSet; +} + +// Record that a particular register space (or set, in the GLSL case) +// has been used in at least one binding, and so it should not +// be used by auto-generated bindings that need to claim entire +// spaces. +static void markSpaceUsed( + ParameterBindingContext* context, + UInt space) +{ + context->shared->usedSpaces.Add(nullptr, space, space+1); +} + +static UInt allocateUnusedSpaces( + ParameterBindingContext* context, + UInt count) +{ + return context->shared->usedSpaces.Allocate(nullptr, count); +} + +static void addExplicitParameterBinding( + ParameterBindingContext* context, + RefPtr<ParameterInfo> parameterInfo, + VarDeclBase* varDecl, + LayoutSemanticInfo const& semanticInfo, + LayoutSize count, + RefPtr<UsedRangeSet> usedRangeSet = nullptr) +{ + auto kind = semanticInfo.kind; + + auto& bindingInfo = parameterInfo->bindingInfo[(int)kind]; + if( bindingInfo.count != 0 ) + { + // We already have a binding here, so we want to + // confirm that it matches the new one that is + // incoming... + if( bindingInfo.count != count + || bindingInfo.index != semanticInfo.index + || bindingInfo.space != semanticInfo.space ) + { + getSink(context)->diagnose(varDecl, Diagnostics::conflictingExplicitBindingsForParameter, getReflectionName(varDecl)); + + auto firstVarDecl = parameterInfo->varLayouts[0]->varDecl.getDecl(); + if( firstVarDecl != varDecl ) + { + getSink(context)->diagnose(firstVarDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(firstVarDecl)); + } + } + + // TODO(tfoley): `register` semantics can technically be + // profile-specific (not sure if anybody uses that)... + } + else + { + bindingInfo.count = count; + bindingInfo.index = semanticInfo.index; + bindingInfo.space = semanticInfo.space; + + if (!usedRangeSet) + { + usedRangeSet = findUsedRangeSetForSpace(context, semanticInfo.space); + + // Record that the particular binding space was + // used by an explicit binding, so that we don't + // claim it for auto-generated bindings that + // need to grab a full space + markSpaceUsed(context, semanticInfo.space); + } + auto overlappedVarLayout = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add( + parameterInfo->varLayouts[0], + semanticInfo.index, + semanticInfo.index + count); + + if (overlappedVarLayout) + { + auto paramA = parameterInfo->varLayouts[0]->varDecl.getDecl(); + auto paramB = overlappedVarLayout->varDecl.getDecl(); + + getSink(context)->diagnose(paramA, Diagnostics::parameterBindingsOverlap, + getReflectionName(paramA), + getReflectionName(paramB)); + + getSink(context)->diagnose(paramB, Diagnostics::seeDeclarationOf, getReflectionName(paramB)); + } + } +} + +static void addExplicitParameterBindings_HLSL( + ParameterBindingContext* context, + RefPtr<ParameterInfo> parameterInfo, + RefPtr<VarLayout> varLayout) +{ + // We only want to apply D3D `register` modifiers when compiling for + // D3D targets. + // + // TODO: Nominally, the `register` keyword allows for a shader + // profile to be specified, so that a given binding only + // applies for a specific profile: + // + // https://docs.microsoft.com/en-us/windows/desktop/direct3dhlsl/dx-graphics-hlsl-variable-register + // + // We might want to consider supporting that syntax in the + // long run, in order to handle bindings for multiple targets + // in a more consistent fashion (whereas using `register` for D3D + // and `[[vk::binding(...)]]` for Vulkan creates a lot of + // visual noise). + // + // For now we do the filtering on target in a very direct fashion: + // + if(!isD3DTarget(context->getTargetRequest())) + return; + + auto typeLayout = varLayout->typeLayout; + auto varDecl = varLayout->varDecl; + + // If the declaration has explicit binding modifiers, then + // here is where we want to extract and apply them... + + // Look for HLSL `register` or `packoffset` semantics. + for (auto semantic : varDecl.getDecl()->GetModifiersOfType<HLSLLayoutSemantic>()) + { + // Need to extract the information encoded in the semantic + LayoutSemanticInfo semanticInfo = ExtractLayoutSemanticInfo(context, semantic); + auto kind = semanticInfo.kind; + if (kind == LayoutResourceKind::None) + continue; + + // TODO: need to special-case when this is a `c` register binding... + + // Find the appropriate resource-binding information + // inside the type, to see if we even use any resources + // of the given kind. + + auto typeRes = typeLayout->FindResourceInfo(kind); + LayoutSize count = 0; + if (typeRes) + { + count = typeRes->count; + } + else + { + // TODO: warning here! + } + + addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count); + } +} + +static void maybeDiagnoseMissingVulkanLayoutModifier( + ParameterBindingContext* context, + DeclRef<VarDeclBase> const& varDecl) +{ + // If the user didn't specify a `binding` (and optional `set`) for Vulkan, + // but they *did* specify a `register` for D3D, then that is probably an + // oversight on their part. + if( auto registerModifier = varDecl.getDecl()->FindModifier<HLSLRegisterSemantic>() ) + { + getSink(context)->diagnose(registerModifier, Diagnostics::registerModifierButNoVulkanLayout, varDecl.GetName()); + } +} + +static void addExplicitParameterBindings_GLSL( + ParameterBindingContext* context, + RefPtr<ParameterInfo> parameterInfo, + RefPtr<VarLayout> varLayout) +{ + + // We only want to apply GLSL-style layout modifers + // when compiling for a Khronos-related target. + // + // TODO: This should have some finer granularity + // so that we are able to distinguish between + // Vulkan and OpenGL as targets. + // + if(!isKhronosTarget(context->getTargetRequest())) + return; + + auto typeLayout = varLayout->typeLayout; + auto varDecl = varLayout->varDecl; + + // The catch in GLSL is that the expected resource type + // is implied by the parameter declaration itself, and + // the `layout` modifier is only allowed to adjust + // the index/offset/etc. + // + + // We also may need to store explicit binding info in a different place, + // in the case of varying input/output, since we don't want to collect + // things globally; + RefPtr<UsedRangeSet> usedRangeSet; + + TypeLayout::ResourceInfo* resInfo = nullptr; + LayoutSemanticInfo semanticInfo; + semanticInfo.index = 0; + semanticInfo.space = 0; + if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::DescriptorTableSlot)) != nullptr ) + { + // Try to find `binding` and `set` + auto attr = varDecl.getDecl()->FindModifier<GLSLBindingAttribute>(); + if (!attr) + { + maybeDiagnoseMissingVulkanLayoutModifier(context, varDecl); + return; + } + semanticInfo.index = attr->binding; + semanticInfo.space = attr->set; + } + else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::RegisterSpace)) != nullptr ) + { + // Try to find `set` + auto attr = varDecl.getDecl()->FindModifier<GLSLBindingAttribute>(); + if (!attr) + { + maybeDiagnoseMissingVulkanLayoutModifier(context, varDecl); + return; + } + if( attr->binding != 0) + { + getSink(context)->diagnose(attr, Diagnostics::wholeSpaceParameterRequiresZeroBinding, varDecl.GetName(), attr->binding); + } + semanticInfo.index = attr->set; + semanticInfo.space = 0; + } + else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::SpecializationConstant)) != nullptr ) + { + // Try to find `constant_id` binding + if(!findLayoutArg<GLSLConstantIDLayoutModifier>(varDecl, &semanticInfo.index)) + return; + } + + // If we didn't find any matches, then bail + if(!resInfo) + return; + + auto kind = resInfo->kind; + auto count = resInfo->count; + semanticInfo.kind = kind; + + addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count, usedRangeSet); +} + +// Given a single parameter, collect whatever information we have on +// how it has been explicitly bound, which may come from multiple declarations +void generateParameterBindings( + ParameterBindingContext* context, + RefPtr<ParameterInfo> parameterInfo) +{ + // There must be at least one declaration for the parameter. + SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.getCount() != 0); + + // Iterate over all declarations looking for explicit binding information. + for( auto& varLayout : parameterInfo->varLayouts ) + { + // Handle HLSL `register` and `packoffset` modifiers + addExplicitParameterBindings_HLSL(context, parameterInfo, varLayout); + + + // Handle GLSL `layout` modifiers + addExplicitParameterBindings_GLSL(context, parameterInfo, varLayout); + } +} + +// Generate the binding information for a shader parameter. +static void completeBindingsForParameterImpl( + ParameterBindingContext* context, + RefPtr<VarLayout> firstVarLayout, + ParameterBindingInfo bindingInfos[kLayoutResourceKindCount], + RefPtr<ParameterInfo> parameterInfo) +{ + // For any resource kind used by the parameter + // we need to update its layout information + // to include a binding for that resource kind. + // + auto firstTypeLayout = firstVarLayout->typeLayout; + + // We need to deal with allocation of full register spaces first, + // since that is the most complicated bit of logic. + // + // We will compute how many full register spaces the parameter + // needs to allocate, across all the kinds of resources it + // consumes, so that we can allocate a contiguous range of + // spaces. + // + UInt spacesToAllocateCount = 0; + for(auto typeRes : firstTypeLayout->resourceInfos) + { + auto kind = typeRes.kind; + + // We want to ignore resource kinds for which the user + // has specified an explicit binding, since those won't + // go into our contiguously allocated range. + // + auto& bindingInfo = bindingInfos[(int)kind]; + if( bindingInfo.count != 0 ) + { + continue; + } + + // Now we inspect the kind of resource to figure out + // its space requirements: + // + switch( kind ) + { + default: + // An unbounded-size array will need its own space. + // + if( typeRes.count.isInfinite() ) + { + spacesToAllocateCount++; + } + break; + + case LayoutResourceKind::RegisterSpace: + // If the parameter consumes any full spaces (e.g., it + // is a `struct` type with one or more unbounded arrays + // for fields), then we will include those spaces in + // our allocaiton. + // + // We assume/require here that we never end up needing + // an unbounded number of spaces. + // TODO: we should enforce that somewhere with an error. + // + spacesToAllocateCount += typeRes.count.getFiniteValue(); + break; + + case LayoutResourceKind::Uniform: + // We want to ignore uniform data for this calculation, + // since any uniform data in top-level shader parameters + // needs to go into a global constant buffer. + // + break; + + case LayoutResourceKind::GenericResource: + // This is more of a marker case, and shouldn't ever + // need a space allocated to it. + break; + } + } + + // If we compute that the parameter needs some number of full + // spaces allocated to it, then we will go ahead and allocate + // contiguous spaces here. + // + UInt firstAllocatedSpace = 0; + if(spacesToAllocateCount) + { + firstAllocatedSpace = allocateUnusedSpaces(context, spacesToAllocateCount); + } + + // We'll then dole the allocated spaces (if any) out to the resource + // categories that need them. + // + UInt currentAllocatedSpace = firstAllocatedSpace; + + for(auto typeRes : firstTypeLayout->resourceInfos) + { + // Did we already apply some explicit binding information + // for this resource kind? + auto kind = typeRes.kind; + auto& bindingInfo = bindingInfos[(int)kind]; + if( bindingInfo.count != 0 ) + { + // If things have already been bound, our work is done. + // + // TODO: it would be good to handle the case where a + // binding specified a space, but not an offset/index + // for some kind of resource. + // + continue; + } + + auto count = typeRes.count; + + // Certain resource kinds require special handling. + // + // Note: This `switch` statement should have a `case` for + // all of the special cases above that affect the computation of + // `spacesToAllocateCount`. + // + switch( kind ) + { + case LayoutResourceKind::RegisterSpace: + { + // The parameter's type needs to consume some number of whole + // register spaces, and we have already allocated a contiguous + // range of spaces above. + // + // As always, we can't handle the case of a parameter that needs + // an infinite number of spaces. + // + SLANG_ASSERT(count.isFinite()); + bindingInfo.count = count; + + // We will use the spaces we've allocated, and bump + // the variable tracking the "current" space by + // the number of spaces consumed. + // + bindingInfo.index = currentAllocatedSpace; + currentAllocatedSpace += count.getFiniteValue(); + + // TODO: what should we store as the "space" for + // an allocation of register spaces? Either zero + // or `space` makes sense, but it isn't clear + // which is a better choice. + bindingInfo.space = 0; + + continue; + } + + case LayoutResourceKind::GenericResource: + { + // `GenericResource` is somewhat confusingly named, + // but simply indicates that the type of this parameter + // in some way depends on a generic parameter that has + // not been bound to a concrete value, so that asking + // specific questions about its resource usage isn't + // really possible. + // + bindingInfo.space = 0; + bindingInfo.count = 1; + bindingInfo.index = 0; + continue; + } + + case LayoutResourceKind::Uniform: + // TODO: we don't currently handle global-scope uniform parameters. + break; + } + + // At this point, we know the parameter consumes some resource + // (e.g., D3D `t` registers or Vulkan `binding`s), and the user + // didn't specify an explicit binding, so we will have to + // assign one for them. + // + // If we are consuming an infinite amount of the given resource + // (e.g., an unbounded array of `Texure2D` requires an infinite + // number of `t` regisers in D3D), then we will go ahead + // and assign a full space: + // + if( count.isInfinite() ) + { + bindingInfo.count = count; + bindingInfo.index = 0; + bindingInfo.space = currentAllocatedSpace; + currentAllocatedSpace++; + } + else + { + // If we have a finite amount of resources, then + // we will go ahead and allocate from the "default" + // space. + + UInt space = context->shared->defaultSpace; + RefPtr<UsedRangeSet> usedRangeSet = findUsedRangeSetForSpace(context, space); + + bindingInfo.count = count; + bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(firstVarLayout, count.getFiniteValue()); + bindingInfo.space = space; + } + } +} + +static void applyBindingInfoToParameter( + RefPtr<VarLayout> 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> 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.getCount() != 0); + auto firstVarLayout = parameterInfo->varLayouts.getFirst(); + + 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 + // declarations: + + for(auto& varLayout : parameterInfo->varLayouts) + { + applyBindingInfoToParameter(varLayout, parameterInfo->bindingInfo); + } +} + +static void completeBindingsForParameter( + ParameterBindingContext* context, + RefPtr<VarLayout> varLayout) +{ + ParameterBindingInfo bindingInfos[kLayoutResourceKindCount]; + completeBindingsForParameterImpl( + context, + varLayout, + bindingInfos, + nullptr); + applyBindingInfoToParameter(varLayout, bindingInfos); +} + + /// Allocate binding location for any "pending" data in a shader parameter. + /// + /// When a parameter contains interface-type fields (recursively), we might + /// not have included them in the base layout for the parameter, and instead + /// need to allocate space for them after all other shader parameters have + /// been laid out. + /// + /// This function should be called on the `pendingVarLayout` field of an + /// existing `VarLayout` to ensure that its pending data has been properly + /// assigned storage. It handles the case where the `pendingVarLayout` + /// field is null. + /// +static void _allocateBindingsForPendingData( + ParameterBindingContext* context, + RefPtr<VarLayout> pendingVarLayout) +{ + if(!pendingVarLayout) return; + + completeBindingsForParameter(context, pendingVarLayout); +} + +struct SimpleSemanticInfo +{ + String name; + int index; +}; + +SimpleSemanticInfo decomposeSimpleSemantic( + HLSLSimpleSemantic* semantic) +{ + auto composedName = semantic->name.Content; + + // look for a trailing sequence of decimal digits + // at the end of the composed name + UInt length = composedName.size(); + UInt indexLoc = length; + while( indexLoc > 0 ) + { + auto c = composedName[indexLoc-1]; + if( c >= '0' && c <= '9' ) + { + indexLoc--; + continue; + } + else + { + break; + } + } + + SimpleSemanticInfo info; + + // + if( indexLoc == length ) + { + // No index suffix + info.name = composedName; + info.index = 0; + } + else + { + // The name is everything before the digits + String stringComposedName(composedName); + + info.name = stringComposedName.subString(0, indexLoc); + info.index = strtol(stringComposedName.begin() + indexLoc, nullptr, 10); + } + return info; +} + +static RefPtr<TypeLayout> processSimpleEntryPointParameter( + ParameterBindingContext* context, + RefPtr<Type> type, + EntryPointParameterState const& inState, + RefPtr<VarLayout> varLayout, + int semanticSlotCount = 1) +{ + EntryPointParameterState state = inState; + state.semanticSlotCount = semanticSlotCount; + + auto optSemanticName = state.optSemanticName; + auto semanticIndex = *state.ioSemanticIndex; + + String semanticName = optSemanticName ? *optSemanticName : ""; + String sn = semanticName.toLower(); + + RefPtr<TypeLayout> typeLayout; + if (sn.startsWith("sv_") + || sn.startsWith("nv_")) + { + // System-value semantic. + + if (state.directionMask & kEntryPointParameterDirection_Output) + { + // Note: I'm just doing something expedient here and detecting `SV_Target` + // outputs and claiming the appropriate register range right away. + // + // TODO: we should really be building up some representation of all of this, + // once we've gone to the trouble of looking it all up... + if( sn == "sv_target" ) + { + // TODO: construct a `ParameterInfo` we can use here so that + // overlapped layout errors get reported nicely. + + auto usedResourceSet = findUsedRangeSetForSpace(context, 0); + usedResourceSet->usedResourceRanges[int(LayoutResourceKind::UnorderedAccess)].Add(nullptr, semanticIndex, semanticIndex + semanticSlotCount); + + + // We also need to track this as an ordinary varying output from the stage, + // since that is how GLSL will want to see it. + // + typeLayout = getSimpleVaryingParameterTypeLayout( + context->layoutContext, + type, + kEntryPointParameterDirection_Output); + } + } + + if (state.directionMask & kEntryPointParameterDirection_Input) + { + if (sn == "sv_sampleindex") + { + state.isSampleRate = true; + } + } + + if( !typeLayout ) + { + // If we didn't compute a special-case layout for the + // system-value parameter (e.g., because it was an + // `SV_Target` output), then create a default layout + // that consumes no input/output varying slots. + // (since system parameters are distinct from + // user-defined parameters for layout purposes) + // + typeLayout = getSimpleVaryingParameterTypeLayout( + context->layoutContext, + type, + 0); + } + + // Remember the system-value semantic so that we can query it later + if (varLayout) + { + varLayout->systemValueSemantic = semanticName; + varLayout->systemValueSemanticIndex = semanticIndex; + } + + // TODO: add some kind of usage information for system input/output + } + else + { + // In this case we have a user-defined semantic, which means + // an ordinary input and/or output varying parameter. + // + typeLayout = getSimpleVaryingParameterTypeLayout( + context->layoutContext, + type, + state.directionMask); + } + + if (state.isSampleRate + && (state.directionMask & kEntryPointParameterDirection_Input) + && (context->stage == Stage::Fragment)) + { + if (auto entryPointLayout = context->entryPointLayout) + { + entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput; + } + } + + *state.ioSemanticIndex += state.semanticSlotCount; + typeLayout->type = type; + + return typeLayout; +} + +static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl( + ParameterBindingContext* context, + Decl* decl, + RefPtr<Type> type, + EntryPointParameterState const& inState, + RefPtr<VarLayout> varLayout) +{ + SimpleSemanticInfo semanticInfo; + int semanticIndex = 0; + + EntryPointParameterState state = inState; + + // If there is no explicit semantic already in effect, *and* we find an explicit + // semantic on the associated declaration, then we'll use it. + if( !state.optSemanticName ) + { + if( auto semantic = decl->FindModifier<HLSLSimpleSemantic>() ) + { + semanticInfo = decomposeSimpleSemantic(semantic); + semanticIndex = semanticInfo.index; + + state.optSemanticName = &semanticInfo.name; + state.ioSemanticIndex = &semanticIndex; + } + } + + if (decl) + { + if (decl->FindModifier<HLSLSampleModifier>()) + { + state.isSampleRate = true; + } + } + + // Default case: either there was an explicit semantic in effect already, + // *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 processEntryPointVaryingParameter(context, type, state, varLayout); +} + +static RefPtr<TypeLayout> processEntryPointVaryingParameter( + ParameterBindingContext* context, + RefPtr<Type> type, + EntryPointParameterState const& state, + RefPtr<VarLayout> varLayout) +{ + // Make sure to associate a stage with every + // varying parameter (including sub-fields of + // `struct`-type parameters), since downstream + // code generation will need to look at the + // stage (possibly on individual leaf fields) to + // decide when to emit things like the `flat` + // interpolation modifier. + // + 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<HLSLStreamOutputType>(type) ) + { + // + + auto elementType = gsStreamType->getElementType(); + + int semanticIndex = 0; + + EntryPointParameterState elementState; + elementState.directionMask = kEntryPointParameterDirection_Output; + elementState.ioSemanticIndex = &semanticIndex; + elementState.isSampleRate = false; + elementState.optSemanticName = nullptr; + elementState.semanticSlotCount = 0; + elementState.stage = state.stage; + elementState.loc = state.loc; + + auto elementTypeLayout = processEntryPointVaryingParameter(context, elementType, elementState, nullptr); + + RefPtr<StreamOutputTypeLayout> typeLayout = new StreamOutputTypeLayout(); + typeLayout->type = type; + typeLayout->rules = elementTypeLayout->rules; + typeLayout->elementTypeLayout = elementTypeLayout; + + for(auto resInfo : elementTypeLayout->resourceInfos) + typeLayout->addResourceUsage(resInfo); + + return typeLayout; + } + + // Raytracing shaders have a slightly different interpretation of their + // "varying" input/output parameters, since they don't have the same + // idea of previous/next stage as the rasterization shader types. + // + if( state.directionMask & kEntryPointParameterDirection_Output ) + { + // Note: we are silently treating `out` parameters as if they + // were `in out` for this test, under the assumption that + // an `out` parameter represents a write-only payload. + + switch(state.stage) + { + default: + // Not a raytracing shader. + break; + + case Stage::Intersection: + case Stage::RayGeneration: + // Don't expect this case to have any `in out` parameters. + getSink(context)->diagnose(state.loc, Diagnostics::dontExpectOutParametersForStage, getStageName(state.stage)); + break; + + case Stage::AnyHit: + case Stage::ClosestHit: + case Stage::Miss: + // `in out` or `out` parameter is payload + return createTypeLayout(context->layoutContext.with( + context->getRulesFamily()->getRayPayloadParameterRules()), + type); + + case Stage::Callable: + // `in out` or `out` parameter is payload + return createTypeLayout(context->layoutContext.with( + context->getRulesFamily()->getCallablePayloadParameterRules()), + type); + + } + } + else + { + switch(state.stage) + { + default: + // Not a raytracing shader. + break; + + case Stage::Intersection: + case Stage::RayGeneration: + case Stage::Miss: + case Stage::Callable: + // Don't expect this case to have any `in` parameters. + // + // TODO: For a miss or callable shader we could interpret + // an `in` parameter as indicating a payload that the + // programmer doesn't intend to write to. + // + getSink(context)->diagnose(state.loc, Diagnostics::dontExpectInParametersForStage, getStageName(state.stage)); + break; + + case Stage::AnyHit: + case Stage::ClosestHit: + // `in` parameter is hit attributes + return createTypeLayout(context->layoutContext.with( + context->getRulesFamily()->getHitAttributesParameterRules()), + type); + } + } + + // If there is an available semantic name and index, + // then we should apply it to this parameter unconditionally + // (that is, not just if it is a leaf parameter). + auto optSemanticName = state.optSemanticName; + if (optSemanticName && varLayout) + { + // Always store semantics in upper-case for + // reflection information, since they are + // supposed to be case-insensitive and + // upper-case is the dominant convention. + String semanticName = *optSemanticName; + String sn = semanticName.toUpper(); + + auto semanticIndex = *state.ioSemanticIndex; + + varLayout->semanticName = sn; + varLayout->semanticIndex = semanticIndex; + varLayout->flags |= VarLayoutFlag::HasSemantic; + } + + // Scalar and vector types are treated as outputs directly + if(auto basicType = as<BasicExpressionType>(type)) + { + return processSimpleEntryPointParameter(context, basicType, state, varLayout); + } + else if(auto vectorType = as<VectorExpressionType>(type)) + { + return processSimpleEntryPointParameter(context, vectorType, state, varLayout); + } + // A matrix is processed as if it was an array of rows + else if( auto matrixType = as<MatrixExpressionType>(type) ) + { + auto rowCount = GetIntVal(matrixType->getRowCount()); + return processSimpleEntryPointParameter(context, matrixType, state, varLayout, (int) rowCount); + } + else if( auto arrayType = as<ArrayExpressionType>(type) ) + { + // Note: Bad Things will happen if we have an array input + // without a semantic already being enforced. + + auto elementCount = (UInt) GetIntVal(arrayType->ArrayLength); + + // We use the first element to derive the layout for the element type + 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 ) + { + processEntryPointVaryingParameter(context, arrayType->baseType, state, nullptr); + } + + RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout(); + arrayTypeLayout->elementTypeLayout = elementTypeLayout; + arrayTypeLayout->type = arrayType; + + for (auto rr : elementTypeLayout->resourceInfos) + { + arrayTypeLayout->findOrAddResourceInfo(rr.kind)->count = rr.count * elementCount; + } + + return arrayTypeLayout; + } + // Ignore a bunch of types that don't make sense here... + else if (auto textureType = as<TextureType>(type)) { return nullptr; } + else if(auto samplerStateType = as<SamplerStateType>(type)) { return nullptr; } + else if(auto constantBufferType = as<ConstantBufferType>(type)) { return nullptr; } + // Catch declaration-reference types late in the sequence, since + // otherwise they will include all of the above cases... + else if( auto declRefType = as<DeclRefType>(type) ) + { + auto declRef = declRefType->declRef; + + if (auto structDeclRef = declRef.as<StructDecl>()) + { + RefPtr<StructTypeLayout> structLayout = new StructTypeLayout(); + structLayout->type = type; + + // Need to recursively walk the fields of the structure now... + for( auto field : GetFields(structDeclRef) ) + { + RefPtr<VarLayout> fieldVarLayout = new VarLayout(); + fieldVarLayout->varDecl = field; + + auto fieldTypeLayout = processEntryPointVaryingParameterDecl( + context, + field.getDecl(), + GetType(field), + state, + fieldVarLayout); + + if(fieldTypeLayout) + { + fieldVarLayout->typeLayout = fieldTypeLayout; + + for (auto rr : fieldTypeLayout->resourceInfos) + { + SLANG_RELEASE_ASSERT(rr.count != 0); + + auto structRes = structLayout->findOrAddResourceInfo(rr.kind); + fieldVarLayout->findOrAddResourceInfo(rr.kind)->index = structRes->count.getFiniteValue(); + structRes->count += rr.count; + } + } + + structLayout->fields.add(fieldVarLayout); + structLayout->mapVarToLayout.Add(field.getDecl(), fieldVarLayout); + } + + return structLayout; + } + else if (auto globalGenericParam = 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; + } + else if (auto associatedTypeParam = declRef.as<AssocTypeDecl>()) + { + RefPtr<TypeLayout> assocTypeLayout = new TypeLayout(); + assocTypeLayout->type = type; + return assocTypeLayout; + } + else + { + SLANG_UNEXPECTED("unhandled type kind"); + } + } + // If we ran into an error in checking the user's code, then skip this parameter + else if( auto errorType = as<ErrorType>(type) ) + { + return nullptr; + } + + SLANG_UNEXPECTED("unhandled type kind"); + UNREACHABLE_RETURN(nullptr); +} + + /// 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>(); + + if( paramDeclRef.getDecl()->HasModifier<HLSLUniformModifier>() ) + { + // 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( paramDeclRef.getDecl()->HasModifier<InModifier>() + || paramDeclRef.getDecl()->HasModifier<InOutModifier>() + || !paramDeclRef.getDecl()->HasModifier<OutModifier>() ) + { + state.directionMask |= kEntryPointParameterDirection_Input; + } + + // If it appears to be an output, process it as such. + if(paramDeclRef.getDecl()->HasModifier<OutModifier>() + || paramDeclRef.getDecl()->HasModifier<InOutModifier>()) + { + state.directionMask |= kEntryPointParameterDirection_Output; + } + + return processEntryPointVaryingParameterDecl( + context, + paramDeclRef.getDecl(), + paramType, + 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<StructTypeLayout> m_structLayout; + UniformLayoutInfo m_structLayoutInfo; + + // We need to compute a layout for any "pending" data inside + // of the parameters being added to the scope, to facilitate + // later allocating space for all the pending parameters after + // the primary shader parameters. + // + StructTypeLayoutBuilder m_pendingDataTypeLayoutBuilder; + + void beginLayout( + ParameterBindingContext* context) + { + 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<VarLayout> 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 ) + { + // 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); + } + + // Any "pending" items on a field type become "pending" items + // on the overall `struct` type layout. + // + // TODO: This logic ends up duplicated between here and the main + // `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 ) + { + m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, m_rules); + auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(firstVarLayout->varDecl, fieldPendingDataTypeLayout); + + m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout(); + + if( parameterInfo ) + { + for( auto& varLayout : parameterInfo->varLayouts ) + { + varLayout->pendingVarLayout = fieldPendingDataVarLayout; + } + } + else + { + firstVarLayout->pendingVarLayout = fieldPendingDataVarLayout; + } + } + } + + 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); + } + + RefPtr<VarLayout> endLayout() + { + // Finish computing the layout for the ordindary data (if any). + // + m_rules->EndStructLayout(&m_structLayoutInfo); + m_pendingDataTypeLayoutBuilder.endLayout(); + + // Copy the final layout information computed for ordinary data + // over to the struct type layout for the scope. + // + m_structLayout->addResourceUsage(LayoutResourceKind::Uniform, m_structLayoutInfo.size); + m_structLayout->uniformAlignment = m_structLayout->uniformAlignment; + + RefPtr<TypeLayout> scopeTypeLayout = m_structLayout; + + // If a constant buffer is needed (because there is a non-zero + // amount of uniform data), then we need to wrap up the layout + // to reflect the constant buffer that will be generated. + // + scopeTypeLayout = createConstantBufferTypeLayoutIfNeeded( + m_context->layoutContext, + scopeTypeLayout); + + // We now have a bunch of layout information, which we should + // record into a suitable object that represents the scope + RefPtr<VarLayout> scopeVarLayout = new VarLayout(); + scopeVarLayout->typeLayout = scopeTypeLayout; + + if( auto pendingTypeLayout = scopeTypeLayout->pendingDataTypeLayout ) + { + RefPtr<VarLayout> pendingVarLayout = new VarLayout(); + pendingVarLayout->typeLayout = pendingTypeLayout; + scopeVarLayout->pendingVarLayout = pendingVarLayout; + } + + return scopeVarLayout; + } +}; + + /// 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, + EntryPoint* entryPoint, + SubstitutionSet typeSubst) +{ + DeclRef<FuncDecl> entryPointFuncDeclRef = entryPoint->getFuncDeclRef(); + + // 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(); + + // 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 `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: + // + // First, the varying parameters of the entry point will have + // their semantics and locations assigned, so we set up state + // for tracking that layout. + // + int defaultSemanticIndex = 0; + EntryPointParameterState state; + state.ioSemanticIndex = &defaultSemanticIndex; + state.optSemanticName = nullptr; + state.semanticSlotCount = 0; + state.stage = entryPoint->getStage(); + + // 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; + + for( auto& shaderParamInfo : entryPoint->getShaderParams() ) + { + auto paramDeclRef = shaderParamInfo.paramDeclRef; + + // 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); + + // Any error messages we emit during the process should + // refer to the location of this parameter. + // + state.loc = paramDeclRef.getLoc(); + + // 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<VarLayout> paramVarLayout = new VarLayout(); + paramVarLayout->varDecl = paramDeclRef; + paramVarLayout->stage = state.stage; + + auto paramTypeLayout = computeEntryPointParameterTypeLayout( + context, + typeSubst, + paramDeclRef, + paramVarLayout, + state); + paramVarLayout->typeLayout = paramTypeLayout; + + // 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; + + // 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); + + // All of the other resources types will be handled in a + // simpler loop that just increments the relevant counters. + // + for (auto paramTypeResInfo : paramTypeLayout->resourceInfos) + { + // 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(); + + // 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(); + + // 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 = GetResultType(entryPointFuncDeclRef)->Substitute(typeSubst).as<Type>(); + SLANG_ASSERT(resultType); + + if( !resultType->Equals(resultType->getSession()->getVoidType()) ) + { + state.loc = entryPointFuncDeclRef.getLoc(); + state.directionMask = kEntryPointParameterDirection_Output; + + RefPtr<VarLayout> resultLayout = new VarLayout(); + resultLayout->stage = state.stage; + + auto resultTypeLayout = processEntryPointVaryingParameterDecl( + context, + entryPointFuncDeclRef.getDecl(), + resultType->Substitute(typeSubst).as<Type>(), + state, + resultLayout); + + if( resultTypeLayout ) + { + resultLayout->typeLayout = resultTypeLayout; + + for (auto rr : resultTypeLayout->resourceInfos) + { + auto entryPointRes = paramsStructLayout->findOrAddResourceInfo(rr.kind); + resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue(); + entryPointRes->count += rr.count; + } + } + + entryPointLayout->resultLayout = resultLayout; + } +} + +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 contextData = *inContext; + auto context = &contextData; + context->stage = Stage::Unknown; + + auto globalGenericSubst = program->getGlobalGenericSubstitution(); + + // We will start by looking for any global generic type parameters. + + for(RefPtr<Module> module : program->getModuleDependencies()) + { + for( auto genParamDecl : module->getModuleDecl()->getMembersOfType<GlobalGenericParamDecl>() ) + { + collectGlobalGenericParameter(context, genParamDecl); + } + } + + // 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. + + for(auto& globalParamInfo : program->getShaderParams() ) + { + // 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); + + collectGlobalScopeParameter(context, globalParamInfo, globalGenericSubst); + } + + // Next consider parameters for entry points + for(auto entryPoint : program->getEntryPoints()) + { + context->stage = entryPoint->getStage(); + collectEntryPointParameters(context, entryPoint, globalGenericSubst); + } + context->entryPointLayout = nullptr; +} + + /// Emit a diagnostic about a uniform parameter at global scope. +void diagnoseGlobalUniform( + SharedParameterBindingContext* sharedContext, + VarDeclBase* varDecl) +{ + // It is entirely possible for Slang to support uniform parameters at the global scope, + // by bundling them into an implicit constant buffer, and indeed the layout algorithm + // implemented in this file computes a layout *as if* the Slang compiler does just that. + // + // The missing link is the downstream IR and code generation steps, where we would need + // to collect all of the global-scope uniforms into a common `struct` type and then + // create a new constant buffer parameter over that type. + // + // For now it is easier to simply ban this case, since most shader authors have + // switched to modern HLSL/GLSL style with `cbuffer` or `uniform` block declarations. + // + // TODO: In the long run it may be best to require *all* global-scope shader parameters + // to be marked with a keyword (e.g., `uniform`) so that ordinary global variable syntax can be + // used safely. + // + getSink(sharedContext)->diagnose(varDecl, Diagnostics::globalUniformsNotSupported, varDecl->getName()); +} + +static int _calcTotalNumUsedRegistersForLayoutResourceKind(ParameterBindingContext* bindingContext, LayoutResourceKind kind) +{ + int numUsed = 0; + for (auto& pair : bindingContext->shared->globalSpaceUsedRangeSets) + { + UsedRangeSet* rangeSet = pair.Value; + const auto& usedRanges = rangeSet->usedResourceRanges[kind]; + for (const auto& usedRange : usedRanges.ranges) + { + numUsed += int(usedRange.end - usedRange.begin); + } + } + return numUsed; +} + +RefPtr<ProgramLayout> generateParameterBindings( + TargetProgram* targetProgram, + DiagnosticSink* sink) +{ + auto program = targetProgram->getProgram(); + auto targetReq = targetProgram->getTargetReq(); + + RefPtr<ProgramLayout> programLayout = new ProgramLayout(); + programLayout->targetProgram = targetProgram; + + // Try to find rules based on the selected code-generation target + auto layoutContext = getInitialLayoutContextForTarget(targetReq, programLayout); + + // If there was no target, or there are no rules for the target, + // then bail out here. + if (!layoutContext.rules) + return nullptr; + + // Create a context to hold shared state during the process + // of generating parameter bindings + SharedParameterBindingContext sharedContext( + layoutContext.getRulesFamily(), + programLayout, + targetReq, + sink); + + // Create a sub-context to collect parameters that get + // declared into the global scope + ParameterBindingContext context; + context.shared = &sharedContext; + context.layoutContext = layoutContext; + + // Walk through AST to discover all the parameters + collectParameters(&context, program); + + // Now walk through the parameters to generate initial binding information + 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. + // + // 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 ) + { + SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.getCount() != 0); + auto firstVarLayout = parameterInfo->varLayouts.getFirst(); + + // Does the field have any uniform data? + if( firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) ) + { + needDefaultConstantBuffer = true; + diagnoseGlobalUniform(&sharedContext, firstVarLayout->varDecl); + } + } + + // Next, we want to determine if there are any global-scope parameters + // that don't just allocate a whole register space to themselves; these + // parameters will need to go into a "default" space, which should always + // be the first space we allocate. + // + // As a starting point, we will definitely need a "default" space if + // we are creating a default constant buffer, since it should get + // a binding in that "default" space. + // + bool needDefaultSpace = needDefaultConstantBuffer; + if (!needDefaultSpace) + { + // Next we will look at the global-scope parameters and see if + // any of them requires a `register` or `binding` that will + // thus need to land in a default space. + // + for (auto& parameterInfo : sharedContext.parameters) + { + SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.getCount() != 0); + auto firstVarLayout = parameterInfo->varLayouts.getFirst(); + + // For each parameter, we will look at each resource it consumes. + // + for (auto resInfo : firstVarLayout->typeLayout->resourceInfos) + { + // We don't care about whole register spaces/sets, since + // we don't need to allocate a default space/set for a parameter + // that itself consumes a whole space/set. + // + if( resInfo.kind == LayoutResourceKind::RegisterSpace ) + continue; + + // We also don't want to consider resource kinds for which + // the variable already has an (explicit) binding, since + // the space from the explicit binding will be used, so + // that a default space isn't needed. + // + if( parameterInfo->bindingInfo[resInfo.kind].count != 0 ) + continue; + + // Otherwise, we have a shader parameter that will need + // a default space or set to live in. + // + needDefaultSpace = true; + break; + } + } + } + + // If we need a space for default bindings, then allocate it here. + if (needDefaultSpace) + { + UInt defaultSpace = 0; + + // Check if space #0 has been allocated yet. If not, then we'll + // want to use it. + if (sharedContext.usedSpaces.contains(0)) + { + // Somebody has already put things in space zero. + // + // TODO: There are two cases to handle here: + // + // 1) If there is any free register ranges in space #0, + // then we should keep using it as the default space. + // + // 2) If somebody went and put an HLSL unsized array into space #0, + // *or* if they manually placed something like a paramter block + // there (which should consume whole spaces), then we need to + // allocate an unused space instead. + // + // For now we don't deal with the concept of unsized arrays, or + // manually assigning parameter blocks to spaces, so we punt + // on this and assume case (1). + + defaultSpace = 0; + } + else + { + // Nobody has used space zero yet, so we need + // to make sure to reserve it for defaults. + defaultSpace = allocateUnusedSpaces(&context, 1); + + // The result of this allocation had better be that + // we got space #0, or else something has gone wrong. + SLANG_ASSERT(defaultSpace == 0); + } + + sharedContext.defaultSpace = defaultSpace; + } + + // If there are any global-scope uniforms, then we need to + // allocate a constant-buffer binding for them here. + // + ParameterBindingAndKindInfo globalConstantBufferBinding = maybeAllocateConstantBufferBinding( + &context, + needDefaultConstantBuffer); + + // Now walk through again to actually give everything + // ranges of registers... + for( auto& parameter : sharedContext.parameters ) + { + completeBindingsForParameter(&context, parameter); + } + + // After we have allocated registers/bindings to everything + // in the global scope we will process the parameters + // of each entry point in order. + // + // Note: the effect of the current implementation 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 ) + { + auto entryPointParamsLayout = entryPoint->parametersLayout; + completeBindingsForParameter(&context, entryPointParamsLayout); + } + + // 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 ) + { + globalScopeLayoutBuilder.addParameter(parameterInfo); + } + + auto globalScopeVarLayout = globalScopeLayoutBuilder.endLayout(); + if( globalConstantBufferBinding.count != 0 ) + { + auto cbInfo = globalScopeVarLayout->findOrAddResourceInfo(globalConstantBufferBinding.kind); + cbInfo->space = globalConstantBufferBinding.space; + cbInfo->index = globalConstantBufferBinding.index; + } + + // After we have laid out all the ordinary parameters, + // we need to go through the global scope plus each entry point, + // and "flush" out any pending data that was associated with + // those scopes as part of dealing with interface-type parameters. + // + _allocateBindingsForPendingData(&context, globalScopeVarLayout->pendingVarLayout); + for( auto entryPoint : sharedContext.programLayout->entryPoints ) + { + _allocateBindingsForPendingData(&context, entryPoint->parametersLayout->pendingVarLayout); + } + + + // HACK: we want global parameters to not have to deal with offsetting + // by the `VarLayout` stored in `globalScopeVarLayout`, so we will scan + // through and for any global parameter that used "pending" data, we will manually + // offset all of its resource infos to account for where the global pending data + // got placed. + // + // TODO: A more appropriate solution would be to pass the `globalScopeVarLayout` + // down into the pass that puts layout information onto global parameters in + // the IR, and apply the offsetting there. + // + for( auto& parameterInfo : sharedContext.parameters ) + { + for( auto varLayout : parameterInfo->varLayouts ) + { + auto pendingVarLayout = varLayout->pendingVarLayout; + if(!pendingVarLayout) continue; + + for( auto& resInfo : pendingVarLayout->resourceInfos ) + { + if( auto globalResInfo = globalScopeVarLayout->pendingVarLayout->FindResourceInfo(resInfo.kind) ) + { + resInfo.index += globalResInfo->index; + resInfo.space += globalResInfo->space; + } + } + } + } + + programLayout->parametersLayout = globalScopeVarLayout; + + { + const int numShaderRecordRegs = _calcTotalNumUsedRegistersForLayoutResourceKind(&context, LayoutResourceKind::ShaderRecord); + if (numShaderRecordRegs > 1) + { + sink->diagnose(SourceLoc(), Diagnostics::tooManyShaderRecordConstantBuffers, numShaderRecordRegs); + } + } + + return programLayout; +} + +ProgramLayout* TargetProgram::getOrCreateLayout(DiagnosticSink* sink) +{ + if( !m_layout ) + { + m_layout = generateParameterBindings(this, sink); + } + return m_layout; +} + +void generateParameterBindings( + Program* program, + TargetRequest* targetReq, + DiagnosticSink* sink) +{ + program->getTargetProgram(targetReq)->getOrCreateLayout(sink); +} + +} // namespace Slang |
