yum-mirror/slang

Making it easier to work with shaders

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

Jay KwakFail slang-test when VVL printed errors (#8280)1681bc67f

master
47.1 KiB1299 linesraw
1// vk-shader-object.cpp
2#include "vk-shader-object.h"
3
4#include "vk-command-buffer.h"
5#include "vk-command-encoder.h"
6#include "vk-transient-heap.h"
7
8namespace gfx
9{
10
11using namespace Slang;
12
13namespace vk
14{
15
16Result ShaderObjectImpl::create(
17    IDevice* device,
18    ShaderObjectLayoutImpl* layout,
19    ShaderObjectImpl** outShaderObject)
20{
21    auto object = RefPtr<ShaderObjectImpl>(new ShaderObjectImpl());
22    SLANG_RETURN_ON_FAIL(object->init(device, layout));
23
24    returnRefPtrMove(outShaderObject, object);
25    return SLANG_OK;
26}
27
28RendererBase* ShaderObjectImpl::getDevice()
29{
30    return m_layout->getDevice();
31}
32
33GfxCount ShaderObjectImpl::getEntryPointCount()
34{
35    return 0;
36}
37
38Result ShaderObjectImpl::getEntryPoint(GfxIndex index, IShaderObject** outEntryPoint)
39{
40    *outEntryPoint = nullptr;
41    return SLANG_OK;
42}
43
44const void* ShaderObjectImpl::getRawData()
45{
46    return m_data.getBuffer();
47}
48
49Size ShaderObjectImpl::getSize()
50{
51    return (Size)m_data.getCount();
52}
53
54// TODO: Change size_t and Index to Size?
55Result ShaderObjectImpl::setData(ShaderOffset const& inOffset, void const* data, size_t inSize)
56{
57    Index offset = inOffset.uniformOffset;
58    Index size = inSize;
59
60    char* dest = m_data.getBuffer();
61    Index availableSize = m_data.getCount();
62
63    // TODO: We really should bounds-check access rather than silently ignoring sets
64    // that are too large, but we have several test cases that set more data than
65    // an object actually stores on several targets...
66    //
67    if (offset < 0)
68    {
69        size += offset;
70        offset = 0;
71    }
72    if ((offset + size) >= availableSize)
73    {
74        size = availableSize - offset;
75    }
76
77    memcpy(dest + offset, data, size);
78
79    m_isConstantBufferDirty = true;
80
81    return SLANG_OK;
82}
83
84Result ShaderObjectImpl::setResource(ShaderOffset const& offset, IResourceView* resourceView)
85{
86    if (offset.bindingRangeIndex < 0)
87        return SLANG_E_INVALID_ARG;
88    auto layout = getLayout();
89    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
90        return SLANG_E_INVALID_ARG;
91    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
92    if (!resourceView)
93    {
94        m_resourceViews[bindingRange.baseIndex + offset.bindingArrayIndex] = nullptr;
95    }
96    else
97    {
98        if (resourceView->getViewDesc()->type == IResourceView::Type::AccelerationStructure)
99        {
100            m_resourceViews[bindingRange.baseIndex + offset.bindingArrayIndex] =
101                static_cast<AccelerationStructureImpl*>(resourceView);
102        }
103        else
104        {
105            m_resourceViews[bindingRange.baseIndex + offset.bindingArrayIndex] =
106                static_cast<ResourceViewImpl*>(resourceView);
107        }
108    }
109    return SLANG_OK;
110}
111
112Result ShaderObjectImpl::setSampler(ShaderOffset const& offset, ISamplerState* sampler)
113{
114    if (offset.bindingRangeIndex < 0)
115        return SLANG_E_INVALID_ARG;
116    auto layout = getLayout();
117    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
118        return SLANG_E_INVALID_ARG;
119    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
120
121    m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] =
122        static_cast<SamplerStateImpl*>(sampler);
123    return SLANG_OK;
124}
125
126Result ShaderObjectImpl::setCombinedTextureSampler(
127    ShaderOffset const& offset,
128    IResourceView* textureView,
129    ISamplerState* sampler)
130{
131    if (offset.bindingRangeIndex < 0)
132        return SLANG_E_INVALID_ARG;
133    auto layout = getLayout();
134    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
135        return SLANG_E_INVALID_ARG;
136    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
137
138    auto& slot = m_combinedTextureSamplers[bindingRange.baseIndex + offset.bindingArrayIndex];
139    slot.textureView = static_cast<TextureResourceViewImpl*>(textureView);
140    slot.sampler = static_cast<SamplerStateImpl*>(sampler);
141    return SLANG_OK;
142}
143
144Result ShaderObjectImpl::init(IDevice* device, ShaderObjectLayoutImpl* layout)
145{
146    m_layout = layout;
147
148    m_constantBufferTransientHeap = nullptr;
149    m_constantBufferTransientHeapVersion = 0;
150    m_isConstantBufferDirty = true;
151
152    // If the layout tells us that there is any uniform data,
153    // then we will allocate a CPU memory buffer to hold that data
154    // while it is being set from the host.
155    //
156    // Once the user is done setting the parameters/fields of this
157    // shader object, we will produce a GPU-memory version of the
158    // uniform data (which includes values from this object and
159    // any existential-type sub-objects).
160    //
161    // TODO: Change size_t to Count?
162    size_t uniformSize = layout->getElementTypeLayout()->getSize();
163    if (uniformSize)
164    {
165        m_data.setCount(uniformSize);
166        memset(m_data.getBuffer(), 0, uniformSize);
167    }
168
169#if 0
170        // If the layout tells us there are any descriptor sets to
171        // allocate, then we do so now.
172        //
173        for(auto descriptorSetInfo : layout->getDescriptorSets())
174        {
175            RefPtr<DescriptorSet> descriptorSet;
176            SLANG_RETURN_ON_FAIL(renderer->createDescriptorSet(descriptorSetInfo->layout, descriptorSet.writeRef()));
177            m_descriptorSets.add(descriptorSet);
178        }
179#endif
180
181    m_resourceViews.setCount(layout->getResourceViewCount());
182    m_samplers.setCount(layout->getSamplerCount());
183    m_combinedTextureSamplers.setCount(layout->getCombinedTextureSamplerCount());
184
185    // If the layout specifies that we have any sub-objects, then
186    // we need to size the array to account for them.
187    //
188    Index subObjectCount = layout->getSubObjectCount();
189    m_objects.setCount(subObjectCount);
190
191    for (auto subObjectRangeInfo : layout->getSubObjectRanges())
192    {
193        auto subObjectLayout = subObjectRangeInfo.layout;
194
195        // In the case where the sub-object range represents an
196        // existential-type leaf field (e.g., an `IBar`), we
197        // cannot pre-allocate the object(s) to go into that
198        // range, since we can't possibly know what to allocate
199        // at this point.
200        //
201        if (!subObjectLayout)
202            continue;
203        //
204        // Otherwise, we will allocate a sub-object to fill
205        // in each entry in this range, based on the layout
206        // information we already have.
207
208        auto& bindingRangeInfo = layout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
209        for (Index i = 0; i < bindingRangeInfo.count; ++i)
210        {
211            RefPtr<ShaderObjectImpl> subObject;
212            SLANG_RETURN_ON_FAIL(
213                ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef()));
214            m_objects[bindingRangeInfo.subObjectIndex + i] = subObject;
215        }
216    }
217
218    return SLANG_OK;
219}
220
221Result ShaderObjectImpl::_writeOrdinaryData(
222    PipelineCommandEncoder* encoder,
223    IBufferResource* buffer,
224    Offset offset,
225    Size destSize,
226    ShaderObjectLayoutImpl* specializedLayout)
227{
228    auto src = m_data.getBuffer();
229    // TODO: Change size_t to Count?
230    auto srcSize = size_t(m_data.getCount());
231
232    SLANG_ASSERT(srcSize <= destSize);
233
234    encoder->uploadBufferDataImpl(buffer, offset, srcSize, src);
235
236    // In the case where this object has any sub-objects of
237    // existential/interface type, we need to recurse on those objects
238    // that need to write their state into an appropriate "pending" allocation.
239    //
240    // Note: Any values that could fit into the "payload" included
241    // in the existential-type field itself will have already been
242    // written as part of `setObject()`. This loop only needs to handle
243    // those sub-objects that do not "fit."
244    //
245    // An implementers looking at this code might wonder if things could be changed
246    // so that *all* writes related to sub-objects for interface-type fields could
247    // be handled in this one location, rather than having some in `setObject()` and
248    // others handled here.
249    //
250    Index subObjectRangeCounter = 0;
251    for (auto const& subObjectRangeInfo : specializedLayout->getSubObjectRanges())
252    {
253        Index subObjectRangeIndex = subObjectRangeCounter++;
254        auto const& bindingRangeInfo =
255            specializedLayout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
256
257        // We only need to handle sub-object ranges for interface/existential-type fields,
258        // because fields of constant-buffer or parameter-block type are responsible for
259        // the ordinary/uniform data of their own existential/interface-type sub-objects.
260        //
261        if (bindingRangeInfo.bindingType != slang::BindingType::ExistentialValue)
262            continue;
263
264        // Each sub-object range represents a single "leaf" field, but might be nested
265        // under zero or more outer arrays, such that the number of existential values
266        // in the same range can be one or more.
267        //
268        auto count = bindingRangeInfo.count;
269
270        // We are not concerned with the case where the existential value(s) in the range
271        // git into the payload part of the leaf field.
272        //
273        // In the case where the value didn't fit, the Slang layout strategy would have
274        // considered the requirements of the value as a "pending" allocation, and would
275        // allocate storage for the ordinary/uniform part of that pending allocation inside
276        // of the parent object's type layout.
277        //
278        // Here we assume that the Slang reflection API can provide us with a single byte
279        // offset and stride for the location of the pending data allocation in the
280        // specialized type layout, which will store the values for this sub-object range.
281        //
282        // TODO: The reflection API functions we are assuming here haven't been implemented
283        // yet, so the functions being called here are stubs.
284        //
285        // TODO: It might not be that a single sub-object range can reliably map to a single
286        // contiguous array with a single stride; we need to carefully consider what the
287        // layout logic does for complex cases with multiple layers of nested arrays and
288        // structures.
289        //
290        Offset subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
291        Size subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
292
293        // If the range doesn't actually need/use the "pending" allocation at all, then
294        // we need to detect that case and skip such ranges.
295        //
296        // TODO: This should probably be handled on a per-object basis by caching a "does it
297        // fit?" bit as part of the information for bound sub-objects, given that we already
298        // compute the "does it fit?" status as part of `setObject()`.
299        //
300        if (subObjectRangePendingDataOffset == 0)
301            continue;
302
303        for (Slang::Index i = 0; i < count; ++i)
304        {
305            auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i];
306
307            RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
308            SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef()));
309
310            auto subObjectOffset =
311                subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride;
312
313            subObject->_writeOrdinaryData(
314                encoder,
315                buffer,
316                offset + subObjectOffset,
317                destSize - subObjectOffset,
318                subObjectLayout);
319        }
320    }
321
322    return SLANG_OK;
323}
324
325void ShaderObjectImpl::writeDescriptor(
326    RootBindingContext& context,
327    VkWriteDescriptorSet const& write)
328{
329    auto device = context.device;
330    device->m_api.vkUpdateDescriptorSets(device->m_device, 1, &write, 0, nullptr);
331}
332
333void ShaderObjectImpl::writeBufferDescriptor(
334    RootBindingContext& context,
335    BindingOffset const& offset,
336    VkDescriptorType descriptorType,
337    BufferResourceImpl* buffer,
338    Offset bufferOffset,
339    Size bufferSize)
340{
341    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
342
343    VkDescriptorBufferInfo bufferInfo = {};
344    if (buffer)
345    {
346        bufferInfo.buffer = buffer->m_buffer.m_buffer;
347    }
348    bufferInfo.offset = bufferOffset;
349    bufferInfo.range = bufferSize;
350
351    VkWriteDescriptorSet write = {};
352    write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
353    write.descriptorCount = 1;
354    write.descriptorType = descriptorType;
355    write.dstArrayElement = 0;
356    write.dstBinding = offset.binding;
357    write.dstSet = descriptorSet;
358    write.pBufferInfo = &bufferInfo;
359
360    writeDescriptor(context, write);
361}
362
363void ShaderObjectImpl::writeBufferDescriptor(
364    RootBindingContext& context,
365    BindingOffset const& offset,
366    VkDescriptorType descriptorType,
367    BufferResourceImpl* buffer)
368{
369    writeBufferDescriptor(
370        context,
371        offset,
372        descriptorType,
373        buffer,
374        0,
375        buffer->getDesc()->sizeInBytes);
376}
377
378void ShaderObjectImpl::writePlainBufferDescriptor(
379    RootBindingContext& context,
380    BindingOffset const& offset,
381    VkDescriptorType descriptorType,
382    ArrayView<RefPtr<ResourceViewInternalBase>> resourceViews)
383{
384    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
385
386    Index count = resourceViews.getCount();
387    for (Index i = 0; i < count; ++i)
388    {
389        VkDescriptorBufferInfo bufferInfo = {};
390        bufferInfo.range = VK_WHOLE_SIZE;
391
392        if (resourceViews[i])
393        {
394            auto boundViewType = static_cast<ResourceViewImpl*>(resourceViews[i].Ptr())->m_type;
395            if (boundViewType == ResourceViewImpl::ViewType::PlainBuffer)
396            {
397                auto bufferView = static_cast<PlainBufferResourceViewImpl*>(resourceViews[i].Ptr());
398                bufferInfo.buffer = bufferView->m_buffer->m_buffer.m_buffer;
399                bufferInfo.offset = bufferView->offset;
400                bufferInfo.range = bufferView->size;
401            }
402        }
403
404        VkWriteDescriptorSet write = {};
405        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
406        write.descriptorCount = 1;
407        write.descriptorType = descriptorType;
408        write.dstArrayElement = uint32_t(i);
409        write.dstBinding = offset.binding;
410        write.dstSet = descriptorSet;
411        write.pBufferInfo = &bufferInfo;
412
413        writeDescriptor(context, write);
414    }
415}
416
417void ShaderObjectImpl::writeTexelBufferDescriptor(
418    RootBindingContext& context,
419    BindingOffset const& offset,
420    VkDescriptorType descriptorType,
421    ArrayView<RefPtr<ResourceViewInternalBase>> resourceViews)
422{
423    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
424
425    Index count = resourceViews.getCount();
426    for (Index i = 0; i < count; ++i)
427    {
428        VkBufferView bufferView = VK_NULL_HANDLE;
429        if (resourceViews[i])
430        {
431            auto boundViewType = static_cast<ResourceViewImpl*>(resourceViews[i].Ptr())->m_type;
432            if (boundViewType == ResourceViewImpl::ViewType::TexelBuffer)
433            {
434                auto resourceView =
435                    static_cast<TexelBufferResourceViewImpl*>(resourceViews[i].Ptr());
436                bufferView = resourceView->m_view;
437            }
438        }
439        VkWriteDescriptorSet write = {};
440        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
441        write.descriptorType = descriptorType;
442        write.dstArrayElement = uint32_t(i);
443        write.dstBinding = offset.binding;
444        write.dstSet = descriptorSet;
445        write.descriptorCount = 1;
446        write.pTexelBufferView = &bufferView;
447        writeDescriptor(context, write);
448    }
449}
450
451void ShaderObjectImpl::writeTextureSamplerDescriptor(
452    RootBindingContext& context,
453    BindingOffset const& offset,
454    VkDescriptorType descriptorType,
455    ArrayView<CombinedTextureSamplerSlot> slots)
456{
457    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
458
459    Index count = slots.getCount();
460    for (Index i = 0; i < count; ++i)
461    {
462        auto texture = slots[i].textureView;
463        auto sampler = slots[i].sampler;
464        VkDescriptorImageInfo imageInfo = {};
465        if (texture)
466        {
467            imageInfo.imageView = texture->m_view;
468            imageInfo.imageLayout = texture->m_layout;
469        }
470        if (sampler)
471        {
472            imageInfo.sampler = sampler->m_sampler;
473        }
474        else
475        {
476            imageInfo.sampler = context.device->m_defaultSampler;
477        }
478
479        VkWriteDescriptorSet write = {};
480        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
481        write.descriptorCount = 1;
482        write.descriptorType = descriptorType;
483        write.dstArrayElement = uint32_t(i);
484        write.dstBinding = offset.binding;
485        write.dstSet = descriptorSet;
486        write.pImageInfo = &imageInfo;
487
488        writeDescriptor(context, write);
489    }
490}
491
492void ShaderObjectImpl::writeAccelerationStructureDescriptor(
493    RootBindingContext& context,
494    BindingOffset const& offset,
495    VkDescriptorType descriptorType,
496    ArrayView<RefPtr<ResourceViewInternalBase>> resourceViews)
497{
498    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
499
500    Index count = resourceViews.getCount();
501    for (Index i = 0; i < count; ++i)
502    {
503        auto accelerationStructure =
504            static_cast<AccelerationStructureImpl*>(resourceViews[i].Ptr());
505        VkWriteDescriptorSetAccelerationStructureKHR writeAS = {};
506        writeAS.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR;
507        if (accelerationStructure)
508        {
509            writeAS.accelerationStructureCount = 1;
510            writeAS.pAccelerationStructures = &accelerationStructure->m_vkHandle;
511        }
512        else
513        {
514            // The Vulkan spec states: If the nullDescriptor feature is not enabled, each element of
515            // pAccelerationStructures must not be VK_NULL_HANDLE
516            SLANG_ASSERT(
517                context.device->m_api.m_extendedFeatures.robustness2Features.nullDescriptor);
518
519            static const VkAccelerationStructureKHR nullHandle = VK_NULL_HANDLE;
520            writeAS.accelerationStructureCount = 1;
521            writeAS.pAccelerationStructures = &nullHandle;
522        }
523        VkWriteDescriptorSet write = {};
524        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
525        write.descriptorCount = 1;
526        write.descriptorType = descriptorType;
527        write.dstArrayElement = uint32_t(i);
528        write.dstBinding = offset.binding;
529        write.dstSet = descriptorSet;
530        write.pNext = &writeAS;
531        writeDescriptor(context, write);
532    }
533}
534
535void ShaderObjectImpl::writeTextureDescriptor(
536    RootBindingContext& context,
537    BindingOffset const& offset,
538    VkDescriptorType descriptorType,
539    ArrayView<RefPtr<ResourceViewInternalBase>> resourceViews)
540{
541    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
542
543    Index count = resourceViews.getCount();
544    for (Index i = 0; i < count; ++i)
545    {
546        VkDescriptorImageInfo imageInfo = {};
547        if (resourceViews[i])
548        {
549            auto boundViewType = static_cast<ResourceViewImpl*>(resourceViews[i].Ptr())->m_type;
550            if (boundViewType == ResourceViewImpl::ViewType::Texture)
551            {
552                auto texture = static_cast<TextureResourceViewImpl*>(resourceViews[i].Ptr());
553                imageInfo.imageView = texture->m_view;
554                imageInfo.imageLayout = texture->m_layout;
555            }
556        }
557        imageInfo.sampler = 0;
558
559        VkWriteDescriptorSet write = {};
560        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
561        write.descriptorCount = 1;
562        write.descriptorType = descriptorType;
563        write.dstArrayElement = uint32_t(i);
564        write.dstBinding = offset.binding;
565        write.dstSet = descriptorSet;
566        write.pImageInfo = &imageInfo;
567
568        writeDescriptor(context, write);
569    }
570}
571
572void ShaderObjectImpl::writeSamplerDescriptor(
573    RootBindingContext& context,
574    BindingOffset const& offset,
575    VkDescriptorType descriptorType,
576    ArrayView<RefPtr<SamplerStateImpl>> samplers)
577{
578    auto descriptorSet = (*context.descriptorSets)[offset.bindingSet];
579
580    Index count = samplers.getCount();
581    for (Index i = 0; i < count; ++i)
582    {
583        auto sampler = samplers[i];
584        VkDescriptorImageInfo imageInfo = {};
585        imageInfo.imageView = 0;
586        imageInfo.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
587        if (sampler)
588        {
589            imageInfo.sampler = sampler->m_sampler;
590        }
591        else
592        {
593            imageInfo.sampler = context.device->m_defaultSampler;
594        }
595
596        VkWriteDescriptorSet write = {};
597        write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
598        write.descriptorCount = 1;
599        write.descriptorType = descriptorType;
600        write.dstArrayElement = uint32_t(i);
601        write.dstBinding = offset.binding;
602        write.dstSet = descriptorSet;
603        write.pImageInfo = &imageInfo;
604
605        writeDescriptor(context, write);
606    }
607}
608
609bool ShaderObjectImpl::shouldAllocateConstantBuffer(TransientResourceHeapImpl* transientHeap)
610{
611    return m_isConstantBufferDirty || m_constantBufferTransientHeap != transientHeap ||
612           m_constantBufferTransientHeapVersion != transientHeap->getVersion();
613}
614
615Result ShaderObjectImpl::_ensureOrdinaryDataBufferCreatedIfNeeded(
616    PipelineCommandEncoder* encoder,
617    ShaderObjectLayoutImpl* specializedLayout)
618{
619    // If data has been changed since last allocation/filling of constant buffer,
620    // we will need to allocate a new one.
621    //
622    if (!shouldAllocateConstantBuffer(encoder->m_commandBuffer->m_transientHeap))
623    {
624        return SLANG_OK;
625    }
626    m_isConstantBufferDirty = false;
627    m_constantBufferTransientHeap = encoder->m_commandBuffer->m_transientHeap;
628    m_constantBufferTransientHeapVersion = encoder->m_commandBuffer->m_transientHeap->getVersion();
629
630    m_constantBufferSize = specializedLayout->getTotalOrdinaryDataSize();
631    if (m_constantBufferSize == 0)
632    {
633        return SLANG_OK;
634    }
635
636    // Once we have computed how large the buffer should be, we can allocate
637    // it from the transient resource heap.
638    //
639    SLANG_RETURN_ON_FAIL(encoder->m_commandBuffer->m_transientHeap->allocateConstantBuffer(
640        m_constantBufferSize,
641        m_constantBuffer,
642        m_constantBufferOffset));
643
644    // Once the buffer is allocated, we can use `_writeOrdinaryData` to fill it in.
645    //
646    // Note that `_writeOrdinaryData` is potentially recursive in the case
647    // where this object contains interface/existential-type fields, so we
648    // don't need or want to inline it into this call site.
649    //
650    SLANG_RETURN_ON_FAIL(_writeOrdinaryData(
651        encoder,
652        m_constantBuffer,
653        m_constantBufferOffset,
654        m_constantBufferSize,
655        specializedLayout));
656
657    return SLANG_OK;
658}
659
660Result ShaderObjectImpl::bindAsValue(
661    PipelineCommandEncoder* encoder,
662    RootBindingContext& context,
663    BindingOffset const& offset,
664    ShaderObjectLayoutImpl* specializedLayout)
665{
666    // We start by iterating over the "simple" (non-sub-object) binding
667    // ranges and writing them to the descriptor sets that are being
668    // passed down.
669    //
670    for (auto bindingRangeInfo : specializedLayout->getBindingRanges())
671    {
672        BindingOffset rangeOffset = offset;
673
674        auto baseIndex = bindingRangeInfo.baseIndex;
675        auto count = (uint32_t)bindingRangeInfo.count;
676        switch (bindingRangeInfo.bindingType)
677        {
678        case slang::BindingType::ConstantBuffer:
679        case slang::BindingType::ParameterBlock:
680        case slang::BindingType::ExistentialValue:
681            break;
682
683        case slang::BindingType::Texture:
684            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
685            rangeOffset.binding += bindingRangeInfo.bindingOffset;
686            writeTextureDescriptor(
687                context,
688                rangeOffset,
689                VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE,
690                m_resourceViews.getArrayView(baseIndex, count));
691            break;
692        case slang::BindingType::MutableTexture:
693            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
694            rangeOffset.binding += bindingRangeInfo.bindingOffset;
695            writeTextureDescriptor(
696                context,
697                rangeOffset,
698                VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
699                m_resourceViews.getArrayView(baseIndex, count));
700            break;
701        case slang::BindingType::CombinedTextureSampler:
702            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
703            rangeOffset.binding += bindingRangeInfo.bindingOffset;
704            writeTextureSamplerDescriptor(
705                context,
706                rangeOffset,
707                VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
708                m_combinedTextureSamplers.getArrayView(baseIndex, count));
709            break;
710
711        case slang::BindingType::Sampler:
712            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
713            rangeOffset.binding += bindingRangeInfo.bindingOffset;
714            writeSamplerDescriptor(
715                context,
716                rangeOffset,
717                VK_DESCRIPTOR_TYPE_SAMPLER,
718                m_samplers.getArrayView(baseIndex, count));
719            break;
720
721        case slang::BindingType::RawBuffer:
722        case slang::BindingType::MutableRawBuffer:
723            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
724            rangeOffset.binding += bindingRangeInfo.bindingOffset;
725            writePlainBufferDescriptor(
726                context,
727                rangeOffset,
728                VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
729                m_resourceViews.getArrayView(baseIndex, count));
730            break;
731
732        case slang::BindingType::TypedBuffer:
733            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
734            rangeOffset.binding += bindingRangeInfo.bindingOffset;
735            writeTexelBufferDescriptor(
736                context,
737                rangeOffset,
738                VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER,
739                m_resourceViews.getArrayView(baseIndex, count));
740            break;
741        case slang::BindingType::MutableTypedBuffer:
742            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
743            rangeOffset.binding += bindingRangeInfo.bindingOffset;
744            writeTexelBufferDescriptor(
745                context,
746                rangeOffset,
747                VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER,
748                m_resourceViews.getArrayView(baseIndex, count));
749            break;
750        case slang::BindingType::RayTracingAccelerationStructure:
751            rangeOffset.bindingSet += bindingRangeInfo.setOffset;
752            rangeOffset.binding += bindingRangeInfo.bindingOffset;
753            writeAccelerationStructureDescriptor(
754                context,
755                rangeOffset,
756                VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR,
757                m_resourceViews.getArrayView(baseIndex, count));
758            break;
759        case slang::BindingType::VaryingInput:
760        case slang::BindingType::VaryingOutput:
761            break;
762
763        default:
764            SLANG_ASSERT(!"unsupported binding type");
765            return SLANG_FAIL;
766            break;
767        }
768    }
769
770    // Once we've handled the simple binding ranges, we move on to the
771    // sub-object ranges, which are generally more involved.
772    //
773    for (auto const& subObjectRange : specializedLayout->getSubObjectRanges())
774    {
775        auto const& bindingRangeInfo =
776            specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex);
777        auto count = bindingRangeInfo.count;
778        auto subObjectIndex = bindingRangeInfo.subObjectIndex;
779
780        auto subObjectLayout = subObjectRange.layout;
781
782        // The starting offset to use for the sub-object
783        // has already been computed and stored as part
784        // of the layout, so we can get to the starting
785        // offset for the range easily.
786        //
787        BindingOffset rangeOffset = offset;
788        rangeOffset += subObjectRange.offset;
789
790        BindingOffset rangeStride = subObjectRange.stride;
791
792        switch (bindingRangeInfo.bindingType)
793        {
794        case slang::BindingType::ConstantBuffer:
795            {
796                BindingOffset objOffset = rangeOffset;
797                for (Index i = 0; i < count; ++i)
798                {
799                    // Binding a constant buffer sub-object is simple enough:
800                    // we just call `bindAsConstantBuffer` on it to bind
801                    // the ordinary data buffer (if needed) and any other
802                    // bindings it recursively contains.
803                    //
804                    ShaderObjectImpl* subObject = m_objects[subObjectIndex + i];
805                    subObject->bindAsConstantBuffer(encoder, context, objOffset, subObjectLayout);
806
807                    // When dealing with arrays of sub-objects, we need to make
808                    // sure to increment the offset for each subsequent object
809                    // by the appropriate stride.
810                    //
811                    objOffset += rangeStride;
812                }
813            }
814            break;
815        case slang::BindingType::ParameterBlock:
816            {
817                BindingOffset objOffset = rangeOffset;
818                for (Index i = 0; i < count; ++i)
819                {
820                    // The case for `ParameterBlock<X>` is not that different
821                    // from `ConstantBuffer<X>`, except that we call `bindAsParameterBlock`
822                    // instead (understandably).
823                    //
824                    ShaderObjectImpl* subObject = m_objects[subObjectIndex + i];
825                    subObject->bindAsParameterBlock(encoder, context, objOffset, subObjectLayout);
826                }
827            }
828            break;
829
830        case slang::BindingType::ExistentialValue:
831            // Interface/existential-type sub-object ranges are the most complicated case.
832            //
833            // First, we can only bind things if we have static specialization information
834            // to work with, which is exactly the case where `subObjectLayout` will be
835            // non-null.
836            //
837            if (subObjectLayout)
838            {
839                // Second, the offset where we want to start binding for existential-type
840                // ranges is a bit different, because we don't wnat to bind at the "primary"
841                // offset that got passed down, but instead at the "pending" offset.
842                //
843                // For the purposes of nested binding, what used to be the pending offset
844                // will now be used as the primary offset.
845                //
846                SimpleBindingOffset objOffset = rangeOffset.pending;
847                SimpleBindingOffset objStride = rangeStride.pending;
848                for (Index i = 0; i < count; ++i)
849                {
850                    // An existential-type sub-object is always bound just as a value,
851                    // which handles its nested bindings and descriptor sets, but
852                    // does not deal with ordianry data. The ordinary data should
853                    // have been handled as part of the buffer for a parent object
854                    // already.
855                    //
856                    ShaderObjectImpl* subObject = m_objects[subObjectIndex + i];
857                    subObject
858                        ->bindAsValue(encoder, context, BindingOffset(objOffset), subObjectLayout);
859                    objOffset += objStride;
860                }
861            }
862            break;
863        case slang::BindingType::RawBuffer:
864        case slang::BindingType::MutableRawBuffer:
865            // No action needed for sub-objects bound though a `StructuredBuffer`.
866            break;
867        default:
868            SLANG_ASSERT(!"unsupported sub-object type");
869            return SLANG_FAIL;
870            break;
871        }
872    }
873
874    return SLANG_OK;
875}
876
877Result ShaderObjectImpl::allocateDescriptorSets(
878    PipelineCommandEncoder* encoder,
879    RootBindingContext& context,
880    BindingOffset const& offset,
881    ShaderObjectLayoutImpl* specializedLayout)
882{
883    assert(specializedLayout->getOwnDescriptorSets().getCount() <= 1);
884    // The number of sets to allocate and their layouts was already pre-computed
885    // as part of the shader object layout, so we use that information here.
886    //
887    for (auto descriptorSetInfo : specializedLayout->getOwnDescriptorSets())
888    {
889        auto descriptorSetHandle =
890            context.descriptorSetAllocator->allocate(descriptorSetInfo.descriptorSetLayout).handle;
891
892        // For each set, we need to write it into the set of descriptor sets
893        // being used for binding. This is done both so that other steps
894        // in binding can find the set to fill it in, but also so that
895        // we can bind all the descriptor sets to the pipeline when the
896        // time comes.
897        //
898        (*context.descriptorSets).add(descriptorSetHandle);
899    }
900
901    return SLANG_OK;
902}
903
904Result ShaderObjectImpl::bindAsParameterBlock(
905    PipelineCommandEncoder* encoder,
906    RootBindingContext& context,
907    BindingOffset const& inOffset,
908    ShaderObjectLayoutImpl* specializedLayout)
909{
910    // Because we are binding into a nested parameter block,
911    // any texture/buffer/sampler bindings will now want to
912    // write into the sets we allocate for this object and
913    // not the sets for any parent object(s).
914    //
915    BindingOffset offset = inOffset;
916    offset.bindingSet = (uint32_t)context.descriptorSets->getCount();
917    offset.binding = 0;
918
919    // TODO: We should also be writing to `offset.pending` here,
920    // because any resource/sampler bindings related to "pending"
921    // data should *also* be writing into the chosen set.
922    //
923    // The challenge here is that we need to compute the right
924    // value for `offset.pending.binding`, so that it writes after
925    // all the other bindings.
926
927    // Writing the bindings for a parameter block is relatively easy:
928    // we just need to allocate the descriptor set(s) needed for this
929    // object and then fill it in like a `ConstantBuffer<X>`.
930    //
931    SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, specializedLayout));
932
933    assert(offset.bindingSet < (uint32_t)context.descriptorSets->getCount());
934    SLANG_RETURN_ON_FAIL(bindAsConstantBuffer(encoder, context, offset, specializedLayout));
935
936    return SLANG_OK;
937}
938
939Result ShaderObjectImpl::bindOrdinaryDataBufferIfNeeded(
940    PipelineCommandEncoder* encoder,
941    RootBindingContext& context,
942    BindingOffset& ioOffset,
943    ShaderObjectLayoutImpl* specializedLayout)
944{
945    // We start by ensuring that the buffer is created, if it is needed.
946    //
947    SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(encoder, specializedLayout));
948
949    // If we did indeed need/create a buffer, then we must bind it into
950    // the given `descriptorSet` and update the base range index for
951    // subsequent binding operations to account for it.
952    //
953    if (m_constantBuffer && m_constantBufferSize > 0)
954    {
955        auto bufferImpl = static_cast<BufferResourceImpl*>(m_constantBuffer);
956        writeBufferDescriptor(
957            context,
958            ioOffset,
959            VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
960            bufferImpl,
961            m_constantBufferOffset,
962            m_constantBufferSize);
963        ioOffset.binding++;
964    }
965
966    return SLANG_OK;
967}
968
969Result ShaderObjectImpl::bindAsConstantBuffer(
970    PipelineCommandEncoder* encoder,
971    RootBindingContext& context,
972    BindingOffset const& inOffset,
973    ShaderObjectLayoutImpl* specializedLayout)
974{
975    // To bind an object as a constant buffer, we first
976    // need to bind its ordinary data (if any) into an
977    // ordinary data buffer, and then bind it as a "value"
978    // which handles any of its recursively-contained bindings.
979    //
980    // The one detail is taht when binding the ordinary data
981    // buffer we need to adjust the `binding` index used for
982    // subsequent operations based on whether or not an ordinary
983    // data buffer was used (and thus consumed a `binding`).
984    //
985    BindingOffset offset = inOffset;
986    SLANG_RETURN_ON_FAIL(
987        bindOrdinaryDataBufferIfNeeded(encoder, context, /*inout*/ offset, specializedLayout));
988    SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, specializedLayout));
989    return SLANG_OK;
990}
991
992Result ShaderObjectImpl::_getSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
993{
994    if (!m_specializedLayout)
995    {
996        SLANG_RETURN_ON_FAIL(_createSpecializedLayout(m_specializedLayout.writeRef()));
997    }
998    returnRefPtr(outLayout, m_specializedLayout);
999    return SLANG_OK;
1000}
1001
1002Result ShaderObjectImpl::_createSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
1003{
1004    ExtendedShaderObjectType extendedType;
1005    SLANG_RETURN_ON_FAIL(getSpecializedShaderObjectType(&extendedType));
1006
1007    auto device = getDevice();
1008    RefPtr<ShaderObjectLayoutImpl> layout;
1009    SLANG_RETURN_ON_FAIL(device->getShaderObjectLayout(
1010        m_layout->m_slangSession,
1011        extendedType.slangType,
1012        m_layout->getContainerType(),
1013        (ShaderObjectLayoutBase**)layout.writeRef()));
1014
1015    returnRefPtrMove(outLayout, layout);
1016    return SLANG_OK;
1017}
1018
1019Result EntryPointShaderObject::create(
1020    IDevice* device,
1021    EntryPointLayout* layout,
1022    EntryPointShaderObject** outShaderObject)
1023{
1024    RefPtr<EntryPointShaderObject> object = new EntryPointShaderObject();
1025    SLANG_RETURN_ON_FAIL(object->init(device, layout));
1026
1027    returnRefPtrMove(outShaderObject, object);
1028    return SLANG_OK;
1029}
1030
1031EntryPointLayout* EntryPointShaderObject::getLayout()
1032{
1033    return static_cast<EntryPointLayout*>(m_layout.Ptr());
1034}
1035
1036Result EntryPointShaderObject::bindAsEntryPoint(
1037    PipelineCommandEncoder* encoder,
1038    RootBindingContext& context,
1039    BindingOffset const& inOffset,
1040    EntryPointLayout* layout)
1041{
1042    BindingOffset offset = inOffset;
1043
1044    // Any ordinary data in an entry point is assumed to be allocated
1045    // as a push-constant range.
1046    //
1047    // TODO: Can we make this operation not bake in that assumption?
1048    //
1049    // TODO: Can/should this function be renamed as just `bindAsPushConstantBuffer`?
1050    //
1051    if (m_data.getCount())
1052    {
1053        // The index of the push constant range to bind should be
1054        // passed down as part of the `offset`, and we will increment
1055        // it here so that any further recursively-contained push-constant
1056        // ranges use the next index.
1057        //
1058        auto pushConstantRangeIndex = offset.pushConstantRange++;
1059
1060        // Information about the push constant ranges (including offsets
1061        // and stage flags) was pre-computed for the entire program and
1062        // stored on the binding context.
1063        //
1064        auto const& pushConstantRange = context.pushConstantRanges[pushConstantRangeIndex];
1065
1066        // We expect that the size of the range as reflected matches the
1067        // amount of ordinary data stored on this object.
1068        //
1069        // TODO: This would not be the case if specialization for interface-type
1070        // parameters led to the entry point having "pending" ordinary data.
1071        //
1072        SLANG_ASSERT(pushConstantRange.size == (uint32_t)m_data.getCount());
1073
1074        auto pushConstantData = m_data.getBuffer();
1075
1076        encoder->m_api->vkCmdPushConstants(
1077            encoder->m_commandBuffer->m_commandBuffer,
1078            context.pipelineLayout,
1079            pushConstantRange.stageFlags,
1080            pushConstantRange.offset,
1081            pushConstantRange.size,
1082            pushConstantData);
1083    }
1084
1085    // Any remaining bindings in the object can be handled through the
1086    // "value" case.
1087    //
1088    SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
1089    return SLANG_OK;
1090}
1091
1092Result EntryPointShaderObject::init(IDevice* device, EntryPointLayout* layout)
1093{
1094    SLANG_RETURN_ON_FAIL(Super::init(device, layout));
1095    return SLANG_OK;
1096}
1097
1098RootShaderObjectLayout* RootShaderObjectImpl::getLayout()
1099{
1100    return static_cast<RootShaderObjectLayout*>(m_layout.Ptr());
1101}
1102
1103RootShaderObjectLayout* RootShaderObjectImpl::getSpecializedLayout()
1104{
1105    RefPtr<ShaderObjectLayoutImpl> specializedLayout;
1106    _getSpecializedLayout(specializedLayout.writeRef());
1107    return static_cast<RootShaderObjectLayout*>(m_specializedLayout.Ptr());
1108}
1109
1110List<RefPtr<EntryPointShaderObject>> const& RootShaderObjectImpl::getEntryPoints() const
1111{
1112    return m_entryPoints;
1113}
1114
1115GfxCount RootShaderObjectImpl::getEntryPointCount()
1116{
1117    return (GfxCount)m_entryPoints.getCount();
1118}
1119
1120Result RootShaderObjectImpl::getEntryPoint(GfxIndex index, IShaderObject** outEntryPoint)
1121{
1122    returnComPtr(outEntryPoint, m_entryPoints[index]);
1123    return SLANG_OK;
1124}
1125
1126Result RootShaderObjectImpl::copyFrom(IShaderObject* object, ITransientResourceHeap* transientHeap)
1127{
1128    SLANG_RETURN_ON_FAIL(Super::copyFrom(object, transientHeap));
1129    if (auto srcObj = dynamic_cast<MutableRootShaderObject*>(object))
1130    {
1131        for (Index i = 0; i < srcObj->m_entryPoints.getCount(); i++)
1132        {
1133            m_entryPoints[i]->copyFrom(srcObj->m_entryPoints[i], transientHeap);
1134        }
1135        return SLANG_OK;
1136    }
1137    return SLANG_FAIL;
1138}
1139
1140Result RootShaderObjectImpl::bindAsRoot(
1141    PipelineCommandEncoder* encoder,
1142    RootBindingContext& context,
1143    RootShaderObjectLayout* layout)
1144{
1145    BindingOffset offset = {};
1146    offset.pending = layout->getPendingDataOffset();
1147
1148    // Note: the operations here are quite similar to what `bindAsParameterBlock` does.
1149    // The key difference in practice is that we do *not* make use of the adjustment
1150    // that `bindOrdinaryDataBufferIfNeeded` applied to the offset passed into it.
1151    //
1152    // The reason for this difference in behavior is that the layout information
1153    // for root shader parameters is in practice *already* offset appropriately
1154    // (so that it ends up using absolute offsets).
1155    //
1156    // TODO: One more wrinkle here is that the `ordinaryDataBufferOffset` below
1157    // might not be correct if `binding=0,set=0` was already claimed via explicit
1158    // binding information. We should really be getting the offset information for
1159    // the ordinary data buffer directly from the reflection information for
1160    // the global scope.
1161
1162    SLANG_RETURN_ON_FAIL(allocateDescriptorSets(encoder, context, offset, layout));
1163
1164    BindingOffset ordinaryDataBufferOffset = offset;
1165    SLANG_RETURN_ON_FAIL(
1166        bindOrdinaryDataBufferIfNeeded(encoder, context, ordinaryDataBufferOffset, layout));
1167
1168    SLANG_RETURN_ON_FAIL(bindAsValue(encoder, context, offset, layout));
1169
1170    auto entryPointCount = layout->getEntryPoints().getCount();
1171    for (Index i = 0; i < entryPointCount; ++i)
1172    {
1173        auto entryPoint = m_entryPoints[i];
1174        auto const& entryPointInfo = layout->getEntryPoint(i);
1175
1176        // Note: we do *not* need to add the entry point offset
1177        // information to the global `offset` because the
1178        // `RootShaderObjectLayout` has already baked any offsets
1179        // from the global layout into the `entryPointInfo`.
1180
1181        entryPoint
1182            ->bindAsEntryPoint(encoder, context, entryPointInfo.offset, entryPointInfo.layout);
1183    }
1184
1185    return SLANG_OK;
1186}
1187
1188Result RootShaderObjectImpl::collectSpecializationArgs(ExtendedShaderObjectTypeList& args)
1189{
1190    SLANG_RETURN_ON_FAIL(ShaderObjectImpl::collectSpecializationArgs(args));
1191    for (auto& entryPoint : m_entryPoints)
1192    {
1193        SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args));
1194    }
1195    return SLANG_OK;
1196}
1197
1198Result RootShaderObjectImpl::init(IDevice* device, RootShaderObjectLayout* layout)
1199{
1200    SLANG_RETURN_ON_FAIL(Super::init(device, layout));
1201    m_specializedLayout = nullptr;
1202    m_entryPoints.clear();
1203    for (auto entryPointInfo : layout->getEntryPoints())
1204    {
1205        RefPtr<EntryPointShaderObject> entryPoint;
1206        SLANG_RETURN_ON_FAIL(
1207            EntryPointShaderObject::create(device, entryPointInfo.layout, entryPoint.writeRef()));
1208        m_entryPoints.add(entryPoint);
1209    }
1210
1211    return SLANG_OK;
1212}
1213
1214Result RootShaderObjectImpl::_createSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
1215{
1216    ExtendedShaderObjectTypeList specializationArgs;
1217    SLANG_RETURN_ON_FAIL(collectSpecializationArgs(specializationArgs));
1218
1219    // Note: There is an important policy decision being made here that we need
1220    // to approach carefully.
1221    //
1222    // We are doing two different things that affect the layout of a program:
1223    //
1224    // 1. We are *composing* one or more pieces of code (notably the shared global/module
1225    //    stuff and the per-entry-point stuff).
1226    //
1227    // 2. We are *specializing* code that includes generic/existential parameters
1228    //    to concrete types/values.
1229    //
1230    // We need to decide the relative *order* of these two steps, because of how it impacts
1231    // layout. The layout for `specialize(compose(A,B), X, Y)` is potentially different
1232    // form that of `compose(specialize(A,X), speciealize(B,Y))`, even when both are
1233    // semantically equivalent programs.
1234    //
1235    // Right now we are using the first option: we are first generating a full composition
1236    // of all the code we plan to use (global scope plus all entry points), and then
1237    // specializing it to the concatenated specialization argumenst for all of that.
1238    //
1239    // In some cases, though, this model isn't appropriate. For example, when dealing with
1240    // ray-tracing shaders and local root signatures, we really want the parameters of each
1241    // entry point (actually, each entry-point *group*) to be allocated distinct storage,
1242    // which really means we want to compute something like:
1243    //
1244    //      SpecializedGlobals = specialize(compose(ModuleA, ModuleB, ...), X, Y, ...)
1245    //
1246    //      SpecializedEP1 = compose(SpecializedGlobals, specialize(EntryPoint1, T, U, ...))
1247    //      SpecializedEP2 = compose(SpecializedGlobals, specialize(EntryPoint2, A, B, ...))
1248    //
1249    // Note how in this case all entry points agree on the layout for the shared/common
1250    // parmaeters, but their layouts are also independent of one another.
1251    //
1252    // Furthermore, in this example, loading another entry point into the system would not
1253    // rquire re-computing the layouts (or generated kernel code) for any of the entry
1254    // points that had already been loaded (in contrast to a compose-then-specialize
1255    // approach).
1256    //
1257    ComPtr<slang::IComponentType> specializedComponentType;
1258    ComPtr<slang::IBlob> diagnosticBlob;
1259    auto result = getLayout()->getSlangProgram()->specialize(
1260        specializationArgs.components.getArrayView().getBuffer(),
1261        specializationArgs.getCount(),
1262        specializedComponentType.writeRef(),
1263        diagnosticBlob.writeRef());
1264
1265    // TODO: print diagnostic message via debug output interface.
1266
1267    if (result != SLANG_OK)
1268        return result;
1269
1270    auto slangSpecializedLayout = specializedComponentType->getLayout();
1271    RefPtr<RootShaderObjectLayout> specializedLayout;
1272    RootShaderObjectLayout::create(
1273        static_cast<DeviceImpl*>(getRenderer()),
1274        specializedComponentType,
1275        slangSpecializedLayout,
1276        specializedLayout.writeRef());
1277
1278    // Note: Computing the layout for the specialized program will have also computed
1279    // the layouts for the entry points, and we really need to attach that information
1280    // to them so that they don't go and try to compute their own specializations.
1281    //
1282    // TODO: Well, if we move to the specialization model described above then maybe
1283    // we *will* want entry points to do their own specialization work...
1284    //
1285    auto entryPointCount = m_entryPoints.getCount();
1286    for (Index i = 0; i < entryPointCount; ++i)
1287    {
1288        auto entryPointInfo = specializedLayout->getEntryPoint(i);
1289        auto entryPointVars = m_entryPoints[i];
1290
1291        entryPointVars->m_specializedLayout = entryPointInfo.layout;
1292    }
1293
1294    returnRefPtrMove(outLayout, specializedLayout);
1295    return SLANG_OK;
1296}
1297
1298} // namespace vk
1299} // namespace gfx