yum-mirror/slang

Making it easier to work with shaders

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

Ellie HermaszewskaMove switch statement bodies to their own lines (#5493)b118451e3

master
38.3 KiB1011 linesraw
1// vk-shader-object-layout.cpp
2#include "vk-shader-object-layout.h"
3
4namespace gfx
5{
6
7using namespace Slang;
8
9namespace vk
10{
11
12Index ShaderObjectLayoutImpl::Builder::findOrAddDescriptorSet(Index space)
13{
14    Index index;
15    if (m_mapSpaceToDescriptorSetIndex.tryGetValue(space, index))
16        return index;
17
18    DescriptorSetInfo info = {};
19    info.space = space;
20
21    index = m_descriptorSetBuildInfos.getCount();
22    m_descriptorSetBuildInfos.add(info);
23
24    m_mapSpaceToDescriptorSetIndex.add(space, index);
25    return index;
26}
27
28VkDescriptorType ShaderObjectLayoutImpl::Builder::_mapDescriptorType(
29    slang::BindingType slangBindingType)
30{
31    switch (slangBindingType)
32    {
33    case slang::BindingType::PushConstant:
34    default:
35        SLANG_ASSERT("unsupported binding type");
36        return VK_DESCRIPTOR_TYPE_MAX_ENUM;
37
38    case slang::BindingType::Sampler:
39        return VK_DESCRIPTOR_TYPE_SAMPLER;
40    case slang::BindingType::CombinedTextureSampler:
41        return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
42    case slang::BindingType::Texture:
43        return VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE;
44    case slang::BindingType::MutableTexture:
45        return VK_DESCRIPTOR_TYPE_STORAGE_IMAGE;
46    case slang::BindingType::TypedBuffer:
47        return VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER;
48    case slang::BindingType::MutableTypedBuffer:
49        return VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER;
50    case slang::BindingType::RawBuffer:
51    case slang::BindingType::MutableRawBuffer:
52        return VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
53    case slang::BindingType::InputRenderTarget:
54        return VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT;
55    case slang::BindingType::InlineUniformData:
56        return VK_DESCRIPTOR_TYPE_INLINE_UNIFORM_BLOCK_EXT;
57    case slang::BindingType::RayTracingAccelerationStructure:
58        return VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR;
59    case slang::BindingType::ConstantBuffer:
60        return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
61    }
62}
63
64/// Add any descriptor ranges implied by this object containing a leaf
65/// sub-object described by `typeLayout`, at the given `offset`.
66
67void ShaderObjectLayoutImpl::Builder::_addDescriptorRangesAsValue(
68    slang::TypeLayoutReflection* typeLayout,
69    BindingOffset const& offset)
70{
71    // First we will scan through all the descriptor sets that the Slang reflection
72    // information believes go into making up the given type.
73    //
74    // Note: We are initializing the sets in order so that their order in our
75    // internal data structures should be deterministically based on the order
76    // in which they are listed in Slang's reflection information.
77    //
78    Index descriptorSetCount = typeLayout->getDescriptorSetCount();
79    for (Index i = 0; i < descriptorSetCount; ++i)
80    {
81        SlangInt descriptorRangeCount = typeLayout->getDescriptorSetDescriptorRangeCount(i);
82        if (descriptorRangeCount == 0)
83            continue;
84        auto descriptorSetIndex =
85            findOrAddDescriptorSet(offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(i));
86    }
87
88    // For actually populating the descriptor sets we prefer to enumerate
89    // the binding ranges of the type instead of the descriptor sets.
90    //
91    Index bindRangeCount = typeLayout->getBindingRangeCount();
92    for (Index i = 0; i < bindRangeCount; ++i)
93    {
94        auto bindingRangeIndex = i;
95        auto bindingRangeType = typeLayout->getBindingRangeType(bindingRangeIndex);
96        switch (bindingRangeType)
97        {
98        default:
99            break;
100
101        // We will skip over ranges that represent sub-objects for now, and handle
102        // them in a separate pass.
103        //
104        case slang::BindingType::ParameterBlock:
105        case slang::BindingType::ConstantBuffer:
106        case slang::BindingType::ExistentialValue:
107        case slang::BindingType::PushConstant:
108            continue;
109        }
110
111        // Given a binding range we are interested in, we will then enumerate
112        // its contained descriptor ranges.
113
114        Index descriptorRangeCount =
115            typeLayout->getBindingRangeDescriptorRangeCount(bindingRangeIndex);
116        if (descriptorRangeCount == 0)
117            continue;
118        auto slangDescriptorSetIndex =
119            typeLayout->getBindingRangeDescriptorSetIndex(bindingRangeIndex);
120        auto descriptorSetIndex = findOrAddDescriptorSet(
121            offset.bindingSet + typeLayout->getDescriptorSetSpaceOffset(slangDescriptorSetIndex));
122        auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
123
124        Index firstDescriptorRangeIndex =
125            typeLayout->getBindingRangeFirstDescriptorRangeIndex(bindingRangeIndex);
126        for (Index j = 0; j < descriptorRangeCount; ++j)
127        {
128            Index descriptorRangeIndex = firstDescriptorRangeIndex + j;
129            auto slangDescriptorType = typeLayout->getDescriptorSetDescriptorRangeType(
130                slangDescriptorSetIndex,
131                descriptorRangeIndex);
132
133            // Certain kinds of descriptor ranges reflected by Slang do not
134            // manifest as descriptors at the Vulkan level, so we will skip those.
135            //
136            switch (slangDescriptorType)
137            {
138            case slang::BindingType::ExistentialValue:
139            case slang::BindingType::InlineUniformData:
140            case slang::BindingType::PushConstant:
141                continue;
142            default:
143                break;
144            }
145
146            auto vkDescriptorType = _mapDescriptorType(slangDescriptorType);
147            VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
148            vkBindingRangeDesc.binding =
149                offset.binding + (uint32_t)typeLayout->getDescriptorSetDescriptorRangeIndexOffset(
150                                     slangDescriptorSetIndex,
151                                     descriptorRangeIndex);
152            vkBindingRangeDesc.descriptorCount =
153                (uint32_t)typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
154                    slangDescriptorSetIndex,
155                    descriptorRangeIndex);
156            vkBindingRangeDesc.descriptorType = vkDescriptorType;
157            vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
158
159            descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
160        }
161    }
162
163    // We skipped over the sub-object ranges when adding descriptors above,
164    // and now we will address that oversight by iterating over just
165    // the sub-object ranges.
166    //
167    Index subObjectRangeCount = typeLayout->getSubObjectRangeCount();
168    for (Index subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount;
169         ++subObjectRangeIndex)
170    {
171        auto bindingRangeIndex =
172            typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
173        auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
174
175        auto subObjectTypeLayout = typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
176        SLANG_ASSERT(subObjectTypeLayout);
177
178        BindingOffset subObjectRangeOffset = offset;
179        subObjectRangeOffset +=
180            BindingOffset(typeLayout->getSubObjectRangeOffset(subObjectRangeIndex));
181
182        switch (bindingType)
183        {
184        // A `ParameterBlock<X>` never contributes descripto ranges to the
185        // decriptor sets of a parent object.
186        //
187        case slang::BindingType::ParameterBlock:
188        default:
189            break;
190
191        case slang::BindingType::ExistentialValue:
192            // An interest/existential-typed sub-object range will only contribute
193            // descriptor ranges to a parent object in the case where it has been
194            // specialied, which is precisely the case where the Slang reflection
195            // information will tell us about its "pending" layout.
196            //
197            if (auto pendingTypeLayout = subObjectTypeLayout->getPendingDataTypeLayout())
198            {
199                BindingOffset pendingOffset = BindingOffset(subObjectRangeOffset.pending);
200                _addDescriptorRangesAsValue(pendingTypeLayout, pendingOffset);
201            }
202            break;
203
204        case slang::BindingType::ConstantBuffer:
205            {
206                // A `ConstantBuffer<X>` range will contribute any nested descriptor
207                // ranges in `X`, along with a leading descriptor range for a
208                // uniform buffer to hold ordinary/uniform data, if there is any.
209
210                SLANG_ASSERT(subObjectTypeLayout);
211
212                auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
213                SLANG_ASSERT(containerVarLayout);
214
215                auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
216                SLANG_ASSERT(elementVarLayout);
217
218                auto elementTypeLayout = elementVarLayout->getTypeLayout();
219                SLANG_ASSERT(elementTypeLayout);
220
221                BindingOffset containerOffset = subObjectRangeOffset;
222                containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
223
224                BindingOffset elementOffset = subObjectRangeOffset;
225                elementOffset += BindingOffset(elementVarLayout);
226
227                _addDescriptorRangesAsConstantBuffer(
228                    elementTypeLayout,
229                    containerOffset,
230                    elementOffset);
231            }
232            break;
233
234        case slang::BindingType::PushConstant:
235            {
236                // This case indicates a `ConstantBuffer<X>` that was marked as being
237                // used for push constants.
238                //
239                // Much of the handling is the same as for an ordinary
240                // `ConstantBuffer<X>`, but of course we need to handle the ordinary
241                // data part differently.
242
243                SLANG_ASSERT(subObjectTypeLayout);
244
245                auto containerVarLayout = subObjectTypeLayout->getContainerVarLayout();
246                SLANG_ASSERT(containerVarLayout);
247
248                auto elementVarLayout = subObjectTypeLayout->getElementVarLayout();
249                SLANG_ASSERT(elementVarLayout);
250
251                auto elementTypeLayout = elementVarLayout->getTypeLayout();
252                SLANG_ASSERT(elementTypeLayout);
253
254                BindingOffset containerOffset = subObjectRangeOffset;
255                containerOffset += BindingOffset(subObjectTypeLayout->getContainerVarLayout());
256
257                BindingOffset elementOffset = subObjectRangeOffset;
258                elementOffset += BindingOffset(elementVarLayout);
259
260                _addDescriptorRangesAsPushConstantBuffer(
261                    elementTypeLayout,
262                    containerOffset,
263                    elementOffset);
264            }
265            break;
266        }
267    }
268}
269
270/// Add the descriptor ranges implied by a `ConstantBuffer<X>` where `X` is
271/// described by `elementTypeLayout`.
272///
273/// The `containerOffset` and `elementOffset` are the binding offsets that
274/// should apply to the buffer itself and the contents of the buffer, respectively.
275///
276
277void ShaderObjectLayoutImpl::Builder::_addDescriptorRangesAsConstantBuffer(
278    slang::TypeLayoutReflection* elementTypeLayout,
279    BindingOffset const& containerOffset,
280    BindingOffset const& elementOffset)
281{
282    // If the type has ordinary uniform data fields, we need to make sure to create
283    // a descriptor set with a constant buffer binding in the case that the shader
284    // object is bound as a stand alone parameter block.
285    if (elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0)
286    {
287        auto descriptorSetIndex = findOrAddDescriptorSet(containerOffset.bindingSet);
288        auto& descriptorSetInfo = m_descriptorSetBuildInfos[descriptorSetIndex];
289        VkDescriptorSetLayoutBinding vkBindingRangeDesc = {};
290        vkBindingRangeDesc.binding = containerOffset.binding;
291        vkBindingRangeDesc.descriptorCount = 1;
292        vkBindingRangeDesc.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
293        vkBindingRangeDesc.stageFlags = VK_SHADER_STAGE_ALL;
294        descriptorSetInfo.vkBindings.add(vkBindingRangeDesc);
295    }
296
297    _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
298}
299
300/// Add the descriptor ranges implied by a `PushConstantBuffer<X>` where `X` is
301/// described by `elementTypeLayout`.
302///
303/// The `containerOffset` and `elementOffset` are the binding offsets that
304/// should apply to the buffer itself and the contents of the buffer, respectively.
305///
306
307void ShaderObjectLayoutImpl::Builder::_addDescriptorRangesAsPushConstantBuffer(
308    slang::TypeLayoutReflection* elementTypeLayout,
309    BindingOffset const& containerOffset,
310    BindingOffset const& elementOffset)
311{
312    // If the type has ordinary uniform data fields, we need to make sure to create
313    // a descriptor set with a constant buffer binding in the case that the shader
314    // object is bound as a stand alone parameter block.
315    auto ordinaryDataSize = (uint32_t)elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM);
316    if (ordinaryDataSize != 0)
317    {
318        auto pushConstantRangeIndex = containerOffset.pushConstantRange;
319
320        VkPushConstantRange vkPushConstantRange = {};
321        vkPushConstantRange.size = ordinaryDataSize;
322        vkPushConstantRange.stageFlags = VK_SHADER_STAGE_ALL; // TODO: be more clever
323
324        while ((uint32_t)m_ownPushConstantRanges.getCount() <= pushConstantRangeIndex)
325        {
326            VkPushConstantRange emptyRange = {0};
327            m_ownPushConstantRanges.add(emptyRange);
328        }
329
330        m_ownPushConstantRanges[pushConstantRangeIndex] = vkPushConstantRange;
331    }
332
333    _addDescriptorRangesAsValue(elementTypeLayout, elementOffset);
334}
335
336/// Add binding ranges to this shader object layout, as implied by the given
337/// `typeLayout`
338
339void ShaderObjectLayoutImpl::Builder::addBindingRanges(slang::TypeLayoutReflection* typeLayout)
340{
341    SlangInt bindingRangeCount = typeLayout->getBindingRangeCount();
342    for (SlangInt r = 0; r < bindingRangeCount; ++r)
343    {
344        slang::BindingType slangBindingType = typeLayout->getBindingRangeType(r);
345        uint32_t count = (uint32_t)typeLayout->getBindingRangeBindingCount(r);
346        slang::TypeLayoutReflection* slangLeafTypeLayout =
347            typeLayout->getBindingRangeLeafTypeLayout(r);
348
349        Index baseIndex = 0;
350        Index subObjectIndex = 0;
351        switch (slangBindingType)
352        {
353        case slang::BindingType::ConstantBuffer:
354        case slang::BindingType::ParameterBlock:
355        case slang::BindingType::ExistentialValue:
356            baseIndex = m_subObjectCount;
357            subObjectIndex = baseIndex;
358            m_subObjectCount += count;
359            break;
360        case slang::BindingType::RawBuffer:
361        case slang::BindingType::MutableRawBuffer:
362            if (slangLeafTypeLayout->getType()->getElementType() != nullptr)
363            {
364                // A structured buffer occupies both a resource slot and
365                // a sub-object slot.
366                subObjectIndex = m_subObjectCount;
367                m_subObjectCount += count;
368            }
369            baseIndex = m_resourceViewCount;
370            m_resourceViewCount += count;
371            break;
372        case slang::BindingType::Sampler:
373            baseIndex = m_samplerCount;
374            m_samplerCount += count;
375            m_totalBindingCount += 1;
376            break;
377
378        case slang::BindingType::CombinedTextureSampler:
379            baseIndex = m_combinedTextureSamplerCount;
380            m_combinedTextureSamplerCount += count;
381            m_totalBindingCount += 1;
382            break;
383
384        case slang::BindingType::VaryingInput:
385            baseIndex = m_varyingInputCount;
386            m_varyingInputCount += count;
387            break;
388
389        case slang::BindingType::VaryingOutput:
390            baseIndex = m_varyingOutputCount;
391            m_varyingOutputCount += count;
392            break;
393        default:
394            baseIndex = m_resourceViewCount;
395            m_resourceViewCount += count;
396            m_totalBindingCount += 1;
397            break;
398        }
399
400        BindingRangeInfo bindingRangeInfo;
401        bindingRangeInfo.bindingType = slangBindingType;
402        bindingRangeInfo.count = count;
403        bindingRangeInfo.baseIndex = baseIndex;
404        bindingRangeInfo.subObjectIndex = subObjectIndex;
405        bindingRangeInfo.isSpecializable = typeLayout->isBindingRangeSpecializable(r);
406        // We'd like to extract the information on the GLSL/SPIR-V
407        // `binding` that this range should bind into (or whatever
408        // other specific kind of offset/index is appropriate to it).
409        //
410        // A binding range represents a logical member of the shader
411        // object type, and it may encompass zero or more *descriptor
412        // ranges* that describe how it is physically bound to pipeline
413        // state.
414        //
415        // If the current bindign range is backed by at least one descriptor
416        // range then we can query the binding offset of that descriptor
417        // range. We expect that in the common case there will be exactly
418        // one descriptor range, and we can extract the information easily.
419        //
420        if (typeLayout->getBindingRangeDescriptorRangeCount(r) != 0)
421        {
422            SlangInt descriptorSetIndex = typeLayout->getBindingRangeDescriptorSetIndex(r);
423            SlangInt descriptorRangeIndex = typeLayout->getBindingRangeFirstDescriptorRangeIndex(r);
424
425            auto set = typeLayout->getDescriptorSetSpaceOffset(descriptorSetIndex);
426            auto bindingOffset = typeLayout->getDescriptorSetDescriptorRangeIndexOffset(
427                descriptorSetIndex,
428                descriptorRangeIndex);
429
430            bindingRangeInfo.setOffset = uint32_t(set);
431            bindingRangeInfo.bindingOffset = uint32_t(bindingOffset);
432        }
433
434        m_bindingRanges.add(bindingRangeInfo);
435    }
436
437    SlangInt subObjectRangeCount = typeLayout->getSubObjectRangeCount();
438    for (SlangInt r = 0; r < subObjectRangeCount; ++r)
439    {
440        SlangInt bindingRangeIndex = typeLayout->getSubObjectRangeBindingRangeIndex(r);
441        auto& bindingRange = m_bindingRanges[bindingRangeIndex];
442        auto slangBindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
443        slang::TypeLayoutReflection* slangLeafTypeLayout =
444            typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
445
446        // A sub-object range can either represent a sub-object of a known
447        // type, like a `ConstantBuffer<Foo>` or `ParameterBlock<Foo>`
448        // (in which case we can pre-compute a layout to use, based on
449        // the type `Foo`) *or* it can represent a sub-object of some
450        // existential type (e.g., `IBar`) in which case we cannot
451        // know the appropraite type/layout of sub-object to allocate.
452        //
453        RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
454        switch (slangBindingType)
455        {
456        default:
457            {
458                auto varLayout = slangLeafTypeLayout->getElementVarLayout();
459                auto subTypeLayout = varLayout->getTypeLayout();
460                ShaderObjectLayoutImpl::createForElementType(
461                    m_renderer,
462                    m_session,
463                    subTypeLayout,
464                    subObjectLayout.writeRef());
465            }
466            break;
467
468        case slang::BindingType::ExistentialValue:
469            if (auto pendingTypeLayout = slangLeafTypeLayout->getPendingDataTypeLayout())
470            {
471                ShaderObjectLayoutImpl::createForElementType(
472                    m_renderer,
473                    m_session,
474                    pendingTypeLayout,
475                    subObjectLayout.writeRef());
476            }
477            break;
478        }
479
480        SubObjectRangeInfo subObjectRange;
481        subObjectRange.bindingRangeIndex = bindingRangeIndex;
482        subObjectRange.layout = subObjectLayout;
483
484        // We will use Slang reflection infromation to extract the offset information
485        // for each sub-object range.
486        //
487        // TODO: We should also be extracting the uniform offset here.
488        //
489        subObjectRange.offset = SubObjectRangeOffset(typeLayout->getSubObjectRangeOffset(r));
490        subObjectRange.stride = SubObjectRangeStride(slangLeafTypeLayout);
491
492        switch (slangBindingType)
493        {
494        case slang::BindingType::ParameterBlock:
495            m_childDescriptorSetCount += subObjectLayout->getTotalDescriptorSetCount();
496            m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
497            break;
498
499        case slang::BindingType::ConstantBuffer:
500            m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
501            m_totalBindingCount += subObjectLayout->getTotalBindingCount();
502            m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
503            break;
504
505        case slang::BindingType::ExistentialValue:
506            if (subObjectLayout)
507            {
508                m_childDescriptorSetCount += subObjectLayout->getChildDescriptorSetCount();
509                m_totalBindingCount += subObjectLayout->getTotalBindingCount();
510                m_childPushConstantRangeCount += subObjectLayout->getTotalPushConstantRangeCount();
511
512                // An interface-type range that includes ordinary data can
513                // increase the size of the ordinary data buffer we need to
514                // allocate for the parent object.
515                //
516                uint32_t ordinaryDataEnd =
517                    subObjectRange.offset.pendingOrdinaryData +
518                    (uint32_t)bindingRange.count * subObjectRange.stride.pendingOrdinaryData;
519
520                if (ordinaryDataEnd > m_totalOrdinaryDataSize)
521                {
522                    m_totalOrdinaryDataSize = ordinaryDataEnd;
523                }
524            }
525            break;
526
527        default:
528            break;
529        }
530
531        m_subObjectRanges.add(subObjectRange);
532    }
533}
534
535Result ShaderObjectLayoutImpl::Builder::setElementTypeLayout(
536    slang::TypeLayoutReflection* typeLayout)
537{
538    typeLayout = _unwrapParameterGroups(typeLayout, m_containerType);
539    m_elementTypeLayout = typeLayout;
540
541    m_totalOrdinaryDataSize = (uint32_t)typeLayout->getSize();
542
543    // Next we will compute the binding ranges that are used to store
544    // the logical contents of the object in memory. These will relate
545    // to the descriptor ranges in the various sets, but not always
546    // in a one-to-one fashion.
547
548    addBindingRanges(typeLayout);
549
550    // Note: This routine does not take responsibility for
551    // adding descriptor ranges at all, because the exact way
552    // that descriptor ranges need to be added varies between
553    // ordinary shader objects, root shader objects, and entry points.
554
555    return SLANG_OK;
556}
557
558SlangResult ShaderObjectLayoutImpl::Builder::build(ShaderObjectLayoutImpl** outLayout)
559{
560    auto layout = RefPtr<ShaderObjectLayoutImpl>(new ShaderObjectLayoutImpl());
561    SLANG_RETURN_ON_FAIL(layout->_init(this));
562
563    returnRefPtrMove(outLayout, layout);
564    return SLANG_OK;
565}
566
567Result ShaderObjectLayoutImpl::createForElementType(
568    DeviceImpl* renderer,
569    slang::ISession* session,
570    slang::TypeLayoutReflection* elementType,
571    ShaderObjectLayoutImpl** outLayout)
572{
573    Builder builder(renderer, session);
574    builder.setElementTypeLayout(elementType);
575
576    // When constructing a shader object layout directly from a reflected
577    // type in Slang, we want to compute the descriptor sets and ranges
578    // that would be used if this object were bound as a parameter block.
579    //
580    // It might seem like we need to deal with the other cases for how
581    // the shader object might be bound, but the descriptor ranges we
582    // compute here will only ever be used in parameter-block case.
583    //
584    // One important wrinkle is that we know that the parameter block
585    // allocated for `elementType` will potentially need a buffer `binding`
586    // for any ordinary data it contains.
587
588    bool needsOrdinaryDataBuffer =
589        builder.m_elementTypeLayout->getSize(SLANG_PARAMETER_CATEGORY_UNIFORM) != 0;
590    uint32_t ordinaryDataBufferCount = needsOrdinaryDataBuffer ? 1 : 0;
591
592    // When binding the object, we know that the ordinary data buffer will
593    // always use a the first available `binding`, so its offset will be
594    // all zeroes.
595    //
596    BindingOffset containerOffset;
597
598    // In contrast, the `binding`s used by all the other entries in the
599    // parameter block will need to be offset by one if there was
600    // an ordinary data buffer.
601    //
602    BindingOffset elementOffset;
603    elementOffset.binding = ordinaryDataBufferCount;
604
605    // Furthermore, any `binding`s that arise due to "pending" data
606    // in the type of the object (due to specialization for existential types)
607    // will need to come after all the other `binding`s that were
608    // part of the "primary" (unspecialized) data.
609    //
610    uint32_t primaryDescriptorCount =
611        ordinaryDataBufferCount + (uint32_t)builder.m_elementTypeLayout->getSize(
612                                      SLANG_PARAMETER_CATEGORY_DESCRIPTOR_TABLE_SLOT);
613    elementOffset.pending.binding = primaryDescriptorCount;
614
615    // Once we've computed the offset information, we simply add the
616    // descriptor ranges as if things were declared as a `ConstantBuffer<X>`,
617    // since that is how things will be laid out inside the parameter block.
618    //
619    builder._addDescriptorRangesAsConstantBuffer(
620        builder.m_elementTypeLayout,
621        containerOffset,
622        elementOffset);
623    return builder.build(outLayout);
624}
625
626ShaderObjectLayoutImpl::~ShaderObjectLayoutImpl()
627{
628    for (auto& descSetInfo : m_descriptorSetInfos)
629    {
630        getDevice()->m_api.vkDestroyDescriptorSetLayout(
631            getDevice()->m_api.m_device,
632            descSetInfo.descriptorSetLayout,
633            nullptr);
634    }
635}
636
637Result ShaderObjectLayoutImpl::_init(Builder const* builder)
638{
639    auto renderer = builder->m_renderer;
640
641    initBase(renderer, builder->m_session, builder->m_elementTypeLayout);
642
643    m_bindingRanges = builder->m_bindingRanges;
644
645    m_descriptorSetInfos = _Move(builder->m_descriptorSetBuildInfos);
646    m_ownPushConstantRanges = builder->m_ownPushConstantRanges;
647    m_resourceViewCount = builder->m_resourceViewCount;
648    m_samplerCount = builder->m_samplerCount;
649    m_combinedTextureSamplerCount = builder->m_combinedTextureSamplerCount;
650    m_childDescriptorSetCount = builder->m_childDescriptorSetCount;
651    m_totalBindingCount = builder->m_totalBindingCount;
652    m_subObjectCount = builder->m_subObjectCount;
653    m_subObjectRanges = builder->m_subObjectRanges;
654    m_totalOrdinaryDataSize = builder->m_totalOrdinaryDataSize;
655
656    m_containerType = builder->m_containerType;
657
658    // Create VkDescriptorSetLayout for all descriptor sets.
659    for (auto& descriptorSetInfo : m_descriptorSetInfos)
660    {
661        VkDescriptorSetLayoutCreateInfo createInfo = {};
662        createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
663        createInfo.pBindings = descriptorSetInfo.vkBindings.getBuffer();
664        createInfo.bindingCount = (uint32_t)descriptorSetInfo.vkBindings.getCount();
665        VkDescriptorSetLayout vkDescSetLayout;
666        SLANG_RETURN_ON_FAIL(renderer->m_api.vkCreateDescriptorSetLayout(
667            renderer->m_api.m_device,
668            &createInfo,
669            nullptr,
670            &vkDescSetLayout));
671        descriptorSetInfo.descriptorSetLayout = vkDescSetLayout;
672    }
673    return SLANG_OK;
674}
675
676DeviceImpl* ShaderObjectLayoutImpl::getDevice()
677{
678    return static_cast<DeviceImpl*>(m_renderer);
679}
680
681Result EntryPointLayout::Builder::build(EntryPointLayout** outLayout)
682{
683    RefPtr<EntryPointLayout> layout = new EntryPointLayout();
684    SLANG_RETURN_ON_FAIL(layout->_init(this));
685
686    returnRefPtrMove(outLayout, layout);
687    return SLANG_OK;
688}
689
690void EntryPointLayout::Builder::addEntryPointParams(slang::EntryPointLayout* entryPointLayout)
691{
692    m_slangEntryPointLayout = entryPointLayout;
693    setElementTypeLayout(entryPointLayout->getTypeLayout());
694    m_shaderStageFlag = VulkanUtil::getShaderStage(entryPointLayout->getStage());
695
696    // Note: we do not bother adding any descriptor sets/ranges here,
697    // because the descriptor ranges of an entry point will simply
698    // be allocated as part of the descriptor sets for the root
699    // shader object.
700}
701
702Result EntryPointLayout::_init(Builder const* builder)
703{
704    auto renderer = builder->m_renderer;
705
706    SLANG_RETURN_ON_FAIL(Super::_init(builder));
707
708    m_slangEntryPointLayout = builder->m_slangEntryPointLayout;
709    m_shaderStageFlag = builder->m_shaderStageFlag;
710    return SLANG_OK;
711}
712
713RootShaderObjectLayout::~RootShaderObjectLayout()
714{
715    if (m_pipelineLayout)
716    {
717        m_renderer->m_api.vkDestroyPipelineLayout(
718            m_renderer->m_api.m_device,
719            m_pipelineLayout,
720            nullptr);
721    }
722}
723
724Index RootShaderObjectLayout::findEntryPointIndex(VkShaderStageFlags stage)
725{
726    auto entryPointCount = m_entryPoints.getCount();
727    for (Index i = 0; i < entryPointCount; ++i)
728    {
729        auto entryPoint = m_entryPoints[i];
730        if (entryPoint.layout->getShaderStageFlag() == stage)
731            return i;
732    }
733    return -1;
734}
735
736Result RootShaderObjectLayout::create(
737    DeviceImpl* renderer,
738    slang::IComponentType* program,
739    slang::ProgramLayout* programLayout,
740    RootShaderObjectLayout** outLayout)
741{
742    RootShaderObjectLayout::Builder builder(renderer, program, programLayout);
743    builder.addGlobalParams(programLayout->getGlobalParamsVarLayout());
744
745    SlangInt entryPointCount = programLayout->getEntryPointCount();
746    for (SlangInt e = 0; e < entryPointCount; ++e)
747    {
748        auto slangEntryPoint = programLayout->getEntryPointByIndex(e);
749
750        EntryPointLayout::Builder entryPointBuilder(renderer, program->getSession());
751        entryPointBuilder.addEntryPointParams(slangEntryPoint);
752
753        RefPtr<EntryPointLayout> entryPointLayout;
754        SLANG_RETURN_ON_FAIL(entryPointBuilder.build(entryPointLayout.writeRef()));
755
756        builder.addEntryPoint(entryPointLayout);
757    }
758
759    SLANG_RETURN_ON_FAIL(builder.build(outLayout));
760
761    return SLANG_OK;
762}
763
764Result RootShaderObjectLayout::_init(Builder const* builder)
765{
766    auto renderer = builder->m_renderer;
767
768    SLANG_RETURN_ON_FAIL(Super::_init(builder));
769
770    m_program = builder->m_program;
771    m_programLayout = builder->m_programLayout;
772    m_entryPoints = _Move(builder->m_entryPoints);
773    m_pendingDataOffset = builder->m_pendingDataOffset;
774    m_renderer = renderer;
775
776    // If the program has unbound specialization parameters,
777    // then we will avoid creating a final Vulkan pipeline layout.
778    //
779    // TODO: We should really create the information necessary
780    // for binding as part of a separate object, so that we have
781    // a clean seperation between what is needed for writing into
782    // a shader object vs. what is needed for binding it to the
783    // pipeline. We eventually need to be able to create bindable
784    // state objects from unspecialized programs, in order to
785    // support dynamic dispatch.
786    //
787    if (m_program->getSpecializationParamCount() != 0)
788        return SLANG_OK;
789
790    // Otherwise, we need to create a final (bindable) layout.
791    //
792    // We will use a recursive walk to collect all the `VkDescriptorSetLayout`s
793    // that are required for the global scope, sub-objects, and entry points.
794    //
795    SLANG_RETURN_ON_FAIL(addAllDescriptorSets());
796
797    // We will also use a recursive walk to collect all the push-constant
798    // ranges needed for this object, sub-objects, and entry points.
799    //
800    SLANG_RETURN_ON_FAIL(addAllPushConstantRanges());
801
802    // Once we've collected the information across the entire
803    // tree of sub-objects
804
805    // Now call Vulkan API to create a pipeline layout.
806    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {};
807    pipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
808    pipelineLayoutCreateInfo.setLayoutCount = (uint32_t)m_vkDescriptorSetLayouts.getCount();
809    pipelineLayoutCreateInfo.pSetLayouts = m_vkDescriptorSetLayouts.getBuffer();
810    if (m_allPushConstantRanges.getCount())
811    {
812        pipelineLayoutCreateInfo.pushConstantRangeCount =
813            (uint32_t)m_allPushConstantRanges.getCount();
814        pipelineLayoutCreateInfo.pPushConstantRanges = m_allPushConstantRanges.getBuffer();
815    }
816    SLANG_RETURN_ON_FAIL(m_renderer->m_api.vkCreatePipelineLayout(
817        m_renderer->m_api.m_device,
818        &pipelineLayoutCreateInfo,
819        nullptr,
820        &m_pipelineLayout));
821    return SLANG_OK;
822}
823
824/// Add all the descriptor sets implied by this root object and sub-objects
825
826Result RootShaderObjectLayout::addAllDescriptorSets()
827{
828    SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(this));
829
830    // Note: the descriptor ranges/sets for direct entry point parameters
831    // were already enumerated into the ranges/sets of the root object itself,
832    // so we don't wnat to add them again.
833    //
834    // We do however have to deal with the possibility that an entry
835    // point could introduce "child" descriptor sets, e.g., because it
836    // has a `ParameterBlock<X>` parameter.
837    //
838    for (auto& entryPoint : getEntryPoints())
839    {
840        SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(entryPoint.layout));
841    }
842
843    return SLANG_OK;
844}
845
846/// Recurisvely add descriptor sets defined by `layout` and sub-objects
847
848Result RootShaderObjectLayout::addAllDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
849{
850    // TODO: This logic assumes that descriptor sets are all contiguous
851    // and have been allocated in a global order that matches the order
852    // of enumeration here.
853
854    for (auto& descSetInfo : layout->getOwnDescriptorSets())
855    {
856        m_vkDescriptorSetLayouts.add(descSetInfo.descriptorSetLayout);
857    }
858
859    SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(layout));
860    return SLANG_OK;
861}
862
863/// Recurisvely add descriptor sets defined by sub-objects of `layout`
864
865Result RootShaderObjectLayout::addChildDescriptorSetsRec(ShaderObjectLayoutImpl* layout)
866{
867    for (auto& subObject : layout->getSubObjectRanges())
868    {
869        auto bindingRange = layout->getBindingRange(subObject.bindingRangeIndex);
870        switch (bindingRange.bindingType)
871        {
872        case slang::BindingType::ParameterBlock:
873            SLANG_RETURN_ON_FAIL(addAllDescriptorSetsRec(subObject.layout));
874            break;
875
876        default:
877            if (auto subObjectLayout = subObject.layout)
878            {
879                SLANG_RETURN_ON_FAIL(addChildDescriptorSetsRec(subObject.layout));
880            }
881            break;
882        }
883    }
884
885    return SLANG_OK;
886}
887
888/// Add all the push-constant ranges implied by this root object and sub-objects
889
890Result RootShaderObjectLayout::addAllPushConstantRanges()
891{
892    SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(this));
893
894    for (auto& entryPoint : getEntryPoints())
895    {
896        SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(entryPoint.layout));
897    }
898
899    return SLANG_OK;
900}
901
902/// Recurisvely add push-constant ranges defined by `layout` and sub-objects
903
904Result RootShaderObjectLayout::addAllPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
905{
906    // TODO: This logic assumes that push-constant ranges are all contiguous
907    // and have been allocated in a global order that matches the order
908    // of enumeration here.
909
910    for (auto pushConstantRange : layout->getOwnPushConstantRanges())
911    {
912        pushConstantRange.offset = m_totalPushConstantSize;
913        m_totalPushConstantSize += pushConstantRange.size;
914
915        m_allPushConstantRanges.add(pushConstantRange);
916    }
917
918    SLANG_RETURN_ON_FAIL(addChildPushConstantRangesRec(layout));
919    return SLANG_OK;
920}
921
922/// Recurisvely add push-constant ranges defined by sub-objects of `layout`
923
924Result RootShaderObjectLayout::addChildPushConstantRangesRec(ShaderObjectLayoutImpl* layout)
925{
926    for (auto& subObject : layout->getSubObjectRanges())
927    {
928        if (auto subObjectLayout = subObject.layout)
929        {
930            SLANG_RETURN_ON_FAIL(addAllPushConstantRangesRec(subObject.layout));
931        }
932    }
933
934    return SLANG_OK;
935}
936
937Result RootShaderObjectLayout::Builder::build(RootShaderObjectLayout** outLayout)
938{
939    RefPtr<RootShaderObjectLayout> layout = new RootShaderObjectLayout();
940    SLANG_RETURN_ON_FAIL(layout->_init(this));
941    returnRefPtrMove(outLayout, layout);
942    return SLANG_OK;
943}
944
945void RootShaderObjectLayout::Builder::addGlobalParams(
946    slang::VariableLayoutReflection* globalsLayout)
947{
948    setElementTypeLayout(globalsLayout->getTypeLayout());
949
950    // We need to populate our descriptor sets/ranges with information
951    // from the layout of the global scope.
952    //
953    // While we expect that the parameter in the global scope start
954    // at an offset of zero, it is also worth querying the offset
955    // information because it could impact the locations assigned
956    // to "pending" data in the case of static specialization.
957    //
958    BindingOffset offset(globalsLayout);
959
960    // Note: We are adding descriptor ranges here based directly on
961    // the type of the global-scope layout. The type layout for the
962    // global scope will either be something like a `struct GlobalParams`
963    // that contains all the global-scope parameters or a `ConstantBuffer<GlobalParams>`
964    // and in either case the `_addDescriptorRangesAsValue` can properly
965    // add all the ranges implied.
966    //
967    // As a result we don't require any special-case logic here to
968    // deal with the possibility of a "default" constant buffer allocated
969    // for global-scope parameters of uniform/ordinary type.
970    //
971    _addDescriptorRangesAsValue(globalsLayout->getTypeLayout(), offset);
972
973    // We want to keep track of the offset that was applied to "pending"
974    // data because we will need it again later when it comes time to
975    // actually bind things.
976    //
977    m_pendingDataOffset = offset.pending;
978}
979
980void RootShaderObjectLayout::Builder::addEntryPoint(EntryPointLayout* entryPointLayout)
981{
982    auto slangEntryPointLayout = entryPointLayout->getSlangLayout();
983    auto entryPointVarLayout = slangEntryPointLayout->getVarLayout();
984
985    // The offset information for each entry point needs to
986    // be adjusted by any offset for "pending" data that
987    // was recorded in the global-scope layout.
988    //
989    // TODO(tfoley): Double-check that this is correct.
990
991    BindingOffset entryPointOffset(entryPointVarLayout);
992    entryPointOffset.pending += m_pendingDataOffset;
993
994    EntryPointInfo info;
995    info.layout = entryPointLayout;
996    info.offset = entryPointOffset;
997
998    // Similar to the case for the global scope, we expect the
999    // type layout for the entry point parameters to be either
1000    // a `struct EntryPointParams` or a `PushConstantBuffer<EntryPointParams>`.
1001    // Rather than deal with the different cases here, we will
1002    // trust the `_addDescriptorRangesAsValue` code to handle
1003    // either case correctly.
1004    //
1005    _addDescriptorRangesAsValue(entryPointVarLayout->getTypeLayout(), entryPointOffset);
1006
1007    m_entryPoints.add(info);
1008}
1009
1010} // namespace vk
1011} // namespace gfx