diff options
| -rw-r--r-- | examples/model-viewer/main.cpp | 35 | ||||
| -rw-r--r-- | examples/model-viewer/shaders.slang | 8 | ||||
| -rw-r--r-- | slang.h | 10 | ||||
| -rw-r--r-- | source/core/slang-string.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-ir-legalize-types.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-ir-lower-tuple-types.cpp | 15 | ||||
| -rw-r--r-- | source/slang/slang-reflection-api.cpp | 214 | ||||
| -rw-r--r-- | source/slang/slang-type-layout.cpp | 13 | ||||
| -rw-r--r-- | source/slang/slang-type-layout.h | 6 | ||||
| -rw-r--r-- | tests/compute/interface-shader-param-in-struct.slang | 6 | ||||
| -rw-r--r-- | tests/language-feature/shader-params/interface-shader-param-ordinary.slang | 53 | ||||
| -rw-r--r-- | tests/language-feature/shader-params/interface-shader-param-ordinary.slang.expected.txt | 4 | ||||
| -rw-r--r-- | tools/gfx/d3d11/render-d3d11.cpp | 965 | ||||
| -rw-r--r-- | tools/gfx/d3d12/render-d3d12.cpp | 1070 | ||||
| -rw-r--r-- | tools/gfx/open-gl/render-gl.cpp | 16 | ||||
| -rw-r--r-- | tools/gfx/renderer-shared.cpp | 27 | ||||
| -rw-r--r-- | tools/gfx/vulkan/render-vk.cpp | 1901 |
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; } }; // @@ -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; } |
