summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2021-04-29 15:27:24 -0700
committerGitHub <noreply@github.com>2021-04-29 15:27:24 -0700
commit37a341775e410c02df5072244becdc416fd15c86 (patch)
tree4f8110596c22140fd529c56915e60283493eaa63
parentaba8ec619de2bcf908901fa33677bb1d35296df8 (diff)
Update gfx back-ends to handle static specialization (#1826)
* Update gfx back-ends to handle static specialization The main goal here is to make the D3D11, D3D12 and Vulkan back-ends support static specialization of interface types in the case where the data for the type won't "fit" in the pre-allocated space for existential values. This includes all cases where the concrete type being specialized to has resources/samplers/etc., as well as any cases where its ordinary/uniform data exceeds the space available. (Note that the CPU and CUDA targets don't need this work since they can (in theory) support arbitrary-size data in the fixed-size existential payload by using pointer indirection. Actually supporting indirection in those cases should be a distinct change) The Slang compiler already performs layout for programs that have this kind of data that doesn't "fit," and it lays them out using an idea of "pending" type layouts. Basically, a type that contains some amount of specialized interface-type fields will produce both a "primary" type layout that just covers the data for the unspecialized case, as well as "pending" type layout that describes the layout for all the extra data needed by specialization. When laying out a `ConstantBuffer<X>` or `ParameterBlocK<X>` ("CB" or "PB"), the front-end will try to place as much of that "pending" data into the layout of the buffer/block itself as is possible. That means that both CBs and PBs will be able to allocate trailing bytes for any ordinary data in the "pending" layout. PBs will be able to allocate any trailing resources/samplers into their layout, but for CBs they will spill out to be part of the pending layout for the buffer itself. In order for the back-ends to properly handle pending data, they need to *either* assume the exact layout rules used by the front-end and try to reproduce them (e.g., by iterating over binding ranges and sub-objects in the exact same order that front-end layout would enumerate them), *or* they need to respect the reflection information produced by the front-end. This change takes the latter approach, trying to make only minimal assumptions about the layout rules being used. This choice is motivated by wanting to decouple the `gfx` implementation from the compiler front-end, especially insofar as this work has made me question whether the current layout rules are the best ones possible. A common theme across all the implementations is to have a fixed-size type that can represent "binding offsets" for the chosen back-end. The offset type has fields that depend on the API-specific way bindings are indexed; e.g., for D3D11 it has offsets for CBV, SRV, UAV, and sampler bindings. This fixed-size offset type can be filled in based on Slang reflecton information, and then used to compute derived offsets with just a few add operations. The simple offset type for each API is then extended to produce an offset type that includes both the offsets for "primary" data and also the offsets for "pending" data. Most logic that traffics in offsets doesn't have to know about this more complicated representation. Making consistent use of these offsets required that I pretty much rewrite the logic that actually applies shader objects to the API state. Doing so might be lowering the efficiency of the system in the near term, but the increase in clarity was important for getting the work done, and it seems like it will also be important if/when we start trying to perform special-case optimizations around root and entry-point parameter setting. While there are many API-specific differences, we can identify a repeated pattern where many steps, whether applying parameters to the pipeline stage or constructing signatures / layouts, can be broken down into three main operations on `ShaderObject`s or their layouts: * `*AsValue()` is the core operation, and is the one used for the `ExistentialValue` case most of the time. It ignores the ordinary data in the object, and instead processes all nested binding ranges (for resources/smaplers) and sub-objects. * `*AsConstantBuffer()` handles the `ConstntBuffer<X>` case, by dealing with the implicit buffer for ordinary data (if it is needed) and then delegates to the `*AsValue()` case. * `*AsParameterBlock()` handles the `ParameterBlock<X>` case, by allocating/preparing/etc. any descriptor tables/sets that would be required for the current object/layout and then delegating to `*AsConstantBuffer()` to do the rest The idea is that by having the parameter block case delegate to the constant buffer case, which delegates to the value/existential case, we can streamline a lot of the logic so that it doesn't seem quite as full of special cases. Note: When preparing this pull request I spent a reasonable amount of time trying to clean up the D3D11 and Vulkan implementations, so they are probably the easiest to read and understand when it comes to the new code. Doing the cleanup work also helped to work out some weird corner case bugs/issues. In contrast, the D3D12 path hasn't had as much attention given to cleanliness and comments, so it really needs some attention down the line to get things into a state that is easier to understand. * fixup: remove debugging code spotted in review
-rw-r--r--examples/model-viewer/main.cpp35
-rw-r--r--examples/model-viewer/shaders.slang8
-rw-r--r--slang.h10
-rw-r--r--source/core/slang-string.h2
-rw-r--r--source/slang/slang-ir-legalize-types.cpp4
-rw-r--r--source/slang/slang-ir-lower-tuple-types.cpp15
-rw-r--r--source/slang/slang-reflection-api.cpp214
-rw-r--r--source/slang/slang-type-layout.cpp13
-rw-r--r--source/slang/slang-type-layout.h6
-rw-r--r--tests/compute/interface-shader-param-in-struct.slang6
-rw-r--r--tests/language-feature/shader-params/interface-shader-param-ordinary.slang53
-rw-r--r--tests/language-feature/shader-params/interface-shader-param-ordinary.slang.expected.txt4
-rw-r--r--tools/gfx/d3d11/render-d3d11.cpp965
-rw-r--r--tools/gfx/d3d12/render-d3d12.cpp1070
-rw-r--r--tools/gfx/open-gl/render-gl.cpp16
-rw-r--r--tools/gfx/renderer-shared.cpp27
-rw-r--r--tools/gfx/vulkan/render-vk.cpp1901
17 files changed, 3345 insertions, 1004 deletions
diff --git a/examples/model-viewer/main.cpp b/examples/model-viewer/main.cpp
index f6aab4c4a..6e21f9050 100644
--- a/examples/model-viewer/main.cpp
+++ b/examples/model-viewer/main.cpp
@@ -151,7 +151,7 @@ struct SimpleMaterial : Material
{
glm::vec3 diffuseColor;
glm::vec3 specularColor;
- float specularity;
+ float specularity = 1.0f;
// Create a shader object that contains the type info and parameter values
// that represent an instance of `SimpleMaterial`.
@@ -282,16 +282,17 @@ RefPtr<Model> loadModel(
struct Light : RefObject
{
- // A light must be able to create a shader object defining its
- // corresponding shader type and parameter values.
- virtual IShaderObject* createShaderObject(RendererContext* context) = 0;
+ // A light must be able to write its state into a shader parameters
+ // of the matching Slang type.
+ //
+ virtual void writeTo(ShaderCursor const& cursor) = 0;
// Retrieves the shader type for this light object.
virtual slang::TypeReflection* getShaderType(RendererContext* context) = 0;
// The shader object for a light will be stashed here
// after it is created.
- ComPtr<IShaderObject> shaderObject;
+// ComPtr<IShaderObject> shaderObject;
};
// Helper function to retrieve the underlying shader type of `T`.
@@ -314,14 +315,10 @@ struct DirectionalLight : Light
static const char* getTypeName() { return "DirectionalLight"; }
- virtual IShaderObject* createShaderObject(RendererContext* context) override
+ virtual void writeTo(ShaderCursor const& cursor)
{
- auto shaderType = ::getShaderType<DirectionalLight>(context);
- shaderObject = context->device->createShaderObject(shaderType);
- gfx::ShaderCursor cursor(shaderObject);
cursor["direction"].setData(&direction, sizeof(direction));
cursor["intensity"].setData(&intensity, sizeof(intensity));
- return shaderObject.get();
}
virtual slang::TypeReflection* getShaderType(RendererContext* context) override
@@ -337,14 +334,10 @@ struct PointLight : Light
static const char* getTypeName() { return "PointLight"; }
- virtual IShaderObject* createShaderObject(RendererContext* context) override
+ virtual void writeTo(ShaderCursor const& cursor)
{
- auto shaderType = ::getShaderType<PointLight>(context);
- shaderObject = context->device->createShaderObject(shaderType);
- gfx::ShaderCursor cursor(shaderObject);
cursor["position"].setData(&position, sizeof(position));
cursor["intensity"].setData(&intensity, sizeof(intensity));
- return shaderObject.get();
}
virtual slang::TypeReflection* getShaderType(RendererContext* context) override
@@ -588,8 +581,7 @@ struct LightEnv : public RefObject
if (lightCount > 0)
{
- lightTypeCursor.setObject(
- lightTypeArray->lights[0]->createShaderObject(context));
+ lightTypeArray->lights[0]->writeTo(lightTypeCursor);
}
else
{
@@ -614,8 +606,7 @@ struct LightEnv : public RefObject
auto arrayCursor = lightTypeCursor["lights"];
for (size_t ii = 0; ii < lightCount; ++ii)
{
- arrayCursor[ii].setObject(
- lightTypeArray->lights[ii]->createShaderObject(context));
+ lightTypeArray->lights[ii]->writeTo(arrayCursor[ii]);
}
}
}
@@ -781,7 +772,11 @@ Result initialize()
lightEnvLayout->addLightType<DirectionalLight>(&context, 2);
lightEnv = new LightEnv(lightEnvLayout, &context);
- lightEnv->add(new PointLight());
+
+ RefPtr<PointLight> pointLight = new PointLight();
+ pointLight->position = glm::vec3(5, 3, 1);
+ pointLight->intensity = glm::vec3(10);
+ lightEnv->add(pointLight);
// Once we have created all our graphcis API and application resources,
// we can start to load models. For now we are keeping things extremely
diff --git a/examples/model-viewer/shaders.slang b/examples/model-viewer/shaders.slang
index 0005b427b..81ec89d03 100644
--- a/examples/model-viewer/shaders.slang
+++ b/examples/model-viewer/shaders.slang
@@ -85,7 +85,13 @@ struct BlinnPhong : IBRDF
float3 H = normalize(L + V);
float nDotH = saturate(dot(N, H));
- return kd*nDotL + ks*pow(nDotH, specularity);
+ // TODO: The current model loading has a bug that is leading
+ // to the `ks` and `specularity` fields being invalid garbage
+ // for our example cube, and the result is a non-finite value
+ // coming out of `evaluate()` if we include the specular term.
+
+// return kd*nDotL + ks*pow(nDotH, specularity);
+ return kd*nDotL;
}
};
//
diff --git a/slang.h b/slang.h
index 673d93d1e..f8f5c3073 100644
--- a/slang.h
+++ b/slang.h
@@ -1991,6 +1991,8 @@ extern "C"
SLANG_API SlangReflectionTypeLayout* spReflectionTypeLayout_getPendingDataTypeLayout(SlangReflectionTypeLayout* type);
SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_getSpecializedTypePendingDataVarLayout(SlangReflectionTypeLayout* type);
+ SLANG_API SlangInt spReflectionType_getSpecializedTypeArgCount(SlangReflectionType* type);
+ SLANG_API SlangReflectionType* spReflectionType_getSpecializedTypeArgType(SlangReflectionType* type, SlangInt index);
SLANG_API SlangInt spReflectionTypeLayout_getBindingRangeCount(SlangReflectionTypeLayout* typeLayout);
SLANG_API SlangBindingType spReflectionTypeLayout_getBindingRangeType(SlangReflectionTypeLayout* typeLayout, SlangInt index);
@@ -2013,6 +2015,7 @@ extern "C"
SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeCount(SlangReflectionTypeLayout* typeLayout);
SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeBindingRangeIndex(SlangReflectionTypeLayout* typeLayout, SlangInt subObjectRangeIndex);
SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeSpaceOffset(SlangReflectionTypeLayout* typeLayout, SlangInt subObjectRangeIndex);
+ SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_getSubObjectRangeOffset(SlangReflectionTypeLayout* typeLayout, SlangInt subObjectRangeIndex);
#if 0
SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeCount(SlangReflectionTypeLayout* typeLayout);
@@ -2695,6 +2698,13 @@ namespace slang
(SlangReflectionTypeLayout*) this,
subObjectRangeIndex);
}
+
+ VariableLayoutReflection* getSubObjectRangeOffset(SlangInt subObjectRangeIndex)
+ {
+ return (VariableLayoutReflection*) spReflectionTypeLayout_getSubObjectRangeOffset(
+ (SlangReflectionTypeLayout*) this,
+ subObjectRangeIndex);
+ }
};
struct Modifier
diff --git a/source/core/slang-string.h b/source/core/slang-string.h
index f42e78ac4..6b9a73bcb 100644
--- a/source/core/slang-string.h
+++ b/source/core/slang-string.h
@@ -279,7 +279,7 @@ namespace Slang
{
public:
UnownedTerminatedStringSlice(char const* b)
- : UnownedStringSlice(b, b + strlen(b))
+ : UnownedStringSlice(b, b + (b?strlen(b):0))
{}
};
diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp
index 9b95e069d..1cd94decb 100644
--- a/source/slang/slang-ir-legalize-types.cpp
+++ b/source/slang/slang-ir-legalize-types.cpp
@@ -2435,7 +2435,7 @@ static LegalVal legalizeGlobalVar(
default:
{
- context->insertBeforeGlobal = irGlobalVar->getNextInst();
+ context->insertBeforeGlobal = irGlobalVar;
IRGlobalNameInfo globalNameInfo;
globalNameInfo.globalVar = irGlobalVar;
@@ -2480,7 +2480,7 @@ static LegalVal legalizeGlobalParam(
default:
{
- context->insertBeforeGlobal = irGlobalParam->getNextInst();
+ context->insertBeforeGlobal = irGlobalParam;
LegalVarChainLink varChain(LegalVarChain(), varLayout);
diff --git a/source/slang/slang-ir-lower-tuple-types.cpp b/source/slang/slang-ir-lower-tuple-types.cpp
index a155ae8d3..429058edf 100644
--- a/source/slang/slang-ir-lower-tuple-types.cpp
+++ b/source/slang/slang-ir-lower-tuple-types.cpp
@@ -121,6 +121,18 @@ namespace Slang
inst->removeAndDeallocate();
}
+ void processTupleType(IRTupleType* inst)
+ {
+ IRBuilder builderStorage;
+ auto builder = &builderStorage;
+ builder->sharedBuilder = &sharedBuilderStorage;
+ builder->setInsertBefore(inst);
+
+ auto loweredTupleInfo = getLoweredTupleType(builder, inst);
+ SLANG_ASSERT(loweredTupleInfo);
+ SLANG_UNUSED(loweredTupleInfo);
+ }
+
void processInst(IRInst* inst)
{
switch (inst->getOp())
@@ -131,6 +143,9 @@ namespace Slang
case kIROp_GetTupleElement:
processGetTupleElement((IRGetTupleElement*)inst);
break;
+ case kIROp_TupleType:
+ processTupleType((IRTupleType*)inst);
+ break;
default:
break;
}
diff --git a/source/slang/slang-reflection-api.cpp b/source/slang/slang-reflection-api.cpp
index e68efde06..d75920fdb 100644
--- a/source/slang/slang-reflection-api.cpp
+++ b/source/slang/slang-reflection-api.cpp
@@ -1056,22 +1056,104 @@ SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_getSpecializedTy
}
}
+SLANG_API SlangInt spReflectionType_getSpecializedTypeArgCount(SlangReflectionType* inType)
+{
+ auto type = convert(inType);
+ if(!type) return 0;
+
+ auto specializedType = as<ExistentialSpecializedType>(type);
+ if(!specializedType) return 0;
+
+ return specializedType->args.getCount();
+}
+
+SLANG_API SlangReflectionType* spReflectionType_getSpecializedTypeArgType(SlangReflectionType* inType, SlangInt index)
+{
+ auto type = convert(inType);
+ if(!type) return nullptr;
+
+ auto specializedType = as<ExistentialSpecializedType>(type);
+ if(!specializedType) return nullptr;
+
+ if(index < 0) return nullptr;
+ if(index >= specializedType->args.getCount()) return nullptr;
+
+ auto argType = as<Type>(specializedType->args[index].val);
+ return convert(argType);
+}
+
namespace Slang
{
+ /// A link in a chain of `VarLayout`s that can be used to compute offset information for a nested field
struct BindingRangePathLink
{
+ BindingRangePathLink()
+ {}
+
BindingRangePathLink(
BindingRangePathLink* parent,
VarLayout* var)
- : parent(parent)
- , var(var)
+ : var(var)
+ , parent(parent)
{}
- BindingRangePathLink* parent;
- VarLayout* var;
+ /// The inner-most variable that contributes to the offset along this path
+ VarLayout* var = nullptr;
+
+ /// The next outer link along the path
+ BindingRangePathLink* parent = nullptr;
+ };
+
+ /// A path leading to some nested field, with both parimary and "pending" data offsets
+ struct BindingRangePath
+ {
+
+ /// The chain of variables that defines the "primary" offset of a nested field
+ BindingRangePathLink* primary = nullptr;
+
+ /// The chain of variables that defines the offset for "pending" data of a nested field
+ BindingRangePathLink* pending = nullptr;
};
+ /// A helper type to construct a `BindingRangePath` that extends an existing path
+ struct ExtendedBindingRangePath : BindingRangePath
+ {
+ /// Construct a path that extends `parent` with offset information from `varLayout`
+ ExtendedBindingRangePath(
+ BindingRangePath const& parent,
+ VarLayout* varLayout)
+ {
+ SLANG_ASSERT(varLayout);
+
+ // We always add another link to the primary chain.
+ //
+ primaryLink = BindingRangePathLink(parent.primary, varLayout);
+ primary = &primaryLink;
+
+ // If the `varLayout` provided has any offset information
+ // for pending data, then we also add a link to the pending
+ // chain, but otherwise we re-use the pending chain from
+ // the parent path.
+ //
+ if(auto pendingLayout = varLayout->pendingVarLayout)
+ {
+ pendingLink = BindingRangePathLink(parent.pending, pendingLayout);
+ pending = &pendingLink;
+ }
+ else
+ {
+ pending = parent.pending;
+ }
+ }
+
+ /// Storage for a link in the primary chain, if needed
+ BindingRangePathLink primaryLink;
+
+ /// Storage for a link in the pending chain, if needed
+ BindingRangePathLink pendingLink;
+ };
+ /// Calculate the offset for resources of the given `kind` in the `path`.
Int _calcIndexOffset(BindingRangePathLink* path, LayoutResourceKind kind)
{
Int result = 0;
@@ -1085,6 +1167,7 @@ namespace Slang
return result;
}
+ /// Calculate the regsiter space / set for resources of the given `kind` in the `path`.
Int _calcSpaceOffset(BindingRangePathLink* path, LayoutResourceKind kind)
{
Int result = 0;
@@ -1208,12 +1291,24 @@ namespace Slang
}
}
-
-
SlangBindingType _calcBindingType(
Slang::TypeLayout* typeLayout,
LayoutResourceKind kind)
{
+ // At the type level, a push-constant buffer and a regular constant
+ // buffer are currently not distinct, so we need to detect push
+ // constant buffers/ranges before we inspect the `typeLayout` to
+ // avoid reflecting them all as ordinary constant buffers.
+ //
+ switch(kind)
+ {
+ default:
+ break;
+
+ case LayoutResourceKind::PushConstantBuffer:
+ return SLANG_BINDING_TYPE_PUSH_CONSTANT;
+ }
+
// If the type or type layout implies a specific binding type
// (e.g., a `Texture2D` implies a texture binding), then we
// will always favor the binding type implied.
@@ -1267,7 +1362,42 @@ namespace Slang
return index;
}
- void addRangesRec(TypeLayout* typeLayout, BindingRangePathLink* path, LayoutSize multiplier)
+ /// Create a single `VarLayout` for `typeLayout` that summarizes all of the offset information in `path`.
+ ///
+ /// Note: This function does not handle "pending" layout information.
+ RefPtr<VarLayout> _createSimpleOffsetVarLayout(TypeLayout* typeLayout, BindingRangePathLink* path)
+ {
+ SLANG_ASSERT(typeLayout);
+
+ RefPtr<VarLayout> varLayout = new VarLayout();
+ varLayout->typeLayout = typeLayout;
+
+ for(auto typeResInfo : typeLayout->resourceInfos)
+ {
+ auto kind = typeResInfo.kind;
+ auto varResInfo = varLayout->findOrAddResourceInfo(kind);
+ varResInfo->index = _calcIndexOffset(path, kind);
+ varResInfo->space = _calcSpaceOffset(path, kind);
+ }
+
+ return varLayout;
+ }
+
+ /// Create a single `VarLayout` for `typeLayout` that summarizes all of the offset information in `path`.
+ RefPtr<VarLayout> createOffsetVarLayout(TypeLayout* typeLayout, BindingRangePath const& path)
+ {
+ auto primaryVarLayout = _createSimpleOffsetVarLayout(typeLayout, path.primary);
+ SLANG_ASSERT(primaryVarLayout);
+
+ if(auto pendingDataTypeLayout = typeLayout->pendingDataTypeLayout)
+ {
+ primaryVarLayout->pendingVarLayout = _createSimpleOffsetVarLayout(pendingDataTypeLayout, path.pending);
+ }
+
+ return primaryVarLayout;
+ }
+
+ void addRangesRec(TypeLayout* typeLayout, BindingRangePath const& path, LayoutSize multiplier)
{
if( auto structTypeLayout = as<StructTypeLayout>(typeLayout) )
{
@@ -1287,9 +1417,8 @@ namespace Slang
auto fieldTypeLayout = fieldVarLayout->getTypeLayout();
-
- BindingRangePathLink fieldLink(path, fieldVarLayout);
- addRangesRec(fieldTypeLayout, &fieldLink, multiplier);
+ ExtendedBindingRangePath fieldPath(path, fieldVarLayout);
+ addRangesRec(fieldTypeLayout, fieldPath, multiplier);
}
return;
}
@@ -1336,7 +1465,7 @@ namespace Slang
{
if( spaceOffset == -1 )
{
- spaceOffset = _calcSpaceOffset(path, kind);
+ spaceOffset = _calcSpaceOffset(path.primary, kind);
}
kind = resInfo.kind;
@@ -1382,24 +1511,17 @@ namespace Slang
bindingRange.firstDescriptorRangeIndex = 0;
bindingRange.descriptorRangeCount = 0;
- if( kind == LayoutResourceKind::PushConstantBuffer )
- {
- if(auto resInfo = parameterGroupTypeLayout->elementVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform))
- {
- bindingRange.count *= resInfo->count;
- }
- }
-
// Every parameter group will introduce a sub-object range,
// which will include bindings based on the type of data
// inside the sub-object.
//
TypeLayout::ExtendedInfo::SubObjectRangeInfo subObjectRange;
subObjectRange.bindingRangeIndex = bindingRangeIndex;
+ subObjectRange.offsetVarLayout = createOffsetVarLayout(typeLayout, path);
subObjectRange.spaceOffset = 0;
- if (kind == LayoutResourceKind::RegisterSpace && path)
+ if (kind == LayoutResourceKind::RegisterSpace && path.primary)
{
- auto resInfo = path->var->FindResourceInfo(LayoutResourceKind::RegisterSpace);
+ auto resInfo = path.primary->var->FindResourceInfo(LayoutResourceKind::RegisterSpace);
subObjectRange.spaceOffset = resInfo->index;
}
// It is possible that the sub-object has descriptor ranges
@@ -1488,16 +1610,7 @@ namespace Slang
descriptorRange.kind = resInfo.kind;
descriptorRange.bindingType = _calcBindingType(typeLayout, resInfo.kind);
descriptorRange.count = multiplier;
- descriptorRange.indexOffset = _calcIndexOffset(path, resInfo.kind);
-
- if( resInfo.kind == LayoutResourceKind::PushConstantBuffer )
- {
- if(auto uniformResInfo = parameterGroupTypeLayout->elementVarLayout->typeLayout->FindResourceInfo(LayoutResourceKind::Uniform))
- {
- descriptorRange.count *= uniformResInfo->count;
- }
- }
-
+ descriptorRange.indexOffset = _calcIndexOffset(path.primary, resInfo.kind);
descriptorSet->descriptorRanges.add(descriptorRange);
}
}
@@ -1548,12 +1661,12 @@ namespace Slang
// up binding information, to ensure that the descriptor ranges
// that get enumerated here have correct register/binding offsets.
//
- BindingRangePathLink elementPath(path, parameterGroupTypeLayout->elementVarLayout);
+ ExtendedBindingRangePath elementPath(path, parameterGroupTypeLayout->elementVarLayout);
Index bindingRangeCountBefore = m_extendedInfo->m_bindingRanges.getCount();
Index subObjectRangeCountBefore = m_extendedInfo->m_subObjectRanges.getCount();
- addRangesRec(parameterGroupTypeLayout->elementVarLayout->typeLayout, &elementPath, multiplier);
+ addRangesRec(parameterGroupTypeLayout->elementVarLayout->typeLayout, elementPath, multiplier);
m_extendedInfo->m_bindingRanges.setCount(bindingRangeCountBefore);
m_extendedInfo->m_subObjectRanges.setCount(subObjectRangeCountBefore);
@@ -1576,10 +1689,6 @@ namespace Slang
// An `interface` type should introduce a binding range and a matching
// sub-object range.
//
- // We currently do *not* allocate any descriptor ranges to represent
- // an interface-type field, since the only direct storage required
- // is all uniform/ordinary data.
- //
TypeLayout::ExtendedInfo::BindingRangeInfo bindingRange;
bindingRange.leafTypeLayout = typeLayout;
bindingRange.bindingType = SLANG_BINDING_TYPE_EXISTENTIAL_VALUE;
@@ -1590,10 +1699,7 @@ namespace Slang
TypeLayout::ExtendedInfo::SubObjectRangeInfo subObjectRange;
subObjectRange.bindingRangeIndex = m_extendedInfo->m_bindingRanges.getCount();
-
- // TODO: if we have "pending" layout information that tells us where
- // data for the sub-object range has been allocated, then we need
- // a way to reference that data here.
+ subObjectRange.offsetVarLayout = createOffsetVarLayout(typeLayout, path);
m_extendedInfo->m_bindingRanges.add(bindingRange);
m_extendedInfo->m_subObjectRanges.add(subObjectRange);
@@ -1730,8 +1836,8 @@ namespace Slang
// `resInfo` representing resouce usage.
//
auto count = resInfo.count * multiplier;
- auto indexOffset = _calcIndexOffset(path, kind);
- auto spaceOffset = _calcSpaceOffset(path, kind);
+ auto indexOffset = _calcIndexOffset(path.primary, kind);
+ auto spaceOffset = _calcSpaceOffset(path.primary, kind);
TypeLayout::ExtendedInfo::DescriptorRangeInfo descriptorRange;
descriptorRange.kind = kind;
@@ -1767,7 +1873,8 @@ namespace Slang
context.m_typeLayout = typeLayout;
context.m_extendedInfo = extendedInfo;
- context.addRangesRec(typeLayout, nullptr, 1);
+ BindingRangePath rootPath;
+ context.addRangesRec(typeLayout, rootPath, 1);
typeLayout->m_extendedInfo = extendedInfo;
}
@@ -2033,6 +2140,25 @@ SLANG_API SlangInt spReflectionTypeLayout_getSubObjectRangeSpaceOffset(
return extTypeLayout->m_subObjectRanges[subObjectRangeIndex].spaceOffset;
}
+SLANG_API SlangReflectionVariableLayout* spReflectionTypeLayout_getSubObjectRangeOffset(
+ SlangReflectionTypeLayout* inTypeLayout,
+ SlangInt subObjectRangeIndex)
+{
+ auto typeLayout = convert(inTypeLayout);
+ if (!typeLayout)
+ return 0;
+
+ auto extTypeLayout = Slang::getExtendedTypeLayout(typeLayout);
+
+ if (subObjectRangeIndex < 0)
+ return 0;
+ if (subObjectRangeIndex >= extTypeLayout->m_subObjectRanges.getCount())
+ return 0;
+
+ return convert(extTypeLayout->m_subObjectRanges[subObjectRangeIndex].offsetVarLayout);
+}
+
+
#if 0
SLANG_API SlangInt spReflectionTypeLayout_getBindingRangeSubObjectRangeIndex(SlangReflectionTypeLayout* inTypeLayout, SlangInt index)
diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp
index a015bfd78..f21722bd7 100644
--- a/source/slang/slang-type-layout.cpp
+++ b/source/slang/slang-type-layout.cpp
@@ -4013,6 +4013,12 @@ static TypeLayoutResult _createTypeLayout(
typeLayout->type = type;
typeLayout->rules = rules;
+ for( auto resInfo : baseTypeLayoutResult.layout->resourceInfos )
+ {
+ if(resInfo.kind != LayoutResourceKind::Uniform)
+ typeLayout->addResourceUsage(resInfo);
+ }
+
RefPtr<VarLayout> pendingDataVarLayout = new VarLayout();
if(auto pendingDataTypeLayout = baseTypeLayoutResult.layout->pendingDataTypeLayout)
{
@@ -4033,6 +4039,7 @@ static TypeLayoutResult _createTypeLayout(
{
if(auto primaryResInfo = baseTypeLayoutResult.layout->FindResourceInfo(kind))
index = primaryResInfo->count.getFiniteValue();
+ typeLayout->addResourceUsage(pendingResInfo);
}
pendingDataVarLayout->AddResourceInfo(kind)->index = index;
}
@@ -4041,6 +4048,12 @@ static TypeLayoutResult _createTypeLayout(
typeLayout->baseTypeLayout = baseTypeLayoutResult.layout;
typeLayout->pendingDataVarLayout = pendingDataVarLayout;
+ typeLayout->uniformAlignment = info.alignment;
+ if( info.size != 0 )
+ {
+ typeLayout->addResourceUsage(LayoutResourceKind::Uniform, info.size);
+ }
+
return makeTypeLayoutResult(typeLayout);
}
diff --git a/source/slang/slang-type-layout.h b/source/slang/slang-type-layout.h
index 88ba72003..52b575787 100644
--- a/source/slang/slang-type-layout.h
+++ b/source/slang/slang-type-layout.h
@@ -310,6 +310,7 @@ struct SimpleArrayLayoutInfo : SimpleLayoutInfo
};
struct LayoutRulesImpl;
+class VarLayout;
// Base class for things that store layout info
class Layout : public RefObject
@@ -436,8 +437,9 @@ public:
struct SubObjectRangeInfo
{
- Int bindingRangeIndex;
- Int spaceOffset;
+ Int bindingRangeIndex = 0;
+ Int spaceOffset = 0;
+ RefPtr<VarLayout> offsetVarLayout;
};
List<RefPtr<DescriptorSetInfo>> m_descriptorSets;
diff --git a/tests/compute/interface-shader-param-in-struct.slang b/tests/compute/interface-shader-param-in-struct.slang
index 62aa093ed..5080e4d57 100644
--- a/tests/compute/interface-shader-param-in-struct.slang
+++ b/tests/compute/interface-shader-param-in-struct.slang
@@ -3,9 +3,9 @@
// This test puts interface-type shader parameters
// inside of structure types to make sure that works
-//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
-//DISABLED_TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -profile sm_6_0 -use-dxil
-//DISABLED_TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -profile sm_6_0 -use-dxil
+//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
// A lot of the setup is the same as for `interface-shader-param`,
// so look there if you want the comments.
diff --git a/tests/language-feature/shader-params/interface-shader-param-ordinary.slang b/tests/language-feature/shader-params/interface-shader-param-ordinary.slang
new file mode 100644
index 000000000..8d475254f
--- /dev/null
+++ b/tests/language-feature/shader-params/interface-shader-param-ordinary.slang
@@ -0,0 +1,53 @@
+// interface-shader-param-ordinary.slang
+
+// This test is for interface-type shader parameters that
+// get specialized to types that include "ordinary" data
+// but also don't fit into the allocation provided for
+// them in the existential-type field itself.
+
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute
+//TEST(compute):COMPARE_COMPUTE_EX:-slang -compute -dx12 -profile sm_6_0 -use-dxil
+//TEST(compute, vulkan):COMPARE_COMPUTE_EX:-vk -compute
+
+interface IModifier
+{
+ int modify(int value);
+}
+
+//TEST_INPUT:set gOutputBuffer = out ubuffer(data=[0 0 0 0], stride=4)
+RWStructuredBuffer<int> gOutputBuffer;
+
+//TEST_INPUT:set delta = 65536
+uniform int delta;
+
+//TEST_INPUT:set gModifier = new MyModifier{ ubuffer(data=[4 3 2 1], stride=4), 3 } }
+uniform IModifier gModifier;
+
+int test(int val)
+{
+ return gModifier.modify(val) + delta;
+}
+
+
+[numthreads(4, 1, 1)]
+void computeMain(
+ uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ let tid = dispatchThreadID.x;
+
+ let inputVal : int = tid;
+ let outputVal = test(inputVal);
+
+ gOutputBuffer[tid] = outputVal;
+}
+
+struct MyModifier : IModifier
+{
+ RWStructuredBuffer<int> data;
+ int extra;
+
+ int modify(int val)
+ {
+ return val*65536 + data[val]*256 + val*extra;
+ }
+}
diff --git a/tests/language-feature/shader-params/interface-shader-param-ordinary.slang.expected.txt b/tests/language-feature/shader-params/interface-shader-param-ordinary.slang.expected.txt
new file mode 100644
index 000000000..434576d50
--- /dev/null
+++ b/tests/language-feature/shader-params/interface-shader-param-ordinary.slang.expected.txt
@@ -0,0 +1,4 @@
+10400
+20303
+30206
+40109
diff --git a/tools/gfx/d3d11/render-d3d11.cpp b/tools/gfx/d3d11/render-d3d11.cpp
index 5108b4d8b..415e0b833 100644
--- a/tools/gfx/d3d11/render-d3d11.cpp
+++ b/tools/gfx/d3d11/render-d3d11.cpp
@@ -383,36 +383,327 @@ protected:
}
};
- struct RootBindingState
+ /// Contextual data and operations required when binding shader objects to the pipeline state
+ struct BindingContext
+ {
+ // One key service that the `BindingContext` provides is abstracting over
+ // the difference between the D3D11 compute and graphics/rasteriation pipelines.
+ // D3D11 has distinct operations for, e.g., `CSSetShaderResources`
+ // for compute vs. `VSSetShaderResources` and `PSSetShaderResources`
+ // for rasterization.
+ //
+ // The context type provides simple operations for setting each class
+ // of resource/sampler, which will be overridden in derived types.
+ //
+ // TODO: These operations should really support binding multiple resources/samplers
+ // in one call, so that we can eventually make more efficient use of the API.
+ //
+ // TODO: We could reasonably also just store the bound resources into
+ // lcoal arrays like we are doing for UAVs, and remove the pipeline-specific
+ // virtual functions. However, doing so would seemingly eliminate any
+ // chance of avoiding redundant binding work when binding changes are
+ // made for a root shader object.
+ //
+ virtual void setCBV(UINT index, ID3D11Buffer* buffer) = 0;
+ virtual void setSRV(UINT index, ID3D11ShaderResourceView* srv) = 0;
+ virtual void setSampler(UINT index, ID3D11SamplerState* sampler) = 0;
+
+ // Unordered Access Views (UAVs) are a somewhat special case in that
+ // the D3D11 API requires them to all be set at once, rather than one
+ // at a time. To support this, we will keep a local array of the UAVs
+ // that have been bound (up to the maximum supported by D3D 11.0)
+ //
+ void setUAV(UINT index, ID3D11UnorderedAccessView* uav)
+ {
+ uavs[index] = uav;
+
+ // We will also track the total number of UAV slots that will
+ // need to be bound (including any gaps that might occur due
+ // to either explicit bindings or RTV bindings that conflict
+ // with the `u` registers for fragment shaders).
+ //
+ if(uavCount <= index)
+ {
+ uavCount = index+1;
+ }
+ }
+
+ /// The values bound for any UAVs
+ ID3D11UnorderedAccessView* uavs[D3D11_PS_CS_UAV_REGISTER_COUNT];
+
+ /// The number of entries in `uavs` that need to be considered when binding to the pipeline
+ UINT uavCount = 0;
+
+ /// The D3D11 device that we are using for binding
+ D3D11Device* device = nullptr;
+
+ /// The D3D11 device context that we are using for binding
+ ID3D11DeviceContext* context = nullptr;
+
+ /// Initialize a binding context for binding to the given `device` and `context`
+ BindingContext(
+ D3D11Device* device,
+ ID3D11DeviceContext* context)
+ : device(device)
+ , context(context)
+ {
+ memset(uavs, 0, sizeof(uavs));
+ }
+ };
+
+ /// A `BindingContext` for binding to the compute pipeline
+ struct ComputeBindingContext : BindingContext
+ {
+ /// Initialize a binding context for binding to the given `device` and `context`
+ ComputeBindingContext(
+ D3D11Device* device,
+ ID3D11DeviceContext* context)
+ : BindingContext(device, context)
+ {}
+
+ void setCBV(UINT index, ID3D11Buffer* buffer) SLANG_OVERRIDE
+ {
+ context->CSSetConstantBuffers(index, 1, &buffer);
+ }
+
+ void setSRV(UINT index, ID3D11ShaderResourceView* srv) SLANG_OVERRIDE
+ {
+ context->CSSetShaderResources(index, 1, &srv);
+ }
+
+ void setSampler(UINT index, ID3D11SamplerState* sampler) SLANG_OVERRIDE
+ {
+ context->CSSetSamplers(index, 1, &sampler);
+ }
+ };
+
+ /// A `BindingContext` for binding to the graphics/rasterization pipeline
+ struct GraphicsBindingContext : BindingContext
{
- List<ID3D11ShaderResourceView*> srvBindings;
- List<ID3D11UnorderedAccessView*> uavBindings;
- List<ID3D11SamplerState*> samplerBindings;
- List<ID3D11Buffer*> constantBuffers;
+ /// Initialize a binding context for binding to the given `device` and `context`
+ GraphicsBindingContext(
+ D3D11Device* device,
+ ID3D11DeviceContext* context)
+ : BindingContext(device, context)
+ {}
+
+ // TODO: The operations here are only dealing with vertex and fragment
+ // shaders for now. We should eventually extend them to handle HS/DS/GS
+ // bindings. (We might want to skip those stages depending on whether
+ // the associated program uses them at all).
+ //
+ // TODO: If we support cases where different stages might use distinct
+ // entry-point parameters, we might need to support some modes where
+ // a "stage mask" is passed in that applies to the bindings.
+ //
+ void setCBV(UINT index, ID3D11Buffer* buffer) SLANG_OVERRIDE
+ {
+ context->VSSetConstantBuffers(index, 1, &buffer);
+ context->PSSetConstantBuffers(index, 1, &buffer);
+ }
+
+ void setSRV(UINT index, ID3D11ShaderResourceView* srv) SLANG_OVERRIDE
+ {
+ context->VSSetShaderResources(index, 1, &srv);
+ context->PSSetShaderResources(index, 1, &srv);
+ }
+
+ void setSampler(UINT index, ID3D11SamplerState* sampler) SLANG_OVERRIDE
+ {
+ context->VSSetSamplers(index, 1, &sampler);
+ context->PSSetSamplers(index, 1, &sampler);
+ }
+ };
+
+ // In order to bind shader parameters to the correct locations, we need to
+ // be able to describe those locations. Most shader parameters will
+ // only consume a single type of D3D11-visible regsiter (e.g., a `t`
+ // register for a txture, or an `s` register for a sampler), and scalar
+ // integers suffice for these cases.
+ //
+ // In more complex cases we might be binding an entire "sub-object" like
+ // a parameter block, an entry point, etc. For the general case, we need
+ // to be able to represent a composite offset that includes offsets for
+ // each of the register classes known to D3D11.
+
+ /// A "simple" binding offset that records an offset in CBV/SRV/UAV/Sampler slots
+ struct SimpleBindingOffset
+ {
+ uint32_t cbv = 0;
+ uint32_t srv = 0;
+ uint32_t uav = 0;
+ uint32_t sampler = 0;
+
+ /// Create a default (zero) offset
+ SimpleBindingOffset()
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ SimpleBindingOffset(slang::VariableLayoutReflection* varLayout)
+ {
+ if(varLayout)
+ {
+ cbv = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_CONSTANT_BUFFER);
+ srv = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SHADER_RESOURCE);
+ uav = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNORDERED_ACCESS);
+ sampler = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SAMPLER_STATE);
+ }
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ cbv += offset.cbv;
+ srv += offset.srv;
+ uav += offset.uav;
+ sampler += offset.sampler;
+ }
+ };
+
+ // While a "simple" binding offset representation will work in many cases,
+ // once we need to deal with layout for programs with interface-type parameters
+ // that have been statically specialized, we also need to track the offset
+ // for where to bind any "pending" data that arises from the process of static
+ // specialization.
+ //
+ // In order to conveniently track both the "primary" and "pending" offset information,
+ // we will define a more complete `BindingOffset` type that combines simple
+ // binding offsets for the primary and pending parts.
+
+ /// A representation of the offset at which to bind a shader parameter or sub-object
+ struct BindingOffset : SimpleBindingOffset
+ {
+ // Offsets for "primary" data are stored directly in the `BindingOffset`
+ // via the inheritance from `SimpleBindingOffset`.
+
+ /// Offset for any "pending" data
+ SimpleBindingOffset pending;
+
+ /// Create a default (zero) offset
+ BindingOffset()
+ {}
+
+ /// Create an offset from a simple offset
+ explicit BindingOffset(SimpleBindingOffset const& offset)
+ : SimpleBindingOffset(offset)
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ BindingOffset(slang::VariableLayoutReflection* varLayout)
+ : SimpleBindingOffset(varLayout)
+ , pending(varLayout->getPendingDataLayout())
+ {}
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(BindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ pending += offset.pending;
+ }
};
class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase
{
public:
+ // A shader object comprises three main kinds of state:
+ //
+ // * Zero or more bytes of ordinary ("uniform") data
+ // * Zero or more *bindings* for textures, buffers, and samplers
+ // * Zero or more *sub-objects* representing nested parameter blocks, etc.
+ //
+ // A shader object *layout* stores information that can be used to
+ // organize these different kinds of state and optimize access to them.
+ //
+ // For example, both texture/buffer/sampler bindings and sub-objects
+ // are organized into logical *binding ranges* by the Slang reflection
+ // API, and a shader object layout will store information about those
+ // ranges in a form that is usable for the D3D11 API:
+
+ /// Information about a logical binding range as reported by Slang reflection
struct BindingRangeInfo
{
+ /// The type of bindings in this range
slang::BindingType bindingType;
+
+ /// The number of bindings in this range
Index count;
+
+ /// The starting index for this range in the appropriate "flat" array in a shader object.
+ /// E.g., for a shader resourve view range, this would be an index into the `m_srvs` array.
Index baseIndex;
- // baseIndex2 is used to specify samplers in a CombinedTextureSampler binding.
- Index baseIndex2;
- // Returns true if this binding range consumes a specialization argument slot.
- bool isSpecializationArg() const
+ /// The offset of this binding range from the start of the sub-object
+ /// in terms of whatever D3D11 register class it consumes. E.g., for
+ /// a `Texture2D` binding range this will represent an offset in
+ /// `t` registers.
+ ///
+ uint32_t registerOffset;
+ };
+
+ // Sometimes we just want to iterate over the ranges that represnet
+ // sub-objects while skipping over the others, because sub-object
+ // ranges often require extra handling or more state.
+ //
+ // For that reason we also store pre-computed information about each
+ // sub-object range.
+
+ /// Offset information for a sub-object range
+ struct SubObjectRangeOffset : BindingOffset
+ {
+ SubObjectRangeOffset()
+ {}
+
+ SubObjectRangeOffset(slang::VariableLayoutReflection* varLayout)
+ : BindingOffset(varLayout)
+ {
+ if(auto pendingLayout = varLayout->getPendingDataLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
+ }
+
+ /// The offset for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
+ };
+
+ /// Stride information for a sub-object range
+ struct SubObjectRangeStride
+ {
+ SubObjectRangeStride()
+ {}
+
+ SubObjectRangeStride(slang::TypeLayoutReflection* typeLayout)
{
- return bindingType == slang::BindingType::ExistentialValue;
+ if(auto pendingLayout = typeLayout->getPendingDataTypeLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
}
+
+ /// The strid for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
};
+ /// Information about a logical binding range as reported by Slang reflection
struct SubObjectRangeInfo
{
- RefPtr<ShaderObjectLayoutImpl> layout;
+ /// The index of the binding range that corresponds to this sub-object range
Index bindingRangeIndex;
+
+ /// The layout expected for objects bound to this range (if known)
+ RefPtr<ShaderObjectLayoutImpl> layout;
+
+ /// The offset to use when binding the first object in this range
+ SubObjectRangeOffset offset;
+
+ /// Stride between consecutive objects in this range
+ SubObjectRangeStride stride;
};
struct Builder
@@ -428,12 +719,21 @@ protected:
List<BindingRangeInfo> m_bindingRanges;
List<SubObjectRangeInfo> m_subObjectRanges;
+ /// The indices of the binding ranges that represent SRVs
+ List<Index> m_srvRanges;
+
+ /// The indices of the binding ranges that represent UAVs
+ List<Index> m_uavRanges;
+
+ /// The indices of the binding ranges that represent samplers
+ List<Index> m_samplerRanges;
+
Index m_srvCount = 0;
Index m_samplerCount = 0;
Index m_uavCount = 0;
Index m_subObjectCount = 0;
- Index m_varyingInputCount = 0;
- Index m_varyingOutputCount = 0;
+
+ uint32_t m_totalOrdinaryDataSize = 0;
Result setElementTypeLayout(slang::TypeLayoutReflection* typeLayout)
{
@@ -441,6 +741,8 @@ protected:
m_elementTypeLayout = typeLayout;
+ m_totalOrdinaryDataSize = (uint32_t) typeLayout->getSize();
+
// Compute the binding ranges that are used to store
// the logical contents of the object in memory.
@@ -468,13 +770,10 @@ protected:
case slang::BindingType::Sampler:
bindingRangeInfo.baseIndex = m_samplerCount;
m_samplerCount += count;
+ m_samplerRanges.add(r);
break;
case slang::BindingType::CombinedTextureSampler:
- bindingRangeInfo.baseIndex = m_srvCount;
- bindingRangeInfo.baseIndex2 = m_samplerCount;
- m_srvCount += count;
- m_samplerCount += count;
break;
case slang::BindingType::MutableRawBuffer:
@@ -482,24 +781,53 @@ protected:
case slang::BindingType::MutableTypedBuffer:
bindingRangeInfo.baseIndex = m_uavCount;
m_uavCount += count;
+ m_uavRanges.add(r);
break;
case slang::BindingType::VaryingInput:
- bindingRangeInfo.baseIndex = m_varyingInputCount;
- m_varyingInputCount += count;
break;
case slang::BindingType::VaryingOutput:
- bindingRangeInfo.baseIndex = m_varyingOutputCount;
- m_varyingOutputCount += count;
break;
default:
bindingRangeInfo.baseIndex = m_srvCount;
m_srvCount += count;
+ m_srvRanges.add(r);
break;
}
+ // We'd like to extract the information on the D3D11 shader
+ // register that this range should bind into.
+ //
+ // A binding range represents a logical member of the shader
+ // object type, and it may encompass zero or more *descriptor
+ // ranges* that describe how it is physically bound to pipeline
+ // state.
+ //
+ // If the current bindign range is backed by at least one descriptor
+ // range then we can query the register offset of that descriptor
+ // range. We expect that in the common case there will be exactly
+ // one descriptor range, and we can extract the information easily.
+ //
+ // TODO: we might eventually need to special-case our handling
+ // of combined texture-sampler ranges since they will need to
+ // store two different offsets.
+ //
+ if(typeLayout->getBindingRangeDescriptorRangeCount(r) != 0)
+ {
+ // The Slang reflection information organizes the descriptor ranges
+ // into "descriptor sets" but D3D11 has no notion like that so we
+ // expect all ranges belong to a single set.
+ //
+ SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
+ SLANG_ASSERT(descriptorSetIndex == 0);
+
+ SlangInt descriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
+ auto registerOffset = typeLayout->getDescriptorSetDescriptorRangeIndexOffset(descriptorSetIndex, descriptorRangeIndex);
+
+ bindingRangeInfo.registerOffset = (uint32_t) registerOffset;
+ }
m_bindingRanges.add(bindingRangeInfo);
}
@@ -508,28 +836,70 @@ protected:
for (SlangInt r = 0; r < subObjectRangeCount; ++r)
{
SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
+ auto& bindingRange = m_bindingRanges[bindingRangeIndex];
+
auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ SubObjectRangeInfo subObjectRange;
+ subObjectRange.bindingRangeIndex = bindingRangeIndex;
+
+ // We will use Slang reflection information to extract the offset and stride
+ // information for each sub-object range.
+ //
+ subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
+ subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
+
// A sub-object range can either represent a sub-object of a known
// type, like a `ConstantBuffer<Foo>` or `ParameterBlock<Foo>`
- // (in which case we can pre-compute a layout to use, based on
- // the type `Foo`) *or* it can represent a sub-object of some
- // existential type (e.g., `IBar`) in which case we cannot
- // know the appropraite type/layout of sub-object to allocate.
+ // *or* it can represent a sub-object of some existential type (e.g., `IBar`).
//
RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
- if (slangBindingType != slang::BindingType::ExistentialValue)
+ switch(slangBindingType)
{
- createForElementType(
- m_renderer,
- slangLeafTypeLayout->getElementTypeLayout(),
- subObjectLayout.writeRef());
- }
+ default:
+ {
+ // In the case of `ConstantBuffer<X>` or `ParameterBlock<X>`
+ // we can construct a layout from the element type directly.
+ //
+ auto elementTypeLayout = slangLeafTypeLayout->getElementTypeLayout();
+ createForElementType(
+ m_renderer,
+ elementTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ break;
- SubObjectRangeInfo subObjectRange;
- subObjectRange.bindingRangeIndex = bindingRangeIndex;
+ case slang::BindingType::ExistentialValue:
+ // In the case of an interface-type sub-object range, we can only
+ // construct a layout if we have static specialization information
+ // that tells us what type we expect to find in that range.
+ //
+ // The static specialization information is expected to take the
+ // form of a "pending" type layotu attached to the interface type
+ // of the leaf type layout.
+ //
+ if(auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
+ {
+ createForElementType(
+ m_renderer,
+ pendingTypeLayout,
+ subObjectLayout.writeRef());
+
+ // An interface-type range that includes ordinary data can
+ // increase the size of the ordinary data buffer we need to
+ // allocate for the parent object.
+ //
+ uint32_t ordinaryDataEnd = subObjectRange.offset.pendingOrdinaryData
+ + (uint32_t) bindingRange.count * subObjectRange.stride.pendingOrdinaryData;
+
+ if(ordinaryDataEnd > m_totalOrdinaryDataSize)
+ {
+ m_totalOrdinaryDataSize = ordinaryDataEnd;
+ }
+ }
+ }
subObjectRange.layout = subObjectLayout;
m_subObjectRanges.add(subObjectRange);
@@ -581,6 +951,18 @@ protected:
{
return m_elementTypeLayout->getType();
}
+
+ /// Get the indices that represent all the SRV ranges in this type
+ List<Index> const& getSRVRanges() const { return m_srvRanges; }
+
+ /// Get the indices that reprsent all the UAV ranges in this type
+ List<Index> const& getUAVRanges() const { return m_uavRanges; }
+
+ /// Get the indices that represnet all the sampler ranges in this type
+ List<Index> const& getSamplerRanges() const { return m_samplerRanges; }
+
+ uint32_t getTotalOrdinaryDataSize() const { return m_totalOrdinaryDataSize; }
+
protected:
Result _init(Builder const* builder)
{
@@ -589,24 +971,32 @@ protected:
initBase(renderer, builder->m_elementTypeLayout);
m_bindingRanges = builder->m_bindingRanges;
+ m_srvRanges = builder->m_srvRanges;
+ m_uavRanges = builder->m_uavRanges;
+ m_samplerRanges = builder->m_samplerRanges;
m_srvCount = builder->m_srvCount;
m_samplerCount = builder->m_samplerCount;
m_uavCount = builder->m_uavCount;
m_subObjectCount = builder->m_subObjectCount;
m_subObjectRanges = builder->m_subObjectRanges;
- m_varyingInputCount = builder->m_varyingInputCount;
- m_varyingOutputCount = builder->m_varyingOutputCount;
+
+ m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
+
return SLANG_OK;
}
List<BindingRangeInfo> m_bindingRanges;
+ List<Index> m_srvRanges;
+ List<Index> m_uavRanges;
+ List<Index> m_samplerRanges;
Index m_srvCount = 0;
Index m_samplerCount = 0;
Index m_uavCount = 0;
Index m_subObjectCount = 0;
Index m_varyingInputCount = 0;
Index m_varyingOutputCount = 0;
+ uint32_t m_totalOrdinaryDataSize = 0;
List<SubObjectRangeInfo> m_subObjectRanges;
};
@@ -617,7 +1007,10 @@ protected:
public:
struct EntryPointInfo
{
- RefPtr<ShaderObjectLayoutImpl> layout;
+ RefPtr<ShaderObjectLayoutImpl> layout;
+
+ /// The offset for this entry point's parameters, relative to the starting offset for the program
+ BindingOffset offset;
};
struct Builder : Super::Builder
@@ -643,18 +1036,21 @@ protected:
void addGlobalParams(slang::VariableLayoutReflection* globalsLayout)
{
setElementTypeLayout(globalsLayout->getTypeLayout());
+ m_pendingDataOffset = BindingOffset(globalsLayout).pending;
}
- void addEntryPoint(SlangStage stage, ShaderObjectLayoutImpl* entryPointLayout)
+ void addEntryPoint(SlangStage stage, ShaderObjectLayoutImpl* entryPointLayout, slang::EntryPointLayout* slangEntryPoint)
{
EntryPointInfo info;
info.layout = entryPointLayout;
+ info.offset = BindingOffset(slangEntryPoint->getVarLayout());
m_entryPoints.add(info);
}
- slang::IComponentType* m_program;
- slang::ProgramLayout* m_programLayout;
- List<EntryPointInfo> m_entryPoints;
+ slang::IComponentType* m_program;
+ slang::ProgramLayout* m_programLayout;
+ List<EntryPointInfo> m_entryPoints;
+ SimpleBindingOffset m_pendingDataOffset;
};
EntryPointInfo& getEntryPoint(Index index) { return m_entryPoints[index]; }
@@ -677,7 +1073,7 @@ protected:
RefPtr<ShaderObjectLayoutImpl> entryPointLayout;
SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType(
renderer, slangEntryPoint->getTypeLayout(), entryPointLayout.writeRef()));
- builder.addEntryPoint(slangEntryPoint->getStage(), entryPointLayout);
+ builder.addEntryPoint(slangEntryPoint->getStage(), entryPointLayout, slangEntryPoint);
}
SLANG_RETURN_ON_FAIL(builder.build(outLayout));
@@ -688,6 +1084,9 @@ protected:
slang::IComponentType* getSlangProgram() const { return m_program; }
slang::ProgramLayout* getSlangProgramLayout() const { return m_programLayout; }
+ /// Get the offset at which "pending" shader parameters for this program start
+ SimpleBindingOffset const& getPendingDataOffset() const { return m_pendingDataOffset; }
+
protected:
Result _init(Builder const* builder)
{
@@ -698,6 +1097,7 @@ protected:
m_program = builder->m_program;
m_programLayout = builder->m_programLayout;
m_entryPoints = builder->m_entryPoints;
+ m_pendingDataOffset = builder->m_pendingDataOffset;
return SLANG_OK;
}
@@ -705,6 +1105,7 @@ protected:
slang::ProgramLayout* m_programLayout = nullptr;
List<EntryPointInfo> m_entryPoints;
+ SimpleBindingOffset m_pendingDataOffset;
};
class ShaderObjectImpl : public ShaderObjectBase
@@ -935,15 +1336,7 @@ protected:
SLANG_NO_THROW Result SLANG_MCALL setCombinedTextureSampler(
ShaderOffset const& offset, IResourceView* textureView, ISamplerState* sampler) SLANG_OVERRIDE
{
- if (offset.bindingRangeIndex < 0)
- return SLANG_E_INVALID_ARG;
- auto layout = getLayout();
- if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
- return SLANG_E_INVALID_ARG;
- auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
- m_srvs[bindingRange.baseIndex + offset.bindingArrayIndex] = static_cast<ShaderResourceViewImpl*>(textureView);
- m_samplers[bindingRange.baseIndex2 + offset.bindingArrayIndex] = static_cast<SamplerStateImpl*>(sampler);
- return SLANG_OK;
+ return SLANG_E_NOT_IMPLEMENTED;
}
public:
@@ -1064,18 +1457,16 @@ protected:
/// Write the uniform/ordinary data of this object into the given `dest` buffer at the given `offset`
Result _writeOrdinaryData(
- D3D11Device* device,
- BufferResourceImpl* buffer,
- size_t offset,
- size_t destSize,
+ void* dest,
+ size_t destSize,
ShaderObjectLayoutImpl* specializedLayout)
{
+ // We start by simply writing in the ordinary data contained directly in this object.
+ //
auto src = m_ordinaryData.getBuffer();
auto srcSize = size_t(m_ordinaryData.getCount());
-
SLANG_ASSERT(srcSize <= destSize);
-
- device->uploadBufferData(buffer, offset, srcSize, src);
+ memcpy(dest, src, srcSize);
// In the case where this object has any sub-objects of
// existential/interface type, we need to recurse on those objects
@@ -1129,8 +1520,8 @@ protected:
// contiguous array with a single stride; we need to carefully consider what the layout
// logic does for complex cases with multiple layers of nested arrays and structures.
//
- size_t subObjectRangePendingDataOffset = _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
- size_t subObjectRangePendingDataStride = _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
+ size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
// If the range doesn't actually need/use the "pending" allocation at all, then
// we need to detect that case and skip such ranges.
@@ -1151,21 +1542,23 @@ protected:
auto subObjectOffset = subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride;
- subObject->_writeOrdinaryData(device, buffer, offset + subObjectOffset, destSize - subObjectOffset, subObjectLayout);
+ auto subObjectDest = (char*)dest + subObjectOffset;
+
+ subObject->_writeOrdinaryData(subObjectDest, destSize - subObjectOffset, subObjectLayout);
}
}
return SLANG_OK;
}
- // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
- // the "flat" Slang refelction information to provide access to the relevant data.
- //
- size_t _getSubObjectRangePendingDataOffset(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; }
- size_t _getSubObjectRangePendingDataStride(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; }
-
- /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
- Result _ensureOrdinaryDataBufferCreatedIfNeeded(D3D11Device* device)
+ /// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
+ ///
+ /// The `specializedLayout` type must represent a specialized layout for this
+ /// type that includes any "pending" data.
+ ///
+ Result _ensureOrdinaryDataBufferCreatedIfNeeded(
+ D3D11Device* device,
+ ShaderObjectLayoutImpl* specializedLayout)
{
// If we have already created a buffer to hold ordinary data, then we should
// simply re-use that buffer rather than re-create it.
@@ -1178,22 +1571,7 @@ protected:
if (m_ordinaryDataBuffer)
return SLANG_OK;
- // Computing the size of the ordinary data buffer is *not* just as simple
- // as using the size of the `m_ordinayData` array that we store. The reason
- // for the added complexity is that interface-type fields may lead to the
- // storage being specialized such that it needs extra appended data to
- // store the concrete values that logically belong in those interface-type
- // fields but wouldn't fit in the fixed-size allocation we gave them.
- //
- // TODO: We need to actually implement that logic by using reflection
- // data computed for the specialized type of this shader object.
- // For now we just make the simple assumption described above despite
- // knowing that it is false.
- //
- RefPtr<ShaderObjectLayoutImpl> specializedLayout;
- SLANG_RETURN_ON_FAIL(_getSpecializedLayout(specializedLayout.writeRef()));
-
- auto specializedOrdinaryDataSize = specializedLayout->getElementTypeLayout()->getSize();
+ auto specializedOrdinaryDataSize = specializedLayout->getTotalOrdinaryDataSize();
if (specializedOrdinaryDataSize == 0)
return SLANG_OK;
@@ -1219,75 +1597,243 @@ protected:
// where this object contains interface/existential-type fields, so we
// don't need or want to inline it into this call site.
//
- SLANG_RETURN_ON_FAIL(_writeOrdinaryData(device, m_ordinaryDataBuffer, 0, specializedOrdinaryDataSize, specializedLayout));
- return SLANG_OK;
+ auto ordinaryData = device->map(m_ordinaryDataBuffer, gfx::MapFlavor::WriteDiscard);
+ auto result = _writeOrdinaryData(ordinaryData, specializedOrdinaryDataSize, specializedLayout);
+ device->unmap(m_ordinaryDataBuffer);
+
+ return result;
}
- /// Bind the buffer for ordinary/uniform data, if needed
+ /// Bind the buffer for ordinary/uniform data, if needed
+ ///
+ /// The `ioOffset` parameter will be updated to reflect the constant buffer
+ /// register consumed by the ordinary data buffer, if one was bound.
+ ///
Result _bindOrdinaryDataBufferIfNeeded(
- D3D11Device* device,
- RootBindingState* bindingState)
+ BindingContext* context,
+ BindingOffset& ioOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
// We start by ensuring that the buffer is created, if it is needed.
//
- SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(device));
+ SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(context->device, specializedLayout));
// If we did indeed need/create a buffer, then we must bind it
// into root binding state.
//
if (m_ordinaryDataBuffer)
{
- bindingState->constantBuffers.add(m_ordinaryDataBuffer->m_buffer);
+ context->setCBV(ioOffset.cbv, m_ordinaryDataBuffer->m_buffer);
+ ioOffset.cbv++;
}
return SLANG_OK;
}
public:
- virtual Result bindObject(D3D11Device* device, RootBindingState* bindingState)
+ /// Bind this object as if it was declared as a `ConstantBuffer<T>` in Slang
+ Result bindAsConstantBuffer(
+ BindingContext* context,
+ BindingOffset const& inOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ // When binding a `ConstantBuffer<X>` we need to first bind a constant
+ // buffer for any "ordinary" data in `X`, and then bind the remaining
+ // resources and sub-objets.
+ //
+ // The one important detail to keep track of its that *if* we bind
+ // a constant buffer for ordinary data we will need to account for
+ // it in the offset we use for binding the remaining data. That
+ // detail is dealt with here by the way that `_bindOrdinaryDataBufferIfNeeded`
+ // will modify the `offset` parameter if it binds anything.
+ //
+ BindingOffset offset = inOffset;
+ SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(context, /*inout*/ offset, specializedLayout));
+
+ // Once the ordinary data buffer is bound, we can move on to binding
+ // the rest of the state, which can use logic shared with the case
+ // for interface-type sub-object ranges.
+ //
+ // Note that this call will use the `offset` value that might have
+ // been modified during `_bindOrindaryDataBufferIfNeeded`.
+ //
+ SLANG_RETURN_ON_FAIL(bindAsValue(context, offset, specializedLayout));
+
+ return SLANG_OK;
+ }
+
+ /// Bind this object as a value that appears in the body of another object.
+ ///
+ /// This case is directly used when binding an object for an interface-type
+ /// sub-object range when static specialization is used. It is also used
+ /// indirectly when binding sub-objects to constant buffer or parameter
+ /// block ranges.
+ ///
+ Result bindAsValue(
+ BindingContext* context,
+ BindingOffset const& offset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- ShaderObjectLayoutImpl* layout = getLayout();
+ // We start by iterating over the binding ranges in this type, isolating
+ // just those ranges that represent SRVs, UAVs, and samplers.
+ // In each loop we will bind the values stored for those binding ranges
+ // to the correct D3D11 register (based on the `registerOffset` field
+ // stored in the bindinge range).
+ //
+ // TODO: These loops could be optimized if we stored parallel arrays
+ // for things like `m_srvs` so that we directly store an array of
+ // `ID3D11ShaderResourceView*` where each entry matches the `gfx`-level
+ // object that was bound (or holds null if nothing is bound).
+ // In that case, we could perform a single `setSRVs()` call for each
+ // binding range.
+ //
+ // TODO: More ambitiously, if the Slang layout algorithm could be modified
+ // so that non-sub-object binding ranges are guaranteed to be contiguous
+ // then a *single* `setSRVs()` call could set all of the SRVs for an object
+ // at once.
- Index baseRangeIndex = 0;
- SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(device, bindingState));
+ for(auto bindingRangeIndex : specializedLayout->getSRVRanges())
+ {
+ auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
+ auto count = (uint32_t) bindingRange.count;
+ auto baseIndex = (uint32_t) bindingRange.baseIndex;
+ auto registerOffset = bindingRange.registerOffset + offset.srv;
+ for(uint32_t i = 0; i < count; ++i)
+ {
+ auto srv = m_srvs[baseIndex + i];
+ context->setSRV(registerOffset + i, srv ? srv->m_srv : nullptr);
+ }
+ }
- for (auto sampler : m_samplers)
- bindingState->samplerBindings.add(sampler ? sampler->m_sampler.get() : nullptr);
-
- for (auto srv : m_srvs)
- bindingState->srvBindings.add(srv ? srv->m_srv : nullptr);
+ for(auto bindingRangeIndex : specializedLayout->getUAVRanges())
+ {
+ auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
+ auto count = (uint32_t) bindingRange.count;
+ auto baseIndex = (uint32_t) bindingRange.baseIndex;
+ auto registerOffset = bindingRange.registerOffset + offset.uav;
+ for(uint32_t i = 0; i < count; ++i)
+ {
+ auto uav = m_uavs[baseIndex + i];
+ context->setUAV(registerOffset + i, uav ? uav->m_uav : nullptr);
+ }
+ }
- for (auto uav : m_uavs)
- bindingState->uavBindings.add(uav ? uav->m_uav : nullptr);
+ for(auto bindingRangeIndex : specializedLayout->getSamplerRanges())
+ {
+ auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
+ auto count = (uint32_t) bindingRange.count;
+ auto baseIndex = (uint32_t) bindingRange.baseIndex;
+ auto registerOffset = bindingRange.registerOffset + offset.sampler;
+ for(uint32_t i = 0; i < count; ++i)
+ {
+ auto sampler = m_samplers[baseIndex + i];
+ context->setSampler(registerOffset + i, sampler ? sampler->m_sampler.get() : nullptr);
+ }
+ }
+
+ // Once all the simple binding ranges are dealt with, we will bind
+ // all of the sub-objects in sub-object ranges.
+ //
+ for(auto const& subObjectRange : specializedLayout->getSubObjectRanges())
+ {
+ auto subObjectLayout = subObjectRange.layout;
+ auto const& bindingRange = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex);
+ Index count = bindingRange.count;
+ Index baseIndex = bindingRange.baseIndex;
+
+ // The starting offset for a sub-object range was computed
+ // from Slang reflection information, so we can apply it here.
+ //
+ BindingOffset rangeOffset = offset;
+ rangeOffset += subObjectRange.offset;
+
+ switch(bindingRange.bindingType)
+ {
+ // For D3D11-compatible compilation targets, the Slang compiler
+ // treats the `ConstantBuffer<T>` and `ParameterBlock<T>` types the same.
+ //
+ case slang::BindingType::ConstantBuffer:
+ case slang::BindingType::ParameterBlock:
+ {
+ BindingOffset objOffset = rangeOffset;
+ for(Index i = 0; i < count; ++i)
+ {
+ auto subObject = m_objects[ baseIndex + i ];
+
+ // Unsurprisingly, we bind each object in the range as
+ // a constant buffer.
+ //
+ subObject->bindAsConstantBuffer(context, objOffset, subObjectLayout);
+
+ // TODO: We need to update `objOffset` by the stride for
+ // this range.
+ }
+ }
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ // We can only bind information for existential-typed sub-object
+ // ranges if we have a static type that we are able to specialize to.
+ //
+ if(subObjectLayout)
+ {
+ // The data for objects in this range will always be bound into
+ // the "pending" allocation for the parent block/buffer/object.
+ // As a result, the offset for the first object in the range
+ // will come from the `pending` part of the range's offset.
+ //
+ BindingOffset objOffset = BindingOffset(rangeOffset.pending);
+
+ for(Index i = 0; i < count; ++i)
+ {
+ auto subObject = m_objects[ baseIndex + i ];
+ subObject->bindAsValue(context, objOffset, subObjectLayout);
+
+ // TODO: We need to update `objOffset` by the stride for
+ // this range.
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
- for (auto subObject : m_objects)
- subObject->bindObject(device, bindingState);
-
return SLANG_OK;
}
- /// Any "ordinary" / uniform data for this object
+ // Because the binding ranges have already been reflected
+ // and organized as part of each shader object layout,
+ // the object itself can store its data in a small number
+ // of simple arrays.
+
+ /// Any "ordinary" / uniform data for this object
List<char> m_ordinaryData;
+ /// The shader resource views (SRVs) that are part of the state of this object
List<RefPtr<ShaderResourceViewImpl>> m_srvs;
- List<RefPtr<SamplerStateImpl>> m_samplers;
-
+ /// The unordered access views (UAVs) that are part of the state of this object
List<RefPtr<UnorderedAccessViewImpl>> m_uavs;
+ /// The samplers that are part of the state of this object
+ List<RefPtr<SamplerStateImpl>> m_samplers;
+
+ /// The sub-objects that are part of the state of this object
List<RefPtr<ShaderObjectImpl>> m_objects;
- /// A constant buffer used to stored ordinary data for this object
- /// and existential-type sub-objects.
- ///
- /// Created on demand with `_createOrdinaryDataBufferIfNeeded()`
+ /// A constant buffer used to stored ordinary data for this object
+ /// and existential-type sub-objects.
+ ///
+ /// Created on demand with `_createOrdinaryDataBufferIfNeeded()`
RefPtr<BufferResourceImpl> m_ordinaryDataBuffer;
- /// Get the layout of this shader object with specialization arguments considered
- ///
- /// This operation should only be called after the shader object has been
- /// fully filled in and finalized.
- ///
+ /// Get the layout of this shader object with specialization arguments considered
+ ///
+ /// This operation should only be called after the shader object has been
+ /// fully filled in and finalized.
+ ///
Result _getSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
{
if (!m_specializedLayout)
@@ -1357,21 +1903,67 @@ protected:
return SLANG_OK;
}
- protected:
- virtual Result bindObject(D3D11Device* device, RootBindingState* bindingState) override
+ /// Bind this object as a root shader object
+ Result bindAsRoot(
+ BindingContext* context,
+ RootShaderObjectLayoutImpl* specializedLayout)
{
- SLANG_RETURN_ON_FAIL(Super::bindObject(device, bindingState));
+ // When binding an entire root shader object, we need to deal with
+ // the way that specialization might have allocated space for "pending"
+ // parameter data after all the primary parameters.
+ //
+ // We start by initializing an offset that will store zeros for the
+ // primary data, an the computed offset from the specialized layout
+ // for pending data.
+ //
+ BindingOffset offset;
+ offset.pending = specializedLayout->getPendingDataOffset();
+
+ // Note: We could *almost* call `bindAsConstantBuffer()` here to bind
+ // the state of the root object itself, but there is an important
+ // detail that means we can't:
+ //
+ // The `_bindOrdinaryDataBufferIfNeeded` operation automatically
+ // increments the offset parameter if it binds a buffer, so that
+ // subsequently bindings will be adjusted. However, the reflection
+ // information computed for root shader parameters is absolute rather
+ // than relative to the default constant buffer (if any).
+ //
+ // TODO: Quite technically, the ordinary data buffer for the global
+ // scope is *not* guaranteed to be at offset zero, so this logic should
+ // really be querying an appropriate absolute offset from `specializedLayout`.
+ //
+ BindingOffset ordinaryDataBufferOffset = offset;
+ SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(context, /*inout*/ ordinaryDataBufferOffset, specializedLayout));
+ SLANG_RETURN_ON_FAIL(bindAsValue(context, offset, specializedLayout));
+ // Once the state stored in the root shader object itself has been bound,
+ // we turn our attention to the entry points and their parameters.
+ //
auto entryPointCount = m_entryPoints.getCount();
for (Index i = 0; i < entryPointCount; ++i)
{
auto entryPoint = m_entryPoints[i];
- SLANG_RETURN_ON_FAIL(entryPoint->bindObject(device, bindingState));
+ auto const& entryPointInfo = specializedLayout->getEntryPoint(i);
+
+ // Each entry point will be bound at some offset relative to where
+ // the root shader parameters start.
+ //
+ BindingOffset entryPointOffset = offset;
+ entryPointOffset += entryPointInfo.offset;
+
+ // An entry point can simply be bound as a constant buffer, because
+ // the absolute offsets as are used for the global scope do not apply
+ // (because entry points don't need to deal with explicit bindings).
+ //
+ SLANG_RETURN_ON_FAIL(entryPoint->bindAsConstantBuffer(context, entryPointOffset, entryPointInfo.layout));
}
return SLANG_OK;
}
+
+ protected:
Result init(IDevice* device, RootShaderObjectLayoutImpl* layout)
{
SLANG_RETURN_ON_FAIL(Super::init(device, layout));
@@ -1487,11 +2079,6 @@ protected:
RefPtr<PipelineStateImpl> m_currentPipelineState;
- RootBindingState m_rootBindingState;
-
- bool m_framebufferBindingDirty = true;
- bool m_shaderBindingDirty = true;
-
uint32_t m_stencilRef = 0;
bool m_depthStencilStateDirty = true;
@@ -1817,7 +2404,13 @@ Result D3D11Device::createFramebuffer(
void D3D11Device::setFramebuffer(IFramebuffer* frameBuffer)
{
- m_framebufferBindingDirty = true;
+ // Note: the framebuffer state will be flushed to the pipeline as part
+ // of binding the root shader object.
+ //
+ // TODO: alternatively we could call `OMSetRenderTargets` here and then
+ // call `OMSetRenderTargetsAndUnorderedAccessViews` later with the option
+ // that preserves the existing RTV/DSV bindings.
+ //
m_currentFramebuffer = static_cast<FramebufferImpl*>(frameBuffer);
}
@@ -2669,6 +3262,8 @@ void D3D11Device::setPipelineState(IPipelineState* state)
m_immediateContext->IASetInputLayout(stateImpl->m_inputLayout->m_layout);
// VS
+
+ // TODO(tfoley): Why the conditional here? If somebody is trying to disable the VS or PS, shouldn't we respect that?
if (programImpl->m_vertexShader)
m_immediateContext->VSSetShader(programImpl->m_vertexShader, nullptr, 0);
@@ -2968,29 +3563,91 @@ void D3D11Device::bindRootShaderObject(IShaderObject* shaderObject)
RootShaderObjectImpl* rootShaderObjectImpl = static_cast<RootShaderObjectImpl*>(shaderObject);
RefPtr<PipelineStateBase> specializedPipeline;
maybeSpecializePipeline(m_currentPipelineState, rootShaderObjectImpl, specializedPipeline);
- setPipelineState(specializedPipeline.Ptr());
-
- m_rootBindingState.samplerBindings.clear();
- m_rootBindingState.srvBindings.clear();
- m_rootBindingState.uavBindings.clear();
- m_rootBindingState.constantBuffers.clear();
- static_cast<ShaderObjectImpl*>(shaderObject)->bindObject(this, &m_rootBindingState);
+ PipelineStateImpl* specializedPipelineImpl = static_cast<PipelineStateImpl*>(specializedPipeline.Ptr());
+ setPipelineState(specializedPipelineImpl);
+
+ // In order to bind the root object we must compute its specialized layout.
+ //
+ // TODO: This is in most ways redundant with `maybeSpecializePipeline` above,
+ // and the two operations should really be one.
+ //
+ RefPtr<ShaderObjectLayoutImpl> specializedRootLayout;
+ rootShaderObjectImpl->_getSpecializedLayout(specializedRootLayout.writeRef());
+ RootShaderObjectLayoutImpl* specializedRootLayoutImpl = static_cast<RootShaderObjectLayoutImpl*>(specializedRootLayout.Ptr());
+
+ // Depending on whether we are binding a compute or a graphics/rasterization
+ // pipeline, we will need to bind any SRVs/UAVs/CBs/samplers using different
+ // D3D11 calls. We deal with that distinction here by instantiating an
+ // appropriate subtype of `BindingContext` based on the pipeline type.
+ //
switch (m_currentPipelineState->desc.type)
{
case PipelineType::Compute:
- m_immediateContext->CSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer());
- m_immediateContext->CSSetUnorderedAccessViews(0, (UINT)m_rootBindingState.uavBindings.getCount(), m_rootBindingState.uavBindings.getBuffer(), nullptr);
- m_immediateContext->CSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer());
- m_immediateContext->CSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer());
+ {
+ ComputeBindingContext context(this, m_immediateContext);
+ rootShaderObjectImpl->bindAsRoot(&context, specializedRootLayoutImpl);
+
+ // Because D3D11 requires all UAVs to be set at once, we did *not* issue
+ // actual binding calls during the `bindAsRoot` step, and instead we
+ // batch them up and set them here.
+ //
+ m_immediateContext->CSSetUnorderedAccessViews(0, context.uavCount, context.uavs, nullptr);
+ }
break;
default:
- m_immediateContext->VSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer());
- m_immediateContext->PSSetShaderResources(0, (UINT)m_rootBindingState.srvBindings.getCount(), m_rootBindingState.srvBindings.getBuffer());
- m_immediateContext->VSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer());
- m_immediateContext->PSSetSamplers(0, (UINT)m_rootBindingState.samplerBindings.getCount(), m_rootBindingState.samplerBindings.getBuffer());
- m_immediateContext->VSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer());
- m_immediateContext->PSSetConstantBuffers(0, (UINT)m_rootBindingState.constantBuffers.getCount(), m_rootBindingState.constantBuffers.getBuffer());
- m_shaderBindingDirty = true;
+ {
+ GraphicsBindingContext context(this, m_immediateContext);
+ rootShaderObjectImpl->bindAsRoot(&context, specializedRootLayoutImpl);
+
+ // Similar to the compute case above, the rasteirzation case needs to
+ // set the UAVs after the call to `bindAsRoot()` completes, but we
+ // also have a few extra wrinkles here that are specific to the D3D 11.0
+ // rasterization pipeline.
+ //
+ // In D3D 11.0, the RTV and UAV binding slots alias, so that a shader
+ // that binds an RTV for `SV_Target0` cannot also bind a UAV for `u0`.
+ // The Slang layout algorithm already accounts for this rule, and assigns
+ // all UAVs to slots taht won't alias the RTVs it knows about.
+ //
+ // In order to account for the aliasing, we need to consider how many
+ // RTVs are bound as part of the active framebuffer, and then adjust
+ // the UAVs that we bind accordingly.
+ //
+ auto rtvCount = (UINT)m_currentFramebuffer->renderTargetViews.getCount();
+ //
+ // The `context` we are using will have computed the number of UAV registers
+ // that might need to be bound, as a range from 0 to `context.uavCount`.
+ // However we need to skip over the first `rtvCount` of those, so the
+ // actual number of UAVs we wnat to bind is smaller:
+ //
+ // Note: As a result we expect that either there were no UAVs bound,
+ // *or* the number of UAV slots bound is higher than the number of
+ // RTVs so that there is something left to actually bind.
+ //
+ SLANG_ASSERT((context.uavCount == 0) || (context.uavCount >= rtvCount));
+ auto bindableUAVCount = context.uavCount - rtvCount;
+ //
+ // Similarly, the actual UAVs we intend to bind will come after the first
+ // `rtvCount` in the array.
+ //
+ auto bindableUAVs = context.uavs + rtvCount;
+
+ // Once the offsetting is accounted for, we set all of the RTVs, DSV,
+ // and UAVs with one call.
+ //
+ // TODO: We may want to use the capability for `OMSetRenderTargetsAnd...`
+ // to only set the UAVs and leave the RTVs/UAVs alone, so that we don't
+ // needlessly re-bind RTVs during a pass.
+ //
+ m_immediateContext->OMSetRenderTargetsAndUnorderedAccessViews(
+ rtvCount,
+ m_currentFramebuffer->d3dRenderTargetViews.getArrayView().getBuffer(),
+ m_currentFramebuffer->d3dDepthStencilView,
+ rtvCount,
+ bindableUAVCount,
+ bindableUAVs,
+ nullptr);
+ }
break;
}
}
@@ -3158,24 +3815,6 @@ void D3D11Device::dispatchCompute(int x, int y, int z)
void D3D11Device::_flushGraphicsState()
{
- auto pipelineType = int(PipelineType::Graphics);
- if (m_framebufferBindingDirty || m_shaderBindingDirty)
- {
- m_framebufferBindingDirty = false;
- m_shaderBindingDirty = false;
-
- auto pipelineState = static_cast<GraphicsPipelineStateImpl*>(m_currentPipelineState.Ptr());
- auto rtvCount = (UINT)m_currentFramebuffer->renderTargetViews.getCount();
- auto uavCount = (UINT)m_rootBindingState.uavBindings.getCount();
- m_immediateContext->OMSetRenderTargetsAndUnorderedAccessViews(
- rtvCount,
- m_currentFramebuffer->d3dRenderTargetViews.getArrayView().getBuffer(),
- m_currentFramebuffer->d3dDepthStencilView,
- rtvCount,
- uavCount,
- m_rootBindingState.uavBindings.getBuffer(),
- nullptr);
- }
if (m_depthStencilStateDirty)
{
m_depthStencilStateDirty = false;
diff --git a/tools/gfx/d3d12/render-d3d12.cpp b/tools/gfx/d3d12/render-d3d12.cpp
index 8cb9ce197..67223088a 100644
--- a/tools/gfx/d3d12/render-d3d12.cpp
+++ b/tools/gfx/d3d12/render-d3d12.cpp
@@ -606,12 +606,18 @@ public:
{
DescriptorHeapReference heap;
uint32_t table;
- };
- struct BindingOffset
- {
- int32_t resource;
- int32_t sampler;
+ /// Get the GPU handle at the specified index
+ SLANG_FORCE_INLINE D3D12_GPU_DESCRIPTOR_HANDLE getGpuHandle(uint32_t index) const
+ {
+ return heap.getGpuHandle(table + index);
+ }
+
+ /// Get the CPU handle at the specified index
+ SLANG_FORCE_INLINE D3D12_CPU_DESCRIPTOR_HANDLE getCpuHandle(uint32_t index) const
+ {
+ return heap.getCpuHandle(table + index);
+ }
};
struct RootBindingState
@@ -619,21 +625,35 @@ public:
TransientResourceHeapImpl* transientHeap;
D3D12Device* device;
ArrayView<DescriptorTable> descriptorTables;
- BindingOffset offset;
- uint32_t rootParamIndex; // The root parameter index of this object.
- uint32_t futureRootParamOffset; // The starting offset of additional sub-object descriptor tables.
};
- struct DescriptorSetInfo
+ struct BindingCounts
{
- uint32_t resourceDescriptorCount = 0;
- uint32_t samplerDescriptorCount = 0;
+ uint32_t rootParam = 0;
+ uint32_t resource = 0;
+ uint32_t sampler = 0;
+
+ BindingCounts()
+ {}
};
- struct BindingLocation
+ struct HeapBindingIndex
{
- int32_t index;
- BindingOffset offsetInDescriptorTable;
+ uint32_t descriptorTableIndex = 0;
+ uint32_t descriptorIndex = 0;
+ };
+
+ struct RootBindingIndex
+ {
+ uint32_t rootParamIndex = 0;
+ HeapBindingIndex resource;
+ HeapBindingIndex sampler;
+ };
+
+ struct DescriptorSetInfo
+ {
+ uint32_t resourceDescriptorCount = 0;
+ uint32_t samplerDescriptorCount = 0;
};
// Provides information on how binding ranges are stored in descriptor tables for
@@ -645,31 +665,79 @@ public:
class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase
{
public:
+
+ /// Information about a single logical binding range
struct BindingRangeInfo
{
+ // Some of the information we store on binding ranges is redundant with
+ // the information that Slang's reflection information stores, but having
+ // it here can make the code more compact and obvious.
+
+ /// The type of binding in this range.
slang::BindingType bindingType;
+
+ /// The number of distinct bindings in this range.
uint32_t count;
- uint32_t spaceIndex;
- uint32_t flatResourceOffset; // Offset in flattend array of resource binding slots.
- BindingLocation binding;
- // Returns true if this binding range consumes a specialization argument slot.
- bool isSpecializationArg() const
+ /// A "flat" index for this range in whatever array provides backing storage for it
+ uint32_t flatIndex;
+ };
+
+ /// Offset information for a sub-object range
+ struct SubObjectRangeOffset : BindingCounts
+ {
+ SubObjectRangeOffset()
+ {}
+
+ SubObjectRangeOffset(slang::VariableLayoutReflection* varLayout)
{
- return bindingType == slang::BindingType::ExistentialValue;
+ if(auto pendingLayout = varLayout->getPendingDataLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
}
+
+ /// The offset for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
};
+
+ /// Stride information for a sub-object range
+ struct SubObjectRangeStride
+ {
+ SubObjectRangeStride()
+ {}
+
+ SubObjectRangeStride(slang::TypeLayoutReflection* typeLayout)
+ {
+ if(auto pendingLayout = typeLayout->getPendingDataTypeLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
+ }
+
+ /// The strid for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
+ };
+
+ /// Information about a sub-objecrt range
struct SubObjectRangeInfo
{
+ /// The index of the binding range corresponding to this sub-object range
+ Index bindingRangeIndex = 0;
+
+ /// Layout information for the type of sub-object expected to be bound, if known
RefPtr<ShaderObjectLayoutImpl> layout;
- Index bindingRangeIndex;
- slang::BindingType bindingType;
- // The offset for the constant buffer descriptor if this
- // sub-object is referenced as a `ConstantBuffer<T>`.
- // For a `ParameterBlock` binding range, this is always 0 since
- // parameter blocks start in a fresh descriptor table.
- BindingOffset descriptorOffset;
+ /// The offset to use when binding the first object in this range
+ SubObjectRangeOffset offset;
+
+ /// Stride between consecutive objects in this range
+ SubObjectRangeStride stride;
+ };
+
+ struct RootParameterInfo
+ {
+ D3D12_ROOT_PARAMETER rootParameter;
};
struct Builder
@@ -683,9 +751,29 @@ public:
slang::TypeLayoutReflection* m_elementTypeLayout;
List<BindingRangeInfo> m_bindingRanges;
List<SubObjectRangeInfo> m_subObjectRanges;
- DescriptorSetInfo m_descriptorSetInfo;
- uint32_t m_subObjectCount = 0;
- uint32_t m_flatResourceCount = 0;
+// DescriptorSetInfo m_descriptorSetInfo;
+
+// uint32_t m_resourceSlotCount = 0;
+// uint32_t m_samplerSlotCount = 0;
+ uint32_t m_subObjectSlotCount = 0;
+
+ BindingCounts m_ownCounts;
+// BindingCounts m_childCounts;
+ BindingCounts m_totalCounts;
+
+ uint32_t m_childRootParameterCount = 0;
+
+ uint32_t m_ordinaryDataBufferCount = 0;
+
+ uint32_t m_totalOrdinaryDataSize = 0;
+
+
+// uint32_t m_totalResourceDescriptorCount = 0;
+// uint32_t m_totalSamplerDescriptorCount = 0;
+
+// uint32_t m_ownRootParameterCount = 0;
+// uint32_t m_childRootParameterCount = 0;
+// uint32_t m_totalRootParameterCount = 0;
void addBindingRangesOfType(slang::TypeLayoutReflection* typeLayout)
{
@@ -695,7 +783,9 @@ public:
// ordinary uniform data fields.
if (typeLayout->getSize(slang::ParameterCategory::Uniform) != 0)
{
- m_descriptorSetInfo.resourceDescriptorCount = 1;
+ m_ordinaryDataBufferCount++;
+ m_ownCounts.resource++;
+// m_descriptorSetInfo.resourceDescriptorCount = 1;
}
for (SlangInt r = 0; r < bindingRangeCount; ++r)
@@ -707,42 +797,25 @@ public:
BindingRangeInfo bindingRangeInfo = {};
bindingRangeInfo.bindingType = slangBindingType;
bindingRangeInfo.count = count;
- bindingRangeInfo.flatResourceOffset = m_flatResourceCount;
- bindingRangeInfo.spaceIndex =
- (uint32_t)typeLayout->getBindingRangeDescriptorSetIndex(r);
+
+// bindingRangeInfo.flatIndex = m_flatResourceCount;
switch (slangBindingType)
{
case slang::BindingType::ConstantBuffer:
case slang::BindingType::ParameterBlock:
case slang::BindingType::ExistentialValue:
- bindingRangeInfo.binding.index = m_subObjectCount;
- m_subObjectCount += count;
+ bindingRangeInfo.flatIndex = m_subObjectSlotCount;
+ m_subObjectSlotCount += count;
break;
case slang::BindingType::Sampler:
- bindingRangeInfo.binding.offsetInDescriptorTable.sampler =
- m_descriptorSetInfo.samplerDescriptorCount;
- m_descriptorSetInfo.samplerDescriptorCount += count;
+ bindingRangeInfo.flatIndex = m_ownCounts.sampler;
+ m_ownCounts.sampler += count;
break;
case slang::BindingType::CombinedTextureSampler:
- bindingRangeInfo.binding.offsetInDescriptorTable.sampler =
- m_descriptorSetInfo.samplerDescriptorCount;
- bindingRangeInfo.binding.offsetInDescriptorTable.resource =
- m_descriptorSetInfo.resourceDescriptorCount;
- m_descriptorSetInfo.samplerDescriptorCount += count;
- m_descriptorSetInfo.resourceDescriptorCount += count;
- m_flatResourceCount += count;
- break;
-
- case slang::BindingType::MutableRawBuffer:
- case slang::BindingType::MutableTexture:
- case slang::BindingType::MutableTypedBuffer:
- bindingRangeInfo.binding.offsetInDescriptorTable.resource =
- m_descriptorSetInfo.resourceDescriptorCount;
- m_descriptorSetInfo.resourceDescriptorCount += count;
- m_flatResourceCount += count;
+ // TODO: support this case...
break;
case slang::BindingType::VaryingInput:
@@ -750,10 +823,8 @@ public:
break;
default:
- bindingRangeInfo.binding.offsetInDescriptorTable.resource =
- m_descriptorSetInfo.resourceDescriptorCount;
- m_descriptorSetInfo.resourceDescriptorCount += count;
- m_flatResourceCount += count;
+ bindingRangeInfo.flatIndex = m_ownCounts.resource;
+ m_ownCounts.resource += count;
break;
}
m_bindingRanges.add(bindingRangeInfo);
@@ -766,16 +837,21 @@ public:
m_elementTypeLayout = typeLayout;
+ m_totalOrdinaryDataSize = (uint32_t) typeLayout->getSize();
+
// Compute the binding ranges that are used to store
// the logical contents of the object in memory.
addBindingRangesOfType(typeLayout);
+ m_totalCounts = m_ownCounts;
+
SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount();
for (SlangInt r = 0; r < subObjectRangeCount; ++r)
{
SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ auto count = (uint32_t) typeLayout->getBindingRangeBindingCount(bindingRangeIndex);
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
@@ -787,7 +863,17 @@ public:
// know the appropraite type/layout of sub-object to allocate.
//
RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
- if (slangBindingType != slang::BindingType::ExistentialValue)
+ if (slangBindingType == slang::BindingType::ExistentialValue)
+ {
+ if(auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
+ {
+ createForElementType(
+ m_renderer,
+ pendingTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ }
+ else
{
createForElementType(
m_renderer,
@@ -798,14 +884,83 @@ public:
SubObjectRangeInfo subObjectRange;
subObjectRange.bindingRangeIndex = bindingRangeIndex;
subObjectRange.layout = subObjectLayout;
- subObjectRange.bindingType = slangBindingType;
- subObjectRange.descriptorOffset.resource =
- m_descriptorSetInfo.resourceDescriptorCount;
- subObjectRange.descriptorOffset.sampler =
- m_descriptorSetInfo.samplerDescriptorCount;
+// subObjectRange.bindingType = slangBindingType;
+
+ subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
+ subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
+
+ subObjectRange.offset.rootParam = m_childRootParameterCount;
+ subObjectRange.offset.resource = m_totalCounts.resource;
+ subObjectRange.offset.sampler = m_totalCounts.sampler;
+
+ BindingCounts objectCounts;
+ switch(slangBindingType)
+ {
+ default:
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ {
+ SLANG_ASSERT(subObjectLayout);
+
+ // The resource and sampler descriptors of a nested
+ // constant buffer will "leak" into those of the
+ // parent type, and we need to account for them
+ // whenever we allocate storage.
+ //
+
+ objectCounts.resource = subObjectLayout->getTotalResourceDescriptorCount();
+ objectCounts.sampler = subObjectLayout->getTotalSamplerDescriptorCount();
+ objectCounts.rootParam = subObjectRange.layout->getChildRootParameterCount();
+ }
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ if(subObjectLayout)
+ {
+ objectCounts.resource = subObjectLayout->getTotalResourceDescriptorCountWithoutOrdinaryDataBuffer();
+ objectCounts.sampler = subObjectLayout->getTotalSamplerDescriptorCount();
+ objectCounts.rootParam = subObjectRange.layout->getChildRootParameterCount();
+
+ // An interface-type range that includes ordinary data can
+ // increase the size of the ordinary data buffer we need to
+ // allocate for the parent object.
+ //
+ uint32_t ordinaryDataEnd = subObjectRange.offset.pendingOrdinaryData
+ + (uint32_t) count * subObjectRange.stride.pendingOrdinaryData;
+
+ if(ordinaryDataEnd > m_totalOrdinaryDataSize)
+ {
+ m_totalOrdinaryDataSize = ordinaryDataEnd;
+ }
+ }
+ break;
+
+ case slang::BindingType::ParameterBlock:
+ {
+ SLANG_ASSERT(subObjectLayout);
+
+ objectCounts.rootParam = subObjectRange.layout->getTotalRootParameterCount();
+ }
+ break;
+ }
+
+ auto rangeResourceCount = count * objectCounts.resource;
+ auto rangeSamplerCount = count * objectCounts.sampler;
+ auto rangeRootParamCount = count * objectCounts.rootParam;
+
+ m_totalCounts.resource += rangeResourceCount;
+ m_totalCounts.sampler += rangeSamplerCount;
+ m_childRootParameterCount += rangeRootParamCount;
+
m_subObjectRanges.add(subObjectRange);
}
+ if(m_totalCounts.resource) m_ownCounts.rootParam++;
+ if(m_totalCounts.sampler) m_ownCounts.rootParam++;
+
+ m_totalCounts.rootParam = m_ownCounts.rootParam + m_childRootParameterCount;
+
return SLANG_OK;
}
@@ -835,13 +990,26 @@ public:
BindingRangeInfo const& getBindingRange(Index index) { return m_bindingRanges[index]; }
- DescriptorSetInfo getDescriptorSetInfo() { return m_descriptorSetInfo; }
+// DescriptorSetInfo getDescriptorSetInfo() { return m_descriptorSetInfo; }
slang::TypeLayoutReflection* getElementTypeLayout() { return m_elementTypeLayout; }
- uint32_t getResourceCount() { return m_resourceSlotCount; }
+ uint32_t getResourceSlotCount() { return m_ownCounts.resource; }
+ uint32_t getSamplerSlotCount() { return m_ownCounts.sampler; }
+ Index getSubObjectSlotCount() { return m_subObjectSlotCount; }
+
+ uint32_t getTotalResourceDescriptorCount() { return m_totalCounts.resource; }
+ uint32_t getTotalSamplerDescriptorCount() { return m_totalCounts.sampler; }
+
+ uint32_t getOrdinaryDataBufferCount() { return m_ordinaryDataBufferCount; }
+ bool hasOrdinaryDataBuffer() { return m_ordinaryDataBufferCount != 0; }
- Index getSubObjectCount() { return m_subObjectCount; }
+ uint32_t getTotalResourceDescriptorCountWithoutOrdinaryDataBuffer() { return m_totalCounts.resource - m_ordinaryDataBufferCount; }
+
+ uint32_t getTotalRootParameterCount() { return m_totalCounts.rootParam; }
+ uint32_t getChildRootParameterCount() { return m_childRootParameterCount; }
+
+ uint32_t getTotalOrdinaryDataSize() const { return m_totalOrdinaryDataSize; }
SubObjectRangeInfo const& getSubObjectRange(Index index)
{
@@ -860,19 +1028,54 @@ public:
initBase(renderer, builder->m_elementTypeLayout);
- m_descriptorSetInfo = builder->m_descriptorSetInfo;
+// m_descriptorSetInfo = builder->m_descriptorSetInfo;
m_bindingRanges = _Move(builder->m_bindingRanges);
- m_subObjectCount = builder->m_subObjectCount;
m_subObjectRanges = builder->m_subObjectRanges;
- m_resourceSlotCount = builder->m_flatResourceCount;
+
+ m_ownCounts = builder->m_ownCounts;
+ m_totalCounts = builder->m_totalCounts;
+ m_subObjectSlotCount = builder->m_subObjectSlotCount;
+ m_childRootParameterCount = builder->m_childRootParameterCount;
+ m_ordinaryDataBufferCount = builder->m_ordinaryDataBufferCount;
+ m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
+
+#if 0
+ m_resourceSlotCount = builder->m_resourceSlotCount;
+ m_samplerSlotCount = builder->m_samplerSlotCount;
+ m_subObjectSlotCount = builder->m_subObjectSlotCount;
+
+ m_totalResourceDescriptorCount = builder->m_totalResourceDescriptorCount;
+ m_totalSamplerDescriptorCount = builder->m_totalSamplerDescriptorCount;
+
+ m_childRootParameterCount = builder->m_childRootParameterCount;
+ m_totalRootParameterCount = builder->m_totalRootParameterCount;
+#endif
+
return SLANG_OK;
}
List<BindingRangeInfo> m_bindingRanges;
- DescriptorSetInfo m_descriptorSetInfo;
- Index m_subObjectCount = 0;
List<SubObjectRangeInfo> m_subObjectRanges;
- uint32_t m_resourceSlotCount;
+
+
+// DescriptorSetInfo m_descriptorSetInfo;
+// Index m_subObjectCount = 0;
+
+ BindingCounts m_ownCounts;
+ BindingCounts m_totalCounts;
+
+// uint32_t m_resourceSlotCount;
+// uint32_t m_samplerSlotCount;
+ uint32_t m_subObjectSlotCount;
+
+// uint32_t m_totalResourceDescriptorCount;
+// uint32_t m_totalSamplerDescriptorCount;
+
+ uint32_t m_childRootParameterCount = 0;
+// uint32_t m_totalRootParameterCount = 0;
+
+ uint32_t m_ordinaryDataBufferCount = 0;
+ uint32_t m_totalOrdinaryDataSize = 0;
};
class RootShaderObjectLayoutImpl : public ShaderObjectLayoutImpl
@@ -883,6 +1086,7 @@ public:
struct EntryPointInfo
{
RefPtr<ShaderObjectLayoutImpl> layout;
+ BindingCounts offset;
};
struct Builder : Super::Builder
@@ -914,6 +1118,17 @@ public:
{
EntryPointInfo info;
info.layout = entryPointLayout;
+
+ info.offset.resource = m_totalCounts.resource;
+ info.offset.sampler = m_totalCounts.sampler;
+ info.offset.rootParam = m_childRootParameterCount;
+
+ m_totalCounts.resource += entryPointLayout->getTotalResourceDescriptorCount();
+ m_totalCounts.sampler += entryPointLayout->getTotalSamplerDescriptorCount();
+
+ // TODO(tfoley): Check this to make sure it is reasonable...
+ m_childRootParameterCount += entryPointLayout->getChildRootParameterCount();
+
m_entryPoints.add(info);
}
@@ -976,13 +1191,15 @@ public:
{
uint32_t spaceOffset = 0; // The `space` index as specified in shader.
+ enum { kRangeTypeCount = 4 };
+
/// An offset to apply for each D3D12 register class, as given
/// by a `D3D12_DESCRIPTOR_RANGE_TYPE`.
///
/// Note that the `D3D12_DESCRIPTOR_RANGE_TYPE` enumeration has
/// values between 0 and 3, inclusive.
///
- uint32_t offsetForRangeType[4] = {0, 0, 0, 0};
+ uint32_t offsetForRangeType[kRangeTypeCount] = {0, 0, 0, 0};
uint32_t& operator[](D3D12_DESCRIPTOR_RANGE_TYPE type)
{
@@ -993,8 +1210,60 @@ public:
{
return offsetForRangeType[int(type)];
}
+
+ BindingRegisterOffset()
+ {}
+
+ BindingRegisterOffset(slang::VariableLayoutReflection* varLayout)
+ {
+ if(varLayout)
+ {
+ spaceOffset = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_REGISTER_SPACE);
+ offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_CBV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_CONSTANT_BUFFER);
+ offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_SRV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SHADER_RESOURCE);
+ offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_UAV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNORDERED_ACCESS);
+ offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SAMPLER_STATE);
+ }
+ }
+
+ void operator+=(BindingRegisterOffset const& other)
+ {
+ spaceOffset += other.spaceOffset;
+ for(int i = 0; i < kRangeTypeCount; ++i)
+ {
+ offsetForRangeType[i] += other.offsetForRangeType[i];
+ }
+ }
+
};
+ struct BindingRegisterOffsetPair
+ {
+ BindingRegisterOffset primary;
+ BindingRegisterOffset pending;
+
+ BindingRegisterOffsetPair()
+ {}
+
+ BindingRegisterOffsetPair(slang::VariableLayoutReflection* varLayout)
+ : primary(varLayout)
+ , pending(varLayout->getPendingDataLayout())
+ {}
+
+ void operator+=(BindingRegisterOffsetPair const& other)
+ {
+ primary += other.primary;
+ pending += other.pending;
+ }
+ };
+
+ Index reserveRootParameters(Index count)
+ {
+ Index result = m_rootParameters.getCount();
+ m_rootParameters.setCount(result + count);
+ return result;
+ }
+
/// Add a new descriptor set to the layout being computed.
///
/// Note that a "descriptor set" in the layout may amount to
@@ -1114,33 +1383,15 @@ public:
}
}
- /// Add binding ranges and parameter blocks to the root signature.
- ///
- /// The layout information is taken from `varLayout` which should
- /// be a layout for either a program or an entry point.
- ///
- /// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of
- /// the descriptor set that binding ranges not belonging to nested
- /// parameter blocks should be added to.
- ///
- /// This routine will use absolute offset information computed from `varLayout`
- /// to apply appropriate space/register offsets to the bindings and parameter
- /// blocks inside the layout.
- ///
- void addBindingRangesAndParameterBlocks(
+ void addAsValue(
slang::VariableLayoutReflection* varLayout,
Index physicalDescriptorSetIndex)
{
- BindingRegisterOffset offset;
- offset.spaceOffset = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_REGISTER_SPACE);
- offset[D3D12_DESCRIPTOR_RANGE_TYPE_CBV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_CONSTANT_BUFFER);
- offset[D3D12_DESCRIPTOR_RANGE_TYPE_SRV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SHADER_RESOURCE);
- offset[D3D12_DESCRIPTOR_RANGE_TYPE_UAV] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNORDERED_ACCESS);
- offset[D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER] = (UINT) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_SAMPLER_STATE);
-
- addBindingRangesAndParameterBlocks(varLayout->getTypeLayout(), physicalDescriptorSetIndex, offset);
+ BindingRegisterOffsetPair offset(varLayout);
+ addAsValue(varLayout->getTypeLayout(), physicalDescriptorSetIndex, offset);
}
+
/// Add binding ranges and parameter blocks to the root signature.
///
/// The layout information is taken from `typeLayout` which should
@@ -1153,10 +1404,31 @@ public:
/// The `offset` encodes information about space and/or register offsets that
/// should be applied to descrptor ranges.
///
- void addBindingRangesAndParameterBlocks(
+ void addAsConstantBuffer(
slang::TypeLayoutReflection* typeLayout,
Index physicalDescriptorSetIndex,
- BindingRegisterOffset const& offset)
+ BindingRegisterOffsetPair const& containerOffset,
+ BindingRegisterOffsetPair const& elementOffset)
+ {
+ if(typeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0)
+ {
+ auto descriptorRangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
+ auto& offsetForRangeType = containerOffset.primary.offsetForRangeType[descriptorRangeType];
+ addDescriptorRange(
+ physicalDescriptorSetIndex,
+ descriptorRangeType,
+ offsetForRangeType,
+ containerOffset.primary.spaceOffset,
+ 1);
+ }
+
+ addAsValue(typeLayout, physicalDescriptorSetIndex, elementOffset);
+ }
+
+ void addAsValue(
+ slang::TypeLayoutReflection* typeLayout,
+ Index physicalDescriptorSetIndex,
+ BindingRegisterOffsetPair const& offset)
{
// Our first task is to add the binding ranges for stuff that is
// directly contained in `typeLayout` rather than via sub-objects.
@@ -1186,68 +1458,100 @@ public:
// For binding ranges that don't represent sub-objects, we will add
// all of the descriptor ranges they encompass to the root signature.
//
- addBindingRange(typeLayout, physicalDescriptorSetIndex, offset, bindingRangeIndex);
+ addBindingRange(typeLayout, physicalDescriptorSetIndex, offset.primary, bindingRangeIndex);
}
- // Next we need to add any sub binding ranges in `ConstantBuffer` bindings.
- //
- Index subObjectCount = typeLayout->getSubObjectRangeCount();
- for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectCount; subObjectRangeIndex++)
+ // Next we need to recursively include everything bound via sub-objects
+ Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; subObjectRangeIndex++)
{
auto bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
- switch (bindingType)
+
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+
+ BindingRegisterOffsetPair subObjectRangeOffset = offset;
+ subObjectRangeOffset += BindingRegisterOffsetPair(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
+
+ switch(bindingType)
{
case slang::BindingType::ConstantBuffer:
{
- // Constant buffer ranges (for `ConstantBuffer<ConcreteType>`) will "leak" their
- // binding ranges into the surrounding type, so we can add them here like any other
- // binding range.
- //
- // Note: It would be valid to allow `slang::BindingType::ConstantBuffer` to be handled
- // in the earlier loop, but that would mean that descriptor ranges coming directly
- // from the fields of `typeLayout` could be broken up with ranges coming from constant-buffer
- // sub-objects. By moving the handling of constant buffers to this later loop, we
- // guarantee that the descritpors used by non-sub-object binding ranges are all
- // contiguous.
- //
- // This call will add all descriptor ranges reported in `typeLayout` that is associated
- // with `bindingRangeIndex`.
- //
- addBindingRange(
- typeLayout,
- physicalDescriptorSetIndex,
- offset,
- bindingRangeIndex);
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
- // We also need to recurse into the element type of the constant buffer to add
- // any binding ranges defined in the element type.
- auto subObjectType =
- typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
- BindingRegisterOffset subOffset;
- subOffset.spaceOffset =
- offset.spaceOffset +
- (uint32_t)typeLayout->getSubObjectRangeSpaceOffset(
- subObjectRangeIndex);
- addParameterBlocks(
- _unwrapParameterGroups(subObjectType),
- physicalDescriptorSetIndex,
- subOffset);
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingRegisterOffsetPair containerOffset = subObjectRangeOffset;
+ containerOffset += BindingRegisterOffsetPair(containerVarLayout);
+
+ BindingRegisterOffsetPair elementOffset = subObjectRangeOffset;
+ elementOffset += BindingRegisterOffsetPair(elementVarLayout);
+
+ addAsConstantBuffer(elementTypeLayout, physicalDescriptorSetIndex, containerOffset, elementOffset);
}
break;
- default:
+
+ case slang::BindingType::ParameterBlock:
+ {
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
+
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingRegisterOffsetPair subDescriptorSetOffset;
+ subDescriptorSetOffset.primary.spaceOffset = subObjectRangeOffset.primary.spaceOffset;
+ subDescriptorSetOffset.pending.spaceOffset = subObjectRangeOffset.pending.spaceOffset;
+
+ auto subPhysicalDescriptorSetIndex = addDescriptorSet();
+
+ BindingRegisterOffsetPair containerOffset = subDescriptorSetOffset;
+ containerOffset += BindingRegisterOffsetPair(containerVarLayout);
+
+ BindingRegisterOffsetPair elementOffset = subDescriptorSetOffset;
+ elementOffset += BindingRegisterOffsetPair(elementVarLayout);
+
+ addAsConstantBuffer(elementTypeLayout, subPhysicalDescriptorSetIndex, containerOffset, elementOffset);
+ }
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ {
+ // Any nested binding ranges in the sub-object will "leak" into the
+ // binding ranges for the surrounding context.
+ //
+ auto specializedTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout();
+ if(specializedTypeLayout)
+ {
+ BindingRegisterOffsetPair pendingOffset;
+ pendingOffset.primary = subObjectRangeOffset.pending;
+
+ addAsValue(specializedTypeLayout, physicalDescriptorSetIndex, pendingOffset);
+ }
+ }
break;
}
}
- addParameterBlocks(typeLayout, physicalDescriptorSetIndex, offset);
+// BindingRegisterOffsetPair pendingOffset;
+// pendingOffset.primary = offset.pending;
+// addPendingResourceBindingRanges(typeLayout, physicalDescriptorSetIndex, pendingOffset);
}
+#if 0
/// Add child parameter blocks defined in `typeLayout` to the root signature.
void addParameterBlocks(
- slang::TypeLayoutReflection* typeLayout,
- Index physicalDescriptorSetIndex,
- BindingRegisterOffset const& offset)
+ slang::TypeLayoutReflection* typeLayout,
+ Index physicalDescriptorSetIndex,
+ BindingRegisterOffsetPair const& offset)
{
Index subObjectCount = typeLayout->getSubObjectRangeCount();
for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectCount;
@@ -1261,6 +1565,24 @@ public:
auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
switch (bindingType)
{
+ case slang::BindingType::ConstantBuffer:
+ {
+ // We also need to recurse into the element type of the constant buffer to add
+ // any binding ranges defined in the element type.
+ auto subObjectType =
+ typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ auto spaceOffset = (uint32_t)typeLayout->getSubObjectRangeSpaceOffset(subObjectRangeIndex);
+
+ BindingRegisterOffsetPair subOffset;
+ subOffset.primary.spaceOffset = offset.primary.spaceOffset + spaceOffset;
+ subOffset.pending.spaceOffset = offset.pending.spaceOffset + spaceOffset;
+ addParameterBlocks(
+ _unwrapParameterGroups(subObjectType),
+ physicalDescriptorSetIndex,
+ subOffset);
+ }
+ break;
+
case slang::BindingType::ParameterBlock:
{
// A parameter block (`ParameterBlock<ConcreteType>`) will always map to
@@ -1293,8 +1615,9 @@ public:
// this point, because `register` offsets from outside of the block
// don't affect layout within the block.
//
- BindingRegisterOffset blockOffset;
- blockOffset.spaceOffset = offset.spaceOffset + (uint32_t)spaceOffset;
+ BindingRegisterOffsetPair blockOffset;
+ blockOffset.primary.spaceOffset = offset.primary.spaceOffset + (uint32_t)spaceOffset;
+ blockOffset.pending.spaceOffset = offset.pending.spaceOffset + (uint32_t)spaceOffset;
// Note: there is an important subtlety going on here. We are passing in
// the type `blockTypeLayout` which corresponds to
@@ -1311,15 +1634,15 @@ public:
blockPhysicalDescriptorSetIndex,
D3D12_DESCRIPTOR_RANGE_TYPE_CBV,
0,
- blockOffset.spaceOffset,
+ blockOffset.primary.spaceOffset,
1);
- blockOffset.offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_CBV] = 1;
+ blockOffset.primary.offsetForRangeType[D3D12_DESCRIPTOR_RANGE_TYPE_CBV] = 1;
}
// Once we have all the details worked out, we can write the binding
// ranges for the block's type into the newly-allocated descriptor set.
//
- addBindingRangesAndParameterBlocks(
+ addAsConstantBuffer(
elementLayout, blockPhysicalDescriptorSetIndex, blockOffset);
}
break;
@@ -1332,7 +1655,109 @@ public:
break;
}
}
+
+// addPendingParameterBlocks(typeLayout, physicalDescriptorSetIndex, offset);
}
+#endif
+
+#if 0
+ void addPendingResourceBindingRanges(
+ slang::TypeLayoutReflection* typeLayout,
+ Index physicalDescriptorSetIndex,
+ BindingRegisterOffsetPair const& offset)
+ {
+ Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; subObjectRangeIndex++)
+ {
+ auto bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
+ auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ switch (bindingType)
+ {
+ case slang::BindingType::ExistentialValue:
+ {
+ // Any nested binding ranges in the sub-object will "leak" into the
+ // binding ranges for the surrounding context.
+ //
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ auto specializedTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout();
+ if(specializedTypeLayout)
+ {
+ // TODO: We need to compute the offsets that should be applied to
+ // any resources bound via the sub-object.
+ BindingRegisterOffsetPair subOffset = offset;
+ subOffset.primary += BindingRegisterOffset(typeLayout->getSubObjectRangePendingDataOffset(subObjectRangeIndex));
+
+ addResourceBindingRanges(specializedTypeLayout, physicalDescriptorSetIndex, subOffset);
+ }
+ }
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ {
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ auto elementTypeLayout = subObjectTypeLayout->getElementTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingRegisterOffsetPair subOffset = offset;
+ subOffset.primary += BindingRegisterOffset(typeLayout->getSubObjectRangePendingDataOffset(subObjectRangeIndex));
+
+ addPendingResourceBindingRanges(elementTypeLayout, physicalDescriptorSetIndex, subOffset);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+#endif
+
+#if 0
+ void addPendingParameterBlocks(
+ slang::TypeLayoutReflection* typeLayout,
+ Index physicalDescriptorSetIndex,
+ BindingRegisterOffset const& offset)
+ {
+ Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; subObjectRangeIndex++)
+ {
+ auto bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
+ auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ switch (bindingType)
+ {
+ case slang::BindingType::ExistentialValue:
+ {
+ // Any nested binding ranges in the sub-object will "leak" into the
+ // binding ranges for the surrounding context.
+ //
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ auto pendingTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout();
+ if(pendingTypeLayout)
+ {
+ // TODO: We need to compute the offsets that should be applied to
+ // any resources bound via the sub-object.
+ BindingRegisterOffset subOffset = offset;
+
+ addParameterBlocks(pendingTypeLayout, physicalDescriptorSetIndex, subOffset);
+ }
+ }
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ case slang::BindingType::ParameterBlock:
+ {
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ BindingRegisterOffset subOffset = offset;
+ addPendingParameterBlocks(subObjectTypeLayout, physicalDescriptorSetIndex, subOffset);
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
+ }
+#endif
D3D12_ROOT_SIGNATURE_DESC& build(
List<D3D12Device::DescriptorSetInfo>& outRootDescriptorSetInfos)
@@ -1385,6 +1810,7 @@ public:
static Result createRootSignatureFromSlang(
D3D12Device* device,
+ RootShaderObjectLayoutImpl* rootLayout,
slang::IComponentType* program,
ID3D12RootSignature** outRootSignature,
List<DescriptorSetInfo>& outRootDescriptorSetInfos)
@@ -1411,7 +1837,7 @@ public:
// parameters.
//
auto rootDescriptorSetIndex = builder.addDescriptorSet();
- builder.addBindingRangesAndParameterBlocks(layout->getGlobalParamsVarLayout(), rootDescriptorSetIndex);
+ builder.addAsValue(layout->getGlobalParamsVarLayout(), rootDescriptorSetIndex);
for (SlangUInt i = 0; i < layout->getEntryPointCount(); i++)
{
@@ -1428,7 +1854,7 @@ public:
// being included in the global root signature as is being done here.
//
auto entryPoint = layout->getEntryPointByIndex(i);
- builder.addBindingRangesAndParameterBlocks(entryPoint->getVarLayout(), rootDescriptorSetIndex);
+ builder.addAsValue(entryPoint->getVarLayout(), rootDescriptorSetIndex);
}
auto& rootSignatureDesc = builder.build(outRootDescriptorSetInfos);
@@ -1476,7 +1902,8 @@ public:
builder.addEntryPoint(slangEntryPoint->getStage(), entryPointLayout);
}
- SLANG_RETURN_ON_FAIL(builder.build(outLayout));
+ RefPtr<RootShaderObjectLayoutImpl> layout;
+ SLANG_RETURN_ON_FAIL(builder.build(layout.writeRef()));
if (program->getSpecializationParamCount() == 0)
{
@@ -1488,10 +1915,14 @@ public:
// it in `m_gpuDescriptorSetInfos`.
SLANG_RETURN_ON_FAIL(createRootSignatureFromSlang(
device,
+ layout,
program,
- (*outLayout)->m_rootSignature.writeRef(),
- (*outLayout)->m_gpuDescriptorSetInfos));
+ layout->m_rootSignature.writeRef(),
+ layout->m_gpuDescriptorSetInfos));
}
+
+ *outLayout = layout.detach();
+
return SLANG_OK;
}
@@ -1548,16 +1979,17 @@ public:
~ShaderObjectImpl()
{
- auto layoutImpl = static_cast<ShaderObjectLayoutImpl*>(m_layout.Ptr());
- if (m_descriptorSet.m_resourceCount)
- {
- m_resourceHeap.freeIfSupported(
- m_descriptorSet.m_resourceTable, m_descriptorSet.m_resourceCount);
- }
- if (m_descriptorSet.m_samplerCount)
+ auto layoutImpl = getLayout();
+ if(layoutImpl)
{
- m_samplerHeap.freeIfSupported(
- m_descriptorSet.m_samplerTable, m_descriptorSet.m_samplerCount);
+ if(auto resourceCount = layoutImpl->getResourceSlotCount())
+ {
+ m_resourceHeap.freeIfSupported(m_descriptorSet.m_resourceTable, resourceCount);
+ }
+ if(auto samplerCount = layoutImpl->getSamplerSlotCount())
+ {
+ m_samplerHeap.freeIfSupported(m_descriptorSet.m_samplerTable, samplerCount);
+ }
}
}
@@ -1625,7 +2057,7 @@ public:
auto bindingRangeIndex = offset.bindingRangeIndex;
auto& bindingRange = layout->getBindingRange(bindingRangeIndex);
- m_objects[bindingRange.binding.index + offset.bindingArrayIndex] = subObject;
+ m_objects[bindingRange.flatIndex + offset.bindingArrayIndex] = subObject;
// If the range being assigned into represents an interface/existential-type leaf field,
// then we need to consider how the `object` being assigned here affects specialization.
@@ -1734,7 +2166,7 @@ public:
return SLANG_E_INVALID_ARG;
auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
- returnComPtr(outObject, m_objects[bindingRange.binding.index + offset.bindingArrayIndex]);
+ returnComPtr(outObject, m_objects[bindingRange.flatIndex + offset.bindingArrayIndex]);
return SLANG_OK;
}
@@ -1750,18 +2182,16 @@ public:
auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView);
auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
- auto descriptorSlotIndex = bindingRange.binding.offsetInDescriptorTable.resource +
- (int32_t)offset.bindingArrayIndex;
+ auto descriptorSlotIndex = bindingRange.flatIndex + (int32_t)offset.bindingArrayIndex;
// Hold a reference to the resource to prevent its destruction.
- m_boundResources[bindingRange.flatResourceOffset + offset.bindingArrayIndex] =
+ m_boundResources[bindingRange.flatIndex + offset.bindingArrayIndex] =
resourceViewImpl->m_resource;
ID3D12Device* d3dDevice = static_cast<D3D12Device*>(getDevice())->m_device;
d3dDevice->CopyDescriptorsSimple(
1,
m_resourceHeap.getCpuHandle(
m_descriptorSet.m_resourceTable +
- bindingRange.binding.offsetInDescriptorTable.resource +
- (int32_t)offset.bindingArrayIndex),
+ bindingRange.flatIndex + (int32_t)offset.bindingArrayIndex),
resourceViewImpl->m_descriptor.cpuHandle,
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
return SLANG_OK;
@@ -1782,7 +2212,7 @@ public:
1,
m_samplerHeap.getCpuHandle(
m_descriptorSet.m_samplerTable +
- bindingRange.binding.offsetInDescriptorTable.sampler +
+ bindingRange.flatIndex +
(int32_t)offset.bindingArrayIndex),
samplerImpl->m_descriptor.cpuHandle,
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
@@ -1794,6 +2224,7 @@ public:
IResourceView* textureView,
ISamplerState* sampler) SLANG_OVERRIDE
{
+#if 0
if (offset.bindingRangeIndex < 0)
return SLANG_E_INVALID_ARG;
auto layout = getLayout();
@@ -1819,6 +2250,7 @@ public:
(int32_t)offset.bindingArrayIndex),
samplerImpl->m_descriptor.cpuHandle,
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
+#endif
return SLANG_OK;
}
@@ -1844,7 +2276,7 @@ public:
SLANG_ASSERT(count == 1);
Index subObjectIndexInRange = 0;
- auto subObject = m_objects[bindingRange.binding.index + subObjectIndexInRange];
+ auto subObject = m_objects[bindingRange.flatIndex + subObjectIndexInRange];
switch (bindingRange.bindingType)
{
@@ -1913,29 +2345,39 @@ public:
memset(m_ordinaryData.getBuffer(), 0, uniformSize);
}
- // Allocate descriptor tables for this shader object.
+ // Each shader object will own CPU descriptor heap memory
+ // for any resource or sampler descriptors it might store
+ // as part of its value.
+ //
+ // This allocate includes a reservation for any constant
+ // buffer descriptor pertaining to the ordinary data,
+ // but does *not* include any descriptors that are managed
+ // as part of sub-objects.
+ //
m_resourceHeap = viewHeap;
m_samplerHeap = samplerHeap;
- auto descSetInfo = layout->getDescriptorSetInfo();
- m_descriptorSet.m_resourceCount = descSetInfo.resourceDescriptorCount;
- if (descSetInfo.resourceDescriptorCount)
+
+ if(auto resourceCount = layout->getResourceSlotCount())
{
- m_descriptorSet.m_resourceTable =
- viewHeap.allocate(descSetInfo.resourceDescriptorCount);
+ m_descriptorSet.m_resourceTable = viewHeap.allocate(resourceCount);
+
+ // We must also ensure that the memory for any resources
+ // referenced by descriptors in this object does not get
+ // freed while the object is still live.
+ //
+ m_boundResources.setCount(resourceCount);
}
- m_descriptorSet.m_samplerCount = descSetInfo.samplerDescriptorCount;
- if (descSetInfo.samplerDescriptorCount)
+
+ if(auto samplerCount = layout->getSamplerSlotCount())
{
- m_descriptorSet.m_samplerTable =
- samplerHeap.allocate(descSetInfo.samplerDescriptorCount);
+ m_descriptorSet.m_samplerTable = samplerHeap.allocate(samplerCount);
}
- m_boundResources.setCount(layout->getResourceCount());
// If the layout specifies that we have any sub-objects, then
// we need to size the array to account for them.
//
- Index subObjectCount = layout->getSubObjectCount();
+ Index subObjectCount = layout->getSubObjectSlotCount();
m_objects.setCount(subObjectCount);
for (auto subObjectRangeInfo : layout->getSubObjectRanges())
@@ -1962,7 +2404,7 @@ public:
RefPtr<ShaderObjectImpl> subObject;
SLANG_RETURN_ON_FAIL(
ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef()));
- m_objects[bindingRangeInfo.binding.index + i] = subObject;
+ m_objects[bindingRangeInfo.flatIndex + i] = subObject;
}
}
@@ -2039,10 +2481,8 @@ public:
// layout logic does for complex cases with multiple layers of nested arrays and
// structures.
//
- size_t subObjectRangePendingDataOffset =
- _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
- size_t subObjectRangePendingDataStride =
- _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
+ size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
// If the range doesn't actually need/use the "pending" allocation at all, then
// we need to detect that case and skip such ranges.
@@ -2056,7 +2496,7 @@ public:
for (uint32_t i = 0; i < count; ++i)
{
- auto subObject = m_objects[bindingRangeInfo.binding.index + i];
+ auto subObject = m_objects[bindingRangeInfo.flatIndex + i];
RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
SLANG_RETURN_ON_FAIL(
@@ -2077,24 +2517,10 @@ public:
return SLANG_OK;
}
- // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
- // the "flat" Slang refelction information to provide access to the relevant data.
- //
- size_t _getSubObjectRangePendingDataOffset(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
- size_t _getSubObjectRangePendingDataStride(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
-
/// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
- Result _ensureOrdinaryDataBufferCreatedIfNeeded(PipelineCommandEncoder* encoder)
+ Result _ensureOrdinaryDataBufferCreatedIfNeeded(
+ PipelineCommandEncoder* encoder,
+ ShaderObjectLayoutImpl* specializedLayout)
{
// If we have already created a buffer to hold ordinary data, then we should
// simply re-use that buffer rather than re-create it.
@@ -2117,15 +2543,7 @@ public:
// store the concrete values that logically belong in those interface-type
// fields but wouldn't fit in the fixed-size allocation we gave them.
//
- // TODO: We need to actually implement that logic by using reflection
- // data computed for the specialized type of this shader object.
- // For now we just make the simple assumption described above despite
- // knowing that it is false.
- //
- RefPtr<ShaderObjectLayoutImpl> specializedLayout;
- SLANG_RETURN_ON_FAIL(getSpecializedLayout(specializedLayout.writeRef()));
-
- m_constantBufferSize = specializedLayout->getElementTypeLayout()->getSize();
+ m_constantBufferSize = specializedLayout->getTotalOrdinaryDataSize();
if (m_constantBufferSize == 0)
{
m_upToDateConstantBufferHeapVersion =
@@ -2180,78 +2598,197 @@ public:
}
public:
- Result bindObject(PipelineCommandEncoder* encoder, RootBindingState* bindingState)
+ RootBindingIndex prepareToBindAsParameterBlock(uint32_t inRootParameterIndex)
{
ShaderObjectLayoutImpl* layout = getLayout();
- SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder));
- uint32_t descTableIndex = bindingState->rootParamIndex;
+
+ auto rootParameterIndex = inRootParameterIndex;
+
+ RootBindingIndex rootBindingIndex;
+
+ if(layout->getTotalResourceDescriptorCount())
+ {
+ rootBindingIndex.resource.descriptorTableIndex = rootParameterIndex++;
+ }
+ if(layout->getTotalSamplerDescriptorCount())
+ {
+ rootBindingIndex.sampler.descriptorTableIndex = rootParameterIndex++;
+ }
+ rootBindingIndex.rootParamIndex = rootParameterIndex;
+
+ return rootBindingIndex;
+ }
+
+ Result bindAsParameterBlock(PipelineCommandEncoder* encoder, RootBindingState& bindingState, uint32_t rootParameterIndex, ShaderObjectLayoutImpl* layout)
+ {
+ auto rootBindingIndex = prepareToBindAsParameterBlock(rootParameterIndex);
+ SLANG_RETURN_ON_FAIL(bindAsConstantBuffer(encoder, bindingState, rootBindingIndex, layout));
+ return SLANG_OK;
+ }
+
+ Result bindAsConstantBuffer(
+ PipelineCommandEncoder* encoder,
+ RootBindingState& bindingState,
+ RootBindingIndex const& rootBindingIndex,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder, specializedLayout));
+ SLANG_RETURN_ON_FAIL(_bindImpl(encoder, bindingState, rootBindingIndex, specializedLayout, 0));
+ return SLANG_OK;
+ }
+
+ Result bindAsValue(
+ PipelineCommandEncoder* encoder,
+ RootBindingState& bindingState,
+ RootBindingIndex const& rootBindingIndex,
+ ShaderObjectLayoutImpl* layout)
+ {
+ SLANG_RETURN_ON_FAIL(_bindImpl(encoder, bindingState, rootBindingIndex, layout, layout->getOrdinaryDataBufferCount()));
+ return SLANG_OK;
+ }
+
+ Result _bindImpl(
+ PipelineCommandEncoder* encoder,
+ RootBindingState& bindingState,
+ RootBindingIndex const& rootBindingIndex,
+ ShaderObjectLayoutImpl* layout,
+ uint32_t skipResourceCount)
+ {
auto& descSet = m_descriptorSet;
- if (descSet.m_resourceCount)
+ if(auto resourceCount = (layout->getResourceSlotCount() - skipResourceCount))
{
- auto gpuDescriptorTable = bindingState->descriptorTables[descTableIndex];
- auto& gpuHeap = gpuDescriptorTable.heap;
+ auto dstIndex = rootBindingIndex.resource;
+ auto& dstTable = bindingState.descriptorTables[dstIndex.descriptorTableIndex];
auto& cpuHeap = m_resourceHeap;
auto cpuDescriptorTable = descSet.m_resourceTable;
- bindingState->device->m_device->CopyDescriptorsSimple(
- UINT(descSet.m_resourceCount),
- gpuHeap.getCpuHandle(
- gpuDescriptorTable.table + bindingState->offset.resource),
- cpuHeap.getCpuHandle(cpuDescriptorTable),
+ bindingState.device->m_device->CopyDescriptorsSimple(
+ UINT(resourceCount),
+ dstTable.getCpuHandle(dstIndex.descriptorIndex),
+ cpuHeap.getCpuHandle(cpuDescriptorTable + skipResourceCount),
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
- bindingState->offset.resource += descSet.m_resourceCount;
- descTableIndex++;
}
- if (descSet.m_samplerCount)
+ if (auto samplerCount = layout->getSamplerSlotCount())
{
- auto gpuDescriptorTable = bindingState->descriptorTables[descTableIndex];
- auto& gpuHeap = gpuDescriptorTable.heap;
+ auto dstIndex = rootBindingIndex.sampler;
+ auto& dstTable = bindingState.descriptorTables[dstIndex.descriptorTableIndex];
auto& cpuHeap = m_samplerHeap;
auto cpuDescriptorTable = (int)descSet.m_samplerTable;
- bindingState->device->m_device->CopyDescriptorsSimple(
- UINT(descSet.m_samplerCount),
- gpuHeap.getCpuHandle(
- gpuDescriptorTable.table + bindingState->offset.sampler),
+ bindingState.device->m_device->CopyDescriptorsSimple(
+ UINT(samplerCount),
+ dstTable.getCpuHandle(dstIndex.descriptorIndex),
cpuHeap.getCpuHandle(cpuDescriptorTable),
D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
- bindingState->offset.sampler += descSet.m_samplerCount;
- descTableIndex++;
}
- bindingState->futureRootParamOffset =
- Math::Max(descTableIndex, bindingState->futureRootParamOffset);
+
auto& subObjectRanges = layout->getSubObjectRanges();
- for (Index i = 0; i < subObjectRanges.getCount(); i++)
+ auto subObjectRangeCount = subObjectRanges.getCount();
+ for (Index i = 0; i < subObjectRangeCount; i++)
{
- auto bindingRange =
- layout->getBindingRange(layout->getSubObjectRange(i).bindingRangeIndex);
- switch (layout->getSubObjectRange(i).bindingType)
+ auto subObjectRange = layout->getSubObjectRange(i);
+ auto bindingRange = layout->getBindingRange(subObjectRange.bindingRangeIndex);
+ auto subObjectLayout = subObjectRange.layout.Ptr();
+ switch(bindingRange.bindingType)
{
+ case slang::BindingType::ConstantBuffer:
+ {
+ auto baseIndex = bindingRange.flatIndex;
+ for (uint32_t j = 0; j < bindingRange.count; j++)
+ {
+ auto& object = m_objects[baseIndex + j];
+
+ RootBindingIndex subIndex = rootBindingIndex;
+ subIndex.rootParamIndex += subObjectRange.offset.rootParam;
+ subIndex.resource.descriptorIndex += subObjectRange.offset.resource;
+ subIndex.sampler.descriptorTableIndex += subObjectRange.offset.sampler;
+
+ object->bindAsConstantBuffer(
+ encoder,
+ bindingState,
+ subIndex,
+ subObjectLayout);
+ }
+ }
+ break;
+
case slang::BindingType::ParameterBlock:
{
- auto baseIndex = bindingRange.binding.index;
+ auto baseIndex = bindingRange.flatIndex;
for (uint32_t j = 0; j < bindingRange.count; j++)
{
- auto newBindingState = *bindingState;
- newBindingState.offset.resource = 0;
- newBindingState.offset.sampler = 0;
- newBindingState.rootParamIndex = bindingState->futureRootParamOffset;
- newBindingState.futureRootParamOffset = newBindingState.rootParamIndex;
- m_objects[baseIndex + j]->bindObject(encoder, &newBindingState);
- bindingState->futureRootParamOffset =
- newBindingState.futureRootParamOffset;
+ auto& object = m_objects[baseIndex + j];
+
+ auto subRootParamIndex = rootBindingIndex.rootParamIndex + subObjectRange.offset.rootParam;
+
+ object->bindAsParameterBlock(
+ encoder,
+ bindingState,
+ subRootParamIndex,
+ subObjectLayout);
}
}
break;
- case slang::BindingType::ConstantBuffer:
+
+ case slang::BindingType::ExistentialValue:
+ if(subObjectLayout)
{
- auto baseIndex = bindingRange.binding.index;
+ auto baseIndex = bindingRange.flatIndex;
for (uint32_t j = 0; j < bindingRange.count; j++)
{
- m_objects[baseIndex + j]->bindObject(encoder, bindingState);
+ auto& object = m_objects[baseIndex + j];
+
+ RootBindingIndex subIndex = rootBindingIndex;
+ subIndex.rootParamIndex += subObjectRange.offset.rootParam;
+ subIndex.resource.descriptorIndex += subObjectRange.offset.resource;
+ subIndex.sampler.descriptorTableIndex += subObjectRange.offset.sampler;
+
+ object->bindAsValue(
+ encoder,
+ bindingState,
+ subIndex,
+ subObjectLayout);
}
}
break;
+ }
+ }
+
+// SLANG_RETURN_ON_FAIL(bindChildRootParameters(encoder, bindingState, rootParameterIndex));
+
+ return SLANG_OK;
+ }
+
+#if 0
+ Result bindChildRootParameters(PipelineCommandEncoder* encoder, RootBindingState& bindingState, SlangInt rootParameterIndex)
+ {
+ ShaderObjectLayoutImpl* layout = getLayout();
+ auto& subObjectRanges = layout->getSubObjectRanges();
+ auto subObjectRangeCount = subObjectRanges.getCount();
+ for (Index i = 0; i < subObjectRangeCount; i++)
+ {
+ auto subObjectRange = layout->getSubObjectRange(i);
+ auto bindingRange = layout->getBindingRange(subObjectRange.bindingRangeIndex);
+ switch(bindingRange.bindingType)
+ {
+ case slang::BindingType::ParameterBlock:
+ {
+ auto baseIndex = bindingRange.flatIndex;
+ auto subRootParameterIndex = rootParameterIndex + subObjectRange.childRootParameterOffset;
+ for (uint32_t j = 0; j < bindingRange.count; j++)
+ {
+ auto& object = m_objects[baseIndex + j];
+
+ object->bindAsParameterBlock(encoder, bindingState, subRootParameterIndex);
+ subRootParameterIndex += subObjectRange.layout->getTotalRootParameterCount();
+ }
+ }
+ break;
+ case slang::BindingType::ConstantBuffer:
+ {
+ // TODO:
+ }
+ break;
case slang::BindingType::ExistentialValue:
// If the existential object contains only ordinary data fields,
// the data is already written into m_ordinaryDataBuffer during `setObject`,
@@ -2263,8 +2800,10 @@ public:
break;
}
}
+
return SLANG_OK;
}
+#endif
/// Any "ordinary" / uniform data for this object
List<char> m_ordinaryData;
@@ -2279,8 +2818,8 @@ public:
{
int32_t m_resourceTable = 0;
int32_t m_samplerTable = 0;
- uint32_t m_resourceCount = 0;
- uint32_t m_samplerCount = 0;
+// uint32_t m_resourceCount = 0;
+// uint32_t m_samplerCount = 0;
};
DescriptorSet m_descriptorSet;
@@ -2374,15 +2913,24 @@ public:
}
public:
- Result bindObject(PipelineCommandEncoder* encoder, RootBindingState* bindingState)
+ Result bindRootObject(PipelineCommandEncoder* encoder, RootBindingState& bindingState, RootShaderObjectLayoutImpl* layout)
{
- SLANG_RETURN_ON_FAIL(Super::bindObject(encoder, bindingState));
+ auto rootBindingIndex = prepareToBindAsParameterBlock(0);
+
+ SLANG_RETURN_ON_FAIL(Super::bindAsConstantBuffer(encoder, bindingState, rootBindingIndex, layout));
auto entryPointCount = m_entryPoints.getCount();
for (Index i = 0; i < entryPointCount; ++i)
{
auto entryPoint = m_entryPoints[i];
- SLANG_RETURN_ON_FAIL(entryPoint->bindObject(encoder, bindingState));
+ auto& entryPointInfo = layout->getEntryPoint(i);
+
+ auto entryPointBindingIndex = rootBindingIndex;
+ entryPointBindingIndex.rootParamIndex += entryPointInfo.offset.rootParam;
+ entryPointBindingIndex.resource.descriptorIndex += entryPointInfo.offset.resource;
+ entryPointBindingIndex.sampler.descriptorIndex += entryPointInfo.offset.sampler;
+
+ SLANG_RETURN_ON_FAIL(entryPoint->bindAsConstantBuffer(encoder, bindingState, entryPointBindingIndex, entryPointInfo.layout));
}
return SLANG_OK;
@@ -3392,7 +3940,7 @@ Result D3D12Device::PipelineCommandEncoder::_bindRenderState(Submitter* submitte
static_cast<RootShaderObjectLayoutImpl*>(specializedRootLayout.Ptr());
ShortList<DescriptorTable> descriptorTables;
- auto descSetInfo = rootLayoutImpl->getDescriptorSetInfo();
+// auto descSetInfo = rootLayoutImpl->getDescriptorSetInfo();
auto heap = m_commandBuffer->m_transientHeap;
for (auto& descSet : rootLayoutImpl->m_gpuDescriptorSetInfos)
{
@@ -3416,7 +3964,7 @@ Result D3D12Device::PipelineCommandEncoder::_bindRenderState(Submitter* submitte
bindState.transientHeap = m_transientHeap;
auto descTablesView = descriptorTables.getArrayView();
bindState.descriptorTables = descTablesView.arrayView;
- SLANG_RETURN_ON_FAIL(rootObjectImpl->bindObject(this, &bindState));
+ SLANG_RETURN_ON_FAIL(rootObjectImpl->bindRootObject(this, bindState, rootLayoutImpl));
for (Index i = 0; i < descriptorTables.getCount(); i++)
{
diff --git a/tools/gfx/open-gl/render-gl.cpp b/tools/gfx/open-gl/render-gl.cpp
index 3ef3c1aa1..53e9dd4b1 100644
--- a/tools/gfx/open-gl/render-gl.cpp
+++ b/tools/gfx/open-gl/render-gl.cpp
@@ -620,12 +620,6 @@ public:
slang::BindingType bindingType;
Index count;
Index baseIndex;
-
- // Returns true if this binding range consumes a specialization argument slot.
- bool isSpecializationArg() const
- {
- return bindingType == slang::BindingType::ExistentialValue;
- }
};
struct SubObjectRangeInfo
@@ -1340,8 +1334,8 @@ public:
// contiguous array with a single stride; we need to carefully consider what the layout
// logic does for complex cases with multiple layers of nested arrays and structures.
//
- size_t subObjectRangePendingDataOffset = _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
- size_t subObjectRangePendingDataStride = _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataOffset = 0; //subObjectRangeInfo.offset.pendingOrdinaryData;
+ size_t subObjectRangePendingDataStride = 0; //subObjectRangeInfo.stride.pendingOrdinaryData;
// If the range doesn't actually need/use the "pending" allocation at all, then
// we need to detect that case and skip such ranges.
@@ -1369,12 +1363,6 @@ public:
return SLANG_OK;
}
- // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
- // the "flat" Slang refelction information to provide access to the relevant data.
- //
- size_t _getSubObjectRangePendingDataOffset(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; }
- size_t _getSubObjectRangePendingDataStride(ShaderObjectLayoutImpl* specializedLayout, Index subObjectRangeIndex) { return 0; }
-
/// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
Result _ensureOrdinaryDataBufferCreatedIfNeeded(GLDevice* device)
{
diff --git a/tools/gfx/renderer-shared.cpp b/tools/gfx/renderer-shared.cpp
index 99af6e4ff..72b4c45f5 100644
--- a/tools/gfx/renderer-shared.cpp
+++ b/tools/gfx/renderer-shared.cpp
@@ -291,6 +291,30 @@ ShaderComponentID ShaderCache::getComponentId(slang::TypeReflection* type)
switch (type->getKind())
{
case slang::TypeReflection::Kind::Specialized:
+ {
+ auto baseType = type->getElementType();
+
+ StringBuilder builder;
+ builder.append(UnownedTerminatedStringSlice(baseType->getName()));
+
+ auto rawType = (SlangReflectionType*) type;
+
+ builder.appendChar('<');
+ SlangInt argCount = spReflectionType_getSpecializedTypeArgCount(rawType);
+ for(SlangInt a = 0; a < argCount; ++a)
+ {
+ if(a != 0) builder.appendChar(',');
+ if(auto rawArgType = spReflectionType_getSpecializedTypeArgType(rawType, a))
+ {
+ auto argType = (slang::TypeReflection*) rawArgType;
+ builder.append(argType->getName());
+ }
+ }
+ builder.appendChar('>');
+ key.typeName = builder.getUnownedSlice();
+ key.updateHash();
+ return getComponentId(key);
+ }
// TODO: collect specialization arguments and append them to `key`.
SLANG_UNIMPLEMENTED_X("specialized type");
default:
@@ -442,7 +466,8 @@ Result RendererBase::maybeSpecializePipeline(
specializedPipelineState->unspecializedPipelineState = currentPipeline;
shaderCache.addSpecializedPipeline(pipelineKey, specializedPipelineState);
}
- outNewPipeline = static_cast<PipelineStateBase*>(specializedPipelineState.Ptr());
+ auto specializedPipelineStateBase = static_cast<PipelineStateBase*>(specializedPipelineState.Ptr());
+ outNewPipeline = specializedPipelineStateBase;
}
return SLANG_OK;
}
diff --git a/tools/gfx/vulkan/render-vk.cpp b/tools/gfx/vulkan/render-vk.cpp
index b3f99ff59..a0860f0e5 100644
--- a/tools/gfx/vulkan/render-vk.cpp
+++ b/tools/gfx/vulkan/render-vk.cpp
@@ -660,28 +660,230 @@ public:
VkPipeline m_pipeline = VK_NULL_HANDLE;
};
+ // In order to bind shader parameters to the correct locations, we need to
+ // be able to describe those locations. Most shader parameters in Vulkan
+ // simply consume a single `binding`, but we also need to deal with
+ // parameters that represent push-constant ranges.
+ //
+ // In more complex cases we might be binding an entire "sub-object" like
+ // a parameter block, an entry point, etc. For the general case, we need
+ // to be able to represent a composite offset that includes offsets for
+ // each of the cases that Vulkan supports.
+
+ /// A "simple" binding offset that records `binding`, `set`, etc. offsets
+ struct SimpleBindingOffset
+ {
+ /// An offset in GLSL/SPIR-V `binding`s
+ uint32_t binding = 0;
+
+ /// The descriptor `set` that the `binding` field should be understood as an index into
+ uint32_t bindingSet = 0;
+
+ /// The starting index for any "child" descriptor sets to start at
+ uint32_t childSet = 0;
+
+ // The distinction between `bindingSet` and `childSet` above is subtle, but
+ // potentially very important when objects contain nested parameter blocks.
+ // Consider:
+ //
+ // struct Stuff { ... }
+ // struct Things
+ // {
+ // Texture2D t;
+ // ParameterBlock<Stuff> stuff;
+ // }
+ //
+ // ParameterBlock<Stuff> gStuff;
+ // Texture2D gTex;
+ // ConstantBuffer<Things> gThings;
+ //
+ // In this example, the global-scope parameters like `gTex` and `gThings`
+ // are expected to be laid out in `set=0`, and we also expect `gStuff`
+ // to be laid out as `set=1`. As a result we expect that `gThings.t`
+ // will be laid out as `binding=1,set=0` (right after `gTex`), but
+ // `gThings.stuff` should be laid out as `set=2`.
+ //
+ // In this case, when binding `gThings` we would want a binding offset
+ // that has a `binding` or 1, a `bindingSet` of 0, and a `childSet` of 2.
+ //
+ // TODO: Validate that any of this works as intended.
+
+ /// The offset in push-constant ranges (not bytes)
+ uint32_t pushConstantRange = 0;
+
+ /// Create a default (zero) offset
+ SimpleBindingOffset()
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ SimpleBindingOffset(slang::VariableLayoutReflection* varLayout)
+ {
+ if(varLayout)
+ {
+ bindingSet = (uint32_t) varLayout->getBindingSpace(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+ binding = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+
+ childSet = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_REGISTER_SPACE);
+
+ pushConstantRange = (uint32_t) varLayout->getOffset(SLANG_PARAMETER_CATEGORY_PUSH_CONSTANT_BUFFER);
+ }
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ binding += offset.binding;
+ bindingSet += offset.bindingSet;
+ childSet += offset.childSet;
+ pushConstantRange += offset.pushConstantRange;
+ }
+ };
+
+ // While a "simple" binding offset representation will work in many cases,
+ // once we need to deal with layout for programs with interface-type parameters
+ // that have been statically specialized, we also need to track the offset
+ // for where to bind any "pending" data that arises from the process of static
+ // specialization.
+ //
+ // In order to conveniently track both the "primary" and "pending" offset information,
+ // we will define a more complete `BindingOffset` type that combines simple
+ // binding offsets for the primary and pending parts.
+
+ /// A representation of the offset at which to bind a shader parameter or sub-object
+ struct BindingOffset : SimpleBindingOffset
+ {
+ // Offsets for "primary" data are stored directly in the `BindingOffset`
+ // via the inheritance from `SimpleBindingOffset`.
+
+ /// Offset for any "pending" data
+ SimpleBindingOffset pending;
+
+ /// Create a default (zero) offset
+ BindingOffset()
+ {}
+
+ /// Create an offset from a simple offset
+ explicit BindingOffset(SimpleBindingOffset const& offset)
+ : SimpleBindingOffset(offset)
+ {}
+
+ /// Create an offset based on offset information in the given Slang `varLayout`
+ BindingOffset(slang::VariableLayoutReflection* varLayout)
+ : SimpleBindingOffset(varLayout)
+ , pending(varLayout->getPendingDataLayout())
+ {}
+
+ /// Add any values in the given `offset`
+ void operator+=(SimpleBindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ }
+
+ /// Add any values in the given `offset`
+ void operator+=(BindingOffset const& offset)
+ {
+ SimpleBindingOffset::operator+=(offset);
+ pending += offset.pending;
+ }
+ };
+
class ShaderObjectLayoutImpl : public ShaderObjectLayoutBase
{
public:
+ // A shader object comprises three main kinds of state:
+ //
+ // * Zero or more bytes of ordinary ("uniform") data
+ // * Zero or more *bindings* for textures, buffers, and samplers
+ // * Zero or more *sub-objects* representing nested parameter blocks, etc.
+ //
+ // A shader object *layout* stores information that can be used to
+ // organize these different kinds of state and optimize access to them.
+ //
+ // For example, both texture/buffer/sampler bindings and sub-objects
+ // are organized into logical *binding ranges* by the Slang reflection
+ // API, and a shader object layout will store information about those
+ // ranges in a form that is usable for the Vulkan API:
+
struct BindingRangeInfo
{
slang::BindingType bindingType;
Index count;
Index baseIndex;
- Index descriptorSetIndex;
- Index rangeIndexInDescriptorSet;
- // Returns true if this binding range consumes a specialization argument slot.
- bool isSpecializationArg() const
+ /// The `binding` offset to apply for this range
+ uint32_t bindingOffset;
+
+ /// The `set` offset to apply for this range
+ uint32_t setOffset;
+
+ // Note: The 99% case is that `setOffset` will be zero. For any shader object
+ // that was allocated from an ordinary Slang type (anything other than a root
+ // shader object in fact), all of the bindings will have been allocated into
+ // a single logical descriptor set.
+ //
+ // TODO: Ideally we could refactor so that only the root shader object layout
+ // stores a set offset for its binding ranges, and all other objects skip
+ // storing a field that never actually matters.
+ };
+
+ // Sometimes we just want to iterate over the ranges that represnet
+ // sub-objects while skipping over the others, because sub-object
+ // ranges often require extra handling or more state.
+ //
+ // For that reason we also store pre-computed information about each
+ // sub-object range.
+
+ /// Offset information for a sub-object range
+ struct SubObjectRangeOffset : BindingOffset
+ {
+ SubObjectRangeOffset()
+ {}
+
+ SubObjectRangeOffset(slang::VariableLayoutReflection* varLayout)
+ : BindingOffset(varLayout)
+ {
+ if(auto pendingLayout = varLayout->getPendingDataLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
+ }
+
+ /// The offset for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
+ };
+
+ /// Stride information for a sub-object range
+ struct SubObjectRangeStride
+ {
+ SubObjectRangeStride()
+ {}
+
+ SubObjectRangeStride(slang::TypeLayoutReflection* typeLayout)
{
- return bindingType == slang::BindingType::ExistentialValue;
+ if(auto pendingLayout = typeLayout->getPendingDataTypeLayout())
+ {
+ pendingOrdinaryData = (uint32_t) pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ }
}
+
+ /// The strid for "pending" ordinary data related to this range
+ uint32_t pendingOrdinaryData = 0;
};
+ /// Information about a logical binding range as reported by Slang reflection
struct SubObjectRangeInfo
{
- RefPtr<ShaderObjectLayoutImpl> layout;
+ /// The index of the binding range that corresponds to this sub-object range
Index bindingRangeIndex;
+
+ /// The layout expected for objects bound to this range (if known)
+ RefPtr<ShaderObjectLayoutImpl> layout;
+
+ /// The offset to use when binding the first object in this range
+ SubObjectRangeOffset offset;
+
+ /// Stride between consecutive objects in this range
+ SubObjectRangeStride stride;
};
struct DescriptorSetInfo
@@ -710,10 +912,23 @@ public:
Index m_subObjectCount = 0;
Index m_varyingInputCount = 0;
Index m_varyingOutputCount = 0;
- uint32_t m_pushConstantSize = 0;
List<DescriptorSetInfo> m_descriptorSetBuildInfos;
Dictionary<Index, Index> m_mapSpaceToDescriptorSetIndex;
+ /// The number of descriptor sets allocated by child/descendent objects
+ uint32_t m_childDescriptorSetCount = 0;
+
+ /// The total number of `binding`s consumed by this object and its children/descendents
+ uint32_t m_totalBindingCount = 0;
+
+ /// The push-constant ranges that belong to this object itself (if any)
+ List<VkPushConstantRange> m_ownPushConstantRanges;
+
+ /// The number of push-constant ranges owned by child/descendent objects
+ uint32_t m_childPushConstantRangeCount = 0;
+
+ uint32_t m_totalOrdinaryDataSize = 0;
+
Index findOrAddDescriptorSet(Index space)
{
Index index;
@@ -765,45 +980,71 @@ public:
}
}
- Result _addDescriptorSets(
- slang::TypeLayoutReflection* typeLayout,
- bool createImplicitConstantBufferForUniforms,
- slang::VariableLayoutReflection* varLayout = nullptr)
+ /// Add any descriptor ranges implied by this object containing a leaf
+ /// sub-object described by `typeLayout`, at the given `offset`.
+ void _addDescriptorRangesAsValue(
+ slang::TypeLayoutReflection* typeLayout,
+ BindingOffset const& offset)
{
- SlangInt descriptorSetCount = typeLayout->getDescriptorSetCount();
- SlangInt defaultDescriptorSetIndex;
- // If the type has ordinary uniform data fields, we need to make sure to create
- // a descriptor set with a constant buffer binding in the case that the shader
- // object is bound as a stand alone parameter block.
- uint32_t bindingOffset = 0;
- if (createImplicitConstantBufferForUniforms && typeLayout->getSize() != 0)
+ // First we will scan through all the descriptor sets that the Slang reflection
+ // information believes go into making up the given type.
+ //
+ // Note: We are initializing the sets in order so that their order in our
+ // internal data structures should be deterministically based on the order
+ // in which they are listed in Slang's reflection information.
+ //
+ Index descriptorSetCount = typeLayout->getDescriptorSetCount();
+ for (Index i = 0; i < descriptorSetCount; ++i)
{
- defaultDescriptorSetIndex = findOrAddDescriptorSet(0);
- auto& descriptorSetInfo = m_descriptorSetBuildInfos[defaultDescriptorSetIndex];
- VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
- vkBindingRangeDesc.binding = 0;
- bindingOffset = 1;
- vkBindingRangeDesc.descriptorCount = 1;
- vkBindingRangeDesc.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
- vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
- descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ SlangInt descriptorRangeCount = typeLayout->getDescriptorSetDescriptorRangeCount(i);
+ if (descriptorRangeCount == 0)
+ continue;
+ auto descriptorSetIndex = findOrAddDescriptorSet(offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(i));
}
- for (SlangInt s = 0; s < descriptorSetCount; ++s)
+ // For actually populating the descriptor sets we prefer to enumerate
+ // the binding ranges of the type instead of the descriptor sets.
+ //
+ Index bindRangeCount = typeLayout->getBindingRangeCount();
+ for( Index i = 0; i < bindRangeCount; ++i )
{
- SlangInt descriptorRangeCount =
- typeLayout->getDescriptorSetDescriptorRangeCount(s);
+ auto bindingRangeIndex = i;
+ auto bindingRangeType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ switch(bindingRangeType)
+ {
+ default:
+ break;
+
+ // We will skip over ranges that represent sub-objects for now, and handle
+ // them in a separate pass.
+ //
+ case slang::BindingType::ParameterBlock:
+ case slang::BindingType::ConstantBuffer:
+ case slang::BindingType::ExistentialValue:
+ case slang::BindingType::PushConstant:
+ continue;
+ }
+
+ // Given a binding range we are interested in, we will then enumerate
+ // its contained descriptor ranges.
+
+ Index descriptorRangeCount = typeLayout->getBindingRangeDescriptorRangeCount(bindingRangeIndex);
if (descriptorRangeCount == 0)
continue;
- auto descriptorSetIndex =
- findOrAddDescriptorSet(typeLayout->getDescriptorSetSpaceOffset(s));
+ auto slangDescriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(bindingRangeIndex);
+ auto descriptorSetIndex = findOrAddDescriptorSet(offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(slangDescriptorSetIndex));
auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
- for (SlangInt r = 0; r < descriptorRangeCount; ++r)
+
+ Index firstDescriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(bindingRangeIndex);
+ for(Index j = 0; j < descriptorRangeCount; ++j)
{
- auto slangBindingType =
- typeLayout->getDescriptorSetDescriptorRangeType(s, r);
+ Index descriptorRangeIndex = firstDescriptorRangeIndex + j;
+ auto slangDescriptorType = typeLayout->getDescriptorSetDescriptorRangeType(slangDescriptorSetIndex, descriptorRangeIndex);
- switch (slangBindingType)
+ // Certain kinds of descriptor ranges reflected by Slang do not
+ // manifest as descriptors at the Vulkan level, so we will skip those.
+ //
+ switch (slangDescriptorType)
{
case slang::BindingType::ExistentialValue:
case slang::BindingType::InlineUniformData:
@@ -813,50 +1054,183 @@ public:
break;
}
- auto vkDescriptorType = _mapDescriptorType(slangBindingType);
+ auto vkDescriptorType = _mapDescriptorType(slangDescriptorType);
VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
- vkBindingRangeDesc.binding = bindingOffset +
- (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(s, r);
- vkBindingRangeDesc.descriptorCount =
- (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
- s, r);
+ vkBindingRangeDesc.binding = offset.binding + (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(slangDescriptorSetIndex, descriptorRangeIndex);
+ vkBindingRangeDesc.descriptorCount = (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(slangDescriptorSetIndex, descriptorRangeIndex);
vkBindingRangeDesc.descriptorType = vkDescriptorType;
vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
- if (varLayout)
+
+ descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ }
+ }
+
+ // We skipped over the sub-object ranges when adding descriptors above,
+ // and now we will address that oversight by iterating over just
+ // the sub-object ranges.
+ //
+ Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for(Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount; ++subObjectRangeIndex)
+ {
+ auto bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
+ auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+
+ auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ BindingOffset subObjectRangeOffset = offset;
+ subObjectRangeOffset += BindingOffset(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
+
+ switch(bindingType)
+ {
+ // A `ParameterBlock<X>` never contributes descripto ranges to the
+ // decriptor sets of a parent object.
+ //
+ case slang::BindingType::ParameterBlock:
+ default:
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ // An interest/existential-typed sub-object range will only contribute descriptor
+ // ranges to a parent object in the case where it has been specialied, which
+ // is precisely the case where the Slang reflection information will tell us
+ // about its "pending" layout.
+ //
+ if(auto pendingTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout())
{
- auto category =
- typeLayout->getDescriptorSetDescriptorRangeCategory(s, r);
- vkBindingRangeDesc.binding += (uint32_t)varLayout->getOffset(category);
+ BindingOffset pendingOffset = BindingOffset(subObjectRangeOffset.pending);
+ _addDescriptorRangesAsValue(pendingTypeLayout, pendingOffset);
}
- descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ {
+ // A `ConstantBuffer<X>` range will contribute any nested descriptor
+ // ranges in `X`, along with a leading descriptor range for a
+ // uniform buffer to hold ordinary/uniform data, if there is any.
+
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
+
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingOffset containerOffset = subObjectRangeOffset;
+ containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
+
+ BindingOffset elementOffset = subObjectRangeOffset;
+ elementOffset += BindingOffset(elementVarLayout);
+
+ _addDescriptorRangesAsConstantBuffer(elementTypeLayout, containerOffset, elementOffset);
+ }
+ break;
+
+ case slang::BindingType::PushConstant:
+ {
+ // This case indicates a `ConstantBuffer<X>` that was marked as being
+ // used for push constants.
+ //
+ // Much of the handling is the same as for an ordinary `ConstantBuffer<X>`,
+ // but of course we need to handle the ordinary data part differently.
+
+ SLANG_ASSERT(subObjectTypeLayout);
+
+ auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
+ SLANG_ASSERT(containerVarLayout);
+
+ auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
+ SLANG_ASSERT(elementVarLayout);
+
+ auto elementTypeLayout = elementVarLayout->getTypeLayout();
+ SLANG_ASSERT(elementTypeLayout);
+
+ BindingOffset containerOffset = subObjectRangeOffset;
+ containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
+
+ BindingOffset elementOffset = subObjectRangeOffset;
+ elementOffset += BindingOffset(elementVarLayout);
+
+ _addDescriptorRangesAsPushConstantBuffer(elementTypeLayout, containerOffset, elementOffset);
+ }
+ break;
}
+
}
- return SLANG_OK;
}
- Result setElementTypeLayout(
- slang::TypeLayoutReflection* typeLayout,
- bool buildDescriptorSetLayout)
+ /// Add the descriptor ranges implied by a `ConstantBuffer<X>` where `X` is
+ /// described by `elementTypeLayout`.
+ ///
+ /// The `containerOffset` and `elementOffset` are the binding offsets that
+ /// should apply to the buffer itself and the contents of the buffer, respectively.
+ ///
+ void _addDescriptorRangesAsConstantBuffer(
+ slang::TypeLayoutReflection* elementTypeLayout,
+ BindingOffset const& containerOffset,
+ BindingOffset const& elementOffset)
{
- // First we will use the Slang layout information to allocate
- // the descriptor set layout(s) required to store values
- // of the given type.
- //
- if (buildDescriptorSetLayout)
+ // If the type has ordinary uniform data fields, we need to make sure to create
+ // a descriptor set with a constant buffer binding in the case that the shader
+ // object is bound as a stand alone parameter block.
+ if (elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0)
{
- SLANG_RETURN_ON_FAIL(_addDescriptorSets(typeLayout, true));
+ auto descriptorSetIndex = findOrAddDescriptorSet(containerOffset.bindingSet);
+ auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
+ VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
+ vkBindingRangeDesc.binding = containerOffset.binding;
+ vkBindingRangeDesc.descriptorCount = 1;
+ vkBindingRangeDesc.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+ vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
+ descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
}
- typeLayout = _unwrapParameterGroups(typeLayout);
+ _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
+ }
- m_elementTypeLayout = typeLayout;
+ /// Add the descriptor ranges implied by a `PushConstantBuffer<X>` where `X` is
+ /// described by `elementTypeLayout`.
+ ///
+ /// The `containerOffset` and `elementOffset` are the binding offsets that
+ /// should apply to the buffer itself and the contents of the buffer, respectively.
+ ///
+ void _addDescriptorRangesAsPushConstantBuffer(
+ slang::TypeLayoutReflection* elementTypeLayout,
+ BindingOffset const& containerOffset,
+ BindingOffset const& elementOffset)
+ {
+ // If the type has ordinary uniform data fields, we need to make sure to create
+ // a descriptor set with a constant buffer binding in the case that the shader
+ // object is bound as a stand alone parameter block.
+ auto ordinaryDataSize = (uint32_t) elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
+ if (ordinaryDataSize != 0)
+ {
+ auto pushConstantRangeIndex = containerOffset.pushConstantRange;
+ VkPushConstantRange vkPushConstantRange = {};
+ vkPushConstantRange.size = ordinaryDataSize;
+ vkPushConstantRange.stageFlags = VK_SHADER_STAGE_ALL; // TODO: be more clever
- // Next we will compute the binding ranges that are used to store
- // the logical contents of the object in memory. These will relate
- // to the descriptor ranges in the various sets, but not always
- // in a one-to-one fashion.
+ while(m_ownPushConstantRanges.getCount() <= pushConstantRangeIndex)
+ {
+ VkPushConstantRange emptyRange = { 0 };
+ m_ownPushConstantRanges.add(emptyRange);
+ }
+
+ m_ownPushConstantRanges[pushConstantRangeIndex] = vkPushConstantRange;
+ }
+
+ _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
+ }
+ /// Add binding ranges to this shader object layout, as implied by the given `typeLayout`
+ void addBindingRanges(
+ slang::TypeLayoutReflection* typeLayout)
+ {
SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
for (SlangInt r = 0; r < bindingRangeCount; ++r)
{
@@ -865,10 +1239,6 @@ public:
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(r);
- SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
- SlangInt rangeIndexInDescriptorSet =
- typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
-
Index baseIndex = 0;
switch (slangBindingType)
{
@@ -882,11 +1252,13 @@ public:
case slang::BindingType::Sampler:
baseIndex = m_samplerCount;
m_samplerCount += count;
+ m_totalBindingCount += 1;
break;
case slang::BindingType::CombinedTextureSampler:
baseIndex = m_combinedTextureSamplerCount;
m_combinedTextureSamplerCount += count;
+ m_totalBindingCount += 1;
break;
case slang::BindingType::VaryingInput:
@@ -901,6 +1273,7 @@ public:
default:
baseIndex = m_resourceViewCount;
m_resourceViewCount += count;
+ m_totalBindingCount += 1;
break;
}
@@ -908,8 +1281,32 @@ public:
bindingRangeInfo.bindingType = slangBindingType;
bindingRangeInfo.count = count;
bindingRangeInfo.baseIndex = baseIndex;
- bindingRangeInfo.descriptorSetIndex = descriptorSetIndex;
- bindingRangeInfo.rangeIndexInDescriptorSet = rangeIndexInDescriptorSet;
+
+ // We'd like to extract the information on the GLSL/SPIR-V
+ // `binding` that this range should bind into (or whatever
+ // other specific kind of offset/index is appropriate to it).
+ //
+ // A binding range represents a logical member of the shader
+ // object type, and it may encompass zero or more *descriptor
+ // ranges* that describe how it is physically bound to pipeline
+ // state.
+ //
+ // If the current bindign range is backed by at least one descriptor
+ // range then we can query the binding offset of that descriptor
+ // range. We expect that in the common case there will be exactly
+ // one descriptor range, and we can extract the information easily.
+ //
+ if(typeLayout->getBindingRangeDescriptorRangeCount(r) != 0)
+ {
+ SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
+ SlangInt descriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
+
+ auto set = typeLayout->getDescriptorSetSpaceOffset(descriptorSetIndex);
+ auto bindingOffset = typeLayout->getDescriptorSetDescriptorRangeIndexOffset(descriptorSetIndex, descriptorRangeIndex);
+
+ bindingRangeInfo.setOffset = uint32_t(set);
+ bindingRangeInfo.bindingOffset = uint32_t(bindingOffset);
+ }
m_bindingRanges.add(bindingRangeInfo);
}
@@ -918,6 +1315,7 @@ public:
for (SlangInt r = 0; r < subObjectRangeCount; ++r)
{
SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
+ auto& bindingRange = m_bindingRanges[bindingRangeIndex];
auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
slang::TypeLayoutReflection* slangLeafTypeLayout =
typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
@@ -930,20 +1328,103 @@ public:
// know the appropraite type/layout of sub-object to allocate.
//
RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
- if (slangBindingType != slang::BindingType::ExistentialValue)
+ switch(slangBindingType)
{
- ShaderObjectLayoutImpl::createForElementType(
- m_renderer,
- slangLeafTypeLayout->getElementTypeLayout(),
- true,
- subObjectLayout.writeRef());
+ default:
+ {
+ auto elementTypeLayout = slangLeafTypeLayout->getElementTypeLayout();
+ ShaderObjectLayoutImpl::createForElementType(
+ m_renderer,
+ elementTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ if(auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
+ {
+ ShaderObjectLayoutImpl::createForElementType(
+ m_renderer,
+ pendingTypeLayout,
+ subObjectLayout.writeRef());
+ }
+ break;
}
SubObjectRangeInfo subObjectRange;
subObjectRange.bindingRangeIndex = bindingRangeIndex;
subObjectRange.layout = subObjectLayout;
+
+ // We will use Slang reflection infromation to extract the offset information
+ // for each sub-object range.
+ //
+ // TODO: We should also be extracting the uniform offset here.
+ //
+ subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
+ subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
+
+ switch(slangBindingType)
+ {
+ case slang::BindingType::ParameterBlock:
+ m_childDescriptorSetCount += subObjectLayout->getTotalDescriptorSetCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+ break;
+
+ case slang::BindingType::ConstantBuffer:
+ m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
+ m_totalBindingCount += subObjectLayout->getTotalBindingCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+ break;
+
+ case slang::BindingType::ExistentialValue:
+ if(subObjectLayout)
+ {
+ m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
+ m_totalBindingCount += subObjectLayout->getTotalBindingCount();
+ m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
+
+ // An interface-type range that includes ordinary data can
+ // increase the size of the ordinary data buffer we need to
+ // allocate for the parent object.
+ //
+ uint32_t ordinaryDataEnd = subObjectRange.offset.pendingOrdinaryData
+ + (uint32_t) bindingRange.count * subObjectRange.stride.pendingOrdinaryData;
+
+ if(ordinaryDataEnd > m_totalOrdinaryDataSize)
+ {
+ m_totalOrdinaryDataSize = ordinaryDataEnd;
+ }
+ }
+ break;
+
+ default:
+ break;
+ }
+
m_subObjectRanges.add(subObjectRange);
}
+ }
+
+ Result setElementTypeLayout(
+ slang::TypeLayoutReflection* typeLayout)
+ {
+ typeLayout = _unwrapParameterGroups(typeLayout);
+ m_elementTypeLayout = typeLayout;
+
+ m_totalOrdinaryDataSize = (uint32_t) typeLayout->getSize();
+
+ // Next we will compute the binding ranges that are used to store
+ // the logical contents of the object in memory. These will relate
+ // to the descriptor ranges in the various sets, but not always
+ // in a one-to-one fashion.
+
+ addBindingRanges(typeLayout);
+
+ // Note: This routine does not take responsibility for
+ // adding descriptor ranges at all, because the exact way
+ // that descriptor ranges need to be added varies between
+ // ordinary shader objects, root shader objects, and entry points.
+
return SLANG_OK;
}
@@ -960,11 +1441,53 @@ public:
static Result createForElementType(
VKDevice* renderer,
slang::TypeLayoutReflection* elementType,
- bool createConstantBufferForOrdinaryData,
ShaderObjectLayoutImpl** outLayout)
{
Builder builder(renderer);
- builder.setElementTypeLayout(elementType, createConstantBufferForOrdinaryData);
+ builder.setElementTypeLayout(elementType);
+
+ // When constructing a shader object layout directly from a reflected
+ // type in Slang, we want to compute the descriptor sets and ranges
+ // that would be used if this object were bound as a parameter block.
+ //
+ // It might seem like we need to deal with the other cases for how
+ // the shader object might be bound, but the descriptor ranges we
+ // compute here will only ever be used in parameter-block case.
+ //
+ // One important wrinkle is that we know that the parameter block
+ // allocated for `elementType` will potentially need a buffer `binding`
+ // for any ordinary data it contains.
+
+ bool needsOrdinaryDataBuffer = elementType->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0;
+ uint32_t ordinaryDataBufferCount = needsOrdinaryDataBuffer ? 1 : 0;
+
+ // When binding the object, we know that the ordinary data buffer will
+ // always use a the first available `binding`, so its offset will be
+ // all zeroes.
+ //
+ BindingOffset containerOffset;
+
+ // In contrast, the `binding`s used by all the other entries in the
+ // parameter block will need to be offset by one if there was
+ // an ordinary data buffer.
+ //
+ BindingOffset elementOffset;
+ elementOffset.binding = ordinaryDataBufferCount;
+
+ // Furthermore, any `binding`s that arise due to "pending" data
+ // in the type of the object (due to specialization for existential types)
+ // will need to come after all the other `binding`s that were
+ // part of the "primary" (unspecialized) data.
+ //
+ uint32_t primaryDescriptorCount = ordinaryDataBufferCount
+ + (uint32_t) elementType->getSize(SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
+ elementOffset.pending.binding = primaryDescriptorCount;
+
+ // Once we've computed the offset information, we simply add the
+ // descriptor ranges as if things were declared as a `ConstantBuffer<X>`,
+ // since that is how things will be laid out inside the parameter block.
+ //
+ builder._addDescriptorRangesAsConstantBuffer(elementType, containerOffset, elementOffset);
return builder.build(outLayout);
}
@@ -977,9 +1500,55 @@ public:
}
}
- List<DescriptorSetInfo> const& getDescriptorSets() { return m_descriptorSetInfos; }
+ /// Get the number of descriptor sets that are allocated for this object itself
+ /// (if it needed to be bound as a parameter block).
+ ///
+ uint32_t getOwnDescriptorSetCount() { return uint32_t(m_descriptorSetInfos.getCount()); }
+
+ /// Get information about the descriptor sets that would be allocated to
+ /// represent this object itself as a parameter block.
+ ///
+ List<DescriptorSetInfo> const& getOwnDescriptorSets() { return m_descriptorSetInfos; }
+
+ /// Get the number of descriptor sets that would need to be allocated and bound
+ /// to represent the children of this object if it were bound as a parameter
+ /// block.
+ ///
+ /// To a first approximation, this is the number of (transitive) children
+ /// that are declared as `ParameterBlock<X>`.
+ ///
+ uint32_t getChildDescriptorSetCount() { return m_childDescriptorSetCount; }
+
+ /// Get the total number of descriptor sets that would need to be allocated and bound
+ /// to represent this object and its children (transitively) as a parameter block.
+ ///
+ uint32_t getTotalDescriptorSetCount() { return getOwnDescriptorSetCount() + getChildDescriptorSetCount(); }
+
+ /// Get the total number of `binding`s required to represent this type and its
+ /// (transitive) children.
+ ///
+ /// Note that this count does *not* include bindings that would be part of child
+ /// parameter blocks, nor does it include the binding for an ordinary data buffer,
+ /// if one is needed.
+ ///
+ uint32_t getTotalBindingCount() { return m_totalBindingCount; }
+
- uint32_t getPushConstantSize() { return m_pushConstantSize; }
+ /// Get the list of push constant ranges required to bind the state of this object itself.
+ List<VkPushConstantRange> const& getOwnPushConstantRanges() const { return m_ownPushConstantRanges; }
+
+ /// Get the number of push constant ranges required to bind the state of this object itself.
+ uint32_t getOwnPushConstantRangeCount() { return (uint32_t) m_ownPushConstantRanges.getCount(); }
+
+ /// Get the number of push constant ranges required to bind the state of the (transitive)
+ /// children of this object.
+ uint32_t getChildPushConstantRangeCount() { return m_childPushConstantRangeCount; }
+
+ /// Get the total number of push constant ranges required to bind the state of this object
+ /// and its (transitive) children.
+ uint32_t getTotalPushConstantRangeCount() { return getOwnPushConstantRangeCount() + getChildPushConstantRangeCount(); }
+
+ uint32_t getTotalOrdinaryDataSize() const { return m_totalOrdinaryDataSize; }
List<BindingRangeInfo> const& getBindingRanges() { return m_bindingRanges; }
@@ -1014,12 +1583,15 @@ public:
m_bindingRanges = builder->m_bindingRanges;
m_descriptorSetInfos = _Move(builder->m_descriptorSetBuildInfos);
- m_pushConstantSize = builder->m_pushConstantSize;
+ m_ownPushConstantRanges = builder->m_ownPushConstantRanges;
m_resourceViewCount = builder->m_resourceViewCount;
m_samplerCount = builder->m_samplerCount;
m_combinedTextureSamplerCount = builder->m_combinedTextureSamplerCount;
+ m_childDescriptorSetCount = builder->m_childDescriptorSetCount;
+ m_totalBindingCount = builder->m_totalBindingCount;
m_subObjectCount = builder->m_subObjectCount;
m_subObjectRanges = builder->m_subObjectRanges;
+ m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
// Create VkDescriptorSetLayout for all descriptor sets.
for (auto& descriptorSetInfo : m_descriptorSetInfos)
@@ -1042,7 +1614,13 @@ public:
Index m_samplerCount = 0;
Index m_combinedTextureSamplerCount = 0;
Index m_subObjectCount = 0;
- uint32_t m_pushConstantSize = 0;
+ List<VkPushConstantRange> m_ownPushConstantRanges;
+ uint32_t m_childPushConstantRangeCount = 0;
+
+ uint32_t m_childDescriptorSetCount = 0;
+ uint32_t m_totalBindingCount = 0;
+ uint32_t m_totalOrdinaryDataSize = 0;
+
List<SubObjectRangeInfo> m_subObjectRanges;
};
@@ -1069,15 +1647,18 @@ public:
void addEntryPointParams(slang::EntryPointLayout* entryPointLayout)
{
m_slangEntryPointLayout = entryPointLayout;
- setElementTypeLayout(entryPointLayout->getTypeLayout(), false);
- m_pushConstantSize = (uint32_t)_unwrapParameterGroups(entryPointLayout->getTypeLayout())
- ->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
- m_stage = VulkanUtil::getShaderStage(entryPointLayout->getStage());
+ setElementTypeLayout(entryPointLayout->getTypeLayout());
+ m_shaderStageFlag = VulkanUtil::getShaderStage(entryPointLayout->getStage());
+
+ // Note: we do not bother adding any descriptor sets/ranges here,
+ // because the descriptor ranges of an entry point will simply
+ // be allocated as part of the descriptor sets for the root
+ // shader object.
}
slang::EntryPointLayout* m_slangEntryPointLayout = nullptr;
- VkShaderStageFlags m_stage;
+ VkShaderStageFlags m_shaderStageFlag;
};
Result _init(Builder const* builder)
@@ -1087,16 +1668,16 @@ public:
SLANG_RETURN_ON_FAIL(Super::_init(builder));
m_slangEntryPointLayout = builder->m_slangEntryPointLayout;
- m_stage = builder->m_stage;
+ m_shaderStageFlag = builder->m_shaderStageFlag;
return SLANG_OK;
}
- VkShaderStageFlags getStage() const { return m_stage; }
+ VkShaderStageFlags getShaderStageFlag() const { return m_shaderStageFlag; }
slang::EntryPointLayout* getSlangLayout() const { return m_slangEntryPointLayout; };
slang::EntryPointLayout* m_slangEntryPointLayout;
- VkShaderStageFlags m_stage;
+ VkShaderStageFlags m_shaderStageFlag;
};
class RootShaderObjectLayout : public ShaderObjectLayoutImpl
@@ -1113,10 +1694,14 @@ public:
}
}
+ /// Information stored for each entry point of the program
struct EntryPointInfo
{
+ /// Layout of the entry point
RefPtr<EntryPointLayout> layout;
- Index rangeOffset;
+
+ /// Offset for binding the entry point, relative to the start of the program
+ BindingOffset offset;
};
struct Builder : Super::Builder
@@ -1140,34 +1725,74 @@ public:
void addGlobalParams(slang::VariableLayoutReflection* globalsLayout)
{
- setElementTypeLayout(globalsLayout->getTypeLayout(), true);
+ setElementTypeLayout(globalsLayout->getTypeLayout());
+
+ // We need to populate our descriptor sets/ranges with information
+ // from the layout of the global scope.
+ //
+ // While we expect that the parameter in the global scope start
+ // at an offset of zero, it is also worth querying the offset
+ // information because it could impact the locations assigned
+ // to "pending" data in the case of static specialization.
+ //
+ BindingOffset offset(globalsLayout);
+
+ // Note: We are adding descriptor ranges here based directly on
+ // the type of the global-scope layout. The type layout for the
+ // global scope will either be something like a `struct GlobalParams`
+ // that contains all the global-scope parameters or a `ConstantBuffer<GlobalParams>`
+ // and in either case the `_addDescriptorRangesAsValue` can properly
+ // add all the ranges implied.
+ //
+ // As a result we don't require any special-case logic here to
+ // deal with the possibility of a "default" constant buffer allocated
+ // for global-scope parameters of uniform/ordinary type.
+ //
+ _addDescriptorRangesAsValue(globalsLayout->getTypeLayout(), offset);
+
+ // We want to keep track of the offset that was applied to "pending"
+ // data because we will need it again later when it comes time to
+ // actually bind things.
+ //
+ m_pendingDataOffset = offset.pending;
}
void addEntryPoint(EntryPointLayout* entryPointLayout)
{
+ auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
+ auto entryPointVarLayout = slangEntryPointLayout->getVarLayout();
+
+ // The offset information for each entry point needs to
+ // be adjusted by any offset for "pending" data that
+ // was recorded in the global-scope layout.
+ //
+ // TODO(tfoley): Double-check that this is correct.
+
+ BindingOffset entryPointOffset(entryPointVarLayout);
+ entryPointOffset.pending += m_pendingDataOffset;
+
EntryPointInfo info;
info.layout = entryPointLayout;
+ info.offset = entryPointOffset;
+
+ // Similar to the case for the global scope, we expect the
+ // type layout for the entry point parameters to be either
+ // a `struct EntryPointParams` or a `PushConstantBuffer<EntryPointParams>`.
+ // Rather than deal with the different cases here, we will
+ // trust the `_addDescriptorRangesAsValue` code to handle
+ // either case correctly.
+ //
+ _addDescriptorRangesAsValue(entryPointVarLayout->getTypeLayout(), entryPointOffset);
- if (m_descriptorSetBuildInfos.getCount())
- {
- info.rangeOffset = m_descriptorSetBuildInfos[0].vkBindings.getCount();
- }
- else
- {
- info.rangeOffset = 0;
- }
-
- auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
- _addDescriptorSets(
- _unwrapParameterGroups(slangEntryPointLayout->getTypeLayout()),
- false,
- slangEntryPointLayout->getVarLayout());
m_entryPoints.add(info);
}
slang::IComponentType* m_program;
slang::ProgramLayout* m_programLayout;
List<EntryPointInfo> m_entryPoints;
+
+ /// Offset to apply to "pending" data from this object, sub-objects, and entry points
+ SimpleBindingOffset m_pendingDataOffset;
};
Index findEntryPointIndex(VkShaderStageFlags stage)
@@ -1176,7 +1801,7 @@ public:
for (Index i = 0; i < entryPointCount; ++i)
{
auto entryPoint = m_entryPoints[i];
- if (entryPoint.layout->getStage() == stage)
+ if (entryPoint.layout->getShaderStageFlag() == stage)
return i;
}
return -1;
@@ -1214,9 +1839,14 @@ public:
return SLANG_OK;
}
+ SimpleBindingOffset const& getPendingDataOffset() const { return m_pendingDataOffset; }
+
slang::IComponentType* getSlangProgram() const { return m_program; }
slang::ProgramLayout* getSlangProgramLayout() const { return m_programLayout; }
+ /// Get all of the push constant ranges that will be bound for this object and all (transitive) sub-objects
+ List<VkPushConstantRange> const& getAllPushConstantRanges() { return m_allPushConstantRanges; }
+
protected:
Result _init(Builder const* builder)
{
@@ -1227,75 +1857,156 @@ public:
m_program = builder->m_program;
m_programLayout = builder->m_programLayout;
m_entryPoints = _Move(builder->m_entryPoints);
+ m_pendingDataOffset = builder->m_pendingDataOffset;
m_renderer = renderer;
+ // If the program has unbound specialization parameters,
+ // then we will avoid creating a final Vulkan pipeline layout.
+ //
+ // TODO: We should really create the information necessary
+ // for binding as part of a separate object, so that we have
+ // a clean seperation between what is needed for writing into
+ // a shader object vs. what is needed for binding it to the
+ // pipeline. We eventually need to be able to create bindable
+ // state objects from unspecialized programs, in order to
+ // support dynamic dispatch.
+ //
if (m_program->getSpecializationParamCount() != 0)
return SLANG_OK;
- // For fully specialized shader programs, we create a Vulkan pipeline layout now.
+ // Otherwise, we need to create a final (bindable) layout.
+ //
+ // We will use a recursive walk to collect all the `VkDescriptorSetLayout`s
+ // that are required for the global scope, sub-objects, and entry points.
+ //
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSets());
- // First, collect `VkDescriptorSetLayout`s for the global scope and all sub-objects
- // referenced via a `ParameterBlock` from shader object layouts.
- SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(this));
+ // We will also use a recursive walk to collect all the push-constant
+ // ranges needed for this object, sub-objects, and entry points.
+ //
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRanges());
- // Next, collect push constant ranges. We will use one descriptor range for each
- // entry point that has uniform parameters.
- uint32_t pushConstantOffset = 0;
- for (auto& entryPoint : m_entryPoints)
- {
- auto size = entryPoint.layout->getPushConstantSize();
- if (size)
- {
- VkPushConstantRange pushConstantRange = {};
- pushConstantRange.offset = pushConstantOffset;
- pushConstantRange.size = size;
- pushConstantRange.stageFlags = entryPoint.layout->getStage();
- m_pushConstantRanges.add(pushConstantRange);
- pushConstantOffset += size;
- }
- }
+ // Once we've collected the information across the entire
+ // tree of sub-objects
// Now call Vulkan API to create a pipeline layout.
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutCreateInfo.setLayoutCount = (uint32_t)m_vkDescriptorSetLayouts.getCount();
pipelineLayoutCreateInfo.pSetLayouts = m_vkDescriptorSetLayouts.getBuffer();
- if (m_pushConstantRanges.getCount())
+ if (m_allPushConstantRanges.getCount())
{
pipelineLayoutCreateInfo.pushConstantRangeCount =
- (uint32_t)m_pushConstantRanges.getCount();
+ (uint32_t)m_allPushConstantRanges.getCount();
pipelineLayoutCreateInfo.pPushConstantRanges =
- m_pushConstantRanges.getBuffer();
+ m_allPushConstantRanges.getBuffer();
}
SLANG_RETURN_ON_FAIL(m_renderer->m_api.vkCreatePipelineLayout(
m_renderer->m_api.m_device, &pipelineLayoutCreateInfo, nullptr, &m_pipelineLayout));
return SLANG_OK;
}
- // Recusively add `VkDescriptorSetLayout` for all descriptor sets used by this and children
- // shader objects and add them to `m_vkDescriptorSetLayouts`.
- Result addDescriptorSetLayoutRec(ShaderObjectLayoutImpl* layout)
+ /// Add all the descriptor sets implied by this root object and sub-objects
+ Result addAllDescriptorSets()
+ {
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(this));
+
+ // Note: the descriptor ranges/sets for direct entry point parameters
+ // were already enumerated into the ranges/sets of the root object itself,
+ // so we don't wnat to add them again.
+ //
+ // We do however have to deal with the possibility that an entry
+ // point could introduce "child" descriptor sets, e.g., because it
+ // has a `ParameterBlock<X>` parameter.
+ //
+ for(auto& entryPoint : getEntryPoints())
+ {
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(entryPoint.layout));
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add descriptor sets defined by `layout` and sub-objects
+ Result addAllDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
{
- for (auto& descSetInfo : layout->getDescriptorSets())
+ // TODO: This logic assumes that descriptor sets are all contiguous
+ // and have been allocated in a global order that matches the order
+ // of enumeration here.
+
+ for (auto& descSetInfo : layout->getOwnDescriptorSets())
{
m_vkDescriptorSetLayouts.add(descSetInfo.descriptorSetLayout);
}
- // Note: entry point parameters in a `RootShaderObject` has already been included
- // in `layout->getDescriptorSets()` during `RootShaderObjectLayout` construction,
- // so we do not need to enumerate entry point array here.
-
- // However, for sub-objects referenced through `ParameterBlock`s, we do need to
- // add their descriptor sets to our pipeline layout.
- // Binding ranges for sub-objects referenced through `ConstantBuffer`s are also
- // included in this object's layout already, so no need to skip those.
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(layout));
+ return SLANG_OK;
+ }
+ /// Recurisvely add descriptor sets defined by sub-objects of `layout`
+ Result addChildDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
+ {
for (auto& subObject : layout->getSubObjectRanges())
{
auto bindingRange = layout->getBindingRange(subObject.bindingRangeIndex);
- if (bindingRange.bindingType == slang::BindingType::ParameterBlock)
+ switch(bindingRange.bindingType)
+ {
+ case slang::BindingType::ParameterBlock:
+ SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(subObject.layout));
+ break;
+
+ default:
+ if(auto subObjectLayout = subObject.layout)
+ {
+ SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(subObject.layout));
+ }
+ break;
+ }
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Add all the push-constant ranges implied by this root object and sub-objects
+ Result addAllPushConstantRanges()
+ {
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(this));
+
+ for(auto& entryPoint : getEntryPoints())
+ {
+ SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(entryPoint.layout));
+ }
+
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add push-constant ranges defined by `layout` and sub-objects
+ Result addAllPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
+ {
+ // TODO: This logic assumes that push-constant ranges are all contiguous
+ // and have been allocated in a global order that matches the order
+ // of enumeration here.
+
+ for (auto pushConstantRange : layout->getOwnPushConstantRanges())
+ {
+ pushConstantRange.offset = m_totalPushConstantSize;
+ m_totalPushConstantSize += pushConstantRange.size;
+
+ m_allPushConstantRanges.add(pushConstantRange);
+ }
+
+ SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(layout));
+ return SLANG_OK;
+ }
+
+ /// Recurisvely add push-constant ranges defined by sub-objects of `layout`
+ Result addChildPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
+ {
+ for (auto& subObject : layout->getSubObjectRanges())
+ {
+ if(auto subObjectLayout = subObject.layout)
{
- SLANG_RETURN_ON_FAIL(addDescriptorSetLayoutRec(subObject.layout));
+ SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(subObject.layout));
}
}
@@ -1308,7 +2019,10 @@ public:
List<EntryPointInfo> m_entryPoints;
VkPipelineLayout m_pipelineLayout = VK_NULL_HANDLE;
Array<VkDescriptorSetLayout, kMaxDescriptorSets> m_vkDescriptorSetLayouts;
- Array<VkPushConstantRange, 8> m_pushConstantRanges;
+ List<VkPushConstantRange> m_allPushConstantRanges;
+ uint32_t m_totalPushConstantSize = 0;
+
+ SimpleBindingOffset m_pendingDataOffset;
VKDevice* m_renderer = nullptr;
};
@@ -1441,7 +2155,6 @@ public:
void flushBindingState(VkPipelineBindPoint pipelineBindPoint)
{
auto& api = *m_api;
- bindRootShaderObjectImpl(pipelineBindPoint);
// Get specialized pipeline state and bind it.
//
@@ -1449,6 +2162,9 @@ public:
m_device->maybeSpecializePipeline(
m_currentPipeline, &m_commandBuffer->m_rootObject, newPipeline);
PipelineStateImpl* newPipelineImpl = static_cast<PipelineStateImpl*>(newPipeline.Ptr());
+
+ bindRootShaderObjectImpl(pipelineBindPoint);
+
auto pipelineBindPointId = getBindPointIndex(pipelineBindPoint);
if (m_boundPipelines[pipelineBindPointId] != newPipelineImpl->m_pipeline)
{
@@ -1459,28 +2175,23 @@ public:
}
};
- union VulkanDescriptorInfo
- {
- VkDescriptorBufferInfo bufferInfo;
- VkDescriptorImageInfo imageInfo;
- };
- struct RootBindingState
+ /// Context information required when binding shader objects to the pipeline
+ struct RootBindingContext
{
- ShortList<VkWriteDescriptorSet, 32> descriptorSetWrites;
- ChunkedList<VulkanDescriptorInfo, 32> descriptorInfos;
- ChunkedList<VkBufferView, 8> bufferViews;
- Array<VkDescriptorSet, kMaxDescriptorSets> descriptorSets;
- ArrayView<VkPushConstantRange> pushConstantRanges;
+ /// The pipeline layout being used for binding
VkPipelineLayout pipelineLayout;
+
+ /// An allocator to use for descriptor sets during binding
DescriptorSetAllocator* descriptorSetAllocator;
+
+ /// The dvice being used
VKDevice* device;
- };
- struct BindingOffset
- {
- uint32_t uniformOffset;
- uint32_t pushConstantRangeOffset;
- uint32_t descriptorSetIndexOffset;
- uint32_t descriptorRangeOffset;
+
+ /// The descriptor sets that are being allocated and bound
+ VkDescriptorSet* descriptorSets;
+
+ /// Information about all the push-constant ranges that should be bound
+ ConstArrayView<VkPushConstantRange> pushConstantRanges;
};
class ShaderObjectImpl : public ShaderObjectBase
@@ -1949,10 +2660,8 @@ public:
// layout logic does for complex cases with multiple layers of nested arrays and
// structures.
//
- size_t subObjectRangePendingDataOffset =
- _getSubObjectRangePendingDataOffset(specializedLayout, subObjectRangeIndex);
- size_t subObjectRangePendingDataStride =
- _getSubObjectRangePendingDataStride(specializedLayout, subObjectRangeIndex);
+ size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
+ size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
// If the range doesn't actually need/use the "pending" allocation at all, then
// we need to detect that case and skip such ranges.
@@ -1987,22 +2696,6 @@ public:
return SLANG_OK;
}
- // As discussed in `_writeOrdinaryData()`, these methods are just stubs waiting for
- // the "flat" Slang refelction information to provide access to the relevant data.
- //
- size_t _getSubObjectRangePendingDataOffset(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
- size_t _getSubObjectRangePendingDataStride(
- ShaderObjectLayoutImpl* specializedLayout,
- Index subObjectRangeIndex)
- {
- return 0;
- }
-
public:
struct CombinedTextureSamplerSlot
{
@@ -2011,218 +2704,219 @@ public:
operator bool() { return textureView && sampler; }
};
- // A shared template function for composing a VkWriteDescriptorSet structure.
- // The signature for `WriteDescriptorInfoFunc` is
- // `void(VkWriteDescriptorSet&, int startElement, int elementCount)`, which sets up
- // `VkWriteDescriptorSet::pBufferInfo`, `pImageInfo` or `pTexelBufferView` fields.
- template<typename WriteDescriptorInfoFunc, typename TResourceArrayView>
- static void _writeDescriptorRange(
- RootBindingState* bindingState,
- BindingOffset offset,
- VkDescriptorType descriptorType,
- TResourceArrayView resourceViews,
- const WriteDescriptorInfoFunc& writeDescriptorInfo)
+ /// Write a single desriptor using the Vulkan API
+ static inline void writeDescriptor(
+ RootBindingContext& context,
+ VkWriteDescriptorSet const& write)
{
- auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
- bool hasNullBinding = false;
- for (auto& ptr : resourceViews)
- {
- if (!ptr)
- {
- hasNullBinding = true;
- break;
- }
- }
- if (hasNullBinding)
- {
- for (Index i = 0; i < resourceViews.getCount(); i++)
- {
- if (!resourceViews[i])
- continue;
- VkWriteDescriptorSet write = {};
- write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- write.descriptorCount = 1;
- write.descriptorType = descriptorType;
- write.dstArrayElement = (uint32_t)i;
- write.dstBinding = offset.descriptorRangeOffset;
- write.dstSet = descriptorSet;
- auto infos = bindingState->descriptorInfos.reserveRange(1);
- writeDescriptorInfo(write, (uint32_t)i, 1);
- bindingState->descriptorSetWrites.add(write);
- }
- return;
- }
-
- VkWriteDescriptorSet write = {};
- write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
- write.descriptorCount = (uint32_t)resourceViews.getCount();
- write.descriptorType = descriptorType;
- write.dstArrayElement = 0;
- write.dstBinding = offset.descriptorRangeOffset;
- write.dstSet = descriptorSet;
- writeDescriptorInfo(write, 0, write.descriptorCount);
- bindingState->descriptorSetWrites.add(write);
+ auto device = context.device;
+ device->m_api.vkUpdateDescriptorSets(
+ device->m_device,
+ 1,
+ &write,
+ 0,
+ nullptr);
}
static void writeBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
BufferResourceImpl* buffer,
size_t bufferOffset,
size_t bufferSize)
{
- auto descriptorSet = bindingState->descriptorSets[offset.descriptorSetIndexOffset];
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ VkDescriptorBufferInfo bufferInfo = {};
+ bufferInfo.buffer = buffer->m_buffer.m_buffer;
+ bufferInfo.offset = bufferOffset;
+ bufferInfo.range = bufferSize;
+
VkWriteDescriptorSet write = {};
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write.descriptorCount = 1;
write.descriptorType = descriptorType;
write.dstArrayElement = 0;
- write.dstBinding = offset.descriptorRangeOffset;
+ write.dstBinding = offset.binding;
write.dstSet = descriptorSet;
- auto& bufferInfo = bindingState->descriptorInfos.reserveRange(1)->bufferInfo;
write.pBufferInfo = &bufferInfo;
- bufferInfo.buffer = buffer->m_buffer.m_buffer;
- bufferInfo.offset = bufferOffset;
- bufferInfo.range = bufferSize;
- bindingState->descriptorSetWrites.add(write);
+
+ writeDescriptor(context, write);
}
static void writeBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
BufferResourceImpl* buffer)
{
writeBufferDescriptor(
- bindingState, offset, descriptorType, buffer, 0, buffer->getDesc()->sizeInBytes);
+ context, offset, descriptorType, buffer, 0, buffer->getDesc()->sizeInBytes);
}
+
static void writePlainBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(count);
- write.pBufferInfo = (VkDescriptorBufferInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
+ auto bufferView = static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkDescriptorBufferInfo bufferInfo = {};
+
+ if(bufferView)
{
- auto bufferView =
- static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
- if (bufferView)
- {
- infos[i].bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
- infos[i].bufferInfo.offset = 0;
- infos[i].bufferInfo.range = bufferView->m_buffer->getDesc()->sizeInBytes;
- }
+ bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
+ bufferInfo.offset = 0;
+ bufferInfo.range = bufferView->m_buffer->getDesc()->sizeInBytes;
}
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pBufferInfo = &bufferInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTexelBufferDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto views = bindingState->bufferViews.reserveRange(write.descriptorCount);
- write.pTexelBufferView = views;
- for (uint32_t i = startElement; i < count; i++)
- {
- views[i] =
- static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr())->m_view;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+ auto resourceView = static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkBufferView bufferView = resourceView->m_view;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pTexelBufferView = &bufferView;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTextureSamplerDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<CombinedTextureSamplerSlot> slots)
{
- auto writeDescriptorInfo = [=](VkWriteDescriptorSet& write,
- uint32_t startElement,
- uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = slots.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = slots[i].textureView;
- auto sampler = slots[i].sampler;
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = texture->m_view;
- imageInfo.imageLayout = texture->m_layout;
- imageInfo.sampler = sampler->m_sampler;
- }
- };
- _writeDescriptorRange(bindingState, offset, descriptorType, slots, writeDescriptorInfo);
+ auto texture = slots[i].textureView;
+ auto sampler = slots[i].sampler;
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = sampler->m_sampler;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeTextureDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<ResourceViewImpl>> resourceViews)
{
- auto writeDescriptorInfo =
- [=](VkWriteDescriptorSet& write, uint32_t startElement, uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = resourceViews.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = texture->m_view;
- imageInfo.imageLayout = texture->m_layout;
- imageInfo.sampler = 0;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, resourceViews, writeDescriptorInfo);
+ auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = texture->m_view;
+ imageInfo.imageLayout = texture->m_layout;
+ imageInfo.sampler = 0;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
static void writeSamplerDescriptor(
- RootBindingState* bindingState,
- BindingOffset offset,
+ RootBindingContext& context,
+ BindingOffset const& offset,
VkDescriptorType descriptorType,
ArrayView<RefPtr<SamplerStateImpl>> samplers)
{
- auto writeDescriptorInfo =
- [=](VkWriteDescriptorSet& write, uint32_t startElement, uint32_t count)
+ auto descriptorSet = context.descriptorSets[offset.bindingSet];
+
+ Index count = samplers.getCount();
+ for(Index i = 0; i < count; ++i)
{
- auto infos = bindingState->descriptorInfos.reserveRange(write.descriptorCount);
- write.pImageInfo = (VkDescriptorImageInfo*)infos;
- for (uint32_t i = startElement; i < count; i++)
- {
- auto texture = samplers[i]->m_sampler;
- auto& imageInfo = ((VkDescriptorImageInfo*)infos)[i];
- imageInfo.imageView = 0;
- imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
- imageInfo.sampler = samplers[i]->m_sampler;
- }
- };
- _writeDescriptorRange(
- bindingState, offset, descriptorType, samplers, writeDescriptorInfo);
+ auto sampler = samplers[i];
+
+ VkDescriptorImageInfo imageInfo = {};
+ imageInfo.imageView = 0;
+ imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
+ imageInfo.sampler = sampler->m_sampler;
+
+ VkWriteDescriptorSet write = {};
+ write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
+ write.descriptorCount = 1;
+ write.descriptorType = descriptorType;
+ write.dstArrayElement = uint32_t(i);
+ write.dstBinding = offset.binding;
+ write.dstSet = descriptorSet;
+ write.pImageInfo = &imageInfo;
+
+ writeDescriptor(context, write);
+ }
}
/// Ensure that the `m_ordinaryDataBuffer` has been created, if it is needed
- Result _ensureOrdinaryDataBufferCreatedIfNeeded(PipelineCommandEncoder* encoder)
+ Result _ensureOrdinaryDataBufferCreatedIfNeeded(
+ PipelineCommandEncoder* encoder,
+ ShaderObjectLayoutImpl* specializedLayout)
{
// If we have already created a buffer to hold ordinary data, then we should
// simply re-use that buffer rather than re-create it.
@@ -2238,22 +2932,7 @@ public:
return SLANG_OK;
}
- // Computing the size of the ordinary data buffer is *not* just as simple
- // as using the size of the `m_ordinayData` array that we store. The reason
- // for the added complexity is that interface-type fields may lead to the
- // storage being specialized such that it needs extra appended data to
- // store the concrete values that logically belong in those interface-type
- // fields but wouldn't fit in the fixed-size allocation we gave them.
- //
- // TODO: We need to actually implement that logic by using reflection
- // data computed for the specialized type of this shader object.
- // For now we just make the simple assumption described above despite
- // knowing that it is false.
- //
- RefPtr<ShaderObjectLayoutImpl> specializedLayout;
- SLANG_RETURN_ON_FAIL(_getSpecializedLayout(specializedLayout.writeRef()));
-
- m_constantBufferSize = specializedLayout->getElementTypeLayout()->getSize();
+ m_constantBufferSize = specializedLayout->getTotalOrdinaryDataSize();
if (m_constantBufferSize == 0)
{
m_upToDateConstantBufferHeapVersion =
@@ -2287,187 +2966,344 @@ public:
return SLANG_OK;
}
- /// Bind the buffer for ordinary/uniform data, if needed
- Result _bindOrdinaryDataBufferIfNeeded(
- PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
- {
- // We are going to need to tweak the base binding range index
- // used for descriptor-set writes if and only if we actually
- // bind a buffer for ordinary data.
- //
- auto& baseRangeIndex = offset.descriptorRangeOffset;
-
- // We start by ensuring that the buffer is created, if it is needed.
- //
- SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder));
-
- // If we did indeed need/create a buffer, then we must bind it into
- // the given `descriptorSet` and update the base range index for
- // subsequent binding operations to account for it.
- //
- if (m_constantBuffer)
- {
- auto bufferImpl = static_cast<BufferResourceImpl*>(m_constantBuffer);
- writeBufferDescriptor(
- bindingState,
- offset,
- VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
- bufferImpl,
- m_constantBufferOffset,
- m_constantBufferSize);
- offset.descriptorRangeOffset++;
- }
-
- return SLANG_OK;
- }
-
public:
- Result bindDescriptorRanges(
+
+ /// Bind this shader object as a "value"
+ ///
+ /// This is the mode used for binding sub-objects for existential-type
+ /// fields, and is also used as part of the implementation of the
+ /// parameter-block and constant-buffer cases.
+ ///
+ Result bindAsValue(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
+ RootBindingContext& context,
+ BindingOffset const& offset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- auto layout = getLayout();
-
- // Fill in the descriptor sets based on binding ranges
+ // We start by iterating over the "simple" (non-sub-object) binding
+ // ranges and writing them to the descriptor sets that are being
+ // passed down.
//
- for (auto bindingRangeInfo : layout->getBindingRanges())
+ for (auto bindingRangeInfo : specializedLayout->getBindingRanges())
{
- auto rangeIndex =
- bindingRangeInfo.rangeIndexInDescriptorSet + offset.descriptorRangeOffset;
+ BindingOffset rangeOffset = offset;
+
auto baseIndex = bindingRangeInfo.baseIndex;
auto count = (uint32_t)bindingRangeInfo.count;
switch (bindingRangeInfo.bindingType)
{
case slang::BindingType::ConstantBuffer:
- for (uint32_t i = 0; i < count; ++i)
- {
- ShaderObjectImpl* subObject = m_objects[baseIndex + i];
- subObject->bindObjectIntoConstantBuffer(encoder, bindingState, offset);
- }
- break;
case slang::BindingType::ParameterBlock:
- for (uint32_t i = 0; i < count; ++i)
- {
- ShaderObjectImpl* subObject = m_objects[baseIndex + i];
- auto newOffset = offset;
- subObject->bindObjectIntoParameterBlock(encoder, bindingState, newOffset);
- offset.pushConstantRangeOffset = newOffset.pushConstantRangeOffset;
- }
+ case slang::BindingType::ExistentialValue:
break;
+
case slang::BindingType::Texture:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::MutableTexture:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::CombinedTextureSampler:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTextureSamplerDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
m_combinedTextureSamplers.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::Sampler:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeSamplerDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_SAMPLER,
m_samplers.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::RawBuffer:
case slang::BindingType::MutableRawBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writePlainBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::TypedBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTexelBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::MutableTypedBuffer:
+ rangeOffset.bindingSet += bindingRangeInfo.setOffset;
+ rangeOffset.binding += bindingRangeInfo.bindingOffset;
writeTexelBufferDescriptor(
- bindingState,
- offset,
+ context,
+ rangeOffset,
VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
m_resourceViews.getArrayView(baseIndex, count));
- offset.descriptorRangeOffset++;
break;
case slang::BindingType::VaryingInput:
case slang::BindingType::VaryingOutput:
break;
+ default:
+ SLANG_ASSERT(!"unsupported binding type");
+ return SLANG_FAIL;
+ break;
+ }
+ }
+
+ // Once we've handled the simpel binding ranges, we move on to the
+ // sub-object ranges, which are generally more involved.
+ //
+ for( auto const& subObjectRange : specializedLayout->getSubObjectRanges() )
+ {
+ auto const& bindingRangeInfo = specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex);
+ auto count = bindingRangeInfo.count;
+ auto baseIndex = bindingRangeInfo.baseIndex;
+
+ auto subObjectLayout = subObjectRange.layout;
+
+ // The starting offset to use for the sub-object
+ // has already been computed and stored as part
+ // of the layout, so we can get to the starting
+ // offset for the range easily.
+ //
+ BindingOffset rangeOffset = offset;
+ rangeOffset += subObjectRange.offset;
+
+ switch( bindingRangeInfo.bindingType )
+ {
+ case slang::BindingType::ConstantBuffer:
+ {
+ BindingOffset objOffset = rangeOffset;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // Binding a constant buffer sub-object is simple enough:
+ // we just call `bindAsConstantBuffer` on it to bind
+ // the ordinary data buffer (if needed) and any other
+ // bindings it recursively contains.
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsConstantBuffer(encoder, context, objOffset, subObjectLayout);
+
+ // When dealing with arrays of sub-objects, we need to make
+ // sure to increment the offset for each subsequent object
+ // by the appropriate stride.
+ //
+ // TODO: We should pre-compute these and simply have
+ // `subObjectRange.stride` to go with `subObjectRange.offset`.
+ //
+ objOffset.binding += subObjectLayout->getTotalBindingCount();
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
+ break;
+ case slang::BindingType::ParameterBlock:
+ {
+ BindingOffset objOffset = rangeOffset;
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // The case for `ParameterBlock<X>` is not that different
+ // from `ConstantBuffer<X>`, except that we call `bindAsParameterBlock`
+ // instead (understandably).
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsParameterBlock(encoder, context, objOffset, subObjectLayout);
+
+ // The logic for striding from one sub-object to another is also
+ // different, given the differences in how constant buffers and
+ // parameter blocks are allocated.
+ //
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
+ break;
+
case slang::BindingType::ExistentialValue:
+ // Interface/existential-type sub-object ranges are the most complicated case.
//
- // TODO: If the existential value is one that "fits" into the storage available,
- // then we should write its data directly into that area. Otherwise, we need
- // to bind its content as "pending" data which will come after any other data
- // beloning to the same set (that is, it's starting descriptorRangeIndex will
- // need to be one after the number of ranges accounted for in the original type)
+ // First, we can only bind things if we have static specialization information
+ // to work with, which is exactly the case where `subObjectLayout` will be non-null.
//
+ if( subObjectLayout )
+ {
+ // Second, the offset where we want to start bindign for existential-type
+ // ranges is a bit different, because we don't wnat to bind at the "primary"
+ // offset that got passed down, but instead at the "pending" offset.
+ //
+ // For the purposes of nested binding, what used to be the pending offset
+ // will now be used as the primary offset.
+ //
+ BindingOffset objOffset = BindingOffset(rangeOffset.pending);
+ for (uint32_t i = 0; i < count; ++i)
+ {
+ // An existential-type sub-object is always bound just as a value,
+ // which handles its nested bindings and descriptor sets, but
+ // does not deal with ordianry data. The ordinary data should
+ // have been handled as part of the buffer for a parent object
+ // already.
+ //
+ ShaderObjectImpl* subObject = m_objects[baseIndex + i];
+ subObject->bindAsValue(encoder, context, objOffset, subObjectLayout);
+
+ objOffset.binding += subObjectLayout->getTotalBindingCount();
+ objOffset.childSet += subObjectLayout->getChildDescriptorSetCount();
+ objOffset.pushConstantRange += subObjectLayout->getTotalPushConstantRangeCount();
+ }
+ }
break;
default:
- SLANG_ASSERT(!"unsupported binding type");
+ SLANG_ASSERT(!"unsupported sub-object type");
return SLANG_FAIL;
break;
}
}
+
+ return SLANG_OK;
+ }
+
+ /// Allocate the descriptor sets needed for binding this object (but not nested parameter blocks)
+ Result allocateDescriptorSets(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ BindingOffset const& offset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ auto baseDescriptorSetIndex = offset.childSet;
+
+ // The number of sets to allocate and their layouts was already pre-computed
+ // as part of the shader object layout, so we use that information here.
+ //
+ for (auto descriptorSetInfo : specializedLayout->getOwnDescriptorSets())
+ {
+ auto descriptorSetHandle = context.descriptorSetAllocator->allocate(
+ descriptorSetInfo.descriptorSetLayout).handle;
+
+ // For each set, we need to write it into the set of descriptor sets
+ // being used for binding. This is done both so that other steps
+ // in binding can find the set to fill it in, but also so that
+ // we can bind all the descriptor sets to the pipeline when the
+ // time comes.
+ //
+ auto descriptorSetIndex = baseDescriptorSetIndex + descriptorSetInfo.space;
+ context.descriptorSets[descriptorSetIndex] = descriptorSetHandle;
+ }
+
return SLANG_OK;
}
- virtual Result bindObjectIntoConstantBuffer(
+ /// Bind this object as a `ParameterBlock<X>`.
+ Result bindAsParameterBlock(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
{
- SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(encoder, bindingState, offset));
+ // Because we are binding into a nested parameter block,
+ // any texture/buffer/sampler bindings will now want to
+ // write into the sets we allocate for this object and
+ // not the sets for any parent object(s).
+ //
+ BindingOffset offset = inOffset;
+ offset.bindingSet = offset.childSet;
+ offset.binding = 0;
+
+ // TODO: We should also be writing to `offset.pending` here,
+ // because any resource/sampler bindings related to "pending"
+ // data should *also* be writing into the chosen set.
+ //
+ // The challenge here is that we need to compute the right
+ // value for `offset.pending.binding`, so that it writes after
+ // all the other bindings.
+
+ // Writing the bindings for a parameter block is relatively easy:
+ // we just need to allocate the descriptor set(s) needed for this
+ // object and then fill it in like a `ConstantBuffer<X>`.
+ //
+ SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, specializedLayout));
+ SLANG_RETURN_ON_FAIL(bindAsConstantBuffer(encoder, context, offset, specializedLayout));
- SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
return SLANG_OK;
}
- virtual Result bindObjectIntoParameterBlock(
+ /// Bind the ordinary data buffer if needed, and adjust `ioOffset` accordingly
+ Result bindOrdinaryDataBufferIfNeeded(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset)
- {
- auto& descriptorSetInfos = getLayout()->getDescriptorSets();
- offset.descriptorSetIndexOffset = (uint32_t)bindingState->descriptorSets.getCount();
- offset.uniformOffset = 0;
- offset.descriptorRangeOffset = 0;
- for (auto info : descriptorSetInfos)
+ RootBindingContext& context,
+ BindingOffset& ioOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ // We start by ensuring that the buffer is created, if it is needed.
+ //
+ SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder, specializedLayout));
+
+ // If we did indeed need/create a buffer, then we must bind it into
+ // the given `descriptorSet` and update the base range index for
+ // subsequent binding operations to account for it.
+ //
+ if (m_constantBuffer)
{
- bindingState->descriptorSets.add(
- bindingState->descriptorSetAllocator->allocate(info.descriptorSetLayout)
- .handle);
+ auto bufferImpl = static_cast<BufferResourceImpl*>(m_constantBuffer);
+ writeBufferDescriptor(
+ context,
+ ioOffset,
+ VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
+ bufferImpl,
+ m_constantBufferOffset,
+ m_constantBufferSize);
+ ioOffset.binding++;
}
- SLANG_RETURN_ON_FAIL(bindObjectIntoConstantBuffer(encoder, bindingState, offset));
+
+ return SLANG_OK;
+ }
+
+ /// Bind this object as a `ConstantBuffer<X>`.
+ Result bindAsConstantBuffer(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ ShaderObjectLayoutImpl* specializedLayout)
+ {
+ // To bind an object as a constant buffer, we first
+ // need to bind its ordinary data (if any) into an
+ // ordinary data buffer, and then bind it as a "value"
+ // which handles any of its recursively-contained bindings.
+ //
+ // The one detail is taht when binding the ordinary data
+ // buffer we need to adjust the `binding` index used for
+ // subsequent operations based on whether or not an ordinary
+ // data buffer was used (and thus consumed a `binding`).
+ //
+ BindingOffset offset = inOffset;
+ SLANG_RETURN_ON_FAIL(bindOrdinaryDataBufferIfNeeded(encoder, context, /*inout*/ offset, specializedLayout));
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, specializedLayout));
return SLANG_OK;
}
@@ -2547,28 +3383,60 @@ public:
EntryPointLayout* getLayout() { return static_cast<EntryPointLayout*>(m_layout.Ptr()); }
- virtual Result bindObjectIntoConstantBuffer(
+ /// Bind this shader object as an entry point
+ Result bindAsEntryPoint(
PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset) override
+ RootBindingContext& context,
+ BindingOffset const& inOffset,
+ EntryPointLayout* layout)
{
- // Set data in `m_ordinaryData` into the push constant range.
+ BindingOffset offset = inOffset;
+
+ // Any ordinary data in an entry point is assumed to be allocated
+ // as a push-constant range.
+ //
+ // TODO: Can we make this operation not bake in that assumption?
+ //
+ // TODO: Can/should this function be renamed as just `bindAsPushConstantBuffer`?
+ //
if (m_ordinaryData.getCount())
{
- auto pushConstantRange =
- bindingState->pushConstantRanges[offset.pushConstantRangeOffset];
+ // The index of the push constant range to bind should be
+ // passed down as part of the `offset`, and we will increment
+ // it here so that any further recursively-contained push-constant
+ // ranges use the next index.
+ //
+ auto pushConstantRangeIndex = offset.pushConstantRange++;
+
+ // Information about the push constant ranges (including offsets
+ // and stage flags) was pre-computed for the entire program and
+ // stored on the binding context.
+ //
+ auto const& pushConstantRange = context.pushConstantRanges[pushConstantRangeIndex];
+
+ // We expect that the size of the range as reflected matches the
+ // amount of ordinary data stored on this object.
+ //
+ // TODO: This would not be the case if specialization for interface-type
+ // parameters led to the entry point having "pending" ordinary data.
+ //
+ SLANG_ASSERT(pushConstantRange.size == (uint32_t) m_ordinaryData.getCount());
+
+ auto pushConstantData = m_ordinaryData.getBuffer();
+
encoder->m_api->vkCmdPushConstants(
encoder->m_commandBuffer->m_commandBuffer,
- bindingState->pipelineLayout,
+ context.pipelineLayout,
pushConstantRange.stageFlags,
pushConstantRange.offset,
pushConstantRange.size,
- m_ordinaryData.getBuffer());
- offset.pushConstantRangeOffset++;
+ pushConstantData);
}
- // Process the rest of binding ranges.
- SLANG_RETURN_ON_FAIL(bindDescriptorRanges(encoder, bindingState, offset));
+ // Any remaining bindings in the object can be handled through the
+ // "value" case.
+ //
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
return SLANG_OK;
}
@@ -2614,18 +3482,50 @@ public:
return SLANG_OK;
}
- virtual Result bindObjectIntoParameterBlock(
- PipelineCommandEncoder* encoder,
- RootBindingState* bindingState,
- BindingOffset& offset) override
+ /// Bind this object as a root shader object
+ Result bindAsRoot(
+ PipelineCommandEncoder* encoder,
+ RootBindingContext& context,
+ RootShaderObjectLayout* layout)
{
- SLANG_RETURN_ON_FAIL(Super::bindObjectIntoParameterBlock(encoder, bindingState, offset));
+ BindingOffset offset = {};
+ offset.pending = layout->getPendingDataOffset();
- // Bind all entry points.
- for (auto& entryPoint : m_entryPoints)
+ // Note: the operations here are quite similar to what `bindAsParameterBlock` does.
+ // The key difference in practice is that we do *not* make use of the adjustment
+ // that `bindOrdinaryDataBufferIfNeeded` applied to the offset passed into it.
+ //
+ // The reason for this difference in behavior is that the layout information
+ // for root shader parameters is in practice *already* offset appropriately
+ // (so that it ends up using absolute offsets).
+ //
+ // TODO: One more wrinkle here is that the `ordinaryDataBufferOffset` below
+ // might not be correct if `binding=0,set=0` was already claimed via explicit
+ // binding information. We should really be getting the offset information for
+ // the ordinary data buffer directly from the reflection information for
+ // the global scope.
+
+ SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, layout));
+
+ BindingOffset ordinaryDataBufferOffset = offset;
+ SLANG_RETURN_ON_FAIL(bindOrdinaryDataBufferIfNeeded(encoder, context, /*inout*/ ordinaryDataBufferOffset, layout));
+
+ SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
+
+ auto entryPointCount = layout->getEntryPoints().getCount();
+ for( Index i = 0; i < entryPointCount; ++i )
{
- entryPoint->bindObjectIntoConstantBuffer(encoder, bindingState, offset);
+ auto entryPoint = m_entryPoints[i];
+ auto const& entryPointInfo = layout->getEntryPoint(i);
+
+ // Note: we do *not* need to add the entry point offset
+ // information to the global `offset` because the
+ // `RootShaderObjectLayout` has already baked any offsets
+ // from the global layout into the `entryPointInfo`.
+
+ entryPoint->bindAsEntryPoint(encoder, context, entryPointInfo.offset, entryPointInfo.layout);
}
+
return SLANG_OK;
}
@@ -3873,33 +4773,50 @@ Result VKDevice::PipelineCommandEncoder::bindRootShaderObjectImpl(
if (!specializedLayout)
return SLANG_FAIL;
- RootBindingState bindState = {};
- bindState.pushConstantRanges = specializedLayout->m_pushConstantRanges.getView();
- bindState.pipelineLayout = specializedLayout->m_pipelineLayout;
- bindState.device = m_device;
- bindState.descriptorSetAllocator = &m_commandBuffer->m_transientHeap->m_descSetAllocator;
-
- // Write bindings into descriptor sets. This step allocate descriptor sets and collects
- // all `VkWriteDescriptorSet` operations in `bindState.descriptorSetWrites`.
- BindingOffset offset = {};
- rootObjectImpl->bindObjectIntoParameterBlock(this, &bindState, offset);
-
- // Execute descriptor writes collected in `bindState.descriptorSetWrites`.
- m_device->m_api.vkUpdateDescriptorSets(
- m_device->m_device,
- (uint32_t)bindState.descriptorSetWrites.getCount(),
- bindState.descriptorSetWrites.getArrayView().arrayView.getBuffer(),
- 0,
- nullptr);
+ // We will set up the context required when binding shader objects
+ // to the pipeline. Note that this is mostly just being packaged
+ // together to minimize the number of parameters that have to
+ // be dealt with in the complex recursive call chains.
+ //
+ RootBindingContext context;
+ context.pipelineLayout = specializedLayout->m_pipelineLayout;
+ context.device = m_device;
+ context.descriptorSetAllocator = &m_commandBuffer->m_transientHeap->m_descSetAllocator;
+ context.pushConstantRanges = specializedLayout->getAllPushConstantRanges().getArrayView();
+
+ // The context includes storage for the descriptor sets we will bind,
+ // and the number of sets we need to make space for is determined
+ // by the specialized program layout.
+ //
+ List<VkDescriptorSet> descriptorSetsStorage;
+ auto descriptorSetCount = specializedLayout->getTotalDescriptorSetCount();
+
+ descriptorSetsStorage.setCount(descriptorSetCount);
+ auto descriptorSets = descriptorSetsStorage.getBuffer();
+
+ context.descriptorSets = descriptorSets;
+
+ // We kick off recursive binding of shader objects to the pipeline (plus
+ // the state in `context`).
+ //
+ // Note: this logic will directly write any push-constant ranges needed,
+ // and will also fill in any descriptor sets. Currently it does not
+ // *bind* the descriptor sets it fills in.
+ //
+ // TODO: It could probably bind the descriptor sets as well.
+ //
+ rootObjectImpl->bindAsRoot(this, context, specializedLayout);
- // Bind descriptor sets.
+ // Once we've filled in all the descriptor sets, we bind them
+ // to the pipeline at once.
+ //
m_device->m_api.vkCmdBindDescriptorSets(
m_commandBuffer->m_commandBuffer,
bindPoint,
specializedLayout->m_pipelineLayout,
0,
- (uint32_t)bindState.descriptorSets.getCount(),
- bindState.descriptorSets.getBuffer(),
+ (uint32_t) descriptorSetCount,
+ descriptorSets,
0,
nullptr);
@@ -5540,7 +6457,7 @@ Result VKDevice::createShaderObjectLayout(
{
RefPtr<ShaderObjectLayoutImpl> layout;
SLANG_RETURN_ON_FAIL(
- ShaderObjectLayoutImpl::createForElementType(this, typeLayout, true, layout.writeRef()));
+ ShaderObjectLayoutImpl::createForElementType(this, typeLayout, layout.writeRef()));
returnRefPtrMove(outLayout, layout);
return SLANG_OK;
}