yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Yong HeFix attribute reflection. (#5823)941f07040

master
42.3 KiB1067 linesraw
1// d3d12-shader-object-layout.cpp
2#include "d3d12-shader-object-layout.h"
3
4#include "d3d12-device.h"
5
6namespace gfx
7{
8namespace d3d12
9{
10
11using namespace Slang;
12
13ShaderObjectLayoutImpl::SubObjectRangeOffset::SubObjectRangeOffset(
14    slang::VariableLayoutReflection* varLayout)
15{
16    if (auto pendingLayout = varLayout->getPendingDataLayout())
17    {
18        pendingOrdinaryData = (uint32_t)pendingLayout->getOffset(SLANG_PARAMETER_CATEGORY_UNIFORM);
19    }
20}
21
22ShaderObjectLayoutImpl::SubObjectRangeStride::SubObjectRangeStride(
23    slang::TypeLayoutReflection* typeLayout)
24{
25    if (auto pendingLayout = typeLayout->getPendingDataTypeLayout())
26    {
27        pendingOrdinaryData = (uint32_t)pendingLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
28    }
29}
30
31bool ShaderObjectLayoutImpl::isBindingRangeRootParameter(
32    SlangSession* globalSession,
33    const char* rootParameterAttributeName,
34    slang::TypeLayoutReflection* typeLayout,
35    Index bindingRangeIndex)
36{
37    bool isRootParameter = false;
38    if (rootParameterAttributeName)
39    {
40        if (auto leafVariable = typeLayout->getBindingRangeLeafVariable(bindingRangeIndex))
41        {
42            if (leafVariable->findAttributeByName(globalSession, rootParameterAttributeName))
43            {
44                isRootParameter = true;
45            }
46        }
47    }
48    return isRootParameter;
49}
50
51Result ShaderObjectLayoutImpl::createForElementType(
52    RendererBase* renderer,
53    slang::ISession* session,
54    slang::TypeLayoutReflection* elementType,
55    ShaderObjectLayoutImpl** outLayout)
56{
57    Builder builder(renderer, session);
58    builder.setElementTypeLayout(elementType);
59    return builder.build(outLayout);
60}
61
62Result ShaderObjectLayoutImpl::init(Builder* builder)
63{
64    auto renderer = builder->m_renderer;
65
66    initBase(renderer, builder->m_session, builder->m_elementTypeLayout);
67
68    m_containerType = builder->m_containerType;
69
70    m_bindingRanges = _Move(builder->m_bindingRanges);
71    m_subObjectRanges = _Move(builder->m_subObjectRanges);
72    m_rootParamsInfo = _Move(builder->m_rootParamsInfo);
73
74    m_ownCounts = builder->m_ownCounts;
75    m_totalCounts = builder->m_totalCounts;
76    m_subObjectCount = builder->m_subObjectCount;
77    m_childRootParameterCount = builder->m_childRootParameterCount;
78    m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
79
80    return SLANG_OK;
81}
82
83Result ShaderObjectLayoutImpl::Builder::setElementTypeLayout(
84    slang::TypeLayoutReflection* typeLayout)
85{
86    typeLayout = _unwrapParameterGroups(typeLayout, m_containerType);
87    m_elementTypeLayout = typeLayout;
88
89    // If the type contains any ordinary data, then we must reserve a buffer
90    // descriptor to hold it when binding as a parameter block.
91    //
92    m_totalOrdinaryDataSize = (uint32_t)typeLayout->getSize();
93    if (m_totalOrdinaryDataSize != 0)
94    {
95        m_ownCounts.resource++;
96    }
97
98    // We will scan over the reflected Slang binding ranges and add them
99    // to our array. There are two main things we compute along the way:
100    //
101    // * For each binding range we compute a `flatIndex` that can be
102    //   used to identify where the values for the given range begin
103    //   in the flattened arrays (e.g., `m_objects`) and descriptor
104    //   tables that hold the state of a shader object.
105    //
106    // * We also update the various counters taht keep track of the number
107    //   of sub-objects, resources, samplers, etc. that are being
108    //   consumed. These counters will contribute to figuring out
109    //   the descriptor table(s) that might be needed to represent
110    //   the object.
111    //
112    SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
113    for (SlangInt r = 0; r < bindingRangeCount; ++r)
114    {
115        slang::BindingType slangBindingType = typeLayout->getBindingRangeType(r);
116        uint32_t count = (uint32_t)typeLayout->getBindingRangeBindingCount(r);
117        slang::TypeLayoutReflection* slangLeafTypeLayout =
118            typeLayout->getBindingRangeLeafTypeLayout(r);
119
120        BindingRangeInfo bindingRangeInfo = {};
121        bindingRangeInfo.bindingType = slangBindingType;
122        bindingRangeInfo.resourceShape = slangLeafTypeLayout->getResourceShape();
123        bindingRangeInfo.count = count;
124        bindingRangeInfo.isRootParameter = isBindingRangeRootParameter(
125            m_renderer->slangContext.globalSession,
126            static_cast<DeviceImpl*>(m_renderer)->m_extendedDesc.rootParameterShaderAttributeName,
127            typeLayout,
128            r);
129        bindingRangeInfo.isSpecializable = typeLayout->isBindingRangeSpecializable(r);
130        switch (slangBindingType)
131        {
132        case slang::BindingType::RawBuffer:
133        case slang::BindingType::TypedBuffer:
134        case slang::BindingType::MutableRawBuffer:
135        case slang::BindingType::MutableTypedBuffer:
136            {
137                auto bufferElementType = slangLeafTypeLayout->getElementTypeLayout();
138                if (bufferElementType)
139                {
140                    bindingRangeInfo.bufferElementStride = (uint32_t)bufferElementType->getStride();
141                }
142            }
143            break;
144        }
145        if (bindingRangeInfo.isRootParameter)
146        {
147            RootParameterInfo rootInfo = {};
148            switch (slangBindingType)
149            {
150            case slang::BindingType::RayTracingAccelerationStructure:
151                rootInfo.type = IResourceView::Type::AccelerationStructure;
152                break;
153            case slang::BindingType::RawBuffer:
154            case slang::BindingType::TypedBuffer:
155                rootInfo.type = IResourceView::Type::ShaderResource;
156                break;
157            case slang::BindingType::MutableRawBuffer:
158            case slang::BindingType::MutableTypedBuffer:
159                rootInfo.type = IResourceView::Type::UnorderedAccess;
160                break;
161            }
162            bindingRangeInfo.baseIndex = (uint32_t)m_rootParamsInfo.getCount();
163            for (uint32_t i = 0; i < count; i++)
164            {
165                m_rootParamsInfo.add(rootInfo);
166            }
167        }
168        else
169        {
170            switch (slangBindingType)
171            {
172            case slang::BindingType::ConstantBuffer:
173            case slang::BindingType::ParameterBlock:
174            case slang::BindingType::ExistentialValue:
175                bindingRangeInfo.baseIndex = m_subObjectCount;
176                bindingRangeInfo.subObjectIndex = m_subObjectCount;
177                m_subObjectCount += count;
178                break;
179            case slang::BindingType::RawBuffer:
180            case slang::BindingType::MutableRawBuffer:
181                if (slangLeafTypeLayout->getType()->getElementType() != nullptr)
182                {
183                    // A structured buffer occupies both a resource slot and
184                    // a sub-object slot.
185                    bindingRangeInfo.subObjectIndex = m_subObjectCount;
186                    m_subObjectCount += count;
187                }
188                bindingRangeInfo.baseIndex = m_ownCounts.resource;
189                m_ownCounts.resource += count;
190                break;
191            case slang::BindingType::Sampler:
192                bindingRangeInfo.baseIndex = m_ownCounts.sampler;
193                m_ownCounts.sampler += count;
194                break;
195
196            case slang::BindingType::CombinedTextureSampler:
197                // TODO: support this case...
198                break;
199
200            case slang::BindingType::VaryingInput:
201            case slang::BindingType::VaryingOutput:
202                break;
203
204            default:
205                bindingRangeInfo.baseIndex = m_ownCounts.resource;
206                m_ownCounts.resource += count;
207                break;
208            }
209        }
210        m_bindingRanges.add(bindingRangeInfo);
211    }
212
213    // At this point we've computed the number of resources/samplers that
214    // the type needs to represent its *own* state, and stored those counts
215    // in `m_ownCounts`. Next we need to consider any resources/samplers
216    // and root parameters needed to represent the state of the transitive
217    // sub-objects of this objet, so that we can compute the total size
218    // of the object when bound to the pipeline.
219
220    m_totalCounts = m_ownCounts;
221
222    SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount();
223    for (SlangInt r = 0; r < subObjectRangeCount; ++r)
224    {
225        SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
226        auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
227        auto count = (uint32_t)typeLayout->getBindingRangeBindingCount(bindingRangeIndex);
228        slang::TypeLayoutReflection* slangLeafTypeLayout =
229            typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
230
231        // A sub-object range can either represent a sub-object of a known
232        // type, like a `ConstantBuffer<Foo>` or `ParameterBlock<Foo>`
233        // (in which case we can pre-compute a layout to use, based on
234        // the type `Foo`) *or* it can represent a sub-object of some
235        // existential type (e.g., `IBar`) in which case we cannot
236        // know the appropraite type/layout of sub-object to allocate.
237        //
238        RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
239        if (slangBindingType == slang::BindingType::ExistentialValue)
240        {
241            if (auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
242            {
243                createForElementType(
244                    m_renderer,
245                    m_session,
246                    pendingTypeLayout,
247                    subObjectLayout.writeRef());
248            }
249        }
250        else
251        {
252            createForElementType(
253                m_renderer,
254                m_session,
255                slangLeafTypeLayout->getElementTypeLayout(),
256                subObjectLayout.writeRef());
257        }
258
259        SubObjectRangeInfo subObjectRange;
260        subObjectRange.bindingRangeIndex = bindingRangeIndex;
261        subObjectRange.layout = subObjectLayout;
262
263        // The Slang reflection API stors offset information for sub-object ranges,
264        // and we care about *some* of that information: in particular, we need
265        // the offset of sub-objects in terms of uniform/ordinary data for the
266        // cases where we need to fill in "pending" data in our ordinary buffer.
267        //
268        subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
269        subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
270
271        // The remaining offset information is computed based on the counters
272        // we are generating here, which depend only on the in-memory layout
273        // decisions being made in our implementation. Remember that the
274        // `register` and `space` values coming from DXBC/DXIL do *not*
275        // dictate the in-memory layout we use.
276        //
277        // Note: One subtle point here is that the `.rootParam` offset we are computing
278        // here does *not* include any root parameters that would be allocated
279        // for the parent object type itself (e.g., for descriptor tables
280        // used if it were bound as a parameter block). The later logic when
281        // we actually go to bind things will need to apply those offsets.
282        //
283        // Note: An even *more* subtle point is that the `.resource` offset
284        // being computed here *does* include the resource descriptor allocated
285        // for holding the ordinary data buffer, if any. The implications of
286        // this for later offset math is subtle.
287        //
288        subObjectRange.offset.rootParam = m_childRootParameterCount;
289        subObjectRange.offset.resource = m_totalCounts.resource;
290        subObjectRange.offset.sampler = m_totalCounts.sampler;
291
292        // Along with the offset information, we also need to compute the
293        // "stride" between consecutive sub-objects in the range. The actual
294        // size/stride of a single object depends on the type of range we
295        // are dealing with.
296        //
297        BindingOffset objectCounts;
298        switch (slangBindingType)
299        {
300        default:
301            {
302                // We only treat buffers of interface types as actual sub-object binding
303                // range.
304                auto bindingRangeTypeLayout =
305                    typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
306                if (!bindingRangeTypeLayout)
307                    continue;
308                auto elementType = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex)
309                                       ->getElementTypeLayout();
310                if (!elementType)
311                    continue;
312                if (elementType->getKind() != slang::TypeReflection::Kind::Interface)
313                {
314                    continue;
315                }
316            }
317            break;
318
319        case slang::BindingType::ConstantBuffer:
320            {
321                SLANG_ASSERT(subObjectLayout);
322
323                // The resource and sampler descriptors of a nested
324                // constant buffer will "leak" into those of the
325                // parent type, and we need to account for them
326                // whenever we allocate storage.
327                //
328                objectCounts.resource = subObjectLayout->getTotalResourceDescriptorCount();
329                objectCounts.sampler = subObjectLayout->getTotalSamplerDescriptorCount();
330                objectCounts.rootParam = subObjectRange.layout->getChildRootParameterCount();
331            }
332            break;
333
334        case slang::BindingType::ParameterBlock:
335            {
336                SLANG_ASSERT(subObjectLayout);
337
338                // In contrast to a constant buffer, a parameter block can hide
339                // the resource and sampler descriptor allocation it uses (since they
340                // are allocated into the tables that make up the parameter block.
341                //
342                // The only resource usage that leaks into the surrounding context
343                // is the number of root parameters consumed.
344                //
345                objectCounts.rootParam = subObjectRange.layout->getTotalRootTableParameterCount();
346            }
347            break;
348
349        case slang::BindingType::ExistentialValue:
350            // An unspecialized existential/interface value cannot consume any resources
351            // as part of the parent object (it needs to fit inside the fixed-size
352            // represnetation of existential types).
353            //
354            // However, if we are statically specializing to a type that doesn't "fit"
355            // we may need to account for additional information that needs to be
356            // allocaated.
357            //
358            if (subObjectLayout)
359            {
360                // The ordinary data for an existential-type value is allocated into
361                // the same buffer as the parent object, so we only want to consider
362                // the resource descriptors *other than* the ordinary data buffer.
363                //
364                // Otherwise the logic here is identical to the constant buffer case.
365                //
366                objectCounts.resource =
367                    subObjectLayout->getTotalResourceDescriptorCountWithoutOrdinaryDataBuffer();
368                objectCounts.sampler = subObjectLayout->getTotalSamplerDescriptorCount();
369                objectCounts.rootParam = subObjectRange.layout->getChildRootParameterCount();
370
371                // Note: In the implementation for some other graphics API (e.g.,
372                // Vulkan) there needs to be more work done to handle the fact that
373                // "pending" data from interface-type sub-objects get allocated to a
374                // distinct offset after all the "primary" data. We are consciously
375                // ignoring that issue here, and the physical layout of a shader object
376                // into the D3D12 binding state may end up interleaving
377                // resources/samplers for "primary" and "pending" data.
378                //
379                // If this choice ever causes issues, we can revisit the approach here.
380
381                // An interface-type range that includes ordinary data can
382                // increase the size of the ordinary data buffer we need to
383                // allocate for the parent object.
384                //
385                uint32_t ordinaryDataEnd =
386                    subObjectRange.offset.pendingOrdinaryData +
387                    (uint32_t)count * subObjectRange.stride.pendingOrdinaryData;
388
389                if (ordinaryDataEnd > m_totalOrdinaryDataSize)
390                {
391                    m_totalOrdinaryDataSize = ordinaryDataEnd;
392                }
393            }
394            break;
395        }
396
397        // Once we've computed the usage for each object in the range, we can
398        // easily compute the usage for the entire range.
399        //
400        auto rangeResourceCount = count * objectCounts.resource;
401        auto rangeSamplerCount = count * objectCounts.sampler;
402        auto rangeRootParamCount = count * objectCounts.rootParam;
403
404        m_totalCounts.resource += rangeResourceCount;
405        m_totalCounts.sampler += rangeSamplerCount;
406        m_childRootParameterCount += rangeRootParamCount;
407
408        m_subObjectRanges.add(subObjectRange);
409    }
410
411    // Once we have added up the resource usage from all the sub-objects
412    // we can look at the total number of resources and samplers that
413    // need to be bound as part of this objects descriptor tables and
414    // that will allow us to decide whether we need to allocate a root
415    // parameter for a resource table or not, ans similarly for a
416    // sampler table.
417    //
418    if (m_totalCounts.resource)
419        m_ownCounts.rootParam++;
420    if (m_totalCounts.sampler)
421        m_ownCounts.rootParam++;
422
423    m_totalCounts.rootParam = m_ownCounts.rootParam + m_childRootParameterCount;
424
425    return SLANG_OK;
426}
427
428Result ShaderObjectLayoutImpl::Builder::build(ShaderObjectLayoutImpl** outLayout)
429{
430    auto layout = RefPtr<ShaderObjectLayoutImpl>(new ShaderObjectLayoutImpl());
431    SLANG_RETURN_ON_FAIL(layout->init(this));
432
433    returnRefPtrMove(outLayout, layout);
434    return SLANG_OK;
435}
436
437Result RootShaderObjectLayoutImpl::Builder::build(RootShaderObjectLayoutImpl** outLayout)
438{
439    RefPtr<RootShaderObjectLayoutImpl> layout = new RootShaderObjectLayoutImpl();
440    SLANG_RETURN_ON_FAIL(layout->init(this));
441
442    returnRefPtrMove(outLayout, layout);
443    return SLANG_OK;
444}
445
446void RootShaderObjectLayoutImpl::Builder::addGlobalParams(
447    slang::VariableLayoutReflection* globalsLayout)
448{
449    setElementTypeLayout(globalsLayout->getTypeLayout());
450}
451
452void RootShaderObjectLayoutImpl::Builder::addEntryPoint(
453    SlangStage stage,
454    ShaderObjectLayoutImpl* entryPointLayout)
455{
456    EntryPointInfo info;
457    info.layout = entryPointLayout;
458
459    info.offset.resource = m_totalCounts.resource;
460    info.offset.sampler = m_totalCounts.sampler;
461    info.offset.rootParam = m_childRootParameterCount;
462
463    m_totalCounts.resource += entryPointLayout->getTotalResourceDescriptorCount();
464    m_totalCounts.sampler += entryPointLayout->getTotalSamplerDescriptorCount();
465
466    // TODO(tfoley): Check this to make sure it is reasonable...
467    m_childRootParameterCount += entryPointLayout->getChildRootParameterCount();
468
469    m_entryPoints.add(info);
470}
471
472Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::translateDescriptorRangeType(
473    slang::BindingType c,
474    D3D12_DESCRIPTOR_RANGE_TYPE* outType)
475{
476    switch (c)
477    {
478    case slang::BindingType::ConstantBuffer:
479        *outType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
480        return SLANG_OK;
481    case slang::BindingType::RawBuffer:
482    case slang::BindingType::Texture:
483    case slang::BindingType::TypedBuffer:
484    case slang::BindingType::RayTracingAccelerationStructure:
485        *outType = D3D12_DESCRIPTOR_RANGE_TYPE_SRV;
486        return SLANG_OK;
487    case slang::BindingType::MutableRawBuffer:
488    case slang::BindingType::MutableTexture:
489    case slang::BindingType::MutableTypedBuffer:
490        *outType = D3D12_DESCRIPTOR_RANGE_TYPE_UAV;
491        return SLANG_OK;
492    case slang::BindingType::Sampler:
493        *outType = D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER;
494        return SLANG_OK;
495    default:
496        return SLANG_FAIL;
497    }
498}
499
500/// Add a new descriptor set to the layout being computed.
501///
502/// Note that a "descriptor set" in the layout may amount to
503/// zero, one, or two different descriptor *tables* in the
504/// final D3D12 root signature. Each descriptor set may
505/// contain zero or more view ranges (CBV/SRV/UAV) and zero
506/// or more sampler ranges. It maps to a view descriptor table
507/// if the number of view ranges is non-zero and to a sampler
508/// descriptor table if the number of sampler ranges is non-zero.
509///
510
511uint32_t RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorSet()
512{
513    auto result = (uint32_t)m_descriptorSets.getCount();
514    m_descriptorSets.add(DescriptorSetLayout{});
515    return result;
516}
517
518Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorRange(
519    Index physicalDescriptorSetIndex,
520    D3D12_DESCRIPTOR_RANGE_TYPE rangeType,
521    UINT registerIndex,
522    UINT spaceIndex,
523    UINT count,
524    bool isRootParameter)
525{
526    if (isRootParameter)
527    {
528        D3D12_ROOT_PARAMETER1 rootParam = {};
529        switch (rangeType)
530        {
531        case D3D12_DESCRIPTOR_RANGE_TYPE_SRV:
532            rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_SRV;
533            break;
534        case D3D12_DESCRIPTOR_RANGE_TYPE_UAV:
535            rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_UAV;
536            break;
537        default:
538            getDebugCallback()->handleMessage(
539                DebugMessageType::Error,
540                DebugMessageSource::Layer,
541                "A shader parameter marked as root parameter is neither SRV nor UAV.");
542            return SLANG_FAIL;
543        }
544        rootParam.ShaderVisibility = D3D12_SHADER_VISIBILITY_ALL;
545        rootParam.Descriptor.RegisterSpace = spaceIndex;
546        rootParam.Descriptor.ShaderRegister = registerIndex;
547        m_rootParameters.add(rootParam);
548        return SLANG_OK;
549    }
550
551    auto& descriptorSet = m_descriptorSets[physicalDescriptorSetIndex];
552
553    D3D12_DESCRIPTOR_RANGE1 range = {};
554    range.RangeType = rangeType;
555    range.NumDescriptors = count;
556    range.BaseShaderRegister = registerIndex;
557    range.RegisterSpace = spaceIndex;
558    range.OffsetInDescriptorsFromTableStart = D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND;
559
560    if (range.RangeType == D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER)
561    {
562        descriptorSet.m_samplerRanges.add(range);
563        descriptorSet.m_samplerCount += range.NumDescriptors;
564    }
565    else
566    {
567        descriptorSet.m_resourceRanges.add(range);
568        descriptorSet.m_resourceCount += range.NumDescriptors;
569    }
570
571    return SLANG_OK;
572}
573
574/// Add one descriptor range as specified in Slang reflection information to the layout.
575///
576/// The layout information is taken from `typeLayout` for the descriptor
577/// range with the given `descriptorRangeIndex` within the logical
578/// descriptor set (reflected by Slang) with the given `logicalDescriptorSetIndex`.
579///
580/// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of
581/// the descriptor set that the range should be added to.
582///
583/// The `offset` encodes information about space and/or register offsets that
584/// should be applied to descrptor ranges.
585///
586/// This operation can fail if the given descriptor range encodes a range that
587/// doesn't map to anything directly supported by D3D12. Higher-level routines
588/// will often want to ignore such failures.
589///
590
591Result RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addDescriptorRange(
592    slang::TypeLayoutReflection* typeLayout,
593    Index physicalDescriptorSetIndex,
594    BindingRegisterOffset const& containerOffset,
595    BindingRegisterOffset const& elementOffset,
596    Index logicalDescriptorSetIndex,
597    Index descriptorRangeIndex,
598    bool isRootParameter)
599{
600    auto bindingType = typeLayout->getDescriptorSetDescriptorRangeType(
601        logicalDescriptorSetIndex,
602        descriptorRangeIndex);
603    auto count = typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
604        logicalDescriptorSetIndex,
605        descriptorRangeIndex);
606    auto index = typeLayout->getDescriptorSetDescriptorRangeIndexOffset(
607        logicalDescriptorSetIndex,
608        descriptorRangeIndex);
609    auto space = typeLayout->getDescriptorSetSpaceOffset(logicalDescriptorSetIndex);
610
611    D3D12_DESCRIPTOR_RANGE_TYPE rangeType;
612    SLANG_RETURN_ON_FAIL(translateDescriptorRangeType(bindingType, &rangeType));
613
614    return addDescriptorRange(
615        physicalDescriptorSetIndex,
616        rangeType,
617        (UINT)index + elementOffset[rangeType],
618        (UINT)space + elementOffset.spaceOffset,
619        (UINT)count,
620        isRootParameter);
621}
622
623/// Add one binding range to the computed layout.
624///
625/// The layout information is taken from `typeLayout` for the binding
626/// range with the given `bindingRangeIndex`.
627///
628/// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of
629/// the descriptor set that the range should be added to.
630///
631/// The `offset` encodes information about space and/or register offsets that
632/// should be applied to descrptor ranges.
633///
634/// Note that a single binding range may encompass zero or more descriptor ranges.
635///
636
637void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addBindingRange(
638    slang::TypeLayoutReflection* typeLayout,
639    Index physicalDescriptorSetIndex,
640    BindingRegisterOffset const& containerOffset,
641    BindingRegisterOffset const& elementOffset,
642    Index bindingRangeIndex)
643{
644    auto logicalDescriptorSetIndex =
645        typeLayout->getBindingRangeDescriptorSetIndex(bindingRangeIndex);
646    auto firstDescriptorRangeIndex =
647        typeLayout->getBindingRangeFirstDescriptorRangeIndex(bindingRangeIndex);
648    Index descriptorRangeCount = typeLayout->getBindingRangeDescriptorRangeCount(bindingRangeIndex);
649    bool isRootParameter = isBindingRangeRootParameter(
650        m_device->slangContext.globalSession,
651        m_device->m_extendedDesc.rootParameterShaderAttributeName,
652        typeLayout,
653        bindingRangeIndex);
654    for (Index i = 0; i < descriptorRangeCount; ++i)
655    {
656        auto descriptorRangeIndex = firstDescriptorRangeIndex + i;
657
658        // Note: we ignore the `Result` returned by `addDescriptorRange()` because we
659        // want to silently skip any ranges that represent kinds of bindings that
660        // don't actually exist in D3D12.
661        //
662        addDescriptorRange(
663            typeLayout,
664            physicalDescriptorSetIndex,
665            containerOffset,
666            elementOffset,
667            logicalDescriptorSetIndex,
668            descriptorRangeIndex,
669            isRootParameter);
670    }
671}
672
673void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsValue(
674    slang::VariableLayoutReflection* varLayout,
675    Index physicalDescriptorSetIndex)
676{
677    BindingRegisterOffsetPair offset(varLayout);
678    auto elementOffset = offset;
679    elementOffset.primary.spaceOffset = 0;
680    elementOffset.pending.spaceOffset = 0;
681    addAsValue(varLayout->getTypeLayout(), physicalDescriptorSetIndex, offset, elementOffset);
682}
683
684/// Add binding ranges and parameter blocks to the root signature.
685///
686/// The layout information is taken from `typeLayout` which should
687/// be a layout for either a program or an entry point.
688///
689/// The `physicalDescriptorSetIndex` is the index in the `m_descriptorSets` array of
690/// the descriptor set that binding ranges not belonging to nested
691/// parameter blocks should be added to.
692///
693/// The `offsetForChildrenThatNeedNewSpace` and `offsetForOrdinaryChildren` parameters
694/// encode information about space and/or register offsets that should be applied to
695/// descrptor ranges. `offsetForChildrenThatNeedNewSpace` will contain a space offset
696/// for children that requires a new space, such as a ParameterBlock.
697/// `offsetForOrdinaryChildren` contains the space for all direct children that should
698/// be placed in.
699///
700
701void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsConstantBuffer(
702    slang::TypeLayoutReflection* typeLayout,
703    Index physicalDescriptorSetIndex,
704    BindingRegisterOffsetPair offsetForChildrenThatNeedNewSpace,
705    BindingRegisterOffsetPair offsetForOrdinaryChildren)
706{
707    if (typeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0)
708    {
709        auto descriptorRangeType = D3D12_DESCRIPTOR_RANGE_TYPE_CBV;
710        auto& offsetForRangeType =
711            offsetForOrdinaryChildren.primary.offsetForRangeType[descriptorRangeType];
712        addDescriptorRange(
713            physicalDescriptorSetIndex,
714            descriptorRangeType,
715            offsetForRangeType,
716            offsetForOrdinaryChildren.primary.spaceOffset,
717            1,
718            false);
719        offsetForRangeType++;
720    }
721
722    addAsValue(
723        typeLayout,
724        physicalDescriptorSetIndex,
725        offsetForChildrenThatNeedNewSpace,
726        offsetForOrdinaryChildren);
727}
728
729void RootShaderObjectLayoutImpl::RootSignatureDescBuilder::addAsValue(
730    slang::TypeLayoutReflection* typeLayout,
731    Index physicalDescriptorSetIndex,
732    BindingRegisterOffsetPair containerOffset,
733    BindingRegisterOffsetPair elementOffset)
734{
735    // Our first task is to add the binding ranges for stuff that is
736    // directly contained in `typeLayout` rather than via sub-objects.
737    //
738    // Our goal is to have the descriptors for directly-contained views/samplers
739    // always be contiguous in CPU and GPU memory, so that we can write
740    // to them easily with a single operaiton.
741    //
742    Index bindingRangeCount = typeLayout->getBindingRangeCount();
743    for (Index bindingRangeIndex = 0; bindingRangeIndex < bindingRangeCount; bindingRangeIndex++)
744    {
745        // We will look at the type of each binding range and intentionally
746        // skip those that represent sub-objects.
747        //
748        auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
749        switch (bindingType)
750        {
751        case slang::BindingType::ConstantBuffer:
752        case slang::BindingType::ParameterBlock:
753        case slang::BindingType::ExistentialValue:
754            continue;
755
756        default:
757            break;
758        }
759
760        // For binding ranges that don't represent sub-objects, we will add
761        // all of the descriptor ranges they encompass to the root signature.
762        //
763        addBindingRange(
764            typeLayout,
765            physicalDescriptorSetIndex,
766            containerOffset.primary,
767            elementOffset.primary,
768            bindingRangeIndex);
769    }
770
771    // Next we need to recursively include everything bound via sub-objects
772    Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
773    for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount;
774         subObjectRangeIndex++)
775    {
776        auto bindingRangeIndex =
777            typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
778        auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
779
780        auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
781
782        BindingRegisterOffsetPair subObjectRangeContainerOffset = containerOffset;
783        subObjectRangeContainerOffset +=
784            BindingRegisterOffsetPair(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
785        BindingRegisterOffsetPair subObjectRangeElementOffset = elementOffset;
786        subObjectRangeElementOffset +=
787            BindingRegisterOffsetPair(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
788        subObjectRangeElementOffset.primary.spaceOffset = elementOffset.primary.spaceOffset;
789        subObjectRangeElementOffset.pending.spaceOffset = elementOffset.pending.spaceOffset;
790
791        switch (bindingType)
792        {
793        case slang::BindingType::ConstantBuffer:
794            {
795                auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
796                SLANG_ASSERT(containerVarLayout);
797
798                auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
799                SLANG_ASSERT(elementVarLayout);
800
801                auto elementTypeLayout = elementVarLayout->getTypeLayout();
802                SLANG_ASSERT(elementTypeLayout);
803
804                BindingRegisterOffsetPair containerOffset = subObjectRangeContainerOffset;
805                containerOffset += BindingRegisterOffsetPair(containerVarLayout);
806
807                BindingRegisterOffsetPair elementOffset = subObjectRangeElementOffset;
808                elementOffset += BindingRegisterOffsetPair(elementVarLayout);
809
810                addAsConstantBuffer(
811                    elementTypeLayout,
812                    physicalDescriptorSetIndex,
813                    containerOffset,
814                    elementOffset);
815            }
816            break;
817
818        case slang::BindingType::ParameterBlock:
819            {
820                auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
821                SLANG_ASSERT(containerVarLayout);
822
823                auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
824                SLANG_ASSERT(elementVarLayout);
825
826                auto elementTypeLayout = elementVarLayout->getTypeLayout();
827                SLANG_ASSERT(elementTypeLayout);
828
829                BindingRegisterOffsetPair subDescriptorSetOffset;
830                subDescriptorSetOffset.primary.spaceOffset =
831                    subObjectRangeContainerOffset.primary.spaceOffset;
832                subDescriptorSetOffset.pending.spaceOffset =
833                    subObjectRangeContainerOffset.pending.spaceOffset;
834
835                auto subPhysicalDescriptorSetIndex = addDescriptorSet();
836
837                // We recursively call `addAsConstantBuffer` to actually generate
838                // the root signature bindings for children in the parameter block.
839                // We must compute `containerOffset`, which include a space offset
840                // that any sub ParameterBlocks should start from, and `elementOffset`
841                // that encodes the space offset of the current parameter block.
842                // The space offset of the current parameter block can be obtained from the
843                // `containerVarLayout`, and the space offset of any sub ParameterBlocks
844                // are obatined from `elementVarLayout`.
845                BindingRegisterOffsetPair offsetForChildrenThatNeedNewSpace =
846                    subDescriptorSetOffset;
847                offsetForChildrenThatNeedNewSpace += BindingRegisterOffsetPair(elementVarLayout);
848                BindingRegisterOffsetPair offsetForOrindaryChildren = subDescriptorSetOffset;
849                offsetForOrindaryChildren += BindingRegisterOffsetPair(containerVarLayout);
850
851                addAsConstantBuffer(
852                    elementTypeLayout,
853                    subPhysicalDescriptorSetIndex,
854                    offsetForChildrenThatNeedNewSpace,
855                    offsetForOrindaryChildren);
856            }
857            break;
858
859        case slang::BindingType::ExistentialValue:
860            {
861                // Any nested binding ranges in the sub-object will "leak" into the
862                // binding ranges for the surrounding context.
863                //
864                auto specializedTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout();
865                if (specializedTypeLayout)
866                {
867                    BindingRegisterOffsetPair pendingOffset;
868                    pendingOffset.primary = subObjectRangeElementOffset.pending;
869
870                    addAsValue(
871                        specializedTypeLayout,
872                        physicalDescriptorSetIndex,
873                        pendingOffset,
874                        pendingOffset);
875                }
876            }
877            break;
878        }
879    }
880}
881
882D3D12_ROOT_SIGNATURE_DESC1& RootShaderObjectLayoutImpl::RootSignatureDescBuilder::build()
883{
884    for (Index i = 0; i < m_descriptorSets.getCount(); i++)
885    {
886        auto& descriptorSet = m_descriptorSets[i];
887        if (descriptorSet.m_resourceRanges.getCount())
888        {
889            D3D12_ROOT_PARAMETER1 rootParam = {};
890            rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
891            rootParam.DescriptorTable.NumDescriptorRanges =
892                (UINT)descriptorSet.m_resourceRanges.getCount();
893            rootParam.DescriptorTable.pDescriptorRanges =
894                descriptorSet.m_resourceRanges.getBuffer();
895            m_rootParameters.add(rootParam);
896        }
897        if (descriptorSet.m_samplerRanges.getCount())
898        {
899            D3D12_ROOT_PARAMETER1 rootParam = {};
900            rootParam.ParameterType = D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE;
901            rootParam.DescriptorTable.NumDescriptorRanges =
902                (UINT)descriptorSet.m_samplerRanges.getCount();
903            rootParam.DescriptorTable.pDescriptorRanges = descriptorSet.m_samplerRanges.getBuffer();
904            m_rootParameters.add(rootParam);
905        }
906    }
907
908    m_rootSignatureDesc.NumParameters = UINT(m_rootParameters.getCount());
909    m_rootSignatureDesc.pParameters = m_rootParameters.getBuffer();
910
911    // TODO: static samplers should be reasonably easy to support...
912    m_rootSignatureDesc.NumStaticSamplers = 0;
913    m_rootSignatureDesc.pStaticSamplers = nullptr;
914
915    // TODO: only set this flag if needed (requires creating root
916    // signature at same time as pipeline state...).
917    //
918    m_rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
919
920    return m_rootSignatureDesc;
921}
922
923Result RootShaderObjectLayoutImpl::createRootSignatureFromSlang(
924    DeviceImpl* device,
925    RootShaderObjectLayoutImpl* rootLayout,
926    slang::IComponentType* program,
927    ID3D12RootSignature** outRootSignature,
928    ID3DBlob** outError)
929{
930    // We are going to build up the root signature by adding
931    // binding/descritpor ranges and nested parameter blocks
932    // based on the computed layout information for `program`.
933    //
934    RootSignatureDescBuilder builder(device);
935    auto layout = program->getLayout();
936
937    // The layout information computed by Slang breaks up shader
938    // parameters into what we can think of as "logical" descriptor
939    // sets based on whether or not parameters have the same `space`.
940    //
941    // We want to basically ignore that decomposition and generate a
942    // single descriptor set to hold all top-level parameters, and only
943    // generate distinct descriptor sets when the shader has opted in
944    // via explicit parameter blocks.
945    //
946    // To achieve this goal, we will manually allocate a default descriptor
947    // set for root parameters in our signature, and then recursively
948    // add all the binding/descriptor ranges implied by the global-scope
949    // parameters.
950    //
951    auto rootDescriptorSetIndex = builder.addDescriptorSet();
952    builder.addAsValue(layout->getGlobalParamsVarLayout(), rootDescriptorSetIndex);
953
954    for (SlangUInt i = 0; i < layout->getEntryPointCount(); i++)
955    {
956        // Entry-point parameters should also be added to the default root
957        // descriptor set.
958        //
959        // We add the parameters using the "variable layout" for the entry point
960        // and not just its type layout, to ensure that any offset information is
961        // applied correctly to the `register` and `space` information for entry-point
962        // parameters.
963        //
964        // Note: When we start to support DXR we will need to handle entry-point parameters
965        // differently because they will need to map to local root signatures rather than
966        // being included in the global root signature as is being done here.
967        //
968        auto entryPoint = layout->getEntryPointByIndex(i);
969        builder.addAsValue(entryPoint->getVarLayout(), rootDescriptorSetIndex);
970    }
971
972    auto& rootSignatureDesc = builder.build();
973    D3D12_VERSIONED_ROOT_SIGNATURE_DESC versionedDesc = {};
974    versionedDesc.Version = D3D_ROOT_SIGNATURE_VERSION_1_1;
975    versionedDesc.Desc_1_1 = rootSignatureDesc;
976    ComPtr<ID3DBlob> signature;
977    ComPtr<ID3DBlob> error;
978    if (SLANG_FAILED(device->m_D3D12SerializeVersionedRootSignature(
979            &versionedDesc,
980            signature.writeRef(),
981            error.writeRef())))
982    {
983        getDebugCallback()->handleMessage(
984            DebugMessageType::Error,
985            DebugMessageSource::Layer,
986            "error: D3D12SerializeRootSignature failed");
987        if (error)
988        {
989            getDebugCallback()->handleMessage(
990                DebugMessageType::Error,
991                DebugMessageSource::Driver,
992                (const char*)error->GetBufferPointer());
993            if (outError)
994                returnComPtr(outError, error);
995        }
996        return SLANG_FAIL;
997    }
998
999    SLANG_RETURN_ON_FAIL(device->m_device->CreateRootSignature(
1000        0,
1001        signature->GetBufferPointer(),
1002        signature->GetBufferSize(),
1003        IID_PPV_ARGS(outRootSignature)));
1004    return SLANG_OK;
1005}
1006
1007Result RootShaderObjectLayoutImpl::create(
1008    DeviceImpl* device,
1009    slang::IComponentType* program,
1010    slang::ProgramLayout* programLayout,
1011    RootShaderObjectLayoutImpl** outLayout,
1012    ID3DBlob** outError)
1013{
1014    RootShaderObjectLayoutImpl::Builder builder(device, program, programLayout);
1015    builder.addGlobalParams(programLayout->getGlobalParamsVarLayout());
1016
1017    SlangInt entryPointCount = programLayout->getEntryPointCount();
1018    for (SlangInt e = 0; e < entryPointCount; ++e)
1019    {
1020        auto slangEntryPoint = programLayout->getEntryPointByIndex(e);
1021        RefPtr<ShaderObjectLayoutImpl> entryPointLayout;
1022        SLANG_RETURN_ON_FAIL(ShaderObjectLayoutImpl::createForElementType(
1023            device,
1024            program->getSession(),
1025            slangEntryPoint->getTypeLayout(),
1026            entryPointLayout.writeRef()));
1027        builder.addEntryPoint(slangEntryPoint->getStage(), entryPointLayout);
1028    }
1029
1030    RefPtr<RootShaderObjectLayoutImpl> layout;
1031    SLANG_RETURN_ON_FAIL(builder.build(layout.writeRef()));
1032
1033    if (program->getSpecializationParamCount() == 0)
1034    {
1035        // For root object, we would like know the union of all binding slots
1036        // including all sub-objects in the shader-object hierarchy, so at
1037        // parameter binding time we can easily know how many GPU descriptor tables
1038        // to create without walking through the shader-object hierarchy again.
1039        // We build out this array along with root signature construction and store
1040        // it in `m_gpuDescriptorSetInfos`.
1041        SLANG_RETURN_ON_FAIL(createRootSignatureFromSlang(
1042            device,
1043            layout,
1044            program,
1045            layout->m_rootSignature.writeRef(),
1046            outError));
1047    }
1048
1049    *outLayout = layout.detach();
1050
1051    return SLANG_OK;
1052}
1053
1054Result RootShaderObjectLayoutImpl::init(Builder* builder)
1055{
1056    auto renderer = builder->m_renderer;
1057
1058    SLANG_RETURN_ON_FAIL(Super::init(builder));
1059
1060    m_program = builder->m_program;
1061    m_programLayout = builder->m_programLayout;
1062    m_entryPoints = builder->m_entryPoints;
1063    return SLANG_OK;
1064}
1065
1066} // namespace d3d12
1067} // namespace gfx