parameter-binding.cpp
// parameter-binding.cpp
#include "parameter-binding.h"
#include "lookup.h"
#include "compiler.h"
#include "type-layout.h"
#include "../../slang.h"
namespace Slang {
struct ParameterInfo;
// Information on ranges of registers already claimed/used
struct UsedRange
{
// What parameter has claimed this range?
ParameterInfo* parameter = nullptr;
// 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
{
List<UsedRange> ranges;
// Add a range to the set, either by extending
// an existing range, 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.
ParameterInfo* Add(UsedRange const& range)
{
ParameterInfo* newParam = range.parameter;
ParameterInfo* existingParam = nullptr;
for (auto& rr : ranges)
{
if (rangesOverlap(rr, range)
&& rr.parameter
&& rr.parameter != newParam)
{
// there was an overlap!
existingParam = rr.parameter;
}
}
for (auto& rr : ranges)
{
if (rr.begin == range.end)
{
rr.begin = range.begin;
return existingParam;
}
else if (rr.end == range.begin)
{
rr.end = range.end;
return existingParam;
}
}
ranges.Add(range);
ranges.Sort();
return existingParam;
}
ParameterInfo* Add(ParameterInfo* param, UInt begin, UInt end)
{
UsedRange range;
range.parameter = param;
range.begin = begin;
range.end = end;
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(ParameterInfo* param, UInt count)
{
UInt begin = 0;
UInt rangeCount = ranges.Count();
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;
size_t index;
size_t count;
};
enum
{
kLayoutResourceKindCount = SLANG_PARAMETER_CATEGORY_COUNT,
};
struct UsedRangeSet : RefObject
{
// Information on what ranges of "registers" have already
// been claimed, for each resource type
UsedRanges usedResourceRanges[kLayoutResourceKindCount];
};
// Information on a single parameter
struct ParameterInfo : RefObject
{
// Layout info for the concrete variables that will make up this parameter
List<RefPtr<VarLayout>> varLayouts;
ParameterBindingInfo bindingInfo[kLayoutResourceKindCount];
// The next parameter that has the same name...
ParameterInfo* nextOfSameName;
// 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
{
// The base compile request
CompileRequest* compileRequest;
// 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;
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;
// What ranges of resource bindings are claimed for particular translation unit?
// This is only used for varying input/output.
//
Dictionary<TranslationUnitRequest*, RefPtr<UsedRangeSet>> translationUnitUsedRangeSets;
// Which register spaces have been claimed so far?
UsedRanges usedSpaces;
// The space to use for auto-generated bindings.
UInt defaultSpace = 0;
TargetRequest* getTargetRequest() { return targetRequest; }
};
static DiagnosticSink* getSink(SharedParameterBindingContext* shared)
{
return &shared->compileRequest->mSink;
}
// State that might be specific to a single translation unit
// or event to an entry point.
struct ParameterBindingContext
{
// The translation unit we are processing right now
TranslationUnitRequest* translationUnit;
// 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;
// A dictionary to accellerate looking up parameters by name
Dictionary<Name*, ParameterInfo*> mapNameToParameterInfo;
// What stage (if any) are we compiling for?
Stage stage;
// The entry point that is being processed right now.
EntryPointLayout* entryPointLayout = nullptr;
// The source language we are trying to use
SourceLanguage sourceLanguage;
TargetRequest* getTargetRequest() { return shared->getTargetRequest(); }
LayoutRulesFamilyImpl* getRulesFamily() { return layoutContext.getRulesFamily(); }
};
static DiagnosticSink* getSink(ParameterBindingContext* context)
{
return getSink(context->shared);
}
struct LayoutSemanticInfo
{
LayoutResourceKind kind; // the register kind
UInt space;
UInt index;
// TODO: need to deal with component-granularity binding...
};
static bool isDigit(char c)
{
return (c >= '0') && (c <= '9');
}
/// Given a string that specifies a name and index (e.g., `COLOR0`),
/// split it into slices for the name part and the index part.
static void splitNameAndIndex(
String 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);
}
LayoutSemanticInfo ExtractLayoutSemanticInfo(
ParameterBindingContext* context,
HLSLLayoutSemantic* semantic)
{
LayoutSemanticInfo info;
info.space = 0;
info.index = 0;
info.kind = LayoutResourceKind::None;
String registerName = semantic->registerName.Content;
if (registerName.Length() == 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);
// All of the register classes we support are single ASCII characters,
// so we really just care about the first byte, but we want to be
// careful and only look at it if the register class name is one
// byte long.
char registerClassChar = registerClassName.size() == 1 ? *registerClassName.begin() : 0;
LayoutResourceKind kind = LayoutResourceKind::None;
switch (registerClassChar)
{
case 'b':
kind = LayoutResourceKind::ConstantBuffer;
break;
case 't':
kind = LayoutResourceKind::ShaderResource;
break;
case 'u':
kind = LayoutResourceKind::UnorderedAccess;
break;
case 's':
kind = LayoutResourceKind::SamplerState;
break;
default:
getSink(context)->diagnose(semantic->registerName, Diagnostics::unknownRegisterClass, registerClassName);
return info;
}
// For a `register` semantic, the register index is not optional (unlike
// how it works for varying input/output semantics).
if( registerIndexDigits.size() == 0 )
{
getSink(context)->diagnose(semantic->registerName, Diagnostics::expectedARegisterIndex, registerClassName);
}
UInt index = 0;
for(auto c : registerIndexDigits)
{
SLANG_ASSERT(isDigit(c));
index = index * 10 + (c - '0');
}
UInt space = 0;
if( auto registerSemantic = dynamic_cast<HLSLRegisterSemantic*>(semantic) )
{
auto const& spaceName = registerSemantic->spaceName.Content;
if(spaceName.Length() != 0)
{
UnownedStringSlice spaceSpelling;
UnownedStringSlice spaceDigits;
splitNameAndIndex(spaceName, spaceSpelling, spaceDigits);
if( spaceSpelling != UnownedTerminatedStringSlice("space") )
{
getSink(context)->diagnose(semantic->registerName, Diagnostics::expectedSpace, spaceSpelling);
}
else if( spaceDigits.size() == 0 )
{
getSink(context)->diagnose(semantic->registerName, Diagnostics::expectedSpaceIndex);
}
else
{
for(auto c : spaceDigits)
{
SLANG_ASSERT(isDigit(c));
space = space * 10 + (c - '0');
}
}
}
}
// TODO: handle component mask part of things...
if( semantic->componentMask.Content.Length() != 0 )
{
getSink(context)->diagnose(semantic->componentMask, Diagnostics::componentMaskNotSupported);
}
info.kind = kind;
info.index = (int) index;
info.space = space;
return info;
}
static Name* getReflectionName(VarDeclBase* varDecl)
{
if (auto reflectionNameModifier = varDecl->FindModifier<ParameterGroupReflectionName>())
return reflectionNameModifier->nameAndLoc.name;
return varDecl->getName();
}
// Information tracked when doing a structural
// match of types.
struct StructuralTypeMatchStack
{
DeclRef<VarDeclBase> leftDecl;
DeclRef<VarDeclBase> rightDecl;
StructuralTypeMatchStack* parent;
};
static void diagnoseParameterTypeMismatch(
ParameterBindingContext* context,
StructuralTypeMatchStack* inStack)
{
SLANG_ASSERT(inStack);
// The bottom-most entry in the stack should represent
// the shader parameters that kicked things off
auto stack = inStack;
while(stack->parent)
stack = stack->parent;
getSink(context)->diagnose(stack->leftDecl, Diagnostics::shaderParameterDeclarationsDontMatch, getReflectionName(stack->leftDecl));
getSink(context)->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl));
}
// Two types that were expected to match did not.
// Inform the user with a suitable message.
static void diagnoseTypeMismatch(
ParameterBindingContext* context,
StructuralTypeMatchStack* inStack)
{
auto stack = inStack;
SLANG_ASSERT(stack);
diagnoseParameterTypeMismatch(context, stack);
auto leftType = GetType(stack->leftDecl);
auto rightType = GetType(stack->rightDecl);
if( stack->parent )
{
getSink(context)->diagnose(stack->leftDecl, Diagnostics::fieldTypeMisMatch, getReflectionName(stack->leftDecl), leftType, rightType);
getSink(context)->diagnose(stack->rightDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(stack->rightDecl));
stack = stack->parent;
if( stack )
{
while( stack->parent )
{
getSink(context)->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl));
stack = stack->parent;
}
}
}
else
{
getSink(context)->diagnose(stack->leftDecl, Diagnostics::shaderParameterTypeMismatch, leftType, rightType);
}
}
// Two types that were expected to match did not.
// Inform the user with a suitable message.
static void diagnoseTypeFieldsMismatch(
ParameterBindingContext* context,
DeclRef<Decl> const& left,
DeclRef<Decl> const& right,
StructuralTypeMatchStack* stack)
{
diagnoseParameterTypeMismatch(context, stack);
getSink(context)->diagnose(left, Diagnostics::fieldDeclarationsDontMatch, left.GetName());
getSink(context)->diagnose(right, Diagnostics::seeOtherDeclarationOf, right.GetName());
if( stack )
{
while( stack->parent )
{
getSink(context)->diagnose(stack->leftDecl, Diagnostics::usedInDeclarationOf, getReflectionName(stack->leftDecl));
stack = stack->parent;
}
}
}
static void collectFields(
DeclRef<AggTypeDecl> declRef,
List<DeclRef<StructField>>& outFields)
{
for( auto fieldDeclRef : getMembersOfType<StructField>(declRef) )
{
if(fieldDeclRef.getDecl()->HasModifier<HLSLStaticModifier>())
continue;
outFields.Add(fieldDeclRef);
}
}
static bool validateTypesMatch(
ParameterBindingContext* context,
Type* left,
Type* right,
StructuralTypeMatchStack* stack);
static bool validateIntValuesMatch(
ParameterBindingContext* context,
IntVal* left,
IntVal* right,
StructuralTypeMatchStack* stack)
{
if(left->EqualsVal(right))
return true;
// TODO: are there other cases we need to handle here?
diagnoseTypeMismatch(context, stack);
return false;
}
static bool validateValuesMatch(
ParameterBindingContext* context,
Val* left,
Val* right,
StructuralTypeMatchStack* stack)
{
if( auto leftType = dynamic_cast<Type*>(left) )
{
if( auto rightType = dynamic_cast<Type*>(right) )
{
return validateTypesMatch(context, leftType, rightType, stack);
}
}
if( auto leftInt = dynamic_cast<IntVal*>(left) )
{
if( auto rightInt = dynamic_cast<IntVal*>(right) )
{
return validateIntValuesMatch(context, leftInt, rightInt, stack);
}
}
if( auto leftWitness = dynamic_cast<SubtypeWitness*>(left) )
{
if( auto rightWitness = dynamic_cast<SubtypeWitness*>(right) )
{
return true;
}
}
diagnoseTypeMismatch(context, stack);
return false;
}
static bool validateGenericSubstitutionsMatch(
ParameterBindingContext* context,
GenericSubstitution* left,
GenericSubstitution* right,
StructuralTypeMatchStack* stack)
{
if( !left )
{
if( !right )
{
return true;
}
diagnoseTypeMismatch(context, stack);
return false;
}
UInt argCount = left->args.Count();
if( argCount != right->args.Count() )
{
diagnoseTypeMismatch(context, stack);
return false;
}
for( UInt aa = 0; aa < argCount; ++aa )
{
auto leftArg = left->args[aa];
auto rightArg = right->args[aa];
if(!validateValuesMatch(context, leftArg, rightArg, stack))
return false;
}
return true;
}
static bool validateThisTypeSubstitutionsMatch(
ParameterBindingContext* /*context*/,
ThisTypeSubstitution* /*left*/,
ThisTypeSubstitution* /*right*/,
StructuralTypeMatchStack* /*stack*/)
{
// TODO: actual checking.
return true;
}
static bool validateSpecializationsMatch(
ParameterBindingContext* context,
SubstitutionSet left,
SubstitutionSet right,
StructuralTypeMatchStack* stack)
{
auto ll = left.substitutions;
auto rr = right.substitutions;
for(;;)
{
// Skip any global generic substitutions.
if(auto leftGlobalGeneric = ll.As<GlobalGenericParamSubstitution>())
{
ll = leftGlobalGeneric->outer;
continue;
}
if(auto rightGlobalGeneric = rr.As<GlobalGenericParamSubstitution>())
{
rr = rightGlobalGeneric->outer;
continue;
}
// If either ran out, then we expect both to have run out.
if(!ll || !rr)
return !ll && !rr;
auto leftSubst = ll;
auto rightSubst = rr;
ll = ll->outer;
rr = rr->outer;
if(auto leftGeneric = leftSubst.As<GenericSubstitution>())
{
if(auto rightGeneric = rightSubst.As<GenericSubstitution>())
{
if(validateGenericSubstitutionsMatch(context, leftGeneric, rightGeneric, stack))
{
continue;
}
}
}
else if(auto leftThisType = leftSubst.As<ThisTypeSubstitution>())
{
if(auto rightThisType = rightSubst.As<ThisTypeSubstitution>())
{
if(validateThisTypeSubstitutionsMatch(context, leftThisType, rightThisType, stack))
{
continue;
}
}
}
return false;
}
return true;
}
// Determine if two types "match" for the purposes of `cbuffer` layout rules.
//
static bool validateTypesMatch(
ParameterBindingContext* context,
Type* left,
Type* right,
StructuralTypeMatchStack* stack)
{
if(left->Equals(right))
return true;
// It is possible that the types don't match exactly, but
// they *do* match structurally.
// Note: the following code will lead to infinite recursion if there
// are ever recursive types. We'd need a more refined system to
// cache the matches we've already found.
if( auto leftDeclRefType = left->As<DeclRefType>() )
{
if( auto rightDeclRefType = right->As<DeclRefType>() )
{
// Are they references to matching decl refs?
auto leftDeclRef = leftDeclRefType->declRef;
auto rightDeclRef = rightDeclRefType->declRef;
// Do the reference the same declaration? Or declarations
// with the same name?
//
// TODO: we should only consider the same-name case if the
// declarations come from translation units being compiled
// (and not an imported module).
if( leftDeclRef.getDecl() == rightDeclRef.getDecl()
|| leftDeclRef.GetName() == rightDeclRef.GetName() )
{
// Check that any generic arguments match
if( !validateSpecializationsMatch(
context,
leftDeclRef.substitutions,
rightDeclRef.substitutions,
stack) )
{
return false;
}
// Check that any declared fields match too.
if( auto leftStructDeclRef = leftDeclRef.As<AggTypeDecl>() )
{
if( auto rightStructDeclRef = rightDeclRef.As<AggTypeDecl>() )
{
List<DeclRef<StructField>> leftFields;
List<DeclRef<StructField>> rightFields;
collectFields(leftStructDeclRef, leftFields);
collectFields(rightStructDeclRef, rightFields);
UInt leftFieldCount = leftFields.Count();
UInt rightFieldCount = rightFields.Count();
if( leftFieldCount != rightFieldCount )
{
diagnoseTypeFieldsMismatch(context, leftDeclRef, rightDeclRef, stack);
return false;
}
for( UInt ii = 0; ii < leftFieldCount; ++ii )
{
auto leftField = leftFields[ii];
auto rightField = rightFields[ii];
if( leftField.GetName() != rightField.GetName() )
{
diagnoseTypeFieldsMismatch(context, leftDeclRef, rightDeclRef, stack);
return false;
}
auto leftFieldType = GetType(leftField);
auto rightFieldType = GetType(rightField);
StructuralTypeMatchStack subStack;
subStack.parent = stack;
subStack.leftDecl = leftField;
subStack.rightDecl = rightField;
if(!validateTypesMatch(context, leftFieldType,rightFieldType, &subStack))
return false;
}
}
}
// Everything seemed to match recursively.
return true;
}
}
}
// If we are looking at `T[N]` and `U[M]` we want to check that
// `T` is structurally equivalent to `U` and `N` is the same as `M`.
else if( auto leftArrayType = left->As<ArrayExpressionType>() )
{
if( auto rightArrayType = right->As<ArrayExpressionType>() )
{
if(!validateTypesMatch(context, leftArrayType->baseType, rightArrayType->baseType, stack) )
return false;
if(!validateValuesMatch(context, leftArrayType->ArrayLength, rightArrayType->ArrayLength, stack))
return false;
return true;
}
}
diagnoseTypeMismatch(context, stack);
return false;
}
// This function is supposed to determine if two global shader
// parameter declarations represent the same logical parameter
// (so that they should get the exact same binding(s) allocated).
//
static bool doesParameterMatch(
ParameterBindingContext* context,
RefPtr<VarLayout> varLayout,
ParameterInfo* parameterInfo)
{
// Any "varying" parameter should automatically be excluded
//
// Note that we use the `typeLayout` field rather than
// looking at resource information on the variable directly,
// because this may be called when binding hasn't been performed.
for (auto rr : varLayout->typeLayout->resourceInfos)
{
switch (rr.kind)
{
case LayoutResourceKind::VertexInput:
case LayoutResourceKind::FragmentOutput:
return false;
default:
break;
}
}
StructuralTypeMatchStack stack;
stack.parent = nullptr;
stack.leftDecl = varLayout->varDecl;
stack.rightDecl = parameterInfo->varLayouts[0]->varDecl;
validateTypesMatch(context, varLayout->typeLayout->type, parameterInfo->varLayouts[0]->typeLayout->type, &stack);
return true;
}
//
// Given a GLSL `layout` modifier, we need to be able to check for
// a particular sub-argument and extract its value if present.
template<typename T>
static bool findLayoutArg(
RefPtr<ModifiableSyntaxNode> syntax,
UInt* outVal)
{
for( auto modifier : syntax->GetModifiersOfType<T>() )
{
if( modifier )
{
*outVal = (UInt) strtoull(modifier->valToken.Content.Buffer(), nullptr, 10);
return true;
}
}
return false;
}
template<typename T>
static bool findLayoutArg(
DeclRef<Decl> declRef,
UInt* outVal)
{
return findLayoutArg<T>(declRef.getDecl(), outVal);
}
//
static bool isGLSLBuiltinName(VarDeclBase* varDecl)
{
return getText(getReflectionName(varDecl)).StartsWith("gl_");
}
RefPtr<Type> tryGetEffectiveTypeForGLSLVaryingInput(
ParameterBindingContext* context,
VarDeclBase* varDecl)
{
if (isGLSLBuiltinName(varDecl))
return nullptr;
auto type = varDecl->getType();
if( varDecl->HasModifier<InModifier>() || type->As<GLSLInputParameterGroupType>())
{
// Special case to handle "arrayed" shader inputs, as used
// for Geometry and Hull input
switch( context->stage )
{
case Stage::Geometry:
case Stage::Hull:
case Stage::Domain:
// Tessellation `patch` variables should stay as written
if( !varDecl->HasModifier<GLSLPatchModifier>() )
{
// Unwrap array type, if prsent
if( auto arrayType = type->As<ArrayExpressionType>() )
{
type = arrayType->baseType.Ptr();
}
}
break;
default:
break;
}
return type;
}
return nullptr;
}
RefPtr<Type> tryGetEffectiveTypeForGLSLVaryingOutput(
ParameterBindingContext* context,
VarDeclBase* varDecl)
{
if (isGLSLBuiltinName(varDecl))
return nullptr;
auto type = varDecl->getType();
if( varDecl->HasModifier<OutModifier>() || type->As<GLSLOutputParameterGroupType>())
{
// Special case to handle "arrayed" shader outputs, as used
// for Hull Shader output
//
// Note(tfoley): there is unfortunate code duplication
// with the `in` case above.
switch( context->stage )
{
case Stage::Hull:
// Tessellation `patch` variables should stay as written
if( !varDecl->HasModifier<GLSLPatchModifier>() )
{
// Unwrap array type, if prsent
if( auto arrayType = type->As<ArrayExpressionType>() )
{
type = arrayType->baseType.Ptr();
}
}
break;
default:
break;
}
return type;
}
return nullptr;
}
RefPtr<TypeLayout>
getTypeLayoutForGlobalShaderParameter_GLSL(
ParameterBindingContext* context,
VarDeclBase* varDecl)
{
auto layoutContext = context->layoutContext;
auto rules = layoutContext.getRulesFamily();
auto type = varDecl->getType();
// A GLSL shader parameter will be marked with
// a qualifier to match the boundary it uses
//
// In the case of a parameter block, we will have
// consumed this qualifier as part of parsing,
// so that it won't be present on the declaration
// any more. As such we also inspect the type
// of the variable.
// 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>() && type->As<ConstantBufferType>() )
{
return CreateTypeLayout(
layoutContext.with(rules->getPushConstantBufferRules()),
type);
}
// TODO(tfoley): We have multiple variations of
// the `uniform` modifier right now, and that
// needs to get fixed...
if( varDecl->HasModifier<HLSLUniformModifier>() || type->As<ConstantBufferType>() )
{
return CreateTypeLayout(
layoutContext.with(rules->getConstantBufferRules()),
type);
}
if( varDecl->HasModifier<GLSLBufferModifier>() || type->As<GLSLShaderStorageBufferType>() )
{
return CreateTypeLayout(
layoutContext.with(rules->getShaderStorageBufferRules()),
type);
}
if (auto effectiveVaryingInputType = tryGetEffectiveTypeForGLSLVaryingInput(context, varDecl))
{
// We expect to handle these elsewhere
SLANG_DIAGNOSE_UNEXPECTED(getSink(context), varDecl, "GLSL varying input");
return CreateTypeLayout(
layoutContext.with(rules->getVaryingInputRules()),
effectiveVaryingInputType);
}
if (auto effectiveVaryingOutputType = tryGetEffectiveTypeForGLSLVaryingOutput(context, varDecl))
{
// We expect to handle these elsewhere
SLANG_DIAGNOSE_UNEXPECTED(getSink(context), varDecl, "GLSL varying output");
return CreateTypeLayout(
layoutContext.with(rules->getVaryingOutputRules()),
effectiveVaryingOutputType);
}
// A `const` global with a `layout(constant_id = ...)` modifier
// is a declaration of a specialization constant.
if( varDecl->HasModifier<GLSLConstantIDLayoutModifier>() )
{
return CreateTypeLayout(
layoutContext.with(rules->getSpecializationConstantRules()),
type);
}
// GLSL says that an "ordinary" global variable
// is just a (thread local) global and not a
// parameter
return nullptr;
}
RefPtr<TypeLayout>
getTypeLayoutForGlobalShaderParameter_HLSL(
ParameterBindingContext* context,
VarDeclBase* varDecl)
{
auto layoutContext = context->layoutContext;
auto rules = layoutContext.getRulesFamily();
auto type = varDecl->getType();
// 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>() && type->As<ConstantBufferType>())
{
return CreateTypeLayout(
layoutContext.with(rules->getPushConstantBufferRules()),
type);
}
// HLSL `static` modifier indicates "thread local"
if(varDecl->HasModifier<HLSLStaticModifier>())
return nullptr;
// HLSL `groupshared` modifier indicates "thread-group local"
if(varDecl->HasModifier<HLSLGroupSharedModifier>())
return nullptr;
// TODO(tfoley): there may be other cases that we need to handle here
// An "ordinary" global variable is implicitly a uniform
// shader parameter.
return CreateTypeLayout(
layoutContext.with(rules->getConstantBufferRules()),
type);
}
// 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)
{
switch( context->sourceLanguage )
{
case SourceLanguage::Slang:
case SourceLanguage::HLSL:
return getTypeLayoutForGlobalShaderParameter_HLSL(context, varDecl);
case SourceLanguage::GLSL:
return getTypeLayoutForGlobalShaderParameter_GLSL(context, varDecl);
default:
SLANG_UNEXPECTED("unhandled source language");
UNREACHABLE_RETURN(nullptr);
}
}
//
enum EntryPointParameterDirection
{
kEntryPointParameterDirection_Input = 0x1,
kEntryPointParameterDirection_Output = 0x2,
};
typedef unsigned int EntryPointParameterDirectionMask;
struct EntryPointParameterState
{
String* optSemanticName = nullptr;
int* ioSemanticIndex = nullptr;
EntryPointParameterDirectionMask directionMask;
int semanticSlotCount;
Stage stage = Stage::Unknown;
bool isSampleRate = false;
SourceLoc loc;
};
static RefPtr<TypeLayout> processEntryPointParameter(
ParameterBindingContext* context,
RefPtr<Type> type,
EntryPointParameterState const& state,
RefPtr<VarLayout> varLayout);
static void collectGlobalScopeGLSLVaryingParameter(
ParameterBindingContext* context,
RefPtr<VarDeclBase> varDecl,
RefPtr<Type> effectiveType,
EntryPointParameterDirection direction)
{
int defaultSemanticIndex = 0;
EntryPointParameterState state;
state.directionMask = direction;
state.ioSemanticIndex = &defaultSemanticIndex;
state.stage = context->stage;
state.loc = varDecl->loc;
RefPtr<VarLayout> varLayout = new VarLayout();
varLayout->varDecl = makeDeclRef(varDecl.Ptr());
varLayout->typeLayout = processEntryPointParameter(
context,
effectiveType,
state,
varLayout);
// Now add it to our list of reflection parameters, so
// that it can get a location assigned later...
ParameterInfo* parameterInfo = new ParameterInfo();
parameterInfo->translationUnit = context->translationUnit;
context->shared->parameters.Add(parameterInfo);
parameterInfo->varLayouts.Add(varLayout);
}
// Collect a single declaration into our set of parameters
static void collectGlobalGenericParameter(
ParameterBindingContext* context,
RefPtr<GlobalGenericParamDecl> paramDecl)
{
RefPtr<GenericParamLayout> layout = new GenericParamLayout();
layout->decl = paramDecl;
layout->index = (int)context->shared->programLayout->globalGenericParams.Count();
context->shared->programLayout->globalGenericParams.Add(layout);
context->shared->programLayout->globalGenericParamsMap[layout->decl->getName()->text] = layout.Ptr();
}
// Collect a single declaration into our set of parameters
static void collectGlobalScopeParameter(
ParameterBindingContext* context,
RefPtr<VarDeclBase> varDecl)
{
// HACK: We need to intercept GLSL varying `in` and `out` here, way earlier
// in the process, so that we can avoid all kinds of nastiness that would
// otherwise be applied to them.
if (context->sourceLanguage == SourceLanguage::GLSL)
{
if (auto effectiveVaryingInputType = tryGetEffectiveTypeForGLSLVaryingInput(context, varDecl))
{
collectGlobalScopeGLSLVaryingParameter(context, varDecl, effectiveVaryingInputType, kEntryPointParameterDirection_Input);
return;
}
if (auto effectiveVaryingOutputType = tryGetEffectiveTypeForGLSLVaryingOutput(context, varDecl))
{
collectGlobalScopeGLSLVaryingParameter(context, varDecl, effectiveVaryingOutputType, kEntryPointParameterDirection_Output);
return;
}
}
// 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,
varDecl.Ptr());
// If we did not find appropriate layout rules, then it
// must mean that this global variable is *not* a shader
// parameter.
if(!typeLayout)
return;
// Now create a variable layout that we can use
RefPtr<VarLayout> varLayout = new VarLayout();
varLayout->typeLayout = typeLayout;
varLayout->varDecl = DeclRef<Decl>(varDecl.Ptr(), nullptr).As<VarDeclBase>();
// This declaration may represent the same logical parameter
// as a declaration that came from a different translation unit.
// If that is the case, we want to re-use the same `VarLayout`
// across both parameters.
//
// First we look for an existing entry matching the name
// of this parameter:
auto parameterName = getReflectionName(varDecl);
ParameterInfo* parameterInfo = nullptr;
if( context->mapNameToParameterInfo.TryGetValue(parameterName, parameterInfo) )
{
// If the parameters have the same name, but don't "match" according to some reasonable rules,
// then we need to bail out.
if( !doesParameterMatch(context, varLayout, parameterInfo) )
{
parameterInfo = nullptr;
}
}
// If we didn't find a matching parameter, then we need to create one here
if( !parameterInfo )
{
parameterInfo = new ParameterInfo();
context->shared->parameters.Add(parameterInfo);
context->mapNameToParameterInfo.AddIfNotExists(parameterName, parameterInfo);
}
else
{
varLayout->flags |= VarLayoutFlag::IsRedeclaration;
}
// Add this variable declaration to the list of declarations for the parameter
parameterInfo->varLayouts.Add(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 void markSpaceUsed(
ParameterBindingContext* context,
UInt space)
{
context->shared->usedSpaces.Add(nullptr, space, space+1);
}
static UInt allocateUnusedSpaces(
ParameterBindingContext* context,
UInt count)
{
return context->shared->usedSpaces.Allocate(nullptr, count);
}
static RefPtr<UsedRangeSet> findUsedRangeSetForTranslationUnit(
ParameterBindingContext* context,
TranslationUnitRequest* translationUnit)
{
if (!translationUnit)
return findUsedRangeSetForSpace(context, 0);
RefPtr<UsedRangeSet> usedRangeSet;
if (context->shared->translationUnitUsedRangeSets.TryGetValue(translationUnit, usedRangeSet))
return usedRangeSet;
usedRangeSet = new UsedRangeSet();
context->shared->translationUnitUsedRangeSets.Add(translationUnit, usedRangeSet);
return usedRangeSet;
}
static void addExplicitParameterBinding(
ParameterBindingContext* context,
RefPtr<ParameterInfo> parameterInfo,
VarDeclBase* varDecl,
LayoutSemanticInfo const& semanticInfo,
UInt count,
RefPtr<UsedRangeSet> usedRangeSet = nullptr)
{
auto kind = semanticInfo.kind;
auto& bindingInfo = parameterInfo->bindingInfo[(int)kind];
if( bindingInfo.count != 0 )
{
// We already have a binding here, so we want to
// confirm that it matches the new one that is
// incoming...
if( bindingInfo.count != count
|| bindingInfo.index != semanticInfo.index
|| bindingInfo.space != semanticInfo.space )
{
getSink(context)->diagnose(varDecl, Diagnostics::conflictingExplicitBindingsForParameter, getReflectionName(varDecl));
auto firstVarDecl = parameterInfo->varLayouts[0]->varDecl.getDecl();
if( firstVarDecl != varDecl )
{
getSink(context)->diagnose(firstVarDecl, Diagnostics::seeOtherDeclarationOf, getReflectionName(firstVarDecl));
}
}
// TODO(tfoley): `register` semantics can technically be
// profile-specific (not sure if anybody uses that)...
}
else
{
bindingInfo.count = count;
bindingInfo.index = semanticInfo.index;
bindingInfo.space = semanticInfo.space;
if (!usedRangeSet)
{
usedRangeSet = findUsedRangeSetForSpace(context, semanticInfo.space);
// Record that the particular binding space was
// used by an explicit binding, so that we don't
// claim it for auto-generated bindings that
// need to grab a full space
markSpaceUsed(context, semanticInfo.space);
}
auto overlappedParameterInfo = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add(
parameterInfo,
semanticInfo.index,
semanticInfo.index + count);
if (overlappedParameterInfo)
{
auto paramA = parameterInfo->varLayouts[0]->varDecl.getDecl();
auto paramB = overlappedParameterInfo->varLayouts[0]->varDecl.getDecl();
getSink(context)->diagnose(paramA, Diagnostics::parameterBindingsOverlap,
getReflectionName(paramA),
getReflectionName(paramB));
getSink(context)->diagnose(paramB, Diagnostics::seeDeclarationOf, getReflectionName(paramB));
}
}
}
static void addExplicitParameterBindings_HLSL(
ParameterBindingContext* context,
RefPtr<ParameterInfo> parameterInfo,
RefPtr<VarLayout> varLayout)
{
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);
int count = 0;
if (typeRes)
{
count = (int) typeRes->count;
}
else
{
// TODO: warning here!
}
addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, count);
}
}
static void addExplicitParameterBindings_GLSL(
ParameterBindingContext* context,
RefPtr<ParameterInfo> parameterInfo,
RefPtr<VarLayout> varLayout)
{
auto typeLayout = varLayout->typeLayout;
auto varDecl = varLayout->varDecl;
// The catch in GLSL is that the expected resource type
// is implied by the parameter declaration itself, and
// the `layout` modifier is only allowed to adjust
// the index/offset/etc.
//
// We also may need to store explicit binding info in a different place,
// in the case of varying input/output, since we don't want to collect
// things globally;
RefPtr<UsedRangeSet> usedRangeSet;
TypeLayout::ResourceInfo* resInfo = nullptr;
LayoutSemanticInfo semanticInfo;
semanticInfo.index = 0;
semanticInfo.space = 0;
if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::DescriptorTableSlot)) != nullptr )
{
// Try to find `binding` and `set`
auto attr = varDecl.getDecl()->FindModifier<GLSLBindingAttribute>();
if (!attr)
{
return;
}
semanticInfo.index = attr->binding;
semanticInfo.space = attr->set;
}
else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::VertexInput)) != nullptr )
{
// Try to find `location` binding
if(!findLayoutArg<GLSLLocationLayoutModifier>(varDecl, &semanticInfo.index))
return;
usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit);
}
else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::FragmentOutput)) != nullptr )
{
// Try to find `location` binding
if(!findLayoutArg<GLSLLocationLayoutModifier>(varDecl, &semanticInfo.index))
return;
usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit);
}
else if( (resInfo = typeLayout->FindResourceInfo(LayoutResourceKind::SpecializationConstant)) != nullptr )
{
// Try to find `constant_id` binding
if(!findLayoutArg<GLSLConstantIDLayoutModifier>(varDecl, &semanticInfo.index))
return;
}
// If we didn't find any matches, then bail
if(!resInfo)
return;
auto kind = resInfo->kind;
auto count = resInfo->count;
semanticInfo.kind = kind;
addExplicitParameterBinding(context, parameterInfo, varDecl, semanticInfo, int(count), usedRangeSet);
}
// Given a single parameter, collect whatever information we have on
// how it has been explicitly bound, which may come from multiple declarations
void generateParameterBindings(
ParameterBindingContext* context,
RefPtr<ParameterInfo> parameterInfo)
{
// There must be at least one declaration for the parameter.
SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
// Iterate over all declarations looking for explicit binding information.
for( auto& varLayout : parameterInfo->varLayouts )
{
// Handle HLSL `register` and `packoffset` modifiers
addExplicitParameterBindings_HLSL(context, parameterInfo, varLayout);
// Handle GLSL `layout` modifiers
addExplicitParameterBindings_GLSL(context, parameterInfo, varLayout);
}
}
// Generate the binding information for a shader parameter.
static void completeBindingsForParameter(
ParameterBindingContext* context,
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.
//
// We will use the first declaration of the parameter as
// a stand-in for all the declarations, so it is important
// that earlier code has validated that the declarations
// "match".
SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
auto firstVarLayout = parameterInfo->varLayouts.First();
auto firstTypeLayout = firstVarLayout->typeLayout;
for(auto typeRes : firstTypeLayout->resourceInfos)
{
// Did we already apply some explicit binding information
// for this resource kind?
auto kind = typeRes.kind;
auto& bindingInfo = parameterInfo->bindingInfo[(int)kind];
if( bindingInfo.count != 0 )
{
// If things have already been bound, our work is done.
continue;
}
auto count = typeRes.count;
// We need to special-case the scenario where
// a parameter wants to claim an entire register
// space to itself (for a parameter block), since
// that can't be handled like other resources.
if (kind == LayoutResourceKind::RegisterSpace)
{
// We need to snag a register space of our own.
UInt space = allocateUnusedSpaces(context, count);
bindingInfo.count = count;
bindingInfo.index = space;
// 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;
}
else if (kind == LayoutResourceKind::GenericResource)
{
bindingInfo.space = 0;
bindingInfo.count = 1;
bindingInfo.index = 0;
continue;
}
// Auto-generated bindings will all go in the same space,
// which was allocated up front.
//
// We don't currently worry about running out of room in
// this space; if the user declares enough parameters
// to overflow the range then we will have other problems
// on our hands.
//
// The one case that might seem like a challenge is unsized
// arrays, since these conceptually require a (countably)
// infinite register range.
//
// This turns out not to be a problem that this code
// needs to handle, for two reasons:
//
// 1) In the D3D case, an unbounded-size array should be
// computed to require one (or more) whole register spaces,
// and so we'd end up in the `RegisterSpace` case above.
//
// 2) In the Vulkan case, an unbounded-size array of
// resources still uses only a single binding, so we
// won't run out of space.
//
UInt space = context->shared->defaultSpace;
RefPtr<UsedRangeSet> usedRangeSet;
switch (kind)
{
default:
usedRangeSet = findUsedRangeSetForSpace(context, space);
break;
case LayoutResourceKind::VertexInput:
case LayoutResourceKind::FragmentOutput:
usedRangeSet = findUsedRangeSetForTranslationUnit(context, parameterInfo->translationUnit);
break;
}
bindingInfo.count = count;
bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(parameterInfo, (int) count);
bindingInfo.space = space;
}
if (firstTypeLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
{
}
// At this point we should have explicit binding locations chosen for
// all the relevant resource kinds, so we can apply these to the
// declarations:
for(auto& varLayout : parameterInfo->varLayouts)
{
for(auto k = 0; k < kLayoutResourceKindCount; ++k)
{
auto kind = LayoutResourceKind(k);
auto& bindingInfo = parameterInfo->bindingInfo[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;
}
}
}
static void collectGlobalScopeParameters(
ParameterBindingContext* context,
ModuleDecl* program)
{
// First enumerate parameters at global scope
// We collect two things here:
// 1. A shader parameter, which is always a variable
// 2. A global entry-point generic parameter type (`__generic_param`),
// which is a GlobalGenericParamDecl
// We collect global generic type parameters in the first pass,
// So we can fill in the correct index into ordinary type layouts
// for generic types in the second pass.
for (auto decl : program->Members)
{
if (auto genParamDecl = decl.As<GlobalGenericParamDecl>())
collectGlobalGenericParameter(context, genParamDecl);
}
for (auto decl : program->Members)
{
if (auto varDecl = decl.As<VarDeclBase>())
collectGlobalScopeParameter(context, varDecl);
}
// Next, we need to enumerate the parameters of
// each entry point (which requires knowing what the
// entry points *are*)
// TODO(tfoley): Entry point functions should be identified
// by looking for a generated modifier that is attached
// to global-scope function declarations.
}
struct SimpleSemanticInfo
{
String name;
int index;
};
SimpleSemanticInfo decomposeSimpleSemantic(
HLSLSimpleSemantic* semantic)
{
auto composedName = semantic->name.Content;
// look for a trailing sequence of decimal digits
// at the end of the composed name
UInt length = composedName.Length();
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
info.name = composedName.SubString(0, indexLoc);
info.index = strtol(composedName.SubString(indexLoc, length - indexLoc).begin(), nullptr, 10);
}
return info;
}
static RefPtr<TypeLayout> processSimpleEntryPointParameter(
ParameterBindingContext* context,
RefPtr<Type> type,
EntryPointParameterState const& inState,
RefPtr<VarLayout> varLayout,
int semanticSlotCount = 1)
{
EntryPointParameterState state = inState;
state.semanticSlotCount = semanticSlotCount;
auto optSemanticName = state.optSemanticName;
auto semanticIndex = *state.ioSemanticIndex;
String semanticName = optSemanticName ? *optSemanticName : "";
String sn = semanticName.ToLower();
RefPtr<TypeLayout> typeLayout = new TypeLayout();
if (sn.StartsWith("sv_")
|| sn.StartsWith("nv_"))
{
// System-value semantic.
if (state.directionMask & kEntryPointParameterDirection_Output)
{
// Note: I'm just doing something expedient here and detecting `SV_Target`
// outputs and claiming the appropriate register range right away.
//
// TODO: we should really be building up some representation of all of this,
// once we've gone to the trouble of looking it all up...
if( sn == "sv_target" )
{
// TODO: construct a `ParameterInfo` we can use here so that
// overlapped layout errors get reported nicely.
auto usedResourceSet = findUsedRangeSetForSpace(context, 0);
usedResourceSet->usedResourceRanges[int(LayoutResourceKind::UnorderedAccess)].Add(nullptr, semanticIndex, semanticIndex + semanticSlotCount);
// We also need to track this as an ordinary varying output from the stage,
// since that is how GLSL will want to see it.
auto rules = context->getRulesFamily()->getVaryingOutputRules();
SimpleLayoutInfo layout = GetLayout(
context->layoutContext.with(rules),
type);
typeLayout->addResourceUsage(layout.kind, layout.size);
}
}
if (state.directionMask & kEntryPointParameterDirection_Input)
{
if (sn == "sv_sampleindex")
{
state.isSampleRate = true;
}
}
// Remember the system-value semantic so that we can query it later
if (varLayout)
{
varLayout->systemValueSemantic = semanticName;
varLayout->systemValueSemanticIndex = semanticIndex;
}
// TODO: add some kind of usage information for system input/output
}
else
{
// user-defined semantic
if (state.directionMask & kEntryPointParameterDirection_Input)
{
auto rules = context->getRulesFamily()->getVaryingInputRules();
SimpleLayoutInfo layout = GetLayout(
context->layoutContext.with(rules),
type);
typeLayout->addResourceUsage(layout.kind, layout.size);
}
if (state.directionMask & kEntryPointParameterDirection_Output)
{
auto rules = context->getRulesFamily()->getVaryingOutputRules();
SimpleLayoutInfo layout = GetLayout(
context->layoutContext.with(rules),
type);
typeLayout->addResourceUsage(layout.kind, layout.size);
}
}
if (state.isSampleRate
&& (state.directionMask & kEntryPointParameterDirection_Input)
&& (context->stage == Stage::Fragment))
{
if (auto entryPointLayout = context->entryPointLayout)
{
entryPointLayout->flags |= EntryPointLayout::Flag::usesAnySampleRateInput;
}
}
*state.ioSemanticIndex += state.semanticSlotCount;
typeLayout->type = type;
return typeLayout;
}
static RefPtr<TypeLayout> processEntryPointParameterDecl(
ParameterBindingContext* context,
Decl* decl,
RefPtr<Type> type,
EntryPointParameterState const& inState,
RefPtr<VarLayout> varLayout)
{
SimpleSemanticInfo semanticInfo;
int semanticIndex = 0;
EntryPointParameterState state = inState;
// If there is no explicit semantic already in effect, *and* we find an explicit
// semantic on the associated declaration, then we'll use it.
if( !state.optSemanticName )
{
if( auto semantic = decl->FindModifier<HLSLSimpleSemantic>() )
{
semanticInfo = decomposeSimpleSemantic(semantic);
semanticIndex = semanticInfo.index;
state.optSemanticName = &semanticInfo.name;
state.ioSemanticIndex = &semanticIndex;
}
}
if (decl)
{
if (decl->FindModifier<HLSLSampleModifier>())
{
state.isSampleRate = true;
}
}
// Default case: either there was an explicit semantic in effect already,
// *or* we couldn't find an explicit semantic to apply on the given
// declaration, so we will just recursive with whatever we have at
// the moment.
return processEntryPointParameter(context, type, state, varLayout);
}
static RefPtr<TypeLayout> processEntryPointParameter(
ParameterBindingContext* context,
RefPtr<Type> type,
EntryPointParameterState const& state,
RefPtr<VarLayout> varLayout)
{
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 = type->As<HLSLStreamOutputType>() )
{
//
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 = processEntryPointParameter(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::Callable:
case Stage::ClosestHit:
case Stage::Miss:
// `in out` or `out` parameter is payload
return CreateTypeLayout(context->layoutContext.with(
context->getRulesFamily()->getRayPayloadParameterRules()),
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 = type->As<BasicExpressionType>())
{
return processSimpleEntryPointParameter(context, basicType, state, varLayout);
}
else if(auto vectorType = type->As<VectorExpressionType>())
{
return processSimpleEntryPointParameter(context, vectorType, state, varLayout);
}
// A matrix is processed as if it was an array of rows
else if( auto matrixType = type->As<MatrixExpressionType>() )
{
auto rowCount = GetIntVal(matrixType->getRowCount());
return processSimpleEntryPointParameter(context, matrixType, state, varLayout, (int) rowCount);
}
else if( auto arrayType = type->As<ArrayExpressionType>() )
{
// 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 = processEntryPointParameter(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 )
{
processEntryPointParameter(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 = type->As<TextureType>()) { return nullptr; }
else if(auto samplerStateType = type->As<SamplerStateType>()) { return nullptr; }
else if(auto constantBufferType = type->As<ConstantBufferType>()) { return nullptr; }
// Catch declaration-reference types late in the sequence, since
// otherwise they will include all of the above cases...
else if( auto declRefType = type->As<DeclRefType>() )
{
auto declRef = declRefType->declRef;
if (auto structDeclRef = declRef.As<StructDecl>())
{
RefPtr<StructTypeLayout> structLayout = new StructTypeLayout();
structLayout->type = type;
// Need to recursively walk the fields of the structure now...
for( auto field : GetFields(structDeclRef) )
{
RefPtr<VarLayout> fieldVarLayout = new VarLayout();
fieldVarLayout->varDecl = field;
auto fieldTypeLayout = processEntryPointParameterDecl(
context,
field.getDecl(),
GetType(field),
state,
fieldVarLayout);
if(fieldTypeLayout)
{
fieldVarLayout->typeLayout = fieldTypeLayout;
for (auto rr : fieldTypeLayout->resourceInfos)
{
SLANG_RELEASE_ASSERT(rr.count != 0);
auto structRes = structLayout->findOrAddResourceInfo(rr.kind);
fieldVarLayout->findOrAddResourceInfo(rr.kind)->index = structRes->count;
structRes->count += rr.count;
}
}
structLayout->fields.Add(fieldVarLayout);
structLayout->mapVarToLayout.Add(field.getDecl(), fieldVarLayout);
}
return structLayout;
}
else if (auto globalGenericParam = declRef.As<GlobalGenericParamDecl>())
{
auto genParamTypeLayout = new GenericParamTypeLayout();
// we should have already populated ProgramLayout::genericEntryPointParams list at this point,
// so we can find the index of this generic param decl in the list
genParamTypeLayout->type = type;
genParamTypeLayout->paramIndex = findGenericParam(context->shared->programLayout->globalGenericParams, globalGenericParam.getDecl());
genParamTypeLayout->findOrAddResourceInfo(LayoutResourceKind::GenericResource)->count++;
return genParamTypeLayout;
}
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 = type->As<ErrorType>() )
{
return nullptr;
}
SLANG_UNEXPECTED("unhandled type kind");
UNREACHABLE_RETURN(nullptr);
}
static void collectEntryPointParameters(
ParameterBindingContext* context,
EntryPointRequest* entryPoint,
SubstitutionSet typeSubst)
{
FuncDecl* entryPointFuncDecl = entryPoint->decl;
if (!entryPointFuncDecl)
{
// Something must have failed earlier, so that
// we didn't find a declaration to match this
// entry point request.
return;
}
// Create the layout object here
auto entryPointLayout = new EntryPointLayout();
entryPointLayout->profile = entryPoint->profile;
entryPointLayout->entryPoint = entryPointFuncDecl;
context->entryPointLayout = entryPointLayout;
context->shared->programLayout->entryPoints.Add(entryPointLayout);
// Okay, we seemingly have an entry-point function, and now we need to collect info on its parameters too
//
// TODO: Long-term we probably want complete information on all inputs/outputs of an entry point,
// but for now we are really just trying to scrape information on fragment outputs, so lets do that:
//
// TODO: check whether we should enumerate the parameters before the return type, or vice versa
int defaultSemanticIndex = 0;
EntryPointParameterState state;
state.ioSemanticIndex = &defaultSemanticIndex;
state.optSemanticName = nullptr;
state.semanticSlotCount = 0;
state.stage = entryPoint->getStage();
for( auto m : entryPointFuncDecl->Members )
{
auto paramDecl = m.As<VarDeclBase>();
if(!paramDecl)
continue;
// We have an entry-point parameter, and need to figure out what to do with it.
state.loc = paramDecl->loc;
// TODO: need to handle `uniform`-qualified parameters here
if (paramDecl->HasModifier<HLSLUniformModifier>())
continue;
state.directionMask = 0;
// If it appears to be an input, process it as such.
if( paramDecl->HasModifier<InModifier>() || paramDecl->HasModifier<InOutModifier>() || !paramDecl->HasModifier<OutModifier>() )
{
state.directionMask |= kEntryPointParameterDirection_Input;
}
// If it appears to be an output, process it as such.
if(paramDecl->HasModifier<OutModifier>() || paramDecl->HasModifier<InOutModifier>())
{
state.directionMask |= kEntryPointParameterDirection_Output;
}
RefPtr<VarLayout> paramVarLayout = new VarLayout();
paramVarLayout->varDecl = makeDeclRef(paramDecl.Ptr());
auto paramTypeLayout = processEntryPointParameterDecl(
context,
paramDecl.Ptr(),
paramDecl->type.type->Substitute(typeSubst).As<Type>(),
state,
paramVarLayout);
// Skip parameters for which we could not compute a layout
if(!paramTypeLayout)
continue;
paramVarLayout->typeLayout = paramTypeLayout;
for (auto rr : paramTypeLayout->resourceInfos)
{
auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind);
paramVarLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count;
entryPointRes->count += rr.count;
}
entryPointLayout->fields.Add(paramVarLayout);
entryPointLayout->mapVarToLayout.Add(paramDecl, paramVarLayout);
}
// If we have a non-`void` output type for the entry point, then process it as
// an output parameter.
auto resultType = entryPointFuncDecl->ReturnType.type;
if( !resultType->Equals(resultType->getSession()->getVoidType()) )
{
state.loc = entryPointFuncDecl->loc;
state.directionMask = kEntryPointParameterDirection_Output;
RefPtr<VarLayout> resultLayout = new VarLayout();
auto resultTypeLayout = processEntryPointParameterDecl(
context,
entryPointFuncDecl,
resultType->Substitute(typeSubst).As<Type>(),
state,
resultLayout);
if( resultTypeLayout )
{
resultLayout->typeLayout = resultTypeLayout;
for (auto rr : resultTypeLayout->resourceInfos)
{
auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind);
resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count;
entryPointRes->count += rr.count;
}
}
entryPointLayout->resultLayout = resultLayout;
}
}
// When doing parameter binding for global-scope stuff in GLSL,
// we may need to know what stage we are compiling for, so that
// we can handle special cases appropriately (e.g., "arrayed"
// inputs and outputs).
static Stage
inferStageForTranslationUnit(
TranslationUnitRequest* translationUnit)
{
// In the specific case where we are compiling GLSL input,
// and have only a single entry point, use the stage
// of the entry point.
//
// TODO: now that we've dropped official GLSL support,
// we probably should drop this as well.
//
if( translationUnit->sourceLanguage == SourceLanguage::GLSL )
{
if( translationUnit->entryPoints.Count() == 1 )
{
return translationUnit->entryPoints[0]->getStage();
}
}
return Stage::Unknown;
}
static void collectModuleParameters(
ParameterBindingContext* inContext,
ModuleDecl* module)
{
// Each loaded module provides a separate (logical) namespace for
// parameters, so that two parameters with the same name, in
// distinct modules, should yield different bindings.
//
ParameterBindingContext contextData = *inContext;
auto context = &contextData;
context->translationUnit = nullptr;
context->stage = Stage::Unknown;
// All imported modules are implicitly Slang code
context->sourceLanguage = SourceLanguage::Slang;
// A loaded module cannot define entry points that
// we'll expose (for now), so we just need to
// consider global-scope parameters.
collectGlobalScopeParameters(context, module);
}
static void collectParameters(
ParameterBindingContext* inContext,
CompileRequest* request)
{
// 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;
for( auto& translationUnit : request->translationUnits )
{
context->translationUnit = translationUnit;
context->stage = inferStageForTranslationUnit(translationUnit.Ptr());
context->sourceLanguage = translationUnit->sourceLanguage;
// First look at global-scope parameters
collectGlobalScopeParameters(context, translationUnit->SyntaxNode.Ptr());
// Next consider parameters for entry points
for( auto& entryPoint : translationUnit->entryPoints )
{
context->stage = entryPoint->getStage();
collectEntryPointParameters(context, entryPoint.Ptr(), SubstitutionSet());
}
context->entryPointLayout = nullptr;
}
// Now collect parameters from loaded modules
for (auto& loadedModule : request->loadedModulesList)
{
collectModuleParameters(context, loadedModule->moduleDecl.Ptr());
}
}
void generateParameterBindings(
TargetRequest* targetReq)
{
CompileRequest* compileReq = targetReq->compileRequest;
// Try to find rules based on the selected code-generation target
auto layoutContext = getInitialLayoutContextForTarget(targetReq);
// If there was no target, or there are no rules for the target,
// then bail out here.
if (!layoutContext.rules)
return;
RefPtr<ProgramLayout> programLayout = new ProgramLayout();
programLayout->targetRequest = targetReq;
targetReq->layout = programLayout;
// Create a context to hold shared state during the process
// of generating parameter bindings
SharedParameterBindingContext sharedContext;
sharedContext.compileRequest = compileReq;
sharedContext.defaultLayoutRules = layoutContext.getRulesFamily();
sharedContext.programLayout = programLayout;
// Create a sub-context to collect parameters that get
// declared into the global scope
ParameterBindingContext context;
context.shared = &sharedContext;
context.translationUnit = nullptr;
context.layoutContext = layoutContext;
// Walk through AST to discover all the parameters
collectParameters(&context, compileReq);
// Now walk through the parameters to generate initial binding information
for( auto& parameter : sharedContext.parameters )
{
generateParameterBindings(&context, parameter);
}
// Determine if there are any global-scope parameters that use `Uniform`
// resources, and thus need to get packaged into a constant buffer.
//
// Note: this doesn't account for GLSL's support for "legacy" uniforms
// at global scope, which don't get assigned a CB.
bool needDefaultConstantBuffer = false;
for( auto& parameterInfo : sharedContext.parameters )
{
SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
auto firstVarLayout = parameterInfo->varLayouts.First();
// Does the field have any uniform data?
if( firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
{
needDefaultConstantBuffer = true;
break;
}
}
// 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)
{
for (auto& parameterInfo : sharedContext.parameters)
{
SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
auto firstVarLayout = parameterInfo->varLayouts.First();
// Does the parameter have any resource usage that isn't just
// allocating a whole register space?
for (auto resInfo : firstVarLayout->typeLayout->resourceInfos)
{
if (resInfo.kind != LayoutResourceKind::RegisterSpace)
{
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.
ParameterBindingInfo globalConstantBufferBinding;
globalConstantBufferBinding.index = 0;
globalConstantBufferBinding.space = 0;
if( needDefaultConstantBuffer )
{
// TODO: this logic is only correct for D3D targets, where
// global-scope uniforms get wrapped into a constant buffer.
UInt space = sharedContext.defaultSpace;
auto usedRangeSet = findUsedRangeSetForSpace(&context, space);
globalConstantBufferBinding.index =
usedRangeSet->usedResourceRanges[
(int)LayoutResourceKind::ConstantBuffer].Allocate(nullptr, 1);
globalConstantBufferBinding.space = space;
}
// Now walk through again to actually give everything
// ranges of registers...
for( auto& parameter : sharedContext.parameters )
{
completeBindingsForParameter(&context, parameter);
}
// TODO: need to deal with parameters declared inside entry-point
// parameter lists at some point...
// Next we need to create a type layout to reflect the information
// we have collected.
// We will lay out any bare uniforms at the global scope into
// a single constant buffer. This is appropriate for HLSL global-scope
// uniforms, and Vulkan GLSL doesn't allow uniforms at global scope,
// so it should work out.
//
// For legacy GLSL targets, we'd probably need a distinct resource
// kind and set of rules here, since legacy uniforms are not the
// same as the contents of a constant buffer.
auto globalScopeRules = context.getRulesFamily()->getConstantBufferRules();
RefPtr<StructTypeLayout> globalScopeStructLayout = new StructTypeLayout();
globalScopeStructLayout->rules = globalScopeRules;
UniformLayoutInfo structLayoutInfo = globalScopeRules->BeginStructLayout();
for( auto& parameterInfo : sharedContext.parameters )
{
SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
auto firstVarLayout = parameterInfo->varLayouts.First();
// Does the field have any uniform data?
auto layoutInfo = firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
size_t uniformSize = layoutInfo ? layoutInfo->count : 0;
if( uniformSize != 0 )
{
// Make sure uniform fields get laid out properly...
UniformLayoutInfo fieldInfo(
uniformSize,
firstVarLayout->typeLayout->uniformAlignment);
size_t uniformOffset = globalScopeRules->AddStructField(
&structLayoutInfo,
fieldInfo);
for( auto& varLayout : parameterInfo->varLayouts )
{
varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset;
}
}
globalScopeStructLayout->fields.Add(firstVarLayout);
for( auto& varLayout : parameterInfo->varLayouts )
{
globalScopeStructLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout);
}
}
globalScopeRules->EndStructLayout(&structLayoutInfo);
RefPtr<TypeLayout> globalScopeLayout = globalScopeStructLayout;
// If there are global-scope uniforms, then we need to wrap
// up a global constant buffer type layout to hold them
if( needDefaultConstantBuffer )
{
auto globalConstantBufferLayout = createParameterGroupTypeLayout(
layoutContext,
nullptr,
globalScopeRules,
globalScopeRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
globalScopeStructLayout);
globalScopeLayout = globalConstantBufferLayout;
}
// We now have a bunch of layout information, which we should
// record into a suitable object that represents the program
RefPtr<VarLayout> globalVarLayout = new VarLayout();
globalVarLayout->typeLayout = globalScopeLayout;
if (needDefaultConstantBuffer)
{
auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer);
cbInfo->space = globalConstantBufferBinding.space;
cbInfo->index = globalConstantBufferBinding.index;
}
programLayout->globalScopeLayout = globalVarLayout;
}
StructTypeLayout* getGlobalStructLayout(
ProgramLayout* programLayout);
RefPtr<ProgramLayout> specializeProgramLayout(
TargetRequest * targetReq,
ProgramLayout* programLayout,
SubstitutionSet typeSubst)
{
RefPtr<ProgramLayout> newProgramLayout;
newProgramLayout = new ProgramLayout();
newProgramLayout->targetRequest = targetReq;
newProgramLayout->globalGenericParams = programLayout->globalGenericParams;
List<RefPtr<TypeLayout>> paramTypeLayouts;
auto globalStructLayout = getGlobalStructLayout(programLayout);
SLANG_ASSERT(globalStructLayout);
RefPtr<StructTypeLayout> structLayout = new StructTypeLayout();
RefPtr<TypeLayout> globalScopeLayout = structLayout;
structLayout->uniformAlignment = globalStructLayout->uniformAlignment;
// Try to find rules based on the selected code-generation target
auto layoutContext = getInitialLayoutContextForTarget(targetReq);
// If there was no target, or there are no rules for the target,
// then bail out here.
if (!layoutContext.rules)
return newProgramLayout;
// we need to initialize a layout context to mark used registers
SharedParameterBindingContext sharedContext;
sharedContext.compileRequest = targetReq->compileRequest;
sharedContext.defaultLayoutRules = layoutContext.getRulesFamily();
sharedContext.programLayout = newProgramLayout;
// Create a sub-context to collect parameters that get
// declared into the global scope
ParameterBindingContext context;
context.shared = &sharedContext;
context.translationUnit = nullptr;
context.layoutContext = layoutContext;
for (auto & translationUnit : targetReq->compileRequest->translationUnits)
{
for (auto & entryPoint : translationUnit->entryPoints)
{
collectEntryPointParameters(&context, entryPoint, typeSubst);
}
context.entryPointLayout = nullptr;
}
auto constantBufferRules = context.getRulesFamily()->getConstantBufferRules();
structLayout->rules = constantBufferRules;
structLayout->fields.SetSize(globalStructLayout->fields.Count());
UniformLayoutInfo structLayoutInfo;
structLayoutInfo.alignment = globalStructLayout->uniformAlignment;
structLayoutInfo.size = 0;
bool anyUniforms = false;
Dictionary<RefPtr<VarLayout>, RefPtr<VarLayout>> varLayoutMapping;
for (uint32_t varId = 0; varId < globalStructLayout->fields.Count(); varId++)
{
auto &varLayout = globalStructLayout->fields[varId];
// To recover layout context, we skip generic resources in the first pass
if (varLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
continue;
SLANG_ASSERT(varLayout->resourceInfos.Count() == varLayout->typeLayout->resourceInfos.Count());
auto uniformInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform);
auto tUniformInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
if (uniformInfo)
{
anyUniforms = true;
SLANG_ASSERT(tUniformInfo);
structLayoutInfo.size = Math::Max(structLayoutInfo.size, uniformInfo->index + tUniformInfo->count);
}
for (UInt i = 0; i < varLayout->resourceInfos.Count(); i++)
{
auto resInfo = varLayout->resourceInfos[i];
auto tresInfo = varLayout->typeLayout->FindResourceInfo(resInfo.kind);
SLANG_ASSERT(tresInfo);
auto usedRangeSet = findUsedRangeSetForSpace(&context, resInfo.space);
markSpaceUsed(&context, resInfo.space);
usedRangeSet->usedResourceRanges[(int)resInfo.kind].Add(
nullptr, // we don't need to track parameter info here
resInfo.index,
resInfo.index + tresInfo->count);
}
structLayout->fields[varId] = varLayout;
varLayoutMapping[varLayout] = varLayout;
}
auto originalGlobalCBufferInfo = programLayout->globalScopeLayout->FindResourceInfo(LayoutResourceKind::ConstantBuffer);
VarLayout::ResourceInfo globalCBufferInfo;
globalCBufferInfo.kind = LayoutResourceKind::None;
globalCBufferInfo.space = 0;
globalCBufferInfo.index = 0;
if (originalGlobalCBufferInfo)
{
globalCBufferInfo.kind = LayoutResourceKind::ConstantBuffer;
globalCBufferInfo.space = originalGlobalCBufferInfo->space;
globalCBufferInfo.index = originalGlobalCBufferInfo->index;
}
// we have the context restored, can continue to layout the generic variables now
for (uint32_t varId = 0; varId < globalStructLayout->fields.Count(); varId++)
{
auto &varLayout = globalStructLayout->fields[varId];
if (varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
{
RefPtr<Type> newType = varLayout->typeLayout->type->Substitute(typeSubst).As<Type>();
RefPtr<TypeLayout> newTypeLayout = CreateTypeLayout(
layoutContext.with(constantBufferRules),
newType);
auto layoutInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
size_t uniformSize = layoutInfo ? layoutInfo->count : 0;
if (uniformSize)
{
if (globalCBufferInfo.kind == LayoutResourceKind::None)
{
// user defined a uniform via a global generic type argument
// but we have not reserved a binding for the global uniform buffer
UInt space = 0;
auto usedRangeSet = findUsedRangeSetForSpace(&context, space);
globalCBufferInfo.kind = LayoutResourceKind::ConstantBuffer;
globalCBufferInfo.index =
usedRangeSet->usedResourceRanges[
(int)LayoutResourceKind::ConstantBuffer].Allocate(nullptr, 1);
globalCBufferInfo.space = space;
}
}
RefPtr<VarLayout> newVarLayout = new VarLayout();
RefPtr<ParameterInfo> paramInfo = new ParameterInfo();
newVarLayout->varDecl = varLayout->varDecl;
newVarLayout->stage = varLayout->stage;
newVarLayout->typeLayout = newTypeLayout;
paramInfo->varLayouts.Add(newVarLayout);
completeBindingsForParameter(&context, paramInfo);
// update uniform layout
if (uniformSize != 0)
{
// Make sure uniform fields get laid out properly...
UniformLayoutInfo fieldInfo(
uniformSize,
newTypeLayout->uniformAlignment);
size_t uniformOffset = layoutContext.getRulesFamily()->getConstantBufferRules()->AddStructField(
&structLayoutInfo,
fieldInfo);
newVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset;
anyUniforms = true;
}
structLayout->fields[varId] = newVarLayout;
varLayoutMapping[varLayout] = newVarLayout;
}
}
for (auto mapping : globalStructLayout->mapVarToLayout)
{
RefPtr<VarLayout> updatedVarLayout = mapping.Value;
varLayoutMapping.TryGetValue(updatedVarLayout, updatedVarLayout);
structLayout->mapVarToLayout[mapping.Key] = updatedVarLayout;
}
// If there are global-scope uniforms, then we need to wrap
// up a global constant buffer type layout to hold them
RefPtr<VarLayout> globalVarLayout = new VarLayout();
if (anyUniforms)
{
auto globalConstantBufferLayout = createParameterGroupTypeLayout(
layoutContext,
nullptr,
constantBufferRules,
constantBufferRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
structLayout);
globalScopeLayout = globalConstantBufferLayout;
auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer);
*cbInfo = globalCBufferInfo;
}
globalVarLayout->typeLayout = globalScopeLayout;
programLayout->globalScopeLayout = globalVarLayout;
newProgramLayout->globalScopeLayout = globalVarLayout;
return newProgramLayout;
}
}