summaryrefslogtreecommitdiffstats
path: root/source/slang/slang-parameter-binding.cpp
diff options
context:
space:
mode:
authorjsmall-nvidia <jsmall@nvidia.com>2019-05-31 17:20:37 -0400
committerGitHub <noreply@github.com>2019-05-31 17:20:37 -0400
commit6cbc3929a54d37bd23cb5efa8e3320ba02f78b2f (patch)
tree5a23cb47782e9e2a77762c90dd35da1005eba8d0 /source/slang/slang-parameter-binding.cpp
parentb81ff3ef968d1cc4e954b31a1812b3c391d17b02 (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.cpp2583
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