summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/emit.cpp72
-rw-r--r--source/slang/ir-entry-point-uniforms.cpp423
-rw-r--r--source/slang/ir-entry-point-uniforms.h12
-rw-r--r--source/slang/ir-glsl-legalize.cpp16
-rw-r--r--source/slang/ir-insts.h3
-rw-r--r--source/slang/ir-link.cpp14
-rw-r--r--source/slang/ir.cpp9
-rw-r--r--source/slang/parameter-binding.cpp1213
-rw-r--r--source/slang/reflection.cpp12
-rw-r--r--source/slang/slang.vcxproj2
-rw-r--r--source/slang/slang.vcxproj.filters6
-rw-r--r--source/slang/type-layout.h21
12 files changed, 1212 insertions, 591 deletions
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index e4662e3c4..224fa3a28 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -3,6 +3,7 @@
#include "../core/slang-writer.h"
#include "ir-dce.h"
+#include "ir-entry-point-uniforms.h"
#include "ir-glsl-legalize.h"
#include "ir-insts.h"
#include "ir-link.h"
@@ -6511,41 +6512,28 @@ EntryPointLayout* findEntryPointLayout(
return nullptr;
}
-// Given a layout computed for a whole program, find
-// the corresponding layout to use when looking up
-// variables at the global scope.
-//
-// It might be that the global scope was logically
-// mapped to a constant buffer, so that we need
-// to "unwrap" that declaration to get at the
-// actual struct type inside.
-StructTypeLayout* getGlobalStructLayout(
- ProgramLayout* programLayout)
+ /// Given a layout computed for a scope, get the layout to use when lookup up variables.
+ ///
+ /// A scope (such as the global scope of a program) groups its
+ /// parameters into a pseudo-`struct` type for layout purposes,
+ /// and in some cases that type will in turn be wrapped in a
+ /// `ConstantBuffer` type to indicate that the parameters needed
+ /// an implicit constant buffer to be allocated.
+ ///
+ /// This function "unwraps" the type layout to find the structure
+ /// type layout that must be stored inside.
+ ///
+StructTypeLayout* getScopeStructLayout(
+ ScopeLayout* scopeLayout)
{
- auto globalScopeLayout = programLayout->globalScopeLayout->typeLayout;
- if( auto gs = as<StructTypeLayout>(globalScopeLayout) )
+ auto scopeTypeLayout = scopeLayout->parametersLayout->typeLayout;
+ if( auto structTypeLayout = as<StructTypeLayout>(scopeTypeLayout) )
{
- return gs;
+ return structTypeLayout;
}
- else if( auto globalConstantBufferLayout = as<ParameterGroupTypeLayout>(globalScopeLayout) )
+ else if( auto constantBufferTypeLayout = as<ParameterGroupTypeLayout>(scopeTypeLayout) )
{
- // TODO: the `cbuffer` case really needs to be emitted very
- // carefully, but that is beyond the scope of what a simple rewriter
- // can easily do (without semantic analysis, etc.).
- //
- // The crux of the problem is that we need to collect all the
- // global-scope uniforms (but not declarations that don't involve
- // uniform storage...) and put them in a single `cbuffer` declaration,
- // so that we can give it an explicit location. The fields in that
- // declaration might use various type declarations, so we'd really
- // need to emit all the type declarations first, and that involves
- // some large scale re orderings.
- //
- // For now we will punt and just emit the declarations normally,
- // and hope that the global-scope block (`$Globals`) gets auto-assigned
- // the same location that we manually assigned it.
-
- auto elementTypeLayout = globalConstantBufferLayout->offsetElementTypeLayout;
+ auto elementTypeLayout = constantBufferTypeLayout->offsetElementTypeLayout;
auto elementTypeStructLayout = as<StructTypeLayout>(elementTypeLayout);
// We expect all constant buffers to contain `struct` types for now
@@ -6560,6 +6548,16 @@ StructTypeLayout* getGlobalStructLayout(
}
}
+ /// Given a layout computed for a program, get the layout to use when lookup up variables.
+ ///
+ /// This is just an alias of `getScopeStructLayout`.
+ ///
+StructTypeLayout* getGlobalStructLayout(
+ ProgramLayout* programLayout)
+{
+ return getScopeStructLayout(programLayout);
+}
+
void legalizeTypes(
TypeLegalizationContext* context,
IRModule* module);
@@ -6657,6 +6655,18 @@ String emitEntryPoint(
// un-specialized IR.
dumpIRIfEnabled(compileRequest, irModule);
+ // Now that we've linked the IR code, any layout/binding
+ // information has been attached to shader parameters
+ // and entry points. Now we are safe to make transformations
+ // that might move code without worrying about losing
+ // the connection between a parameter and its layout.
+ //
+ // An easy transformation of this kind is to take uniform
+ // parameters of a shader entry point and move them into
+ // the global scope instead.
+ //
+ moveEntryPointUniformParamsToGlobalScope(irModule);
+
// Desguar any union types, since these will be illegal on
// various targets.
//
diff --git a/source/slang/ir-entry-point-uniforms.cpp b/source/slang/ir-entry-point-uniforms.cpp
new file mode 100644
index 000000000..64deec1c5
--- /dev/null
+++ b/source/slang/ir-entry-point-uniforms.cpp
@@ -0,0 +1,423 @@
+// ir-entry-point-uniforms.cpp
+#include "ir-entry-point-uniforms.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+
+#include "mangle.h"
+
+namespace Slang
+{
+
+
+// The transformation in this file will solve the problem of taking
+// code like the following:
+//
+// float4 fragmentMain(
+// uniform Texture2D t,
+// uniform SamplerState s;
+// uniform float4 c,
+// float2 uv : UV) : SV_Target
+// {
+// return t.Sample(s, uv) + c;
+// }
+//
+// and transforming into code like this:
+//
+// struct Params
+// {
+// Texture2D t;
+// SamplerState s;
+// float4 c;
+// }
+// ConstantBuffer<Params> params;
+//
+// float4 fragmentMain(
+// float2 uv : UV) : SV_Target
+// {
+// return params.t.Sample(params.s, uv) + params.c;
+// }
+//
+// As can be seen in this example, the `uniform` parameters
+// declared as entry point parameters have been moved into
+// a `struct` declaration that we then use to declare a global
+// shader parameter that is a `ConstantBuffer`. We then
+// rewrite references to those parameters to refer to the
+// contents of the new constant buffer instead.
+//
+// We perform this transformation after the target-specific
+// linking step, because that will have attached layout information
+// to the entry point and its parameters. We need that layout
+// information so that we can:
+//
+// * Identify which parameters are uniform vs. varying.
+// * Have an appropriate layout to attached to the synthesized
+// global shader parameter `params`.
+//
+// One additional wrinkle this pass has to deal with is that
+// in the case where the shader doesn't have any "ordinary"
+// uniform parameters like `c` (e.g., it only has resource/object
+// parameters), we do *not* wrap the parameter `struct` in
+// a `ConstantBuffer`. For example, suppose we have:
+//
+// float4 fragmentMain(
+// uniform Texture2D t,
+// uniform SamplerState s;
+// float2 uv : UV) : SV_Target
+// {
+// return t.Sample(s, uv);
+// }
+//
+// In this case the output of the transformation shold be:
+//
+// struct Params
+// {
+// Texture2D t;
+// SamplerState s;
+// }
+// Params params;
+//
+// float4 fragmentMain(
+// float2 uv : UV) : SV_Target
+// {
+// return params.t.Sample(params.s, uv) + params.c;
+// }
+//
+// Note that this pass should always come before type legalization,
+// which will take responsibility for turning a variable like
+// `params` above into individual variables for the `t` and
+// `s` fields.
+
+// The overall structure here is similar to many other IR passes.
+// We define a "context" structure to encapsulate the pass.
+//
+struct MoveEntryPointUniformParametersToGlobalScope
+{
+ // We'll hang on to the module we are processing,
+ // so that we can refer to it when setting up `IRBuilder`s.
+ //
+ IRModule* module;
+
+ // We will process a whole module by visiting all
+ // its global functions, looking for entry points.
+ //
+ void processModule()
+ {
+ // Note that we are only looking at true global-scope
+ // functions and not functions nested inside of
+ // IR generics. When using generic entry points, this
+ // pass should be run after the entry point(s) have
+ // been specialized to their generic type parameters.
+
+ for( auto inst : module->getGlobalInsts() )
+ {
+ // We are only interested in entry points.
+ //
+ // Every entry point must be a function.
+ //
+ auto func = as<IRFunc>(inst);
+ if( !func )
+ continue;
+
+ // Entry points will always have the `[entryPoint]`
+ // decoration to differentiate them from ordinary
+ // functions.
+ //
+ // TODO: we could make `IREntryPoint` a subclass of
+ // `IRFunc` if desired, to avoid having to attach
+ // an explicit decoration to identify them.
+ //
+ if( !func->findDecorationImpl(kIROp_EntryPointDecoration) )
+ continue;
+
+ // If we fine a candidate entry point, then we
+ // will process it.
+ //
+ processEntryPoint(func);
+ }
+ }
+
+ void processEntryPoint(IRFunc* func)
+ {
+ // We expect all entry points to have explicit layout information attached.
+ //
+ // We will assert that we have the information we need, but try to be
+ // defensive and bail out in the failure case in release builds.
+ //
+ auto funcLayoutDecoration = func->findDecoration<IRLayoutDecoration>();
+ SLANG_ASSERT(funcLayoutDecoration);
+ if(!funcLayoutDecoration)
+ return;
+
+ auto entryPointLayout = dynamic_cast<EntryPointLayout*>(funcLayoutDecoration->getLayout());
+ SLANG_ASSERT(entryPointLayout);
+ if(!entryPointLayout)
+ return;
+
+ // The parameter layout for an entry point will either be a structure
+ // type layout, or a constant buffer (a case of parameter group)
+ // wrapped around such a structure.
+ //
+ // If we are in the latter case we will need to make sure to allocate
+ // an explicit IR constant buffer for that wrapper,
+ //
+ auto entryPointParamsLayout = entryPointLayout->parametersLayout;
+ bool needConstantBuffer = entryPointParamsLayout->typeLayout.as<ParameterGroupTypeLayout>() != nullptr;
+
+ // We will set up an IR builder so that we are ready to generate code.
+ //
+ SharedIRBuilder sharedBuilderStorage;
+ auto sharedBuilder = &sharedBuilderStorage;
+ sharedBuilder->module = module;
+ sharedBuilder->session = module->getSession();
+
+ IRBuilder builderStorage;
+ auto builder = &builderStorage;
+ builder->sharedBuilder = sharedBuilder;
+
+ // *If* the entry point has any uniform parameter then we want to create a
+ // structure type to house them, and a global shader parameter (either
+ // an instance of that type or a constant buffer).
+ //
+ // We only want to create these if actually needed, so we will declare
+ // them here and then initialize them on-demand.
+ //
+ IRStructType* paramStructType = nullptr;
+ IRGlobalParam* globalParam = nullptr;
+
+ // We will be removing any uniform parameters we run into, so we
+ // need to iterate the parameter list carefully to deal with
+ // us modifying it along the way.
+ //
+ IRParam* nextParam = nullptr;
+ for( IRParam* param = func->getFirstParam(); param; param = nextParam )
+ {
+ nextParam = param->getNextParam();
+
+ // We expect all entry-point parameters to have layout information,
+ // but we will be defensive and skip parameters without the required
+ // information when we are in a release build.
+ //
+ auto layoutDecoration = param->findDecoration<IRLayoutDecoration>();
+ SLANG_ASSERT(layoutDecoration);
+ if(!layoutDecoration)
+ continue;
+ auto paramLayout = dynamic_cast<VarLayout*>(layoutDecoration->getLayout());
+ SLANG_ASSERT(paramLayout);
+ if(!paramLayout)
+ continue;
+
+ // A parameter that has varying input/output behavior should be left alone,
+ // since this pass is only supposed to apply to uniform (non-varying)
+ // parameters.
+ //
+ if(isVaryingParameter(paramLayout))
+ continue;
+
+ // At this point we know that `param` is not a varying shader parameter,
+ // so that we want to turn it into an equivalent global shader parameter.
+ //
+ // If this is the first parameter we are running into, then we need
+ // to deal with creating the structure type and global shader
+ // parameter that our transformed entry point will use.
+ //
+ if( !paramStructType )
+ {
+ // First we create the structure to hold the parameters.
+ //
+ builder->setInsertBefore(func);
+ paramStructType = builder->createStructType();
+
+ if( needConstantBuffer )
+ {
+ // If we need a constant buffer, then the global
+ // shader parameter will be a `ConstantBuffer<paramStructType>`
+ //
+ auto constantBufferType = builder->getConstantBufferType(paramStructType);
+ globalParam = builder->createGlobalParam(constantBufferType);
+ }
+ else
+ {
+ // Otherwise, the global shader parameter is just
+ // an instance of `paramStructType`.
+ //
+ globalParam = builder->createGlobalParam(paramStructType);
+ }
+
+ // No matter what, the global shader parameter should have the layout
+ // information from the entry point attached to it, so that the
+ // contained parameters will end up in the right place(s).
+ //
+ builder->addLayoutDecoration(globalParam, entryPointParamsLayout);
+ }
+
+ // Now that we've ensured the global `struct` type and shader paramter
+ // exist, we need to add a field to the `struct` to represent the
+ // current parameter.
+ //
+
+ auto paramType = param->getFullType();
+
+ builder->setInsertBefore(paramStructType);
+ auto paramFieldKey = builder->createStructKey();
+ auto paramField = builder->createStructField(paramStructType, paramFieldKey, paramType);
+ SLANG_UNUSED(paramField);
+
+ // We will transfer all decorations on the parameter over to the key
+ // so that they can affect downstream emit logic.
+ //
+ // TODO: We should double-check whether any of the decorations should
+ // be moved to the *field* instead.
+ //
+ param->transferDecorationsTo(paramFieldKey);
+
+ // There is a bit of a hacky issue, where downstream passes (notably
+ // type legalization) require the field keys for `struct` types to
+ // have mangled names, because those mangled names will be used to
+ // lookup field layout information inside of the layout information
+ // for the `struct` type.
+ //
+ // TODO: We should fix that design choice in how layout information
+ // is stored, to avoid the reliance on name strings.
+ //
+ builder->addExportDecoration(paramFieldKey, getMangledName(paramLayout->varDecl).getUnownedSlice());
+
+ // At this point we want to eliminate the original entry point
+ // parameter, in favor of the `struct` field we declared.
+ // That required replacing any uses of the parameter with
+ // appropriate code to pull out the field.
+ //
+ // We *could* extract the field at the start of the shader
+ // and then do a `replaceAllUsesWith` to propragate it
+ // down, but in practice we expect that it is better for
+ // performance to "rematerialize" the value of a shader
+ // parameter as close to where it is used as possible.
+ //
+ // We are therefore going to replace the uses one at a time.
+ //
+ while(auto use = param->firstUse )
+ {
+ // Given a `use` of the paramter, we will insert
+ // the replacement code right before the instruction
+ // that is doing the using.
+ //
+ builder->setInsertBefore(use->getUser());
+
+ // The way to extract the field that corresponds
+ // to the parameter depends on whether or not
+ // we generated a constant buffer.
+ //
+ IRInst* fieldVal = nullptr;
+ if( needConstantBuffer )
+ {
+ // A constant buffer behaves like a pointer
+ // at the IR level, so we first do a pointer
+ // offset operation to compute what amounts
+ // to `&cb->field`, and then load from that address.
+ //
+ auto fieldAddress = builder->emitFieldAddress(
+ builder->getPtrType(paramType),
+ globalParam,
+ paramFieldKey);
+ fieldVal = builder->emitLoad(fieldAddress);
+ }
+ else
+ {
+ // In the ordinary struct case, the parameter
+ // has an ordinary `struct` type (not a pointer),
+ // so we just extract the field directly.
+ //
+ fieldVal = builder->emitFieldExtract(
+ paramType,
+ globalParam,
+ paramFieldKey);
+ }
+
+ // We replace the value used at this use site, which
+ // will have a side effect of making `use` no longer
+ // be on the list of uses for `param`, so that when
+ // we get back to the top of the loop the list of
+ // uses will be shorter.
+ //
+ use->set(fieldVal);
+ }
+
+ // Once we've replaced all the uses of `param`, we
+ // can go ahead and remove it completely.
+ //
+ param->removeAndDeallocate();
+ }
+ }
+
+ // We need to be able to determine if a parameter is logically
+ // a "varying" parameter based on its layout.
+ //
+ bool isVaryingParameter(VarLayout* layout)
+ {
+ // If *any* of the resources consumed by the parameter
+ // is a varying resource kind (e.g., varying input) then
+ // we consider the whole parameter to be varying.
+ //
+ // This is reasonable because there is no way to declare
+ // a parameter that mixes varying and non-varying fields.
+ //
+ for( auto resInfo : layout->resourceInfos )
+ {
+ if(isVaryingResourceKind(resInfo.kind))
+ return true;
+ }
+
+ // Varying parameters with "system value" semantics currently show up as
+ // consuming no resources, so we need to special-case that here.
+ //
+ // Note: an empty `struct` parameter would also show up the same way, but
+ // we should eliminate any such parameters later on during type legalization.
+ //
+ if(layout->resourceInfos.Count() == 0)
+ return true;
+
+ // if none of the above tests determined that the
+ // parameter was varying, then we can safely consider
+ // it to be non-varying (uniform):
+ return false;
+ }
+
+ // In order to determine whether a parameter is varying based on its
+ // layout, we need to know which resource kinds represent varying
+ // shader parameters.
+ //
+ bool isVaryingResourceKind(LayoutResourceKind kind)
+ {
+ switch( kind )
+ {
+ default:
+ return false;
+
+ // Note: The set of cases that are considered
+ // varying here would need to be extended if we
+ // add more fine-grained resource kinds (e.g.,
+ // if we ever add an explicit resource kind
+ // for geometry shader output streams).
+ //
+ // Ordinary varying input/output:
+ case LayoutResourceKind::VaryingInput:
+ case LayoutResourceKind::VaryingOutput:
+ //
+ // Ray-tracing shader input/output:
+ case LayoutResourceKind::CallablePayload:
+ case LayoutResourceKind::HitAttributes:
+ case LayoutResourceKind::RayPayload:
+ return true;
+ }
+ }
+};
+
+void moveEntryPointUniformParamsToGlobalScope(
+ IRModule* module)
+{
+ MoveEntryPointUniformParametersToGlobalScope context;
+ context.module = module;
+ context.processModule();
+}
+
+}
diff --git a/source/slang/ir-entry-point-uniforms.h b/source/slang/ir-entry-point-uniforms.h
new file mode 100644
index 000000000..5fcfab167
--- /dev/null
+++ b/source/slang/ir-entry-point-uniforms.h
@@ -0,0 +1,12 @@
+// ir-entry-point-uniform.h
+#pragma once
+
+namespace Slang
+{
+struct IRModule;
+
+ /// Move any uniform parameters of entry points to the global scope instead.
+void moveEntryPointUniformParamsToGlobalScope(
+ IRModule* module);
+
+}
diff --git a/source/slang/ir-glsl-legalize.cpp b/source/slang/ir-glsl-legalize.cpp
index 0c49667d8..7dc88a0fa 100644
--- a/source/slang/ir-glsl-legalize.cpp
+++ b/source/slang/ir-glsl-legalize.cpp
@@ -1516,18 +1516,16 @@ void legalizeEntryPointForGLSL(
// to be at the start of the "ordinary" instructions in the block:
builder.setInsertBefore(firstBlock->getFirstOrdinaryInst());
- UInt paramCounter = 0;
for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
{
- UInt paramIndex = paramCounter++;
-
- // We assume that the entry-point layout includes information
- // on each parameter, and that these arrays are kept aligned.
- // Note that this means that any transformations that mess
- // with function signatures will need to also update layout info...
+ // We assume that the entry-point parameters will all have
+ // layout information attached to them, which is kept up-to-date
+ // by any transformations affecting the parameter list.
//
- SLANG_ASSERT(entryPointLayout->fields.Count() > paramIndex);
- auto paramLayout = entryPointLayout->fields[paramIndex];
+ auto paramLayoutDecoration = pp->findDecoration<IRLayoutDecoration>();
+ SLANG_ASSERT(paramLayoutDecoration);
+ auto paramLayout = dynamic_cast<VarLayout*>(paramLayoutDecoration->getLayout());
+ SLANG_ASSERT(paramLayout);
legalizeEntryPointParameterForGLSL(
&context,
diff --git a/source/slang/ir-insts.h b/source/slang/ir-insts.h
index 8662569ba..6b12612ef 100644
--- a/source/slang/ir-insts.h
+++ b/source/slang/ir-insts.h
@@ -759,6 +759,9 @@ struct IRBuilder
return getFuncType(paramTypes.Count(), paramTypes.Buffer(), resultType);
}
+ IRConstantBufferType* getConstantBufferType(
+ IRType* elementType);
+
IRConstExprRate* getConstExprRate();
IRGroupSharedRate* getGroupSharedRate();
diff --git a/source/slang/ir-link.cpp b/source/slang/ir-link.cpp
index 610658d51..dba4fc2d1 100644
--- a/source/slang/ir-link.cpp
+++ b/source/slang/ir-link.cpp
@@ -8,9 +8,6 @@
namespace Slang
{
-StructTypeLayout* getGlobalStructLayout(
- ProgramLayout* programLayout);
-
// Needed for lookup up entry-point layouts.
//
// TODO: maybe arrange so that codegen is driven from the layout layer
@@ -721,14 +718,15 @@ IRFunc* specializeIRForEntryPoint(
// than having to look it up on the original entry-point layout.
if( auto firstBlock = clonedFunc->getFirstBlock() )
{
- UInt paramLayoutCount = entryPointLayout->fields.Count();
+ auto paramsStructLayout = getScopeStructLayout(entryPointLayout);
+ UInt paramLayoutCount = paramsStructLayout->fields.Count();
UInt paramCounter = 0;
for( auto pp = firstBlock->getFirstParam(); pp; pp = pp->getNextParam() )
{
UInt paramIndex = paramCounter++;
if( paramIndex < paramLayoutCount )
{
- auto paramLayout = entryPointLayout->fields[paramIndex];
+ auto paramLayout = paramsStructLayout->fields[paramIndex];
context->builder->addLayoutDecoration(
pp,
paramLayout);
@@ -1227,7 +1225,7 @@ IRSpecializationState* createIRSpecializationState(
// Next, we want to optimize lookup for layout infromation
// associated with global declarations, so that we can
// look things up based on the IR values (using mangled names)
- auto globalStructLayout = getGlobalStructLayout(newProgramLayout);
+ auto globalStructLayout = getScopeStructLayout(newProgramLayout);
for (auto globalVarLayout : globalStructLayout->fields)
{
auto mangledName = getMangledName(globalVarLayout->varDecl);
@@ -1235,6 +1233,10 @@ IRSpecializationState* createIRSpecializationState(
}
// for now, clone all unreferenced witness tables
+ //
+ // TODO: This step should *not* be needed with the current IR
+ // specialization approach, so we should consider removing it.
+ //
for (auto sym :context->getSymbols())
{
if (sym.Value->irGlobalValue->op == kIROp_WitnessTable)
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index f622804b2..0a5b8491c 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1710,6 +1710,15 @@ namespace Slang
(IRInst* const*) paramTypes);
}
+ IRConstantBufferType* IRBuilder::getConstantBufferType(IRType* elementType)
+ {
+ IRInst* operands[] = { elementType };
+ return (IRConstantBufferType*) getType(
+ kIROp_ConstantBufferType,
+ 1,
+ operands);
+ }
+
IRConstExprRate* IRBuilder::getConstExprRate()
{
return (IRConstExprRate*)getType(kIROp_ConstExprRate);
diff --git a/source/slang/parameter-binding.cpp b/source/slang/parameter-binding.cpp
index 076b29659..904ec3129 100644
--- a/source/slang/parameter-binding.cpp
+++ b/source/slang/parameter-binding.cpp
@@ -15,7 +15,7 @@ struct ParameterInfo;
struct UsedRange
{
// What parameter has claimed this range?
- ParameterInfo* parameter = nullptr;
+ VarLayout* parameter;
// Begin/end of the range (half-open interval)
UInt begin;
@@ -69,7 +69,7 @@ struct UsedRanges
// then we return that parameter so that the
// caller can issue an error.
//
- ParameterInfo* Add(UsedRange range)
+ VarLayout* Add(UsedRange range)
{
// The invariant on entry to this
// function is that the `ranges` array
@@ -86,8 +86,8 @@ struct UsedRanges
// match the parameter on `range`, so that
// the compiler can issue useful diagnostics.
//
- ParameterInfo* newParam = range.parameter;
- ParameterInfo* existingParam = nullptr;
+ VarLayout* newParam = range.parameter;
+ VarLayout* existingParam = nullptr;
// A clever algorithm might use a binary
// search to identify the first entry in `ranges`
@@ -210,7 +210,7 @@ struct UsedRanges
return existingParam;
}
- ParameterInfo* Add(ParameterInfo* param, UInt begin, UInt end)
+ VarLayout* Add(VarLayout* param, UInt begin, UInt end)
{
UsedRange range;
range.parameter = param;
@@ -219,7 +219,7 @@ struct UsedRanges
return Add(range);
}
- ParameterInfo* Add(ParameterInfo* param, UInt begin, LayoutSize end)
+ VarLayout* Add(VarLayout* param, UInt begin, LayoutSize end)
{
UsedRange range;
range.parameter = param;
@@ -246,7 +246,7 @@ struct UsedRanges
// Try to find space for `count` entries
- UInt Allocate(ParameterInfo* param, UInt count)
+ UInt Allocate(VarLayout* param, UInt count)
{
UInt begin = 0;
@@ -279,11 +279,16 @@ struct UsedRanges
struct ParameterBindingInfo
{
- size_t space;
- size_t index;
+ size_t space = 0;
+ size_t index = 0;
LayoutSize count;
};
+struct ParameterBindingAndKindInfo : ParameterBindingInfo
+{
+ LayoutResourceKind kind = LayoutResourceKind::None;
+};
+
enum
{
kLayoutResourceKindCount = SLANG_PARAMETER_CATEGORY_COUNT,
@@ -353,11 +358,6 @@ struct SharedParameterBindingContext
//
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;
@@ -1098,91 +1098,16 @@ RefPtr<Type> tryGetEffectiveTypeForGLSLVaryingOutput(
return nullptr;
}
-RefPtr<TypeLayout>
-getTypeLayoutForGlobalShaderParameter_GLSL(
+ /// 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)
-{
- 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>() && as<ConstantBufferType>(type) )
- {
- 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>() || as<ConstantBufferType>(type) )
- {
- return CreateTypeLayout(
- layoutContext.with(rules->getConstantBufferRules()),
- type);
- }
-
- if( varDecl->HasModifier<GLSLBufferModifier>() || as<GLSLShaderStorageBufferType>(type) )
- {
- 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)
+ VarDeclBase* varDecl,
+ Type* type)
{
auto layoutContext = context->layoutContext;
auto rules = layoutContext.getRulesFamily();
- auto type = varDecl->getType();
if( varDecl->HasModifier<ShaderRecordNVLayoutModifier>() && as<ConstantBufferType>(type) )
{
@@ -1217,32 +1142,13 @@ getTypeLayoutForGlobalShaderParameter_HLSL(
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(
+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);
- }
+ return getTypeLayoutForGlobalShaderParameter(context, varDecl, varDecl->getType());
}
-
//
enum EntryPointParameterDirection
@@ -1264,44 +1170,12 @@ struct EntryPointParameterState
};
-static RefPtr<TypeLayout> processEntryPointParameter(
+static RefPtr<TypeLayout> processEntryPointVaryingParameter(
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,
@@ -1319,25 +1193,6 @@ 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.
@@ -1422,22 +1277,6 @@ static UInt allocateUnusedSpaces(
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,
@@ -1486,15 +1325,15 @@ static void addExplicitParameterBinding(
// need to grab a full space
markSpaceUsed(context, semanticInfo.space);
}
- auto overlappedParameterInfo = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add(
- parameterInfo,
+ auto overlappedVarLayout = usedRangeSet->usedResourceRanges[(int)semanticInfo.kind].Add(
+ parameterInfo->varLayouts[0],
semanticInfo.index,
semanticInfo.index + count);
- if (overlappedParameterInfo)
+ if (overlappedVarLayout)
{
auto paramA = parameterInfo->varLayouts[0]->varDecl.getDecl();
- auto paramB = overlappedParameterInfo->varLayouts[0]->varDecl.getDecl();
+ auto paramB = overlappedVarLayout->varDecl.getDecl();
getSink(context)->diagnose(paramA, Diagnostics::parameterBindingsOverlap,
getReflectionName(paramA),
@@ -1641,22 +1480,6 @@ static void addExplicitParameterBindings_GLSL(
semanticInfo.index = attr->set;
semanticInfo.space = 0;
}
- 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
@@ -1697,21 +1520,16 @@ void generateParameterBindings(
}
// Generate the binding information for a shader parameter.
-static void completeBindingsForParameter(
+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.
//
- // 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;
// We need to deal with allocation of full register spaces first,
@@ -1731,7 +1549,7 @@ static void completeBindingsForParameter(
// has specified an explicit binding, since those won't
// go into our contiguously allocated range.
//
- auto& bindingInfo = parameterInfo->bindingInfo[(int)kind];
+ auto& bindingInfo = bindingInfos[(int)kind];
if( bindingInfo.count != 0 )
{
continue;
@@ -1798,7 +1616,7 @@ static void completeBindingsForParameter(
// Did we already apply some explicit binding information
// for this resource kind?
auto kind = typeRes.kind;
- auto& bindingInfo = parameterInfo->bindingInfo[(int)kind];
+ auto& bindingInfo = bindingInfos[(int)kind];
if( bindingInfo.count != 0 )
{
// If things have already been bound, our work is done.
@@ -1892,26 +1710,53 @@ static void completeBindingsForParameter(
// 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;
- }
+ RefPtr<UsedRangeSet> usedRangeSet = findUsedRangeSetForSpace(context, space);
bindingInfo.count = count;
- bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(parameterInfo, count.getFiniteValue());
-
+ bindingInfo.index = usedRangeSet->usedResourceRanges[(int)kind].Allocate(firstVarLayout, count.getFiniteValue());
bindingInfo.space = space;
}
}
+}
+
+static void applyBindingInfoToParameter(
+ RefPtr<VarLayout> varLayout,
+ ParameterBindingInfo bindingInfos[kLayoutResourceKindCount])
+{
+ for(auto k = 0; k < kLayoutResourceKindCount; ++k)
+ {
+ auto kind = LayoutResourceKind(k);
+ auto& bindingInfo = bindingInfos[k];
+
+ // skip resources we aren't consuming
+ if(bindingInfo.count == 0)
+ continue;
+
+ // Add a record to the variable layout
+ auto varRes = varLayout->AddResourceInfo(kind);
+ varRes->space = (int) bindingInfo.space;
+ varRes->index = (int) bindingInfo.index;
+ }
+}
+
+// Generate the binding information for a shader parameter.
+static void completeBindingsForParameter(
+ ParameterBindingContext* context,
+ RefPtr<ParameterInfo> parameterInfo)
+{
+ // We will use the first declaration of the parameter as
+ // a stand-in for all the declarations, so it is important
+ // that earlier code has validated that the declarations
+ // "match".
+
+ SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
+ auto firstVarLayout = parameterInfo->varLayouts.First();
+
+ completeBindingsForParameterImpl(
+ context,
+ firstVarLayout,
+ parameterInfo->bindingInfo,
+ parameterInfo);
// At this point we should have explicit binding locations chosen for
// all the relevant resource kinds, so we can apply these to the
@@ -1919,23 +1764,25 @@ static void completeBindingsForParameter(
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;
- }
+ applyBindingInfoToParameter(varLayout, parameterInfo->bindingInfo);
}
}
+static void completeBindingsForParameter(
+ ParameterBindingContext* context,
+ RefPtr<VarLayout> varLayout)
+{
+ ParameterBindingInfo bindingInfos[kLayoutResourceKindCount];
+ completeBindingsForParameterImpl(
+ context,
+ varLayout,
+ bindingInfos,
+ nullptr);
+ applyBindingInfoToParameter(varLayout, bindingInfos);
+}
+
+
+
static void collectGlobalScopeParameters(
ParameterBindingContext* context,
ModuleDecl* program)
@@ -2121,7 +1968,7 @@ static RefPtr<TypeLayout> processSimpleEntryPointParameter(
return typeLayout;
}
-static RefPtr<TypeLayout> processEntryPointParameterDecl(
+static RefPtr<TypeLayout> processEntryPointVaryingParameterDecl(
ParameterBindingContext* context,
Decl* decl,
RefPtr<Type> type,
@@ -2159,20 +2006,15 @@ static RefPtr<TypeLayout> processEntryPointParameterDecl(
// *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);
+ return processEntryPointVaryingParameter(context, type, state, varLayout);
}
-static RefPtr<TypeLayout> processEntryPointParameter(
+static RefPtr<TypeLayout> processEntryPointVaryingParameter(
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 = as<HLSLStreamOutputType>(type) )
@@ -2192,7 +2034,7 @@ static RefPtr<TypeLayout> processEntryPointParameter(
elementState.stage = state.stage;
elementState.loc = state.loc;
- auto elementTypeLayout = processEntryPointParameter(context, elementType, elementState, nullptr);
+ auto elementTypeLayout = processEntryPointVaryingParameter(context, elementType, elementState, nullptr);
RefPtr<StreamOutputTypeLayout> typeLayout = new StreamOutputTypeLayout();
typeLayout->type = type;
@@ -2316,13 +2158,13 @@ static RefPtr<TypeLayout> processEntryPointParameter(
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);
+ 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 )
{
- processEntryPointParameter(context, arrayType->baseType, state, nullptr);
+ processEntryPointVaryingParameter(context, arrayType->baseType, state, nullptr);
}
RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout();
@@ -2357,7 +2199,7 @@ static RefPtr<TypeLayout> processEntryPointParameter(
RefPtr<VarLayout> fieldVarLayout = new VarLayout();
fieldVarLayout->varDecl = field;
- auto fieldTypeLayout = processEntryPointParameterDecl(
+ auto fieldTypeLayout = processEntryPointVaryingParameterDecl(
context,
field.getDecl(),
GetType(field),
@@ -2409,6 +2251,210 @@ static RefPtr<TypeLayout> processEntryPointParameter(
UNREACHABLE_RETURN(nullptr);
}
+ /// Compute the type layout for a parameter declared directly on an entry point.
+static RefPtr<TypeLayout> computeEntryPointParameterTypeLayout(
+ ParameterBindingContext* context,
+ SubstitutionSet typeSubst,
+ RefPtr<ParamDecl> paramDecl,
+ RefPtr<VarLayout> paramVarLayout,
+ EntryPointParameterState& state)
+{
+ auto paramType = paramDecl->type.type->Substitute(typeSubst).as<Type>();
+
+ if( paramDecl->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( 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;
+ }
+
+ return processEntryPointVaryingParameterDecl(
+ context,
+ paramDecl.Ptr(),
+ paramDecl->type.type->Substitute(typeSubst).as<Type>(),
+ state,
+ paramVarLayout);
+ }
+}
+
+// There are multiple places where we need to compute the layout
+// for a "scope" such as the global scope or an entry point.
+// The `ScopeLayoutBuilder` encapsulates the logic around:
+//
+// * Doing layout for the ordinary/uniform fields, which involves
+// using the `struct` layout rules for constant buffers on
+// the target.
+//
+// * Creating a final type/var layout that reflects whether the
+// scope needs a constant buffer to be allocated to it.
+//
+struct ScopeLayoutBuilder
+{
+ ParameterBindingContext* m_context = nullptr;
+ LayoutRulesImpl* m_rules = nullptr;
+ RefPtr<StructTypeLayout> m_structLayout;
+ UniformLayoutInfo m_structLayoutInfo;
+ bool m_needConstantBuffer = false;
+
+ void beginLayout(
+ ParameterBindingContext* context)
+ {
+ m_context = context;
+ m_rules = context->getRulesFamily()->getConstantBufferRules();
+ m_structLayout = new StructTypeLayout();
+ m_structLayout->rules = m_rules;
+
+ m_structLayoutInfo = m_rules->BeginStructLayout();
+ }
+
+ void _addParameter(
+ RefPtr<VarLayout> firstVarLayout,
+ ParameterInfo* parameterInfo)
+ {
+ // Does the parameter have any uniform data?
+ auto layoutInfo = firstVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
+ LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0;
+ if( uniformSize != 0 )
+ {
+ m_needConstantBuffer = true;
+
+ // Make sure uniform fields get laid out properly...
+
+ UniformLayoutInfo fieldInfo(
+ uniformSize,
+ firstVarLayout->typeLayout->uniformAlignment);
+
+ LayoutSize uniformOffset = m_rules->AddStructField(
+ &m_structLayoutInfo,
+ fieldInfo);
+
+ if( parameterInfo )
+ {
+ for( auto& varLayout : parameterInfo->varLayouts )
+ {
+ varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
+ }
+ }
+ else
+ {
+ firstVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
+ }
+ }
+
+ m_structLayout->fields.Add(firstVarLayout);
+
+ if( parameterInfo )
+ {
+ for( auto& varLayout : parameterInfo->varLayouts )
+ {
+ m_structLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout);
+ }
+ }
+ else
+ {
+ m_structLayout->mapVarToLayout.Add(firstVarLayout->varDecl.getDecl(), firstVarLayout);
+ }
+ }
+
+ void addParameter(
+ RefPtr<VarLayout> varLayout)
+ {
+ _addParameter(varLayout, nullptr);
+ }
+
+ void addParameter(
+ ParameterInfo* parameterInfo)
+ {
+ SLANG_RELEASE_ASSERT(parameterInfo->varLayouts.Count() != 0);
+ auto firstVarLayout = parameterInfo->varLayouts.First();
+
+ _addParameter(firstVarLayout, parameterInfo);
+ }
+
+ RefPtr<VarLayout> endLayout()
+ {
+ m_rules->EndStructLayout(&m_structLayoutInfo);
+
+ RefPtr<TypeLayout> scopeTypeLayout = m_structLayout;
+
+ // If the caller decided to allocate a constant buffer for
+ // the ordinary data, then we need to wrap up the structure
+ // type (layout) in a constant buffer type (layout).
+ //
+ if( m_needConstantBuffer )
+ {
+ auto constantBufferLayout = createParameterGroupTypeLayout(
+ m_context->layoutContext,
+ nullptr,
+ m_rules,
+ m_rules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
+ m_structLayout);
+
+ scopeTypeLayout = constantBufferLayout;
+ }
+
+ // 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;
+ return scopeVarLayout;
+ }
+};
+
+ /// Helper routine to allocate a constant buffer binding if one is needed.
+ ///
+ /// This function primarily exists to encapsulate the logic for allocating
+ /// the resources required for a constant buffer in the appropriate
+ /// target-specific fashion.
+ ///
+static ParameterBindingAndKindInfo maybeAllocateConstantBufferBinding(
+ ParameterBindingContext* context,
+ bool needConstantBuffer)
+{
+ if( !needConstantBuffer ) return ParameterBindingAndKindInfo();
+
+ UInt space = context->shared->defaultSpace;
+ auto usedRangeSet = findUsedRangeSetForSpace(context, space);
+
+ auto layoutInfo = context->getRulesFamily()->getConstantBufferRules()->GetObjectLayout(
+ ShaderParameterKind::ConstantBuffer);
+
+ ParameterBindingAndKindInfo info;
+ info.kind = layoutInfo.kind;
+ info.count = layoutInfo.size;
+ info.index = usedRangeSet->usedResourceRanges[(int)layoutInfo.kind].Allocate(nullptr, layoutInfo.size.getFiniteValue());
+ info.space = space;
+ return info;
+}
+
+ /// Iterate over the parameters of an entry point to compute its requirements.
+ ///
static void collectEntryPointParameters(
ParameterBindingContext* context,
EntryPointRequest* entryPoint,
@@ -2420,17 +2466,27 @@ static void collectEntryPointParameters(
// 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();
+ // We will take responsibility for creating and filling in
+ // the `EntryPointLayout` object here.
+ //
+ RefPtr<EntryPointLayout> entryPointLayout = new EntryPointLayout();
entryPointLayout->profile = entryPoint->profile;
entryPointLayout->entryPoint = entryPointFuncDecl;
- context->entryPointLayout = entryPointLayout;
+ // The entry point layout must be added to the output
+ // program layout so that it can be accessed by reflection.
+ //
context->shared->programLayout->entryPoints.Add(entryPointLayout);
+ // For the duration of our parameter collection work we will
+ // establish this entry point as the current one in the context.
+ //
+ context->entryPointLayout = entryPointLayout;
+
// Note: this isn't really the best place for this logic to sit,
// but it is the simplest place where we have a direct correspondence
// between a single `EntryPointRequest` and its matching `EntryPointLayout`,
@@ -2443,77 +2499,105 @@ static void collectEntryPointParameters(
entryPointLayout->taggedUnionTypeLayouts.Add(typeLayout);
}
- // Okay, we seemingly have an entry-point function, and now we need to collect info on its parameters too
+ // 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:
//
- // 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:
+ // First, the varying parameters of the entry point will have
+ // their semantics and locations assigned, so we set up state
+ // for tracking that layout.
//
- // 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 = as<VarDeclBase>(m);
- if(!paramDecl)
- continue;
+ // Second, we will compute offsets for any "ordinary" data
+ // in the parameter list (e.g., a `uniform float4x4 mvp` parameter),
+ // which is what the `ScopeLayoutBuilder` is designed to help with.
+ //
+ ScopeLayoutBuilder scopeBuilder;
+ scopeBuilder.beginLayout(context);
+ auto paramsStructLayout = scopeBuilder.m_structLayout;
- // We have an entry-point parameter, and need to figure out what to do with it.
+ for( auto paramDecl : entryPointFuncDecl->getMembersOfType<ParamDecl>() )
+ {
+ // Any error messages we emit during the process should
+ // refer to the location of this parameter.
+ //
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;
- }
-
+ // 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 = makeDeclRef(paramDecl);
+ paramVarLayout->varDecl = makeDeclRef(paramDecl.Ptr());
+ paramVarLayout->stage = state.stage;
- auto paramTypeLayout = processEntryPointParameterDecl(
+ auto paramTypeLayout = computeEntryPointParameterTypeLayout(
context,
+ typeSubst,
paramDecl,
- paramDecl->type.type->Substitute(typeSubst).dynamicCast<Type>(),
- state,
- paramVarLayout);
+ paramVarLayout,
+ state);
+ paramVarLayout->typeLayout = paramTypeLayout;
- // Skip parameters for which we could not compute a layout
+ // 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;
- paramVarLayout->typeLayout = paramTypeLayout;
+ // Now that we've computed the layout to use for the parameter,
+ // we need to add its resource usage to that of the entry
+ // point as a whole.
+ //
+ // Any "ordinary" data (e.g., a `float4x4`) needs to be accounted
+ // for using the `ScopeLayoutBuilder`, since it will handle
+ // the details of target-specific `struct` type layout.
+ //
+ scopeBuilder.addParameter(paramVarLayout);
- for (auto rr : paramTypeLayout->resourceInfos)
+ // All of the other resources types will be handled in a
+ // simpler loop that just increments the relevant counters.
+ //
+ for (auto paramTypeResInfo : paramTypeLayout->resourceInfos)
{
- auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind);
- paramVarLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue();
- entryPointRes->count += rr.count;
- }
+ // We need to skip ordinary data because it is being
+ // handled by the `scopeBuilder`.
+ //
+ if(paramTypeResInfo.kind == LayoutResourceKind::Uniform)
+ continue;
+
+ // Whatever resources the parameter uses, we need to
+ // assign the parameter's location/register/binding offset to
+ // be the sum of everything added so far.
+ //
+ auto entryPointResInfo = paramsStructLayout->findOrAddResourceInfo(paramTypeResInfo.kind);
+ paramVarLayout->findOrAddResourceInfo(paramTypeResInfo.kind)->index = entryPointResInfo->count.getFiniteValue();
- entryPointLayout->fields.Add(paramVarLayout);
- entryPointLayout->mapVarToLayout.Add(paramDecl, paramVarLayout);
+ // We then need to add the resources consumed by the parameter
+ // to those consumed by the entry point.
+ //
+ entryPointResInfo->count += paramTypeResInfo.count;
+ }
}
+ entryPointLayout->parametersLayout = scopeBuilder.endLayout();
- // If we have a non-`void` output type for the entry point, then process it as
- // an output parameter.
+ // 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 = entryPointFuncDecl->ReturnType.type;
if( !resultType->Equals(resultType->getSession()->getVoidType()) )
{
@@ -2521,8 +2605,9 @@ static void collectEntryPointParameters(
state.directionMask = kEntryPointParameterDirection_Output;
RefPtr<VarLayout> resultLayout = new VarLayout();
+ resultLayout->stage = state.stage;
- auto resultTypeLayout = processEntryPointParameterDecl(
+ auto resultTypeLayout = processEntryPointVaryingParameterDecl(
context,
entryPointFuncDecl,
resultType->Substitute(typeSubst).dynamicCast<Type>(),
@@ -2535,7 +2620,7 @@ static void collectEntryPointParameters(
for (auto rr : resultTypeLayout->resourceInfos)
{
- auto entryPointRes = entryPointLayout->findOrAddResourceInfo(rr.kind);
+ auto entryPointRes = paramsStructLayout->findOrAddResourceInfo(rr.kind);
resultLayout->findOrAddResourceInfo(rr.kind)->index = entryPointRes->count.getFiniteValue();
entryPointRes->count += rr.count;
}
@@ -2687,6 +2772,7 @@ void generateParameterBindings(
context.shared = &sharedContext;
context.translationUnit = nullptr;
context.layoutContext = layoutContext;
+
// Walk through AST to discover all the parameters
collectParameters(&context, compileReq);
@@ -2787,24 +2873,10 @@ void generateParameterBindings(
// 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;
- }
-
+ //
+ ParameterBindingAndKindInfo globalConstantBufferBinding = maybeAllocateConstantBufferBinding(
+ &context,
+ needDefaultConstantBuffer);
// Now walk through again to actually give everything
// ranges of registers...
@@ -2813,135 +2885,200 @@ void generateParameterBindings(
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.
+ // After we have allocated registers/bindings to everything
+ // in the global scope we will process the parameters
+ // of each entry point in order.
//
- // 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 )
+ // Note: the effect of the current implemetnation is to
+ // allocate non-overlapping registers/bindings between all
+ // the entry points in the compile request (e.g., if you
+ // have a vertex and fragment shader being compiled together,
+ // we will allocate distinct constant buffer registers for
+ // their uniform parameters).
+ //
+ // TODO: We probably need to provide some more nuanced control
+ // over whether entry points get overlapping or non-overlapping
+ // bindings. It seems clear that if we were compiling multiple
+ // compute kernels in one invocation we'd want them to get
+ // overlapping bindings, because we cannot ever have them bound
+ // together in a single pipeline state.
+ //
+ // Similarly, entry point parameters of DirectX Raytracing (DXR)
+ // shaders should probably be allowed to overlap by default,
+ // since those parameters should really go into the "local root signature."
+ // (Note: there is a bit more subtlety around ray tracing
+ // shaders that will be assembled into a "hit group")
+ //
+ // For now we are just doing the simplest thing, which will be
+ // appropriate for:
+ //
+ // * Compiling a single compute shader in a compile request.
+ // * Compiling some number of rasterization shader entry points
+ // in a single request, to be used together.
+ // * Compiling a single ray-tracing shader in a compile request.
+ //
+ for( auto entryPoint : sharedContext.programLayout->entryPoints )
{
- 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);
- LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0;
- if( uniformSize != 0 )
- {
- // Make sure uniform fields get laid out properly...
-
- UniformLayoutInfo fieldInfo(
- uniformSize,
- firstVarLayout->typeLayout->uniformAlignment);
-
- LayoutSize uniformOffset = globalScopeRules->AddStructField(
- &structLayoutInfo,
- fieldInfo);
-
- for( auto& varLayout : parameterInfo->varLayouts )
- {
- varLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
- }
- }
-
- globalScopeStructLayout->fields.Add(firstVarLayout);
-
- for( auto& varLayout : parameterInfo->varLayouts )
- {
- globalScopeStructLayout->mapVarToLayout.Add(varLayout->varDecl.getDecl(), varLayout);
- }
+ auto entryPointParamsLayout = entryPoint->parametersLayout;
+ completeBindingsForParameter(&context, entryPointParamsLayout);
}
- 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 )
+ // 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 )
{
- auto globalConstantBufferLayout = createParameterGroupTypeLayout(
- layoutContext,
- nullptr,
- globalScopeRules,
- globalScopeRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
- globalScopeStructLayout);
-
- globalScopeLayout = globalConstantBufferLayout;
+ globalScopeLayoutBuilder.addParameter(parameterInfo);
}
- // 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 globalScopeVarLayout = globalScopeLayoutBuilder.endLayout();
+ if( globalConstantBufferBinding.count != 0 )
{
- auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer);
+ auto cbInfo = globalScopeVarLayout->findOrAddResourceInfo(globalConstantBufferBinding.kind);
cbInfo->space = globalConstantBufferBinding.space;
cbInfo->index = globalConstantBufferBinding.index;
}
- programLayout->globalScopeLayout = globalVarLayout;
+ programLayout->parametersLayout = globalScopeVarLayout;
}
-StructTypeLayout* getGlobalStructLayout(
- ProgramLayout* programLayout);
-
RefPtr<ProgramLayout> specializeProgramLayout(
- TargetRequest * targetReq,
- ProgramLayout* programLayout,
+ TargetRequest* targetReq,
+ ProgramLayout* oldProgramLayout,
SubstitutionSet typeSubst)
{
+ // The goal of the layout specialization step is to take an existing `ProgramLayout`,
+ // and add a layout to any parameter(s) that could not be laid out previously, because
+ // they had a dependence on generic type parameters that made layout impossible at
+ // the time.
+ //
+ // TODO: It would be far simpler to just "re-do" the entire layout process, just
+ // with knowledge of what the global type substitution is, but that would mean that
+ // global parameters that come after a generic-dependent parameter might change
+ // their location/binding/register depending on what types are plugged in.
+ // Our current design preserves the layout for any global parameter that was placed during
+ // the initial layout of a program (before the generic arguments were know).
+ // It isn't clear that this design choice pays off in practice, since there is lot
+ // of complexity in this function.
+
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);
+ newProgramLayout->globalGenericParams = oldProgramLayout->globalGenericParams;
- // If there was no target, or there are no rules for the target,
- // then bail out here.
- if (!layoutContext.rules)
- return newProgramLayout;
+ // The basic idea will be to iterate over the parameters in the old layout,
+ // and "pick up where we left off" in terms of allocating registers to things.
+ //
+ // That means we will look at the existing parameters (that were laid out already)
+ // and mark any registers/bytes/bindings/etc. that they occupy as "used" so
+ // that the subsequent layout of the generic-dependency parameters will not
+ // collide with them.
+ //
+ // We will use the same kind of context type as the original parameter binding
+ // step did, so we initialize its state here:
+
+ auto layoutContext = getInitialLayoutContextForTarget(targetReq);
+ SLANG_ASSERT(layoutContext.rules);
-
- // we need to initialize a layout context to mark used registers
SharedParameterBindingContext sharedContext;
sharedContext.compileRequest = targetReq->compileRequest;
sharedContext.defaultLayoutRules = layoutContext.getRulesFamily();
sharedContext.programLayout = newProgramLayout;
sharedContext.targetRequest = targetReq;
- // 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;
-
-
+
+ // We will also need state for laying out any global-scope parameters
+ // that include ordinary/uniform data.
+ //
+ auto oldGlobalStructLayout = getGlobalStructLayout(oldProgramLayout);
+ SLANG_ASSERT(oldGlobalStructLayout);
+
+ ScopeLayoutBuilder newGlobalScopeLayoutBuilder;
+ newGlobalScopeLayoutBuilder.beginLayout(&context);
+ auto& newGlobalStructLayoutInfo = newGlobalScopeLayoutBuilder.m_structLayoutInfo;
+ auto newGlobalStructLayout = newGlobalScopeLayoutBuilder.m_structLayout;
+
+ // The initial state for uniform layout will be based on whatever
+ // global-scope ordinary/uniform parameters were laid out before.
+ // The alignment can be read directly from the old global layout.
+ //
+ newGlobalStructLayoutInfo.alignment = oldGlobalStructLayout->uniformAlignment;
+ newGlobalStructLayoutInfo.size = 0;
+
+ // The remaining information needs to be collected by looking at
+ // the individual parameters in the existing layout.
+ //
+ bool oldAnyUniforms = false;
+ for(auto oldVarLayout : oldGlobalStructLayout->fields)
+ {
+ // If a parameter made use of a global generic parameter, then we would
+ // have skipped applying layout to it in the original layout process,
+ // and so we should skip it for the process of recovering the existing
+ // layout information.
+ //
+ if (oldVarLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
+ continue;
+
+ // Otherwise, we will "reserve" any resources that the parameter was
+ // determined to consume.
+ //
+ // The easy case is any registers/bindings used for textures/sampler/etc.
+ // We iterate over the kinds of resources consumed by teh parameter.
+ //
+ for( auto varResInfo : oldVarLayout->resourceInfos )
+ {
+ // For each kind of resource consumed the `varResInfo` will tell us
+ // the start of the consumed range, whle the type will be needed
+ // to tell us the amount of resources consumed.
+ //
+ if( auto typeResInfo = oldVarLayout->typeLayout->FindResourceInfo(varResInfo.kind) )
+ {
+ // We will mark the range of resources consumed by theis parameter
+ // as "used" so that it cannot be claimed by later parameters.
+ //
+ auto usedRangeSet = findUsedRangeSetForSpace(&context, varResInfo.space);
+ markSpaceUsed(&context, varResInfo.space);
+ usedRangeSet->usedResourceRanges[(int)varResInfo.kind].Add(
+ nullptr, // we don't need to track parameter info here
+ varResInfo.index,
+ varResInfo.index + typeResInfo->count);
+ }
+ }
+
+ // The more subtle case is when the parameter consumes ordinary bytes
+ // of uniform (constant buffer) memory, because we do not use the
+ // same "used range" model to allocate space for ordinary data.
+ //
+ // Instead, we simply track the highest byte offset covered by any parameter.
+ //
+ if (auto varUniformInfo = oldVarLayout->FindResourceInfo(LayoutResourceKind::Uniform))
+ {
+ oldAnyUniforms = true;
+
+ if( auto typeUniformInfo = oldVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
+ {
+ newGlobalStructLayoutInfo.size = maximum(
+ newGlobalStructLayoutInfo.size,
+ varUniformInfo->index + typeUniformInfo->count);
+ }
+ }
+ }
+
+ // Rather than attempt to re-use the entry-point layout information
+ // that was collected in the first pass, we will re-collect the
+ // information for entry points from scratch.
+ //
+ // This ensures that when an entry point makes use of a generic type
+ // parameter, the layout of its parameter list strictly follows
+ // the declaration order.
+ //
for (auto & translationUnit : targetReq->compileRequest->translationUnits)
{
for (auto & entryPoint : translationUnit->entryPoints)
@@ -2951,137 +3088,145 @@ RefPtr<ProgramLayout> specializeProgramLayout(
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++)
+ // Now that we've marked thing as being used, we can make a second
+ // sweep to compute the requirements of any generic-dependent parameters.
+ //
+ // Along the way we will build up the new layout for the global-scope
+ // structure type, including the offsets of all ordinary/uniform fields.
+ //
+
+ bool newAnyUniforms = oldAnyUniforms;
+ List<RefPtr<VarLayout>> newVarLayouts;
+ Dictionary<RefPtr<VarLayout>, RefPtr<VarLayout>> mapOldLayoutToNew;
+ for(auto oldVarLayout : oldGlobalStructLayout->fields)
{
- auto &varLayout = globalStructLayout->fields[varId];
- // To recover layout context, we skip generic resources in the first pass
- if (varLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
+ // In this pass, the variables that *don't* depend on generic parameters
+ // are the easy ones to handle. We can just copy them over to the new layout.
+ //
+ if(!oldVarLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
+ {
+ newGlobalStructLayout->fields.Add(oldVarLayout);
continue;
+ }
- if (auto uniformInfo = varLayout->FindResourceInfo(LayoutResourceKind::Uniform))
- {
- anyUniforms = true;
+ // In the case where things are generic-dependent, we need to re-do
+ // the type layout process on the type that results from doing
+ // substutition with the global generic arguments.
+ //
+ RefPtr<Type> oldType = oldVarLayout->getTypeLayout()->getType();
+ RefPtr<Type> newType = oldType->Substitute(typeSubst).as<Type>();
- if( auto tUniformInfo = varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform) )
- {
- structLayoutInfo.size = maximum(structLayoutInfo.size, uniformInfo->index + tUniformInfo->count);
- }
- }
- for( auto resInfo : varLayout->resourceInfos )
+ RefPtr<TypeLayout> newTypeLayout = getTypeLayoutForGlobalShaderParameter(
+ &context,
+ oldVarLayout->varDecl,
+ newType);
+
+ RefPtr<VarLayout> newVarLayout = new VarLayout();
+ newVarLayout->varDecl = oldVarLayout->varDecl;
+ newVarLayout->stage = oldVarLayout->stage;
+ newVarLayout->typeLayout = newTypeLayout;
+
+ newGlobalScopeLayoutBuilder.addParameter(newVarLayout);
+ newVarLayouts.Add(newVarLayout);
+ mapOldLayoutToNew.Add(oldVarLayout, newVarLayout);
+
+ if(auto uniformInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform))
{
- if( auto tresInfo = varLayout->typeLayout->FindResourceInfo(resInfo.kind) )
+ if(uniformInfo->count != 0)
{
- 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);
+ newAnyUniforms = true;
+ diagnoseGlobalUniform(&sharedContext, newVarLayout->varDecl);
}
}
- 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)
+ auto newGlobalScopeVarLayout = newGlobalScopeLayoutBuilder.endLayout();
+
+ // We had better have made a copy of every field in the original layout.
+ //
+ SLANG_ASSERT(oldGlobalStructLayout->fields.Count() == newGlobalStructLayout->fields.Count());
+
+ // If there were no global-scope uniforms before, but there
+ // are now that we've done global substitution, then we
+ // need to allocate a global constant buffer to hold them.
+ //
+ auto newGlobalConstantBufferBinding = maybeAllocateConstantBufferBinding(&context, newAnyUniforms && !oldAnyUniforms);
+
+ // Now we need to "complete" finding for each of the new parameters,
+ // which is the step that actually allocates resource to them.
+ //
+ // Note: we don't support generic-dependent parameters with explicit bindings,
+ // so we should probably emit an error message about that in the original
+ // layout step.
+ //
+ for(auto newVarLayout : newVarLayouts)
{
- globalCBufferInfo.kind = LayoutResourceKind::ConstantBuffer;
- globalCBufferInfo.space = originalGlobalCBufferInfo->space;
- globalCBufferInfo.index = originalGlobalCBufferInfo->index;
+ completeBindingsForParameter(&context, newVarLayout);
}
- // we have the context restored, can continue to layout the generic variables now
- for (uint32_t varId = 0; varId < globalStructLayout->fields.Count(); varId++)
+
+ // One remaining missing step is that the `StructLayout` type maintains
+ // a map from variable declarations to their layouts, and in some cases
+ // multiple declarations will map to the same layout (because, e.g., the
+ // same `cbuffer` was declared in both a vertex and fragment shader file).
+ //
+ // We need to clone that remapping information over from the old program
+ // layout. This is why we created the `mapOldLayoutToNew` mapping in
+ // the preceding loop.
+ //
+ // TODO: This step would be easier if the `StructLayout::mapVarToLayout`
+ // dictionary were instead a mapping from variable declaration to the
+ // *index* of the corresponding layout in the `fields` array.
+ //
+ for(auto entry : oldGlobalStructLayout->mapVarToLayout)
{
- auto &varLayout = globalStructLayout->fields[varId];
- if (varLayout->typeLayout->FindResourceInfo(LayoutResourceKind::GenericResource))
- {
- RefPtr<Type> newType = varLayout->typeLayout->type->Substitute(typeSubst).dynamicCast<Type>();
- RefPtr<TypeLayout> newTypeLayout = CreateTypeLayout(
- layoutContext.with(constantBufferRules),
- newType);
- auto layoutInfo = newTypeLayout->FindResourceInfo(LayoutResourceKind::Uniform);
- LayoutSize uniformSize = layoutInfo ? layoutInfo->count : 0;
- if (uniformSize != 0)
- {
- 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);
- LayoutSize uniformOffset = layoutContext.getRulesFamily()->getConstantBufferRules()->AddStructField(
- &structLayoutInfo,
- fieldInfo);
- newVarLayout->findOrAddResourceInfo(LayoutResourceKind::Uniform)->index = uniformOffset.getFiniteValue();
- anyUniforms = true;
-
- diagnoseGlobalUniform(&sharedContext, varLayout->varDecl);
- }
- structLayout->fields[varId] = newVarLayout;
- varLayoutMapping[varLayout] = newVarLayout;
- }
+ RefPtr<VarLayout> varLayout = entry.Value;
+ mapOldLayoutToNew.TryGetValue(varLayout, varLayout);
+ newGlobalStructLayout->mapVarToLayout[entry.Key] = varLayout;
}
- for (auto mapping : globalStructLayout->mapVarToLayout)
+
+ // Just as for the initial computation of layout, we will complete
+ // binding for entry-point parameters *after* we have laid out
+ // all the global-scope parameters.
+ //
+ // Note that this includes layout of generic-dependent global scope
+ // parameters, so it is possible for entry point uniform parameters
+ // to end up with a different register/binding after generic specialization.
+ // (There really isn't a great way around that)
+ //
+ for( auto entryPoint : sharedContext.programLayout->entryPoints )
{
- RefPtr<VarLayout> updatedVarLayout = mapping.Value;
- varLayoutMapping.TryGetValue(updatedVarLayout, updatedVarLayout);
- structLayout->mapVarToLayout[mapping.Key] = updatedVarLayout;
+ auto entryPointParamsLayout = entryPoint->parametersLayout;
+ completeBindingsForParameter(&context, entryPointParamsLayout);
}
- // 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)
+ // As a last step we need to set up the binding/offset information
+ // for the global scope itself.
+ //
+ // We will start by copying whatever information was in the old layout.
+ //
{
- auto globalConstantBufferLayout = createParameterGroupTypeLayout(
- layoutContext,
- nullptr,
- constantBufferRules,
- constantBufferRules->GetObjectLayout(ShaderParameterKind::ConstantBuffer),
- structLayout);
+ auto oldGlobalScopeVarLayout = oldProgramLayout->parametersLayout;
+ for( auto oldResInfo : oldGlobalScopeVarLayout->resourceInfos )
+ {
+ auto newResInfo = newGlobalScopeVarLayout->findOrAddResourceInfo(oldResInfo.kind);
+ newResInfo->space = oldResInfo.space;
+ newResInfo->kind = oldResInfo.kind;
+ }
+ }
- globalScopeLayout = globalConstantBufferLayout;
- auto cbInfo = globalVarLayout->findOrAddResourceInfo(LayoutResourceKind::ConstantBuffer);
- *cbInfo = globalCBufferInfo;
+ // If we had to create a constant buffer to house the global-scope
+ // ordinary/uniform data, then we need to make sure to set that
+ // information on the global scope.
+ //
+ if(newGlobalConstantBufferBinding.kind != LayoutResourceKind::None )
+ {
+ auto resInfo = newGlobalScopeVarLayout->findOrAddResourceInfo(newGlobalConstantBufferBinding.kind);
+ resInfo->space = newGlobalConstantBufferBinding.space;
+ resInfo->index = newGlobalConstantBufferBinding.index;
}
- globalVarLayout->typeLayout = globalScopeLayout;
- programLayout->globalScopeLayout = globalVarLayout;
- newProgramLayout->globalScopeLayout = globalVarLayout;
+
+ newProgramLayout->parametersLayout = newGlobalScopeVarLayout;
return newProgramLayout;
}
-}
+
+} // namespace Slang
diff --git a/source/slang/reflection.cpp b/source/slang/reflection.cpp
index 76e687483..ce42cf10d 100644
--- a/source/slang/reflection.cpp
+++ b/source/slang/reflection.cpp
@@ -1155,7 +1155,7 @@ SLANG_API unsigned spReflectionEntryPoint_getParameterCount(
auto entryPointLayout = convert(inEntryPoint);
if(!entryPointLayout) return 0;
- return getParameterCount(entryPointLayout);
+ return getParameterCount(entryPointLayout->parametersLayout->typeLayout);
}
SLANG_API SlangReflectionVariableLayout* spReflectionEntryPoint_getParameterByIndex(
@@ -1165,7 +1165,7 @@ SLANG_API SlangReflectionVariableLayout* spReflectionEntryPoint_getParameterByIn
auto entryPointLayout = convert(inEntryPoint);
if(!entryPointLayout) return 0;
- return convert(getParameterByIndex(entryPointLayout, index));
+ return convert(getParameterByIndex(entryPointLayout->parametersLayout->typeLayout, index));
}
SLANG_API SlangStage spReflectionEntryPoint_getStage(SlangReflectionEntryPoint* inEntryPoint)
@@ -1276,12 +1276,6 @@ SLANG_API SlangReflectionType* spReflectionTypeParameter_GetConstraintByIndex(Sl
// Shader Reflection
-namespace Slang
-{
- StructTypeLayout* getGlobalStructLayout(
- ProgramLayout* programLayout);
-}
-
SLANG_API unsigned spReflection_GetParameterCount(SlangReflection* inProgram)
{
auto program = convert(inProgram);
@@ -1365,7 +1359,7 @@ SLANG_API SlangUInt spReflection_getGlobalConstantBufferBinding(SlangReflection*
{
auto program = convert(inProgram);
if (!program) return 0;
- auto cb = program->globalScopeLayout->FindResourceInfo(LayoutResourceKind::ConstantBuffer);
+ auto cb = program->parametersLayout->FindResourceInfo(LayoutResourceKind::ConstantBuffer);
if (!cb) return 0;
return cb->index;
}
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 1ad408e73..b0ac37440 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -185,6 +185,7 @@
<ClInclude Include="ir-constexpr.h" />
<ClInclude Include="ir-dce.h" />
<ClInclude Include="ir-dominators.h" />
+ <ClInclude Include="ir-entry-point-uniforms.h" />
<ClInclude Include="ir-glsl-legalize.h" />
<ClInclude Include="ir-inst-defs.h" />
<ClInclude Include="ir-insts.h" />
@@ -240,6 +241,7 @@
<ClCompile Include="ir-constexpr.cpp" />
<ClCompile Include="ir-dce.cpp" />
<ClCompile Include="ir-dominators.cpp" />
+ <ClCompile Include="ir-entry-point-uniforms.cpp" />
<ClCompile Include="ir-glsl-legalize.cpp" />
<ClCompile Include="ir-legalize-types.cpp" />
<ClCompile Include="ir-link.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 9e3de4b93..0a44f9f57 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -54,6 +54,9 @@
<ClInclude Include="ir-dominators.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-entry-point-uniforms.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="ir-glsl-legalize.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -215,6 +218,9 @@
<ClCompile Include="ir-dominators.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-entry-point-uniforms.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="ir-glsl-legalize.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/source/slang/type-layout.h b/source/slang/type-layout.h
index da2f0e4f7..418f4684d 100644
--- a/source/slang/type-layout.h
+++ b/source/slang/type-layout.h
@@ -575,6 +575,18 @@ public:
LayoutSize tagOffset;
};
+ /// Layout for a scoped entity like a program, module, or entry point
+class ScopeLayout : public Layout
+{
+public:
+ // The layout for the parameters of this entity.
+ //
+ RefPtr<VarLayout> parametersLayout;
+};
+
+StructTypeLayout* getScopeStructLayout(
+ ScopeLayout* programLayout);
+
// Layout information for a single shader entry point
// within a program
//
@@ -584,7 +596,7 @@ public:
//
// TODO: where to store layout info for the return
// type of the function?
-class EntryPointLayout : public StructTypeLayout
+class EntryPointLayout : public ScopeLayout
{
public:
// The corresponding function declaration
@@ -617,9 +629,10 @@ public:
};
// Layout information for the global scope of a program
-class ProgramLayout : public Layout
+class ProgramLayout : public ScopeLayout
{
public:
+ /*
// We store a layout for the declarations at the global
// scope. Note that this will *either* be a single
// `StructTypeLayout` with the fields stored directly,
@@ -634,6 +647,7 @@ public:
// to store them).
//
RefPtr<VarLayout> globalScopeLayout;
+ */
// We catalog the requested entry points here,
// and any entry-point-specific parameter data
@@ -646,6 +660,9 @@ public:
TargetRequest* targetRequest = nullptr;
};
+StructTypeLayout* getGlobalStructLayout(
+ ProgramLayout* programLayout);
+
struct LayoutRulesFamilyImpl;
// A delineation of shader parameter types into fine-grained