// slang-parameter-binding.cpp
#include "slang-parameter-binding.h"
#include "slang-lookup.h"
#include "slang-compiler.h"
#include "slang-type-layout.h"
#include "slang-ir-string-hash.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 variable that represents this parameter
RefPtr<VarLayout> varLayout;
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;
// Any NVAPI slot binding information that has been generated
List<NVAPISlotModifier*> nvapiSlotModifiers;
TargetRequest* getTargetRequest() { return targetRequest; }
DiagnosticSink* getSink() { return m_sink; }
Linkage* getLinkage() { return targetRequest->getLinkage(); }
};
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(); }
ASTBuilder* getASTBuilder() { return shared->getLinkage()->getASTBuilder(); }
Linkage* getLinkage() { return shared->getLinkage(); }
};
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.getLength() )
{
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 extractHLSLLayoutSemanticInfo(
UnownedStringSlice registerName,
SourceLoc registerLoc,
UnownedStringSlice spaceName,
SourceLoc spaceLoc,
DiagnosticSink* sink
)
{
LayoutSemanticInfo info;
info.space = 0;
info.index = 0;
info.kind = LayoutResourceKind::None;
if (registerName.getLength() == 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)
{
sink->diagnose(registerLoc, 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.getLength() == 0 )
{
sink->diagnose(registerLoc, Diagnostics::expectedARegisterIndex, registerClassName);
}
UInt index = 0;
for(auto c : registerIndexDigits)
{
SLANG_ASSERT(isDigit(c));
index = index * 10 + (c - '0');
}
UInt space = 0;
if(spaceName.getLength() != 0)
{
UnownedStringSlice spaceSpelling;
UnownedStringSlice spaceDigits;
splitNameAndIndex(spaceName, spaceSpelling, spaceDigits);
if( kind == LayoutResourceKind::RegisterSpace )
{
sink->diagnose(spaceLoc, Diagnostics::unexpectedSpecifierAfterSpace, spaceName);
}
else if( spaceSpelling != UnownedTerminatedStringSlice("space") )
{
sink->diagnose(spaceLoc, Diagnostics::expectedSpace, spaceSpelling);
}
else if( spaceDigits.getLength() == 0 )
{
sink->diagnose(spaceLoc, Diagnostics::expectedSpaceIndex);
}
else
{
for(auto c : spaceDigits)
{
SLANG_ASSERT(isDigit(c));
space = space * 10 + (c - '0');
}
}
}
info.kind = kind;
info.index = (int) index;
info.space = space;
return info;
}
LayoutSemanticInfo ExtractLayoutSemanticInfo(
ParameterBindingContext* context,
HLSLLayoutSemantic* semantic)
{
Token const& registerToken = semantic->registerName;
Token defaultSpaceToken;
Token const* spaceToken = &defaultSpaceToken;
if( auto registerSemantic = as<HLSLRegisterSemantic>(semantic) )
{
spaceToken = ®isterSemantic->spaceName;
}
LayoutSemanticInfo info = extractHLSLLayoutSemanticInfo(
registerToken.getContent(),
registerToken.loc,
spaceToken->getContent(),
spaceToken->loc,
getSink(context));
// TODO: handle component mask part of things...
if( semantic->componentMask.hasContent())
{
getSink(context)->diagnose(semantic->componentMask, Diagnostics::componentMaskNotSupported);
}
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(
ModifiableSyntaxNode* syntax,
UInt* outVal)
{
for( auto modifier : syntax->getModifiersOfType<T>() )
{
if( modifier )
{
*outVal = (UInt) strtoull(String(modifier->valToken.getContent()).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);
}
// TODO: The cases below for detecting globals that aren't actually
// shader parameters should be redundant now that the semantic
// checking logic is responsible for populating the list of
// parameters on a `Program`. We should be able to clean up
// the code by removing these two cases, and the related null
// pointer checks in the code that calls this.
// 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,
Type* type,
EntryPointParameterState const& state,
RefPtr<VarLayout> varLayout);
static RefPtr<VarLayout> _createVarLayout(
TypeLayout* typeLayout,
DeclRef<VarDeclBase> varDeclRef)
{
RefPtr<VarLayout> varLayout = new VarLayout();
varLayout->typeLayout = typeLayout;
varLayout->varDecl = varDeclRef;
if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout)
{
RefPtr<VarLayout> pendingVarLayout = new VarLayout();
pendingVarLayout->varDecl = varDeclRef;
pendingVarLayout->typeLayout = pendingDataTypeLayout;
varLayout->pendingVarLayout = pendingVarLayout;
}
return varLayout;
}
// Collect a single declaration into our set of parameters
static void collectGlobalScopeParameter(
ParameterBindingContext* context,
ShaderParamInfo const& shaderParamInfo,
SubstitutionSet globalGenericSubst)
{
auto astBuilder = context->getASTBuilder();
auto varDeclRef = shaderParamInfo.paramDeclRef;
// We apply any substitutions for global generic parameters here.
auto type = as<Type>(getType(astBuilder, varDeclRef)->substitute(astBuilder, globalGenericSubst));
// 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 = _createVarLayout(typeLayout, varDeclRef);
// The logic in `check.cpp` that created the `ShaderParamInfo`
// 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 created var layout to the parameter information structure,
// so that we can update it as we proceed with parameter binding.
//
parameterInfo->varLayout = varLayout;
}
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 VarLayout* markSpaceUsed(
ParameterBindingContext* context,
VarLayout* varLayout,
UInt space)
{
return context->shared->usedSpaces.Add(varLayout, space, space+1);
}
static UInt allocateUnusedSpaces(
ParameterBindingContext* context,
UInt count)
{
return context->shared->usedSpaces.Allocate(nullptr, count);
}
static bool shouldDisableDiagnostic(
Decl* decl,
DiagnosticInfo const& diagnosticInfo)
{
for( auto dd = decl; dd; dd = dd->parentDecl )
{
for( auto modifier : dd->modifiers )
{
auto allowAttr = as<AllowAttribute>(modifier);
if(!allowAttr)
continue;
if(allowAttr->diagnostic == &diagnosticInfo)
return true;
}
}
return false;
}
static void addExplicitParameterBinding(
ParameterBindingContext* context,
RefPtr<ParameterInfo> parameterInfo,
VarDeclBase* varDecl,
LayoutSemanticInfo const& semanticInfo,
LayoutSize count)
{
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));
}
// 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;
VarLayout* overlappedVarLayout = nullptr;
if( kind == LayoutResourceKind::RegisterSpace )
{
// Parameter is being bound to an entire space, so we
// need to mark the given space as used and report
// an error if another parameter was already allocated
// there.
//
overlappedVarLayout = markSpaceUsed(context, parameterInfo->varLayout, semanticInfo.index);
}
else
{
auto 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, parameterInfo->varLayout, semanticInfo.space);
overlappedVarLayout = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add(
parameterInfo->varLayout,
semanticInfo.index,
semanticInfo.index + count);
}
if (overlappedVarLayout)
{
auto paramA = parameterInfo->varLayout->varDecl.getDecl();
auto paramB = overlappedVarLayout->varDecl.getDecl();
auto& diagnosticInfo = Diagnostics::parameterBindingsOverlap;
// If *both* of the shader parameters declarations agree
// that overlapping bindings should be allowed, then we
// will not emit a diagnostic. Otherwise, we will warn
// the user because such overlapping bindings are likely
// to indicate a programming error.
//
if(shouldDisableDiagnostic(paramA, diagnosticInfo)
&& shouldDisableDiagnostic(paramB, diagnosticInfo))
{
}
else
{
getSink(context)->diagnose(paramA, diagnosticInfo,
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.
//
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 )
{
DeclRef<Decl> varDecl2(varDecl);
// Try to find `constant_id` binding
if(!findLayoutArg<GLSLConstantIDLayoutModifier>(varDecl2, &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);
}
// 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 have been a declaration for the parameter.
SLANG_RELEASE_ASSERT(parameterInfo->varLayout);
// We will look for explicit binding information on the declaration.
auto varLayout = parameterInfo->varLayout;
// Handle HLSL `register` and `packoffset` modifiers
addExplicitParameterBindings_HLSL(context, parameterInfo, varLayout);
// Handle GLSL `layout` modifiers and `[vk::...]` attributes.
//
// TODO: We should deprecate the support for `layout` and then rename
// these `_HLSL` and `_GLSL` functions to be more explicit and clear
// about the fact that they are specific to the *target* and not to
// the *source language* (as they were at one point).
//
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)
{
auto varLayout = parameterInfo->varLayout;
SLANG_RELEASE_ASSERT(varLayout);
completeBindingsForParameterImpl(
context,
varLayout,
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:
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.getContent();
// look for a trailing sequence of decimal digits
// at the end of the composed name
UInt length = composedName.getLength();
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,
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;
// First we check for a system-value semantic, operating
// under the assumption that *any* semantic with an `SV_`
// or `NV_` prefix is a system value.
//
if (sn.startsWith("sv_")
|| sn.startsWith("nv_"))
{
// Fragment shader color/render target outputs need to be handled
// specially, because they are declared with an `SV`-prefixed
// "system value" semantic, but in practice they are ordinary
// user-defined outputs.
//
// TODO: We should consider allowing fragment-shader outputs
// with arbitrary semantics, and simply treat them as if
// they were declared with `SV_Target`.
//
if( (state.directionMask & kEntryPointParameterDirection_Output)
&& (state.stage == Stage::Fragment)
&& (sn == "sv_target") )
{
// Note: For D3D shader models 5.0 and below, each `SV_Target<N>`
// output conflicts with UAV register `u<N>`.
//
if( isD3DTarget(context->getTargetRequest()) )
{
auto version = context->getTargetRequest()->getTargetProfile().getVersion();
if( version <= ProfileVersion::DX_5_0 )
{
// We will address the conflict here by claiming the corresponding
// `u` register.
//
// Note: because entry point parameters get processed *before*
// registers get assigned to global-scope parameters, this
// allocation will prevent register `u<N>` from being auto-assigned
// to any global parameter.
//
// 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);
}
}
// A fragment shader output is effectively a user-defined output,
// even if it was declared with `SV_Target`.
//
typeLayout = getSimpleVaryingParameterTypeLayout(
context->layoutContext,
type,
kEntryPointParameterDirection_Output);
}
else
{
// For a system-value parameter (that didn't match the
// `SV_Target` special case above) we create a default
// layout that consumes no input/output varying slots.
//
// The rationale here is that system parameters are distinct
// form user-defined parameters for layout purposes, and
// in particular should not be assigned `location`s on
// GLSL-based targets.
//
typeLayout = getSimpleVaryingParameterTypeLayout(
context->layoutContext,
type,
0);
// We need to compute whether an entry point consumes
// any sample-rate inputs, and along with explicitly
// `sample`-qualified parameters, we also need to
// detect use of `SV_SampleIndex` as an input.
//
if (state.directionMask & kEntryPointParameterDirection_Input)
{
if (sn == "sv_sampleindex")
{
state.isSampleRate = true;
}
}
}
// For any case of a system-value semantic (including `SV_Target`)
// we record the system-value semantic so it can be queried
// via reflection.
//
// TODO: We might want to consider skipping this step for
// `SV_Target` outputs and treating them consistently as
// just user-defined outputs.
//
if (varLayout)
{
varLayout->systemValueSemantic = semanticName;
varLayout->systemValueSemanticIndex = semanticIndex;
}
// TODO: We might want to consider tracking some kind of usage
// information for system inputs/outputs. In particular, it
// would be good to check for and diagnose overlapping system
// value declarations.
// TODO: We should eventually be checking that system values
// are appropriate to the stage that they appear on, and also
// map the system value semantic string over to an `enum`
// type of known/supported system value semantics.
}
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;
}
/// Compute layout information for an entry-point parameter `decl`.
///
/// This function should be used for a top-level entry point varying
/// parameter or a field of a structure used for varying parameters,
/// but *not* for any recursive case that operates on a type without
/// an associated declaration (e.g., recursing on `X` when dealing
/// with a parameer of type `X[]`).
///
/// This function is responsible for processing any atributes or
/// other modifiers on the declaration that should impact out layout
/// is computed.
///
static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl(
ParameterBindingContext* context,
Decl* decl,
Type* type,
EntryPointParameterState const& inState,
RefPtr<VarLayout> varLayout)
{
// One of our responsibilities when recursing through varying
// parameters is to compute the semantic name/index for each
// parameter.
//
// Semantics can either be declared per field/parameter:
//
// struct Output
// {
// float4 a : A;
// float4 b : B;
// }
//
// or they can be applied to an entire aggregate type:
//
// void entryPoint(out Output o : OUTPUT) { ... }
//
// When these both of the above cases apply to a
// leaf parameter/field, then the policy is that the
// "outer-most" semantic wins. Thus in the case above,
// `o.a` gets semantic `OUTPUT0` and `o.b` gets semantic
// `OUTPUT1`.
// By default the state we use for processing the
// parameter/field `decl` will be the state that was
// inherited from the outer context (if any).
//
EntryPointParameterState state = inState;
// If there is already a semantic name coming from the
// outer context, we will use it, but if there is no
// outer semantic *and* the current field/parameter `decl`
// has an explicit semantic, we will use that.
//
// Note: we allocate the storage for the variables that
// will track the semantic state outside the conditional,
// so that they are in scope for the recusrivse call
// coming up.
//
SimpleSemanticInfo semanticInfo;
int semanticIndex = 0;
if( !state.optSemanticName )
{
if( auto semantic = decl->findModifier<HLSLSimpleSemantic>() )
{
semanticInfo = decomposeSimpleSemantic(semantic);
semanticIndex = semanticInfo.index;
state.optSemanticName = &semanticInfo.name;
state.ioSemanticIndex = &semanticIndex;
}
}
// One of our tasks is to track whether a fragment shader
// has any sample-rate varying inputs. To that end, we
// will pass down a marker if this parameter was declared
// with the `sample` modifier, so that we can detect
// sample-rate inputs at the leaves.
//
if (decl)
{
if (decl->findModifier<HLSLSampleModifier>())
{
state.isSampleRate = true;
}
}
// With the state to use for assigning semantics computed,
// we now do processing that depends on the type of
// the parameter, which may involve recursing into its
// fields.
//
// The result of this step is the type layout to use for
// our field/parameter `decl` in this context.
//
auto typeLayout = processEntryPointVaryingParameter(context, type, state, varLayout);
// For Khronos targets (OpenGL and Vulkan), we need to process
// the `[[vk::location(...)]]` and `[[vk::index(...)]]` attributes,
// if present.
//
// TODO: In principle we should *also* be using the data from
// `SV_Target<N>` semantics as an equivalent to `location = <N>`
// when targetting Vulkan. Right now we are kind of skating by
// on the fact that people almost always declare `SV_Target`s
// in numerical order, so that our automatic assignment of
// `location`s in declaration order coincidentally matches
// the `SV_Target` order.
//
if( isKhronosTarget(context->getTargetRequest()) )
{
if( auto locationAttr = decl->findModifier<GLSLLocationAttribute>() )
{
int location = locationAttr->value;
int index = 0;
if( auto indexAttr = decl->findModifier<GLSLIndexAttribute>() )
{
index = indexAttr->value;
}
// TODO: We should eventually include validation that a non-zero
// `vk::index` is only valid for fragment shader color outputs.
// Once we've extracted the data from the attribute(s), we
// need to apply it to the `varLayout` for the parameter/field `decl`.
//
LayoutResourceKind kinds[] = { LayoutResourceKind::VaryingInput, LayoutResourceKind::VaryingOutput };
for( auto kind : kinds )
{
auto typeResInfo = typeLayout->FindResourceInfo(kind);
if(!typeResInfo)
continue;
auto varResInfo = varLayout->findOrAddResourceInfo(kind);
varResInfo->index = location;
// Note: OpenGL and Vulkan represent dual-source color blending
// differently from multiple render targets (MRT) at the source
// level.
//
// When using MRT, GLSL (and thus SPIR-V) looks like this:
//
// layout(location = 0) vec4 a;
// layout(location = 1) vec4 b;
//
// When using dual-source blending the GLSL/SPIR-V looks like:
//
// layout(location = 0) vec4 a;
// layout(location = 0, index = 1) vec4 b;
//
// Thus for a parameter of kind `VaryingOutput` when targetting
// GLSL/SPIR-V, we need a way to encode the value that was pased
// for `index` on the secondary color output.
//
// We are already using the `index` field in the `VarLayout::ResourceInfo`
// to store what GLSL/SPIR-V calls the "location," so we will
// hijack the `space` field (which is usually unused for varying
// parameters) to store the GLSL/SPIR-V "index" value.
//
varResInfo->space = index;
}
}
else if( auto indexAttr = decl->findModifier<GLSLIndexAttribute>() )
{
getSink(context)->diagnose(indexAttr, Diagnostics::vkIndexWithoutVkLocation, decl->getName());
}
}
return typeLayout;
}
static RefPtr<TypeLayout> processEntryPointVaryingParameter(
ParameterBindingContext* context,
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;
// We will recursively walk the fields of a `struct` type
// to compute layouts for those fields.
//
// Along the way, we may find fields with explicit layout
// annotations, along with fields that have no explicit
// layout. We will consider it an error to have a mix of
// the two.
//
// TODO: We could support a mix of implicit and explicit
// layout by performing layout on fields in two passes,
// much like is done for the global scope. This would
// complicate layout significantly for little practical
// benefit, so it is very much a "nice to have" rather
// than a "must have" feature.
//
Decl* firstExplicit = nullptr;
Decl* firstImplicit = nullptr;
for( auto field : getFields(structDeclRef, MemberFilterStyle::Instance) )
{
RefPtr<VarLayout> fieldVarLayout = new VarLayout();
fieldVarLayout->varDecl = field;
structLayout->fields.add(fieldVarLayout);
structLayout->mapVarToLayout.Add(field.getDecl(), fieldVarLayout);
auto fieldTypeLayout = processEntryPointVaryingParameterDecl(
context,
field.getDecl(),
getType(context->getASTBuilder(), field),
state,
fieldVarLayout);
SLANG_ASSERT(fieldTypeLayout);
if(!fieldTypeLayout)
continue;
fieldVarLayout->typeLayout = fieldTypeLayout;
// The field needs to have offset information stored
// in `fieldVarLayout` for every kind of resource
// consumed by `fieldTypeLayout`.
//
for(auto fieldTypeResInfo : fieldTypeLayout->resourceInfos)
{
SLANG_RELEASE_ASSERT(fieldTypeResInfo.count != 0);
auto kind = fieldTypeResInfo.kind;
auto structTypeResInfo = structLayout->findOrAddResourceInfo(kind);
auto fieldResInfo = fieldVarLayout->FindResourceInfo(kind);
if( !fieldResInfo )
{
if(!firstImplicit) firstImplicit = field;
// In the implicit-layout case, we assign the field
// the next available offset after the fields that
// have preceded it.
//
fieldResInfo = fieldVarLayout->findOrAddResourceInfo(kind);
fieldResInfo->index = structTypeResInfo->count.getFiniteValue();
structTypeResInfo->count += fieldTypeResInfo.count;
}
else
{
if(!firstExplicit) firstExplicit = field;
// In the explicit case, the field already has offset
// information, and we just need to update the computed
// size of the `struct` type to account for the field.
//
auto fieldEndOffset = fieldResInfo->index + fieldTypeResInfo.count;
structTypeResInfo->count = maximum(structTypeResInfo->count, fieldEndOffset);
}
}
}
if( firstImplicit && firstExplicit )
{
getSink(context)->diagnose(firstImplicit, Diagnostics::mixingImplicitAndExplicitBindingForVaryingParams, firstImplicit->getName(), firstExplicit->getName());
}
return structLayout;
}
else if (auto globalGenericParamDecl = declRef.as<GlobalGenericParamDecl>())
{
auto& layoutContext = context->layoutContext;
if( auto concreteType = findGlobalGenericSpecializationArg(
layoutContext,
globalGenericParamDecl) )
{
// If we know what concrete type has been used to specialize
// the global generic type parameter, then we should use
// the concrete type instead.
//
// Note: it should be illegal for the user to use a generic
// type parameter in a varying parameter list without giving
// it an explicit user-defined semantic. Otherwise, it would be possible
// that the concrete type that gets plugged in is a user-defined
// `struct` that uses some `SV_` semantics in its definition,
// so that any static information about what system values
// the entry point uses would be incorrect.
//
return processEntryPointVaryingParameter(context, concreteType, state, varLayout);
}
else
{
// If we don't know a concrete type, then we aren't generating final
// code, so the reflection information should show the generic
// type parameter.
//
// We don't make any attempt to assign varying parameter resources
// to the generic type, since we can't know how many "slots"
// of varying input/output it would consume.
//
return createTypeLayoutForGlobalGenericTypeParam(layoutContext, type, globalGenericParamDecl);
}
}
else if (auto associatedTypeParam = declRef.as<AssocTypeDecl>())
{
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,
DeclRef<VarDeclBase> paramDeclRef,
RefPtr<VarLayout> paramVarLayout,
EntryPointParameterState& state)
{
auto paramType = getType(context->getASTBuilder(), paramDeclRef);
SLANG_ASSERT(paramType);
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;
TypeLayoutContext m_layoutContext;
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,
TypeLayoutContext layoutContext)
{
m_context = context;
m_layoutContext = layoutContext;
auto rules = layoutContext.rules;
m_structLayout = new StructTypeLayout();
m_structLayout->rules = rules;
m_structLayoutInfo = rules->BeginStructLayout();
}
void beginLayout(
ParameterBindingContext* context)
{
beginLayout(context, context->layoutContext);
}
void _addParameter(
RefPtr<VarLayout> varLayout)
{
// Does the parameter have any uniform data?
auto layoutInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0;
if( uniformSize != 0 )
{
// Make sure uniform fields get laid out properly...
UniformLayoutInfo fieldInfo(
uniformSize,
varLayout->typeLayout->uniformAlignment);
auto rules = m_layoutContext.rules;
LayoutSize uniformOffset = rules->AddStructField(
&m_structLayoutInfo,
fieldInfo);
varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
}
m_structLayout->fields.add(varLayout);
m_structLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout);
}
void addParameter(
RefPtr<VarLayout> varLayout)
{
_addParameter(varLayout);
// 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 = varLayout->typeLayout->pendingDataTypeLayout )
{
auto rules = m_layoutContext.rules;
m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, rules);
auto fieldPendingDataVarLayout = m_pendingDataTypeLayoutBuilder.addField(varLayout->varDecl, fieldPendingDataTypeLayout);
m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout();
varLayout->pendingVarLayout = fieldPendingDataVarLayout;
}
}
void addParameter(
ParameterInfo* parameterInfo)
{
auto varLayout = parameterInfo->varLayout;
SLANG_RELEASE_ASSERT(varLayout);
_addParameter(varLayout);
// Global parameters will have their non-orindary/uniform
// pending data handled by the main parameter binding
// logic, but we still need to construct a layout
// that includes any pending data.
//
if(auto fieldPendingVarLayout = varLayout->pendingVarLayout)
{
auto fieldPendingTypeLayout = fieldPendingVarLayout->typeLayout;
auto rules = m_layoutContext.rules;
m_pendingDataTypeLayoutBuilder.beginLayoutIfNeeded(nullptr, rules);
m_structLayout->pendingDataTypeLayout = m_pendingDataTypeLayoutBuilder.getTypeLayout();
auto fieldUniformLayoutInfo = fieldPendingTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
LayoutSize fieldUniformSize = fieldUniformLayoutInfo ? fieldUniformLayoutInfo->count : 0;
if( fieldUniformSize != 0 )
{
// Make sure uniform fields get laid out properly...
UniformLayoutInfo fieldInfo(
fieldUniformSize,
fieldPendingTypeLayout->uniformAlignment);
LayoutSize uniformOffset = rules->AddStructField(
m_pendingDataTypeLayoutBuilder.getStructLayoutInfo(),
fieldInfo);
fieldPendingVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
}
m_pendingDataTypeLayoutBuilder.getTypeLayout()->fields.add(fieldPendingVarLayout);
}
}
RefPtr<VarLayout> endLayout()
{
// Finish computing the layout for the ordindary data (if any).
//
auto rules = m_layoutContext.rules;
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_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;
}
};
// Scope layout builder specialized to the case of "simple"
// scopes (more or less everything but the global scope)
//
struct SimpleScopeLayoutBuilder : ScopeLayoutBuilder
{
typedef ScopeLayoutBuilder Super;
// Add a "simple" parameter that cannot have any user-defined
// register or binding modifiers, so that its layout computation
// can be simplified greatly.
//
void addSimpleParameter(
RefPtr<VarLayout> varLayout)
{
// The main `addParameter` logic will deal with any ordinary/uniform data,
// and with the "pending" part of the layout.
//
addParameter(varLayout);
// That leaves us to deal with the resource usage that isn't
// handled by `addParameter`, which we will defer until
// `endLayout()` is called.
}
RefPtr<VarLayout> endLayout()
{
// In order to support a mix of parameters with explicit
// and implicit layout, we will process the parameters in
// two phases.
//
// In the first phase we will collect information about
// resource ranges already claimed by parameters in the
// scope.
//
UsedRanges usedRangeSet[kLayoutResourceKindCount];
for( auto paramVarLayout : m_structLayout->fields )
{
auto paramTypeLayout = paramVarLayout->getTypeLayout();
for (auto paramTypeResInfo : paramTypeLayout->resourceInfos)
{
auto kind = paramTypeResInfo.kind;
if (kind == LayoutResourceKind::Uniform) continue;
// We will look for an explicit/existing binding in
// the parameter var layout, which would represent
// an explicit binding, and skip the parameter if
// we don't find one.
//
auto paramResInfo = paramVarLayout->FindResourceInfo(kind);
if(!paramResInfo)
continue;
// If we found an explicit binding, then we need
// to add it to our set for tracking.
//
auto startOffset = paramResInfo->index;
auto endOffset = startOffset + paramTypeResInfo.count;
usedRangeSet[int(kind)].Add(paramVarLayout, startOffset, endOffset);
}
}
//
// Next we iterate over the parameters again, and assign
// unused ranges to all of those that didn't have ranges
// explicitly bound.
//
for( auto paramVarLayout : m_structLayout->fields )
{
auto paramTypeLayout = paramVarLayout->getTypeLayout();
for (auto paramTypeResInfo : paramTypeLayout->resourceInfos)
{
auto kind = paramTypeResInfo.kind;
if (kind == LayoutResourceKind::Uniform) continue;
// We only care about parameters that are not already
// explicitly bound, so we will skip those that already
// have offset information for `kind`.
//
auto paramResInfo = paramVarLayout->FindResourceInfo(kind);
if(paramResInfo)
continue;
paramResInfo = paramVarLayout->findOrAddResourceInfo(kind);
paramResInfo->index = usedRangeSet[int(kind)].Allocate(paramVarLayout, paramTypeResInfo.count.getFiniteValue());
}
}
//
// Finally, we need to compute the overall resource usage of
// the scope/aggregate, so that it includes the ranges consumed
// by all of the parameters/fields.
//
for( auto paramVarLayout : m_structLayout->fields )
{
auto paramTypeLayout = paramVarLayout->getTypeLayout();
for (auto paramTypeResInfo : paramTypeLayout->resourceInfos)
{
auto kind = paramTypeResInfo.kind;
if (kind == LayoutResourceKind::Uniform) continue;
auto paramResInfo = paramVarLayout->FindResourceInfo(kind);
SLANG_ASSERT(paramResInfo);
if(!paramResInfo) continue;
auto startOffset = paramResInfo->index;
auto endOffset = startOffset + paramTypeResInfo.count;
auto scopeResInfo = m_structLayout->findOrAddResourceInfo(paramTypeResInfo.kind);
scopeResInfo->count = maximum(scopeResInfo->count, endOffset);
}
}
// Once we are done providing explicit offsets for all the parameters,
// we can defer to the base `ScopeLayoutBuilder` logic to decide
// whether to allocate a default constant buffer or anything like that.
//
return Super::endLayout();
}
};
/// 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;
}
/// Remove resource usage from `typeLayout` that should only be stored per-entry-point.
///
/// This is used when constructing the overall layout for an entry point, to make sure
/// that certain kinds of resource usage from the entry point don't "leak" into
/// the resource usage of the overall program.
///
static void removePerEntryPointParameterKinds(
TypeLayout* typeLayout)
{
typeLayout->removeResourceUsage(LayoutResourceKind::VaryingInput);
typeLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput);
typeLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord);
typeLayout->removeResourceUsage(LayoutResourceKind::HitAttributes);
typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam);
typeLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam);
}
static void removePerEntryPointParameterKinds(
VarLayout* varLayout)
{
removePerEntryPointParameterKinds(varLayout->typeLayout);
varLayout->removeResourceUsage(LayoutResourceKind::VaryingInput);
varLayout->removeResourceUsage(LayoutResourceKind::VaryingOutput);
varLayout->removeResourceUsage(LayoutResourceKind::ShaderRecord);
varLayout->removeResourceUsage(LayoutResourceKind::HitAttributes);
varLayout->removeResourceUsage(LayoutResourceKind::ExistentialObjectParam);
varLayout->removeResourceUsage(LayoutResourceKind::ExistentialTypeParam);
}
/// Iterate over the parameters of an entry point to compute its requirements.
///
static RefPtr<EntryPointLayout> collectEntryPointParameters(
ParameterBindingContext* context,
EntryPoint* entryPoint,
EntryPoint::EntryPointSpecializationInfo* specializationInfo)
{
auto astBuilder = context->getASTBuilder();
// We will take responsibility for creating and filling in
// the `EntryPointLayout` object here.
//
RefPtr<EntryPointLayout> entryPointLayout = new EntryPointLayout();
entryPointLayout->profile = entryPoint->getProfile();
entryPointLayout->name = entryPoint->getName();
// 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);
DeclRef<FuncDecl> entryPointFuncDeclRef = entryPoint->getFuncDeclRef();
// HACK: We might have an `EntryPoint` that has been deserialized, in
// which case we don't currently have access to its AST-level information,
// and as a result we cannot collect parameter information from it.
//
if( !entryPointFuncDeclRef )
{
// TODO: figure out what fields we absolutely need to fill in.
RefPtr<StructTypeLayout> paramsTypeLayout = new StructTypeLayout();
RefPtr<VarLayout> paramsLayout = new VarLayout();
paramsLayout->typeLayout = paramsTypeLayout;
entryPointLayout->parametersLayout = paramsLayout;
return entryPointLayout;
}
// If specialization was applied to the entry point, then the side-band
// information that was generated will have a more specialized reference
// to the entry point with generic parameters filled in. We should
// use that version if it is available.
//
if(specializationInfo)
entryPointFuncDeclRef = specializationInfo->specializedFuncDeclRef;
auto entryPointType = DeclRefType::create(astBuilder, entryPointFuncDeclRef);
entryPointLayout->entryPoint = entryPointFuncDeclRef;
// For the duration of our parameter collection work we will
// establish this entry point as the current one in the context.
//
context->entryPointLayout = entryPointLayout;
// 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.
//
TypeLayoutContext layoutContext = context->layoutContext;
if(isKhronosTarget(context->getTargetRequest()))
{
// For Vulkan/SPIR-V targets, there are various cases for
// how parameters that would otherwise just be a `ConstantBuffer<...>`
// get passed, that the compiler and application need to agree
// on.
//
// As a matter of policy, the Slang compiler will interpret
// direct entry-point `uniform` parameters as being passed
// using whatever is the most natural and efficient mechanism
// based on the shader stage.
//
// In the case of rasterization and compute shaders, this means
// passing entry-point `uniform` parmaeters via a "push constant"
// buffer.
//
// In the case of ray-tracing shaders, this means passing entry-point
// `uniform` parameters via the "shader record."
//
switch( entryPoint->getStage() )
{
default:
layoutContext = layoutContext.with(layoutContext.getRulesFamily()->getPushConstantBufferRules());
break;
case Stage::AnyHit:
case Stage::Callable:
case Stage::ClosestHit:
case Stage::Intersection:
case Stage::Miss:
case Stage::RayGeneration:
layoutContext = layoutContext.with(layoutContext.getRulesFamily()->getShaderRecordConstantBufferRules());
break;
}
}
SimpleScopeLayoutBuilder scopeBuilder;
scopeBuilder.beginLayout(context, layoutContext);
auto paramsStructLayout = scopeBuilder.m_structLayout;
paramsStructLayout->type = entryPointType;
for( auto& shaderParamInfo : entryPoint->getShaderParams() )
{
auto paramDeclRef = shaderParamInfo.paramDeclRef;
// Any generic specialization applied to the entry-point function
// must also be applied to its parameters.
paramDeclRef.substitutions = entryPointFuncDeclRef.substitutions;
// When computing layout for an entry-point parameter,
// we want to make sure that the layout context has access
// to the existential type arguments (if any) that were
// provided for the entry-point existential type parameters (if any).
//
if(specializationInfo)
{
auto& existentialSpecializationArgs = specializationInfo->existentialSpecializationArgs;
auto genericSpecializationParamCount = entryPoint->getGenericSpecializationParamCount();
context->layoutContext = context->layoutContext
.withSpecializationArgs(
existentialSpecializationArgs.getBuffer(),
existentialSpecializationArgs.getCount())
.withSpecializationArgsOffsetBy(
shaderParamInfo.firstSpecializationParamIndex - genericSpecializationParamCount);
}
// Any error messages we emit during the process should
// refer to the location of this parameter.
//
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,
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.
//
scopeBuilder.addSimpleParameter(paramVarLayout);
}
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(astBuilder, entryPointFuncDeclRef);
SLANG_ASSERT(resultType);
if( !resultType->equals(astBuilder->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,
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;
}
// We don't want certain kinds of resource usage within an entry
// point to "leak" into the overall resource usage of the entry
// point and thus lead to offsetting of successive entry points.
//
// For example if we have a vertex and a fragment entry point
// in the some program, and each has one varying input, then
// the both the vertex and fragment varying outputs should have
// a location/index of zero. It would be bad if the fragment
// input (or whichever entry point comes second in the global
// ordering) started at location one, because then it wouldn't
// line up correctly with any vertex stage outputs.
//
// We handle this with a bit of a kludge, by removing the
// particular `LayoutResourceKind`s that are susceptible to
// this problem from the overall resource usage of the entry
// point.
//
removePerEntryPointParameterKinds(paramsStructLayout);
removePerEntryPointParameterKinds(entryPointLayout->parametersLayout);
return entryPointLayout;
}
/// Visitor used by `collectGlobalGenericArguments`
struct CollectGlobalGenericArgumentsVisitor : ComponentTypeVisitor
{
CollectGlobalGenericArgumentsVisitor(
ParameterBindingContext* context)
: m_context(context)
{}
ParameterBindingContext* m_context;
void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(entryPoint);
SLANG_UNUSED(specializationInfo);
}
void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(module);
if(!specializationInfo)
return;
for(auto& globalGenericArg : specializationInfo->genericArgs)
{
if(auto globalGenericTypeParamDecl = as<GlobalGenericParamDecl>(globalGenericArg.paramDecl))
{
m_context->shared->programLayout->globalGenericArgs.Add(globalGenericTypeParamDecl, globalGenericArg.argVal);
}
}
}
void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
visitChildren(composite, specializationInfo);
}
void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE
{
specialized->getBaseComponentType()->acceptVisitor(this, specialized->getSpecializationInfo());
}
};
/// Collect an ordered list of all the specialization arguments given for global generic specialization parameters in `program`.
///
/// This information is used to accelerate the process of mapping a global generic type
/// to its definition during type layout.
///
static void collectGlobalGenericArguments(
ParameterBindingContext* context,
ComponentType* program)
{
CollectGlobalGenericArgumentsVisitor visitor(context);
program->acceptVisitor(&visitor, nullptr);
}
/// Collect information about the (unspecialized) specialization parameters of `program` into `context`.
///
/// This function computes the reflection/layout for for the specialization parameters, so
/// that they can be exposed to the API user.
///
static void collectSpecializationParams(
ParameterBindingContext* context,
ComponentType* program)
{
auto specializationParamCount = program->getSpecializationParamCount();
for(Index ii = 0; ii < specializationParamCount; ++ii)
{
auto specializationParam = program->getSpecializationParam(ii);
switch(specializationParam.flavor)
{
case SpecializationParam::Flavor::GenericType:
case SpecializationParam::Flavor::GenericValue:
{
RefPtr<GenericSpecializationParamLayout> paramLayout = new GenericSpecializationParamLayout();
paramLayout->decl = as<Decl>(specializationParam.object);
context->shared->programLayout->specializationParams.add(paramLayout);
}
break;
case SpecializationParam::Flavor::ExistentialType:
case SpecializationParam::Flavor::ExistentialValue:
{
RefPtr<ExistentialSpecializationParamLayout> paramLayout = new ExistentialSpecializationParamLayout();
paramLayout->type = as<Type>(specializationParam.object);
context->shared->programLayout->specializationParams.add(paramLayout);
}
break;
default:
SLANG_UNEXPECTED("unhandled specialization parameter flavor");
break;
}
}
}
/// Visitor used by `collectParameters()`
struct CollectParametersVisitor : ComponentTypeVisitor
{
CollectParametersVisitor(
ParameterBindingContext* context)
: m_context(context)
{}
ParameterBindingContext* m_context;
void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
// The parameters of a composite component type can
// be determined by just visiting its children in order.
//
visitChildren(composite, specializationInfo);
}
void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE
{
// The parameters of a specialized component type
// are just those of its base component type, with
// appropriate specialization information passed
// along.
//
visitChildren(specialized);
// While we are at it, we will also make note of any
// tagged-union types that were used as part of the
// specialization arguments, since we need to make
// sure that their layout information is computed
// and made available for IR code generation.
//
// Note: this isn't really the best place for this logic to sit,
// but it is the simplest place where we can collect all the tagged
// union types that get referenced by a program.
//
for( auto taggedUnionType : specialized->getTaggedUnionTypes() )
{
SLANG_ASSERT(taggedUnionType);
auto substType = taggedUnionType;
auto typeLayout = createTypeLayout(m_context->layoutContext, substType);
m_context->shared->programLayout->taggedUnionTypeLayouts.add(typeLayout);
}
}
void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
// An entry point is a leaf case.
//
// In our current model an entry point does not introduce
// any global shader parameters, but in practice it effectively
// acts a lot like a single global shader parameter named after
// the entry point and with a `struct` type that combines
// all the `uniform` entry point parameters.
//
// Later passes will need to make sure that the entry point
// gets enumerated in the right order relative to any global
// shader parameters.
//
ParameterBindingContext contextData = *m_context;
auto context = &contextData;
context->stage = entryPoint->getStage();
collectEntryPointParameters(context, entryPoint, specializationInfo);
}
void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
// A single module represents a leaf case for layout.
//
// We will enumerate the (global) shader parameters declared
// in the module and add each to our canonical ordering.
//
auto paramCount = module->getShaderParamCount();
ExpandedSpecializationArg* specializationArgs = specializationInfo
? specializationInfo->existentialArgs.getBuffer()
: nullptr;
for(Index pp = 0; pp < paramCount; ++pp)
{
auto shaderParamInfo = module->getShaderParam(pp);
if(specializationArgs)
{
m_context->layoutContext = m_context->layoutContext.withSpecializationArgs(
specializationArgs,
shaderParamInfo.specializationParamCount);
specializationArgs += shaderParamInfo.specializationParamCount;
}
collectGlobalScopeParameter(m_context, shaderParamInfo, SubstitutionSet());
}
if( auto moduleDecl = module->getModuleDecl() )
{
if( auto nvapiSlotModifier = moduleDecl->findModifier<NVAPISlotModifier>() )
{
m_context->shared->nvapiSlotModifiers.add(nvapiSlotModifier);
}
}
}
};
/// Recursively collect the global shader parameters and entry points in `program`.
///
/// This function is used to establish the global ordering of parameters and
/// entry points used for layout.
///
static void collectParameters(
ParameterBindingContext* inContext,
ComponentType* program)
{
// All of the parameters in translation units directly
// referenced in the compile request are part of one
// logical namespace/"linkage" so that two parameters
// with the same name should represent the same
// parameter, and get the same binding(s)
ParameterBindingContext contextData = *inContext;
auto context = &contextData;
context->stage = Stage::Unknown;
CollectParametersVisitor visitor(context);
program->acceptVisitor(&visitor, nullptr);
}
/// Emit a diagnostic about a uniform/ordinary parameter at global scope.
void diagnoseGlobalUniform(
SharedParameterBindingContext* sharedContext,
VarDeclBase* varDecl)
{
// This subroutine gets invoked if a shader parameter containing
// "ordinary" data (sometimes just called "uniform" data) is present
// at the global scope.
//
// Slang can support such parameters by aggregating them into
// an implicit constant buffer, but it is also common for programmers
// to accidentally declare a global-scope shader parameter when they
// meant to declare a global variable instead:
//
// int gCounter = 0; // this is a shader parameter, not a global
//
// In order to avoid mistakes, we'd like to warn the user when
// they write code like the above, and hint to them that they
// should make their intention more explicit with a keyword:
//
// static int gCounter = 0; // this is now a (static) global
//
// uniform int gCounter; // this is now explicitly a shader parameter
//
// We skip the diagnostic whenever the variable was explicitly `uniform`,
// under the assumption that the programmer who added that modifier
// knew what they were opting into.
//
if(varDecl->hasModifier<HLSLUniformModifier>())
return;
getSink(sharedContext)->diagnose(varDecl, Diagnostics::globalUniformNotExpected, 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;
}
static bool _isCPUTarget(CodeGenTarget target)
{
switch (target)
{
case CodeGenTarget::CPPSource:
case CodeGenTarget::CSource:
case CodeGenTarget::Executable:
case CodeGenTarget::SharedLibrary:
case CodeGenTarget::HostCallable:
{
return true;
}
default: return false;
}
}
static bool _isPTXTarget(CodeGenTarget target)
{
switch (target)
{
case CodeGenTarget::CUDASource:
case CodeGenTarget::PTX:
{
return true;
}
default: return false;
}
}
/// Keep track of the running global counter for entry points and global parameters visited.
///
/// Because of explicit `register` and `[[vk::binding(...)]]` support, parameter binding
/// needs to proceed in multiple passes, and each pass must both visit the things that
/// need layout (parameters and entry points) in the same order in each pass, and must
/// also be able to look up the side-band information that flows between passes.
///
/// Currently the `ParameterBindingContext` keeps separate arrays for global shader
/// parameters and entry points, but in the global ordering for layout they can be
/// interleaved. There is also no simple tracking structure that relates a global
/// parameter or entry point to its index in those arrays. Instead, we just keep
/// running counters during our passes over the program so that we can easily
/// compute the linear index of each entry point and global parameter as it
/// is encountered.
///
struct ParameterBindingVisitorCounters
{
Index entryPointCounter = 0;
Index globalParamCounter = 0;
};
/// Recursive routine to "complete" all binding for parameters and entry points in `componentType`.
///
/// This includes allocation of as-yet-unused register/binding ranges to parameters (which
/// will then affect the ranges of registers/bindings that are available to subsequent
/// parameters), and imporantly *also* includes allocate of space to any "pending"
/// data for interface/existential type parameters/fields.
///
static void _completeBindings(
ParameterBindingContext* context,
ComponentType* componentType,
ParameterBindingVisitorCounters* ioCounters);
/// A visitor used by `_completeBindings`.
///
/// This visitor walks the structure of a `ComponentType` to ensure that
/// any shader parameters (and entry points) it contains that *don't*
/// have explicit bindings on them get allocated registers/bindings
/// as appropriate.
///
/// The main complication of this visitor is how it handles the
/// `SpecializedComponentType` case, because a specialized component
/// type needs to be handled as an atomic unit that lays out the
/// same in all contexts.
///
struct CompleteBindingsVisitor : ComponentTypeVisitor
{
CompleteBindingsVisitor(ParameterBindingContext* context, ParameterBindingVisitorCounters* counters)
: m_context(context)
, m_counters(counters)
{}
ParameterBindingContext* m_context;
ParameterBindingVisitorCounters* m_counters;
void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(entryPoint);
SLANG_UNUSED(specializationInfo);
// We compute the index of the entry point in the global ordering,
// so we can look up the tracking data in our context. As a result
// we don't actually make use of the parameters that were passed in.
//
auto globalEntryPointIndex = m_counters->entryPointCounter++;
auto globalEntryPointInfo = m_context->shared->programLayout->entryPoints[globalEntryPointIndex];
// We mostly treat an entry point like a single shader parameter that
// uses its `parametersLayout`.
//
completeBindingsForParameter(m_context, globalEntryPointInfo->parametersLayout);
}
void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(specializationInfo);
// A module is a leaf case: we just want to visit each parameter.
visitLeafParams(module);
}
void visitLeafParams(ComponentType* componentType)
{
auto paramCount = componentType->getShaderParamCount();
for(Index ii = 0; ii < paramCount; ++ii)
{
auto globalParamIndex = m_counters->globalParamCounter++;
auto globalParamInfo = m_context->shared->parameters[globalParamIndex];
completeBindingsForParameter(m_context, globalParamInfo);
}
}
void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
// We just wnat to recurse on the children of the composite in order.
visitChildren(composite, specializationInfo);
}
void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE
{
// The handling of a specialized component type here is subtle.
//
// We do *not* simply recurse on the base component type.
// Doing so would ensure that the parameters would get
// registers/bindings allocated to them, but it wouldn't
// allocate space for the "pending" data related to
// existential/interface parameters.
//
// Instead, we recursive through `_completeBindings`,
// which has the job of allocating space for the parameters,
// and then for any "pending" data required.
//
// Handling things this way ensures that a particular
// `SpecializedComponentType` gets laid out exactly
// the same wherever it gets used, rather than
// getting laid out differently when it is placed
// into different compositions.
//
auto base = specialized->getBaseComponentType();
_completeBindings(m_context, base, m_counters);
}
};
/// A visitor used by `_completeBindings`.
///
/// This visitor is used to follow up after the `CompleteBindingsVisitor`
/// any ensure that any "pending" data required by the parameters that
/// got laid out now gets a location.
///
/// To make a concrete example:
///
/// Texture2D a;
/// IThing b;
/// Texture2D c;
///
/// If these parameters were laid out with `b` specialized to a type
/// that contains a single `Texture2D`, then the `CompleteBindingsVisitor`
/// would visit `a`, `b`, and then `c` in order. It would give `a` the
/// first register/binding available (say, `t0`). It would then make
/// a note that due to specialization, `b`, needs a `t` register as well,
/// but it *cannot* be allocated just yet, because doing so would change
/// the location of `c`, so it is marked as "pending." Then `c` would
/// be visited and get `t1`. As a result the registers given to `a`
/// and `c` are independent of how `b` gets specialized.
///
/// Next, the `FlushPendingDataVisitor` comes through and applies to
/// the parameters again. For `a` there is no pending data, but for
/// `b` there is a pending request for a `t` register, so it gets allocated
/// now (getting `t2`). The `c` parameter then has no pending data, so
/// we are done.
///
/// *When* the pending data gets flushed is then significant. In general,
/// the order in which modules get composed an specialized is signficaint.
/// The module above (let's call it `M`) has one specialization parameter
/// (for `b`), and if we want to compose it with another module `N` that
/// has no specialization parameters, we could compute either:
///
/// compose(specialize(M, SomeType), N)
///
/// or:
///
/// specialize(compose(M,N), SomeType)
///
/// In the first case, the "pending" data for `M` gets flushed right after `M`,
/// so that `specialize(M,SomeType)` can have a consistent layout
/// regardless of how it is used. In the second case, the pending data for
/// `M` only gets flushed after `N`'s parameters are allocated, thus guaranteeing
/// that the `compose(M,N)` part has a consistent layout regardless of what
/// type gets plugged in during specialization.
///
/// There are trade-offs to be made by an application about which approach
/// to prefer, and the compiler supports either policy choice.
///
struct FlushPendingDataVisitor : ComponentTypeVisitor
{
FlushPendingDataVisitor(ParameterBindingContext* context, ParameterBindingVisitorCounters* counters)
: m_context(context)
, m_counters(counters)
{}
ParameterBindingContext* m_context;
ParameterBindingVisitorCounters* m_counters;
void visitEntryPoint(EntryPoint* entryPoint, EntryPoint::EntryPointSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(entryPoint);
SLANG_UNUSED(specializationInfo);
auto globalEntryPointIndex = m_counters->entryPointCounter++;
auto globalEntryPointInfo = m_context->shared->programLayout->entryPoints[globalEntryPointIndex];
// We need to allocate space for any "pending" data that
// appeared in the entry-point parameter list.
//
_allocateBindingsForPendingData(m_context, globalEntryPointInfo->parametersLayout->pendingVarLayout);
}
void visitModule(Module* module, Module::ModuleSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
SLANG_UNUSED(specializationInfo);
visitLeafParams(module);
}
void visitLeafParams(ComponentType* componentType)
{
// In the "leaf" case we just allocate space for any
// pending data in the parameters, in order.
//
auto paramCount = componentType->getShaderParamCount();
for(Index ii = 0; ii < paramCount; ++ii)
{
auto globalParamIndex = m_counters->globalParamCounter++;
auto globalParamInfo = m_context->shared->parameters[globalParamIndex];
auto varLayout = globalParamInfo->varLayout;
_allocateBindingsForPendingData(m_context, varLayout->pendingVarLayout);
}
}
void visitComposite(CompositeComponentType* composite, CompositeComponentType::CompositeSpecializationInfo* specializationInfo) SLANG_OVERRIDE
{
visitChildren(composite, specializationInfo);
}
void visitSpecialized(SpecializedComponentType* specialized) SLANG_OVERRIDE
{
// Because `SpecializedComponentType` was a special case for `CompleteBindingsVisitor`,
// it ends up being a special case here too.
//
// The `CompleteBindings...` pass treated a `SpecializedComponentType`
// as an atomic unit. Any "pending" data that came from its parameters
// will already have been dealt with, so it would be incorrect for
// us to recurse into `specialized`.
//
// Instead, we just need to *skip* `specialized`, since it was
// completely handled already. This isn't quite as simple
// as just doing nothing, because our passes are using
// some global counters to find the absolute/linear index
// of each parameter and entry point as it is encountered.
// We will simply bump those counters by the number of
// parameters and entry points contained under `specialized`,
// which is luckily provided by the `ComponentType` API.
//
m_counters->globalParamCounter += specialized->getShaderParamCount();
m_counters->entryPointCounter += specialized->getEntryPointCount();
}
};
static void _completeBindings(
ParameterBindingContext* context,
ComponentType* componentType,
ParameterBindingVisitorCounters* ioCounters)
{
ParameterBindingVisitorCounters savedCounters = *ioCounters;
CompleteBindingsVisitor completeBindingsVisitor(context, ioCounters);
componentType->acceptVisitor(&completeBindingsVisitor, nullptr);
FlushPendingDataVisitor flushVisitor(context, &savedCounters);
componentType->acceptVisitor(&flushVisitor, nullptr);
}
/// "Complete" binding of parametesr in the given `program`.
///
/// Completing binding involves both assigning registers/bindings
/// to an parameters that didn't get explicit locations, and then
/// also providing locations to any "pending" data that needed
/// space allocated (used for existential/interface type parameters).
///
static void _completeBindings(
ParameterBindingContext* context,
ComponentType* program)
{
// The process of completing binding has a recursive structure,
// so we will immediately delegate to a subroutine that handles
// the recursion.
//
ParameterBindingVisitorCounters counters;
_completeBindings(context, program, &counters);
}
RefPtr<ProgramLayout> generateParameterBindings(
TargetProgram* targetProgram,
DiagnosticSink* sink)
{
auto program = targetProgram->getProgram();
auto targetReq = targetProgram->getTargetReq();
RefPtr<ProgramLayout> programLayout = new ProgramLayout();
programLayout->targetProgram = targetProgram;
{
auto& pool = programLayout->hashedStringLiteralPool;
program->enumerateIRModules([&](IRModule* module) { findGlobalHashedStringLiterals(module, pool); });
}
// 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;
// We want to start by finding out what (if anything) has
// been bound to the global generic parameters of the
// program, since we need to know these types to compute
// layout for parameters that use the generic type parameters.
//
collectGlobalGenericArguments(&context, program);
// Next we want to collect a full listing of all the shader
// parameters that need to be considered for layout, along
// with all of the entry points, which also need their
// parameters laid out and thus act pretty much like global
// parameters themselves.
//
collectParameters(&context, program);
// We will also collect basic information on the specialization
// parameters exposed by the program.
//
// Whereas `collectGlobalGenericArguments` was collecting the
// concrete types that have been plugged into specialization
// parameters, this step is about collecting the *unspecialized*
// parameters (if any) for the purposes of reflection.
//
collectSpecializationParams(&context, program);
// Once we have a canonical list of all the shader parameters
// (and entry points) in need of layout, we will walk through
// the parameters that might have explicit binding annotations,
// and "reserve" the registers/bindings/etc. that those parameters
// declare so that subequent automatic layout steps do not try to
// overlap them.
//
// Along the way we will issue diagnostics if there appear to
// be overlapping, conflicting, or inconsistent explicit bindings.
//
// Note that we do *not* support explicit binding annotations
// on entry point parameters, so we only consider global shader
// parameters here.
//
// (Also note that explicit bindings end up being the main
// source of complexity in the layout system, and we could greatly
// simplify this file by eliminating support for explicit
// binding in the future)
//
for( auto& parameter : sharedContext.parameters )
{
generateParameterBindings(&context, parameter);
}
// It is possible that code has specified an explicit location
// for the UAV used to communicate with NVAPI, but the code
// is not actually including/using the NVAPI header. We still
// need to ensure that user-defined shader parameters do not
// conflict with the location that will be used by NVAPI.
//
if( isD3DTarget(targetReq) )
{
// Information about the NVAPI parameter was recorded
// on the AST `ModuleDecl`s during earlier stages of
// compilation, and there might be multiple modules
// as input to layout and back-end compilation.
//
// We do not take responsibility for diagnostic conflicts
// at this point, and simply process all of the modifiers
// we see.
//
for( auto nvapiSlotModifier : sharedContext.nvapiSlotModifiers )
{
// For a given modifier, we start by parsing the semantic
// strings that were specified, just as we would if they
// had been specified via a `register(...)` semantic.
//
auto info = extractHLSLLayoutSemanticInfo(
nvapiSlotModifier->registerName.getUnownedSlice(),
nvapiSlotModifier->loc,
nvapiSlotModifier->spaceName.getUnownedSlice(),
nvapiSlotModifier->loc,
sink);
auto kind = info.kind;
if (kind == LayoutResourceKind::None)
continue;
// The NVAPI parameter always uses a single register.
//
LayoutSize count = 1;
// We are going to mark the register range declared for the
// NVAPI parameter as used.
//
// Note: It is possible that the range has *already* been
// marked as used, because the `g_NvidiaExt` parameter has
// already been processed by the `generateParameterBindings`
// logic above. We don't worry about that case and do not
// diagnose an error.
//
// Note: It is *also* possible that some non-NVAPI parameter
// with an explicit binding will collide with the NVAPI
// parameter, and we also do not diagnose a problem in
// that case.
//
// TODO: We could probably make the user experience nicer
// here in the case of conflicts, but we are already deep
// into a do-what-I-mean edge case.
//
// Note: In the case where the user sets up the NVAPI
// register/space via the front-end (e.g., by setting a
// `NV_SHADER_EXTN_SLOT` macro), but doesn't actually
// `#include` the NVAPI header, we will *still* reserve
// the appropriate register so that it won't be used
// by user paraemter.
//
// That policy means that simply defining a particular
// macro can alter layout behavior, which is conceptually
// kind of a mess, but also seems to be the best possible
// answer given the constraints.
//
auto usedRangeSet = findUsedRangeSetForSpace(&context, info.space);
markSpaceUsed(&context, nullptr, info.space);
usedRangeSet->usedResourceRanges[(int)kind].Add(
nullptr,
info.index,
info.index + count);
}
}
// Once we have a canonical list of all the parameters, we can
// detect if there are any global-scope parameters that make use
// of `LayoutResourceKind::Uniform`, since such parameters would
// need to be packaged into a "default" constant buffer.
// The fxc/dxc compilers support this step, and in reflection
// refer to the generated constant buffer as `$Globals`.
//
// Note that this logic doesn't account for the existance of
// "legacy" (non-buffer-bound) uniforms in GLSL for OpenGL.
// If we wanted to support legaqcy uniforms we would probably
// want to do so through a different feature.
//
bool needDefaultConstantBuffer = false;
// On a CPU target, it's okay to have global scope parameters that use Uniform resources (because on CPU
// all resources are 'Uniform')
// TODO(JS): We'll just assume the same with CUDA target for now..
if (!_isCPUTarget(targetReq->getTarget()) && !_isPTXTarget(targetReq->getTarget()))
{
for( auto& parameterInfo : sharedContext.parameters )
{
auto varLayout = parameterInfo->varLayout;
SLANG_RELEASE_ASSERT(varLayout);
// Does the field have any uniform data?
if( varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
{
needDefaultConstantBuffer = true;
diagnoseGlobalUniform(&sharedContext, varLayout->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)
{
auto varLayout = parameterInfo->varLayout;
SLANG_RELEASE_ASSERT(varLayout);
// For each parameter, we will look at each resource it consumes.
//
for (auto resInfo : varLayout->typeLayout->resourceInfos)
{
// We 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;
// We also want to exclude certain resource kinds from
// consideration, since parameters using those resource
// kinds wouldn't be allocated into the default space
// anyway.
//
switch( resInfo.kind )
{
case LayoutResourceKind::RegisterSpace:
case LayoutResourceKind::PushConstantBuffer:
continue;
default:
break;
}
// Otherwise, we have a shader parameter that will need
// a default space or set to live in.
//
needDefaultSpace = true;
break;
}
}
// We also need a default space for any entry-point parameters
// that consume appropriate resource kinds.
//
for(auto& entryPoint : sharedContext.programLayout->entryPoints)
{
auto paramsLayout = entryPoint->parametersLayout;
for(auto resInfo : paramsLayout->resourceInfos )
{
switch(resInfo.kind)
{
default:
break;
case LayoutResourceKind::RegisterSpace:
case LayoutResourceKind::VaryingInput:
case LayoutResourceKind::VaryingOutput:
case LayoutResourceKind::HitAttributes:
case LayoutResourceKind::RayPayload:
continue;
}
needDefaultSpace = true;
break;
}
}
}
// If we need a space for default bindings, then allocate it here.
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 that all of the explicit bindings have been dealt with
// and we've also allocate any space/buffer that is required
// for global-scope parameters, we will go through the
// shader parameters and entry points yet again, in order
// to actually allocate specific bindings/registers to
// parameters and entry points that need them.
//
_completeBindings(&context, program);
// Next we need to create a type layout to reflect the information
// we have collected, and we will use the `ScopeLayoutBuilder`
// 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;
}
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);
if( m_layout )
{
m_irModuleForLayout = createIRModuleForLayout(sink);
}
}
return m_layout;
}
void generateParameterBindings(
ComponentType* program,
TargetRequest* targetReq,
DiagnosticSink* sink)
{
program->getTargetProgram(targetReq)->getOrCreateLayout(sink);
}
} // namespace Slang