summaryrefslogtreecommitdiffstats
path: root/source/slang/parameter-binding.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/parameter-binding.cpp')
-rw-r--r--source/slang/parameter-binding.cpp2583
1 files changed, 0 insertions, 2583 deletions
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp
deleted file mode 100644
index bdb76a005..000000000
--- a/source/slang/parameter-binding.cpp
+++ /dev/null
@@ -1,2583 +0,0 @@
-// parameter-binding.cpp
-#include "parameter-binding.h"
-
-#include "lookup.h"
-#include "compiler.h"
-#include "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