diff options
| author | lucy96chen <47800040+lucy96chen@users.noreply.github.com> | 2022-05-17 10:56:14 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-05-17 10:56:14 -0700 |
| commit | 5a3aa6159e0ef0241b528812e1d138f0d7055f22 (patch) | |
| tree | 71d286e06030ee73f0b739e071cd58dd05d507d1 /tools/gfx/d3d12/d3d12-shader-object-layout.cpp | |
| parent | 716e75b9ed1acfaee3dc7f3bc347ad17fca65e05 (diff) | |
Split render-d3d12.h/cpp into a set of smaller files (#2231)
* Split render-d3d12 into numerous smaller files to make the code easier to parse
* Added all new D3D12 files created from splitting render-d3d12
* Fixed several uses of attachment still floating around; Changed resource-d3d12 and descriptor-heap-d3d12 to match naming conventions of new d3d12 implementation header files
* Readded files with name changes because changing them from inside VS apparently results in them being treated as new files
* Merged in externals changes from master
* Small cleanup changes
* Rerun CI
Co-authored-by: Theresa Foley <10618364+tangent-vector@users.noreply.github.com>
Diffstat (limited to 'tools/gfx/d3d12/d3d12-shader-object-layout.cpp')
| -rw-r--r-- | tools/gfx/d3d12/d3d12-shader-object-layout.cpp | 1004 |
1 files changed, 1004 insertions, 0 deletions
diff --git a/tools/gfx/d3d12/d3d12-shader-object-layout.cpp b/tools/gfx/d3d12/d3d12-shader-object-layout.cpp new file mode 100644 index 000000000..3e8598d51 --- /dev/null +++ b/tools/gfx/d3d12/d3d12-shader-object-layout.cpp @@ -0,0 +1,1004 @@ +// d3d12-shader-object-layout.cpp +#include "d3d12-shader-object-layout.h" + +#include "d3d12-device.h" + +namespace gfx +{ +namespace d3d12 +{ + +using namespace Slang; + +ShaderObjectLayoutImpl::SubObjectRangeOffset::SubObjectRangeOffset( + slang::VariableLayoutReflection* varLayout) +{ + if (auto pendingLayout = varLayout->getPendingDataLayout()) + { + pendingOrdinaryData = (uint32_t)pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM); + } +} + +ShaderObjectLayoutImpl::SubObjectRangeStride::SubObjectRangeStride( + slang::TypeLayoutReflection* typeLayout) +{ + if (auto pendingLayout = typeLayout->getPendingDataTypeLayout()) + { + pendingOrdinaryData = (uint32_t)pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM); + } +} + +bool ShaderObjectLayoutImpl::isBindingRangeRootParameter( + SlangSession* globalSession, + const char* rootParameterAttributeName, + slang::TypeLayoutReflection* typeLayout, + Index bindingRangeIndex) +{ + bool isRootParameter = false; + if (rootParameterAttributeName) + { + if (auto leafVariable = typeLayout->getBindingRangeLeafVariable(bindingRangeIndex)) + { + if (leafVariable->findUserAttributeByName(globalSession, rootParameterAttributeName)) + { + isRootParameter = true; + } + } + } + return isRootParameter; +} + +Result ShaderObjectLayoutImpl::createForElementType( + RendererBase* renderer, + slang::TypeLayoutReflection* elementType, + ShaderObjectLayoutImpl** outLayout) +{ + Builder builder(renderer); + builder.setElementTypeLayout(elementType); + return builder.build(outLayout); +} + +Result ShaderObjectLayoutImpl::init(Builder* builder) +{ + auto renderer = builder->m_renderer; + + initBase(renderer, builder->m_elementTypeLayout); + + m_containerType = builder->m_containerType; + + m_bindingRanges = _Move(builder->m_bindingRanges); + m_subObjectRanges = _Move(builder->m_subObjectRanges); + m_rootParamsInfo = _Move(builder->m_rootParamsInfo); + + m_ownCounts = builder->m_ownCounts; + m_totalCounts = builder->m_totalCounts; + m_subObjectCount = builder->m_subObjectCount; + m_childRootParameterCount = builder->m_childRootParameterCount; + m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize; + + return SLANG_OK; +} + +Result ShaderObjectLayoutImpl::Builder::setElementTypeLayout( + slang::TypeLayoutReflection* typeLayout) +{ + typeLayout = _unwrapParameterGroups(typeLayout, m_containerType); + m_elementTypeLayout = typeLayout; + + // If the type contains any ordinary data, then we must reserve a buffer + // descriptor to hold it when binding as a parameter block. + // + m_totalOrdinaryDataSize = (uint32_t)typeLayout->getSize(); + if (m_totalOrdinaryDataSize != 0) + { + m_ownCounts.resource++; + } + + // We will scan over the reflected Slang binding ranges and add them + // to our array. There are two main things we compute along the way: + // + // * For each binding range we compute a `flatIndex` that can be + // used to identify where the values for the given range begin + // in the flattened arrays (e.g., `m_objects`) and descriptor + // tables that hold the state of a shader object. + // + // * We also update the various counters taht keep track of the number + // of sub-objects, resources, samplers, etc. that are being + // consumed. These counters will contribute to figuring out + // the descriptor table(s) that might be needed to represent + // the object. + // + SlangInt bindingRangeCount = typeLayout->getBindingRangeCount(); + for (SlangInt r = 0; r < bindingRangeCount; ++r) + { + slang::BindingType slangBindingType = typeLayout->getBindingRangeType(r); + uint32_t count = (uint32_t)typeLayout->getBindingRangeBindingCount(r); + slang::TypeLayoutReflection* slangLeafTypeLayout = + typeLayout->getBindingRangeLeafTypeLayout(r); + BindingRangeInfo bindingRangeInfo = {}; + bindingRangeInfo.bindingType = slangBindingType; + bindingRangeInfo.resourceShape = slangLeafTypeLayout->getResourceShape(); + bindingRangeInfo.count = count; + bindingRangeInfo.isRootParameter = isBindingRangeRootParameter( + m_renderer->slangContext.globalSession, + static_cast<DeviceImpl*>(m_renderer)->m_extendedDesc.rootParameterShaderAttributeName, + typeLayout, + r); + if (bindingRangeInfo.isRootParameter) + { + RootParameterInfo rootInfo = {}; + switch (slangBindingType) + { + case slang::BindingType::RayTracingAccelerationStructure: + rootInfo.type = IResourceView::Type::AccelerationStructure; + break; + case slang::BindingType::RawBuffer: + case slang::BindingType::TypedBuffer: + rootInfo.type = IResourceView::Type::ShaderResource; + break; + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTypedBuffer: + rootInfo.type = IResourceView::Type::UnorderedAccess; + break; + } + bindingRangeInfo.baseIndex = (uint32_t)m_rootParamsInfo.getCount(); + for (uint32_t i = 0; i < count; i++) + { + m_rootParamsInfo.add(rootInfo); + } + } + else + { + switch (slangBindingType) + { + case slang::BindingType::ConstantBuffer: + case slang::BindingType::ParameterBlock: + case slang::BindingType::ExistentialValue: + bindingRangeInfo.baseIndex = m_subObjectCount; + bindingRangeInfo.subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + break; + case slang::BindingType::RawBuffer: + case slang::BindingType::MutableRawBuffer: + if (slangLeafTypeLayout->getType()->getElementType() != nullptr) + { + // A structured buffer occupies both a resource slot and + // a sub-object slot. + bindingRangeInfo.subObjectIndex = m_subObjectCount; + m_subObjectCount += count; + } + bindingRangeInfo.baseIndex = m_ownCounts.resource; + m_ownCounts.resource += count; + break; + case slang::BindingType::Sampler: + bindingRangeInfo.baseIndex = m_ownCounts.sampler; + m_ownCounts.sampler += count; + break; + + case slang::BindingType::CombinedTextureSampler: + // TODO: support this case... + break; + + case slang::BindingType::VaryingInput: + case slang::BindingType::VaryingOutput: + break; + + default: + bindingRangeInfo.baseIndex = m_ownCounts.resource; + m_ownCounts.resource += count; + break; + } + } + m_bindingRanges.add(bindingRangeInfo); + } + + // At this point we've computed the number of resources/samplers that + // the type needs to represent its *own* state, and stored those counts + // in `m_ownCounts`. Next we need to consider any resources/samplers + // and root parameters needed to represent the state of the transitive + // sub-objects of this objet, so that we can compute the total size + // of the object when bound to the pipeline. + + 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); + + // 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. + // + RefPtr<ShaderObjectLayoutImpl> subObjectLayout; + if (slangBindingType == slang::BindingType::ExistentialValue) + { + if (auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout()) + { + createForElementType(m_renderer, pendingTypeLayout, subObjectLayout.writeRef()); + } + } + else + { + createForElementType( + m_renderer, + slangLeafTypeLayout->getElementTypeLayout(), + subObjectLayout.writeRef()); + } + + SubObjectRangeInfo subObjectRange; + subObjectRange.bindingRangeIndex = bindingRangeIndex; + subObjectRange.layout = subObjectLayout; + + // The Slang reflection API stors offset information for sub-object ranges, + // and we care about *some* of that information: in particular, we need + // the offset of sub-objects in terms of uniform/ordinary data for the + // cases where we need to fill in "pending" data in our ordinary buffer. + // + subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r)); + subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout); + + // The remaining offset information is computed based on the counters + // we are generating here, which depend only on the in-memory layout + // decisions being made in our implementation. Remember that the + // `register` and `space` values coming from DXBC/DXIL do *not* + // dictate the in-memory layout we use. + // + // Note: One subtle point here is that the `.rootParam` offset we are computing + // here does *not* include any root parameters that would be allocated + // for the parent object type itself (e.g., for descriptor tables + // used if it were bound as a parameter block). The later logic when + // we actually go to bind things will need to apply those offsets. + // + // Note: An even *more* subtle point is that the `.resource` offset + // being computed here *does* include the resource descriptor allocated + // for holding the ordinary data buffer, if any. The implications of + // this for later offset math is subtle. + // + subObjectRange.offset.rootParam = m_childRootParameterCount; + subObjectRange.offset.resource = m_totalCounts.resource; + subObjectRange.offset.sampler = m_totalCounts.sampler; + + // Along with the offset information, we also need to compute the + // "stride" between consecutive sub-objects in the range. The actual + // size/stride of a single object depends on the type of range we + // are dealing with. + // + BindingOffset objectCounts; + switch (slangBindingType) + { + default: + { + // We only treat buffers of interface types as actual sub-object binding + // range. + auto bindingRangeTypeLayout = + typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex); + if (!bindingRangeTypeLayout) + continue; + auto elementType = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex) + ->getElementTypeLayout(); + if (!elementType) + continue; + if (elementType->getKind() != slang::TypeReflection::Kind::Interface) + { + continue; + } + } + 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::ParameterBlock: + { + SLANG_ASSERT(subObjectLayout); + + // In contrast to a constant buffer, a parameter block can hide + // the resource and sampler descriptor allocation it uses (since they + // are allocated into the tables that make up the parameter block. + // + // The only resource usage that leaks into the surrounding context + // is the number of root parameters consumed. + // + objectCounts.rootParam = subObjectRange.layout->getTotalRootTableParameterCount(); + } + break; + + case slang::BindingType::ExistentialValue: + // An unspecialized existential/interface value cannot consume any resources + // as part of the parent object (it needs to fit inside the fixed-size + // represnetation of existential types). + // + // However, if we are statically specializing to a type that doesn't "fit" + // we may need to account for additional information that needs to be + // allocaated. + // + if (subObjectLayout) + { + // The ordinary data for an existential-type value is allocated into + // the same buffer as the parent object, so we only want to consider + // the resource descriptors *other than* the ordinary data buffer. + // + // Otherwise the logic here is identical to the constant buffer case. + // + objectCounts.resource = + subObjectLayout->getTotalResourceDescriptorCountWithoutOrdinaryDataBuffer(); + objectCounts.sampler = subObjectLayout->getTotalSamplerDescriptorCount(); + objectCounts.rootParam = subObjectRange.layout->getChildRootParameterCount(); + + // Note: In the implementation for some other graphics API (e.g., + // Vulkan) there needs to be more work done to handle the fact that + // "pending" data from interface-type sub-objects get allocated to a + // distinct offset after all the "primary" data. We are consciously + // ignoring that issue here, and the physical layout of a shader object + // into the D3D12 binding state may end up interleaving + // resources/samplers for "primary" and "pending" data. + // + // If this choice ever causes issues, we can revisit the approach here. + + // 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; + } + + // Once we've computed the usage for each object in the range, we can + // easily compute the usage for the entire range. + // + 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); + } + + // Once we have added up the resource usage from all the sub-objects + // we can look at the total number of resources and samplers that + // need to be bound as part of this objects descriptor tables and + // that will allow us to decide whether we need to allocate a root + // parameter for a resource table or not, ans similarly for a + // sampler table. + // + 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; +} + +Result ShaderObjectLayoutImpl::Builder::build(ShaderObjectLayoutImpl** outLayout) +{ + auto layout = RefPtr<ShaderObjectLayoutImpl>(new ShaderObjectLayoutImpl()); + SLANG_RETURN_ON_FAIL(layout->init(this)); + + returnRefPtrMove(outLayout, layout); + return SLANG_OK; +} + +Result RootShaderObjectLayoutImpl::Builder::build(RootShaderObjectLayoutImpl** outLayout) +{ + RefPtr<RootShaderObjectLayoutImpl> layout = new RootShaderObjectLayoutImpl(); + SLANG_RETURN_ON_FAIL(layout->init(this)); + + returnRefPtrMove(outLayout, layout); + return SLANG_OK; +} + +void RootShaderObjectLayoutImpl::Builder::addGlobalParams( + slang::VariableLayoutReflection* globalsLayout) +{ + setElementTypeLayout(globalsLayout->getTypeLayout()); +} + +void RootShaderObjectLayoutImpl::Builder::addEntryPoint( + SlangStage stage, ShaderObjectLayoutImpl* entryPointLayout) +{ + 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); +} + +Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::translateDescriptorRangeType( + slang::BindingType c, D3D12_DESCRIPTOR_RANGE_TYPE* outType) +{ + switch (c) + { + case slang::BindingType::ConstantBuffer: + *outType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV; + return SLANG_OK; + case slang::BindingType::RawBuffer: + case slang::BindingType::Texture: + case slang::BindingType::TypedBuffer: + case slang::BindingType::RayTracingAccelerationStructure: + *outType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV; + return SLANG_OK; + case slang::BindingType::MutableRawBuffer: + case slang::BindingType::MutableTexture: + case slang::BindingType::MutableTypedBuffer: + *outType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV; + return SLANG_OK; + case slang::BindingType::Sampler: + *outType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER; + return SLANG_OK; + default: + return SLANG_FAIL; + } +} + +/// Add a new descriptor set to the layout being computed. +/// +/// Note that a "descriptor set" in the layout may amount to +/// zero, one, or two different descriptor *tables* in the +/// final D3D12 root signature. Each descriptor set may +/// contain zero or more view ranges (CBV/SRV/UAV) and zero +/// or more sampler ranges. It maps to a view descriptor table +/// if the number of view ranges is non-zero and to a sampler +/// descriptor table if the number of sampler ranges is non-zero. +/// + +uint32_t RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorSet() +{ + auto result = (uint32_t)m_descriptorSets.getCount(); + m_descriptorSets.add(DescriptorSetLayout{}); + return result; +} + +Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorRange( + Index physicalDescriptorSetIndex, + D3D12_DESCRIPTOR_RANGE_TYPE rangeType, + UINT registerIndex, + UINT spaceIndex, + UINT count, + bool isRootParameter) +{ + if (isRootParameter) + { + D3D12_ROOT_PARAMETER rootParam = {}; + switch (rangeType) + { + case D3D12_DESCRIPTOR_RANGE_TYPE_SRV: + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV; + break; + case D3D12_DESCRIPTOR_RANGE_TYPE_UAV: + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV; + break; + default: + getDebugCallback()->handleMessage( + DebugMessageType::Error, + DebugMessageSource::Layer, + "A shader parameter marked as root parameter is neither SRV nor UAV."); + return SLANG_FAIL; + } + rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL; + rootParam.Descriptor.RegisterSpace = spaceIndex; + rootParam.Descriptor.ShaderRegister = registerIndex; + m_rootParameters.add(rootParam); + return SLANG_OK; + } + + auto& descriptorSet = m_descriptorSets[physicalDescriptorSetIndex]; + + D3D12_DESCRIPTOR_RANGE range = {}; + range.RangeType = rangeType; + range.NumDescriptors = count; + range.BaseShaderRegister = registerIndex; + range.RegisterSpace = spaceIndex; + range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND; + + if (range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER) + { + descriptorSet.m_samplerRanges.add(range); + descriptorSet.m_samplerCount += range.NumDescriptors; + } + else + { + descriptorSet.m_resourceRanges.add(range); + descriptorSet.m_resourceCount += range.NumDescriptors; + } + + return SLANG_OK; +} + +/// Add one descriptor range as specified in Slang reflection information to the layout. +/// +/// The layout information is taken from `typeLayout` for the descriptor +/// range with the given `descriptorRangeIndex` within the logical +/// descriptor set (reflected by Slang) with the given `logicalDescriptorSetIndex`. +/// +/// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of +/// the descriptor set that the range should be added to. +/// +/// The `offset` encodes information about space and/or register offsets that +/// should be applied to descrptor ranges. +/// +/// This operation can fail if the given descriptor range encodes a range that +/// doesn't map to anything directly supported by D3D12. Higher-level routines +/// will often want to ignore such failures. +/// + +Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorRange( + slang::TypeLayoutReflection* typeLayout, + Index physicalDescriptorSetIndex, + BindingRegisterOffset const& containerOffset, + BindingRegisterOffset const& elementOffset, + Index logicalDescriptorSetIndex, + Index descriptorRangeIndex, + bool isRootParameter) +{ + auto bindingType = typeLayout->getDescriptorSetDescriptorRangeType( + logicalDescriptorSetIndex, descriptorRangeIndex); + auto count = typeLayout->getDescriptorSetDescriptorRangeDescriptorCount( + logicalDescriptorSetIndex, descriptorRangeIndex); + auto index = typeLayout->getDescriptorSetDescriptorRangeIndexOffset( + logicalDescriptorSetIndex, descriptorRangeIndex); + auto space = typeLayout->getDescriptorSetSpaceOffset(logicalDescriptorSetIndex); + + D3D12_DESCRIPTOR_RANGE_TYPE rangeType; + SLANG_RETURN_ON_FAIL(translateDescriptorRangeType(bindingType, &rangeType)); + + return addDescriptorRange( + physicalDescriptorSetIndex, + rangeType, + (UINT)index + elementOffset[rangeType], + (UINT)space + containerOffset.spaceOffset, + (UINT)count, + isRootParameter); +} + +/// Add one binding range to the computed layout. +/// +/// The layout information is taken from `typeLayout` for the binding +/// range with the given `bindingRangeIndex`. +/// +/// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of +/// the descriptor set that the range should be added to. +/// +/// The `offset` encodes information about space and/or register offsets that +/// should be applied to descrptor ranges. +/// +/// Note that a single binding range may encompass zero or more descriptor ranges. +/// + +void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addBindingRange( + slang::TypeLayoutReflection* typeLayout, + Index physicalDescriptorSetIndex, + BindingRegisterOffset const& containerOffset, + BindingRegisterOffset const& elementOffset, + Index bindingRangeIndex) +{ + auto logicalDescriptorSetIndex = + typeLayout->getBindingRangeDescriptorSetIndex(bindingRangeIndex); + auto firstDescriptorRangeIndex = + typeLayout->getBindingRangeFirstDescriptorRangeIndex(bindingRangeIndex); + Index descriptorRangeCount = typeLayout->getBindingRangeDescriptorRangeCount(bindingRangeIndex); + bool isRootParameter = isBindingRangeRootParameter( + m_device->slangContext.globalSession, + m_device->m_extendedDesc.rootParameterShaderAttributeName, + typeLayout, + bindingRangeIndex); + for (Index i = 0; i < descriptorRangeCount; ++i) + { + auto descriptorRangeIndex = firstDescriptorRangeIndex + i; + + // Note: we ignore the `Result` returned by `addDescriptorRange()` because we + // want to silently skip any ranges that represent kinds of bindings that + // don't actually exist in D3D12. + // + addDescriptorRange( + typeLayout, + physicalDescriptorSetIndex, + containerOffset, + elementOffset, + logicalDescriptorSetIndex, + descriptorRangeIndex, + isRootParameter); + } +} + +void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsValue( + slang::VariableLayoutReflection* varLayout, Index physicalDescriptorSetIndex) +{ + BindingRegisterOffsetPair offset(varLayout); + addAsValue(varLayout->getTypeLayout(), physicalDescriptorSetIndex, offset, offset); +} + +/// Add binding ranges and parameter blocks to the root signature. +/// +/// The layout information is taken from `typeLayout` 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. +/// +/// The `offset` encodes information about space and/or register offsets that +/// should be applied to descrptor ranges. +/// + +void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsConstantBuffer( + slang::TypeLayoutReflection* typeLayout, + Index physicalDescriptorSetIndex, + 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, + false); + } + + addAsValue(typeLayout, physicalDescriptorSetIndex, containerOffset, elementOffset); +} + +void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsValue( + slang::TypeLayoutReflection* typeLayout, + Index physicalDescriptorSetIndex, + BindingRegisterOffsetPair const& containerOffset, + BindingRegisterOffsetPair const& elementOffset) +{ + // Our first task is to add the binding ranges for stuff that is + // directly contained in `typeLayout` rather than via sub-objects. + // + // Our goal is to have the descriptors for directly-contained views/samplers + // always be contiguous in CPU and GPU memory, so that we can write + // to them easily with a single operaiton. + // + Index bindingRangeCount = typeLayout->getBindingRangeCount(); + for (Index bindingRangeIndex = 0; bindingRangeIndex < bindingRangeCount; bindingRangeIndex++) + { + // We will look at the type of each binding range and intentionally + // skip those that represent sub-objects. + // + auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex); + switch (bindingType) + { + case slang::BindingType::ConstantBuffer: + case slang::BindingType::ParameterBlock: + case slang::BindingType::ExistentialValue: + continue; + + default: + break; + } + + // 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, + containerOffset.primary, + elementOffset.primary, + bindingRangeIndex); + } + + // 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); + + auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex); + + BindingRegisterOffsetPair subObjectRangeContainerOffset = containerOffset; + subObjectRangeContainerOffset += + BindingRegisterOffsetPair(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex)); + BindingRegisterOffsetPair subObjectRangeElementOffset = elementOffset; + subObjectRangeElementOffset += + BindingRegisterOffsetPair(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex)); + + switch (bindingType) + { + case slang::BindingType::ConstantBuffer: + { + auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout(); + SLANG_ASSERT(containerVarLayout); + + auto elementVarLayout = subObjectTypeLayout->getElementVarLayout(); + SLANG_ASSERT(elementVarLayout); + + auto elementTypeLayout = elementVarLayout->getTypeLayout(); + SLANG_ASSERT(elementTypeLayout); + + BindingRegisterOffsetPair containerOffset = subObjectRangeContainerOffset; + containerOffset += BindingRegisterOffsetPair(containerVarLayout); + + BindingRegisterOffsetPair elementOffset = subObjectRangeElementOffset; + elementOffset += BindingRegisterOffsetPair(elementVarLayout); + + addAsConstantBuffer( + elementTypeLayout, physicalDescriptorSetIndex, containerOffset, elementOffset); + } + break; + + 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 = + subObjectRangeElementOffset.primary.spaceOffset; + subDescriptorSetOffset.pending.spaceOffset = + subObjectRangeElementOffset.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 = subObjectRangeElementOffset.pending; + + addAsValue( + specializedTypeLayout, + physicalDescriptorSetIndex, + pendingOffset, + pendingOffset); + } + } + break; + } + } +} + +D3D12_ROOT_SIGNATURE_DESC& RootShaderObjectLayoutImpl::RootSignatureDescBuilder::build() +{ + for (Index i = 0; i < m_descriptorSets.getCount(); i++) + { + auto& descriptorSet = m_descriptorSets[i]; + if (descriptorSet.m_resourceRanges.getCount()) + { + D3D12_ROOT_PARAMETER rootParam = {}; + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.DescriptorTable.NumDescriptorRanges = + (UINT)descriptorSet.m_resourceRanges.getCount(); + rootParam.DescriptorTable.pDescriptorRanges = + descriptorSet.m_resourceRanges.getBuffer(); + m_rootParameters.add(rootParam); + } + if (descriptorSet.m_samplerRanges.getCount()) + { + D3D12_ROOT_PARAMETER rootParam = {}; + rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE; + rootParam.DescriptorTable.NumDescriptorRanges = + (UINT)descriptorSet.m_samplerRanges.getCount(); + rootParam.DescriptorTable.pDescriptorRanges = descriptorSet.m_samplerRanges.getBuffer(); + m_rootParameters.add(rootParam); + } + } + + m_rootSignatureDesc.NumParameters = UINT(m_rootParameters.getCount()); + m_rootSignatureDesc.pParameters = m_rootParameters.getBuffer(); + + // TODO: static samplers should be reasonably easy to support... + m_rootSignatureDesc.NumStaticSamplers = 0; + m_rootSignatureDesc.pStaticSamplers = nullptr; + + // TODO: only set this flag if needed (requires creating root + // signature at same time as pipeline state...). + // + m_rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; + + return m_rootSignatureDesc; +} + +Result RootShaderObjectLayoutImpl::createRootSignatureFromSlang( + DeviceImpl* device, + RootShaderObjectLayoutImpl* rootLayout, + slang::IComponentType* program, + ID3D12RootSignature** outRootSignature, + ID3DBlob** outError) +{ + // We are going to build up the root signature by adding + // binding/descritpor ranges and nested parameter blocks + // based on the computed layout information for `program`. + // + RootSignatureDescBuilder builder(device); + auto layout = program->getLayout(); + + // The layout information computed by Slang breaks up shader + // parameters into what we can think of as "logical" descriptor + // sets based on whether or not parameters have the same `space`. + // + // We want to basically ignore that decomposition and generate a + // single descriptor set to hold all top-level parameters, and only + // generate distinct descriptor sets when the shader has opted in + // via explicit parameter blocks. + // + // To achieve this goal, we will manually allocate a default descriptor + // set for root parameters in our signature, and then recursively + // add all the binding/descriptor ranges implied by the global-scope + // parameters. + // + auto rootDescriptorSetIndex = builder.addDescriptorSet(); + builder.addAsValue(layout->getGlobalParamsVarLayout(), rootDescriptorSetIndex); + + for (SlangUInt i = 0; i < layout->getEntryPointCount(); i++) + { + // Entry-point parameters should also be added to the default root + // descriptor set. + // + // We add the parameters using the "variable layout" for the entry point + // and not just its type layout, to ensure that any offset information is + // applied correctly to the `register` and `space` information for entry-point + // parameters. + // + // Note: When we start to support DXR we will need to handle entry-point parameters + // differently because they will need to map to local root signatures rather than + // being included in the global root signature as is being done here. + // + auto entryPoint = layout->getEntryPointByIndex(i); + builder.addAsValue(entryPoint->getVarLayout(), rootDescriptorSetIndex); + } + + auto& rootSignatureDesc = builder.build(); + + ComPtr<ID3DBlob> signature; + ComPtr<ID3DBlob> error; + if (SLANG_FAILED(device->m_D3D12SerializeRootSignature( + &rootSignatureDesc, + D3D_ROOT_SIGNATURE_VERSION_1, + signature.writeRef(), + error.writeRef()))) + { + getDebugCallback()->handleMessage( + DebugMessageType::Error, + DebugMessageSource::Layer, + "error: D3D12SerializeRootSignature failed"); + if (error) + { + getDebugCallback()->handleMessage( + DebugMessageType::Error, + DebugMessageSource::Driver, + (const char*)error->GetBufferPointer()); + if (outError) + returnComPtr(outError, error); + } + return SLANG_FAIL; + } + + SLANG_RETURN_ON_FAIL(device->m_device->CreateRootSignature( + 0, + signature->GetBufferPointer(), + signature->GetBufferSize(), + IID_PPV_ARGS(outRootSignature))); + return SLANG_OK; +} + +Result RootShaderObjectLayoutImpl::create( + DeviceImpl* device, + slang::IComponentType* program, + slang::ProgramLayout* programLayout, + RootShaderObjectLayoutImpl** outLayout, + ID3DBlob** outError) +{ + RootShaderObjectLayoutImpl::Builder builder(device, program, programLayout); + builder.addGlobalParams(programLayout->getGlobalParamsVarLayout()); + + SlangInt entryPointCount = programLayout->getEntryPointCount(); + for (SlangInt e = 0; e < entryPointCount; ++e) + { + auto slangEntryPoint = programLayout->getEntryPointByIndex(e); + RefPtr<ShaderObjectLayoutImpl> entryPointLayout; + SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType( + device, slangEntryPoint->getTypeLayout(), entryPointLayout.writeRef())); + builder.addEntryPoint(slangEntryPoint->getStage(), entryPointLayout); + } + + RefPtr<RootShaderObjectLayoutImpl> layout; + SLANG_RETURN_ON_FAIL(builder.build(layout.writeRef())); + + if (program->getSpecializationParamCount() == 0) + { + // For root object, we would like know the union of all binding slots + // including all sub-objects in the shader-object hierarchy, so at + // parameter binding time we can easily know how many GPU descriptor tables + // to create without walking through the shader-object hierarchy again. + // We build out this array along with root signature construction and store + // it in `m_gpuDescriptorSetInfos`. + SLANG_RETURN_ON_FAIL(createRootSignatureFromSlang( + device, layout, program, layout->m_rootSignature.writeRef(), outError)); + } + + *outLayout = layout.detach(); + + return SLANG_OK; +} + +Result RootShaderObjectLayoutImpl::init(Builder* builder) +{ + auto renderer = builder->m_renderer; + + SLANG_RETURN_ON_FAIL(Super::init(builder)); + + m_program = builder->m_program; + m_programLayout = builder->m_programLayout; + m_entryPoints = builder->m_entryPoints; + return SLANG_OK; +} + +} // namespace d3d12 +} // namespace gfx |
