yum-mirror/slang

Making it easier to work with shaders

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

kaizhangNVImplement parameter block to slang-gfx for Metal backend (#6577)ac0dc491e

master
30.9 KiB810 linesraw
1// metal-shader-object.cpp
2#include "metal-shader-object.h"
3
4#include "metal-device.h"
5#include "metal-sampler.h"
6
7namespace gfx
8{
9
10using namespace Slang;
11
12namespace metal
13{
14
15Result ShaderObjectImpl::create(
16    IDevice* device,
17    ShaderObjectLayoutImpl* layout,
18    ShaderObjectImpl** outShaderObject)
19{
20    auto object = RefPtr<ShaderObjectImpl>(new ShaderObjectImpl());
21    SLANG_RETURN_ON_FAIL(object->init(device, layout));
22
23    returnRefPtrMove(outShaderObject, object);
24    return SLANG_OK;
25}
26
27ShaderObjectImpl::~ShaderObjectImpl() {}
28
29SLANG_NO_THROW Result SLANG_MCALL
30ShaderObjectImpl::setData(ShaderOffset const& inOffset, void const* data, size_t inSize)
31{
32    Index offset = inOffset.uniformOffset;
33    Index size = inSize;
34
35    char* dest = m_data.getBuffer();
36    Index availableSize = m_data.getCount();
37
38    // TODO: We really should bounds-check access rather than silently ignoring sets
39    // that are too large, but we have several test cases that set more data than
40    // an object actually stores on several targets...
41    //
42    if (offset < 0)
43    {
44        size += offset;
45        offset = 0;
46    }
47    if ((offset + size) >= availableSize)
48    {
49        size = availableSize - offset;
50    }
51
52    memcpy(dest + offset, data, size);
53
54    m_isConstantBufferDirty = true;
55    m_isArgumentBufferDirty = true;
56    return SLANG_OK;
57}
58
59SLANG_NO_THROW Result SLANG_MCALL
60ShaderObjectImpl::setResource(ShaderOffset const& offset, IResourceView* resourceView)
61{
62    if (offset.bindingRangeIndex < 0)
63        return SLANG_E_INVALID_ARG;
64    auto layout = getLayout();
65    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
66        return SLANG_E_INVALID_ARG;
67    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
68
69    auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView);
70    switch (bindingRange.bindingType)
71    {
72    case slang::BindingType::Texture:
73    case slang::BindingType::MutableTexture:
74        SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::ViewType::Texture);
75        m_textures[bindingRange.baseIndex + offset.bindingArrayIndex] =
76            static_cast<TextureResourceViewImpl*>(resourceView);
77
78        // For parameter blocks, we just need to set the resource ID of the texture to argument
79        // buffer
80        if (getLayout()->isParameterBlock())
81        {
82            auto resourceId =
83                static_cast<TextureResourceViewImpl*>(resourceView)->m_textureView->gpuResourceID();
84            setData(offset, &resourceId, sizeof(resourceId));
85        }
86        break;
87    case slang::BindingType::RawBuffer:
88    case slang::BindingType::ConstantBuffer:
89    case slang::BindingType::MutableRawBuffer:
90        SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::ViewType::Buffer);
91        m_buffers[bindingRange.baseIndex + offset.bindingArrayIndex] =
92            static_cast<BufferResourceViewImpl*>(resourceView);
93
94        // For parameter blocks, we just need to set the GPU address of the buffer to argument
95        // buffer
96        if (getLayout()->isParameterBlock())
97        {
98            DeviceAddress gpuAddress =
99                static_cast<BufferResourceViewImpl*>(resourceView)->m_buffer->getDeviceAddress();
100            setData(offset, &gpuAddress, sizeof(gpuAddress));
101        }
102        break;
103    case slang::BindingType::TypedBuffer:
104    case slang::BindingType::MutableTypedBuffer:
105        SLANG_ASSERT(!"Not implemented");
106        // SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::ViewType::TexelBuffer);
107        // m_textures[bindingRange.baseIndex + offset.bindingArrayIndex] =
108        // static_cast<TextureResourceViewImpl*>(resourceView);
109        break;
110    }
111    m_isArgumentBufferDirty = true;
112    return SLANG_OK;
113}
114
115SLANG_NO_THROW Result SLANG_MCALL
116ShaderObjectImpl::setSampler(ShaderOffset const& offset, ISamplerState* sampler)
117{
118    if (offset.bindingRangeIndex < 0)
119        return SLANG_E_INVALID_ARG;
120    auto layout = getLayout();
121    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
122        return SLANG_E_INVALID_ARG;
123    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
124
125    m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] =
126        static_cast<SamplerStateImpl*>(sampler);
127
128    // For parameter blocks, we just need to set the GPU address of the buffer to argument buffer
129    if (layout->isParameterBlock())
130    {
131        auto resourceId = static_cast<SamplerStateImpl*>(sampler)->m_samplerState->gpuResourceID();
132        setData(offset, &resourceId, sizeof(resourceId));
133    }
134    m_isArgumentBufferDirty = true;
135    return SLANG_OK;
136}
137
138Result ShaderObjectImpl::init(IDevice* device, ShaderObjectLayoutImpl* layout)
139{
140    m_layout = layout;
141
142    // If the layout tells us that there is any uniform data,
143    // then we will allocate a CPU memory buffer to hold that data
144    // while it is being set from the host.
145    //
146    // Once the user is done setting the parameters/fields of this
147    // shader object, we will produce a GPU-memory version of the
148    // uniform data (which includes values from this object and
149    // any existential-type sub-objects).
150    //
151    size_t uniformSize = 0;
152    if (layout->isParameterBlock())
153        uniformSize = layout->getParameterBlockTypeLayout()->getSize();
154    else
155        uniformSize = layout->getElementTypeLayout()->getSize();
156
157    if (uniformSize)
158    {
159        m_data.setCount(uniformSize);
160        memset(m_data.getBuffer(), 0, uniformSize);
161    }
162
163    m_buffers.setCount(layout->getBufferCount());
164    m_textures.setCount(layout->getTextureCount());
165    m_samplers.setCount(layout->getSamplerCount());
166
167    // If the layout specifies that we have any sub-objects, then
168    // we need to size the array to account for them.
169    //
170    Index subObjectCount = layout->getSubObjectCount();
171    m_objects.setCount(subObjectCount);
172
173    for (auto subObjectRangeInfo : layout->getSubObjectRanges())
174    {
175        auto subObjectLayout = subObjectRangeInfo.layout;
176
177        // In the case where the sub-object range represents an
178        // existential-type leaf field (e.g., an `IBar`), we
179        // cannot pre-allocate the object(s) to go into that
180        // range, since we can't possibly know what to allocate
181        // at this point.
182        //
183        if (!subObjectLayout)
184            continue;
185        //
186        // Otherwise, we will allocate a sub-object to fill
187        // in each entry in this range, based on the layout
188        // information we already have.
189
190        auto& bindingRangeInfo = layout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
191        for (Index i = 0; i < bindingRangeInfo.count; ++i)
192        {
193            RefPtr<ShaderObjectImpl> subObject;
194
195            if (bindingRangeInfo.bindingType == slang::BindingType::ParameterBlock ||
196                bindingRangeInfo.bindingType == slang::BindingType::ConstantBuffer)
197                subObjectLayout->setIsParameterBlock();
198
199            SLANG_RETURN_ON_FAIL(
200                ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef()));
201            m_objects[bindingRangeInfo.subObjectIndex + i] = subObject;
202        }
203    }
204    m_isArgumentBufferDirty = true;
205    return SLANG_OK;
206}
207
208Result ShaderObjectImpl::_writeOrdinaryData(
209    void* dest,
210    size_t destSize,
211    ShaderObjectLayoutImpl* layout)
212{
213    // We start by simply writing in the ordinary data contained directly in this object.
214    //
215    auto src = m_data.getBuffer();
216    auto srcSize = size_t(m_data.getCount());
217    SLANG_ASSERT(srcSize <= destSize);
218    memcpy(dest, src, srcSize);
219
220    // In the case where this object has any sub-objects of
221    // existential/interface type, we need to recurse on those objects
222    // that need to write their state into an appropriate "pending" allocation.
223    //
224    // Note: Any values that could fit into the "payload" included
225    // in the existential-type field itself will have already been
226    // written as part of `setObject()`. This loop only needs to handle
227    // those sub-objects that do not "fit."
228    //
229    // An implementers looking at this code might wonder if things could be changed
230    // so that *all* writes related to sub-objects for interface-type fields could
231    // be handled in this one location, rather than having some in `setObject()` and
232    // others handled here.
233    //
234    Index subObjectRangeCounter = 0;
235    for (auto const& subObjectRangeInfo : layout->getSubObjectRanges())
236    {
237        Index subObjectRangeIndex = subObjectRangeCounter++;
238        auto const& bindingRangeInfo =
239            layout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
240
241        // We only need to handle sub-object ranges for interface/existential-type fields,
242        // because fields of constant-buffer or parameter-block type are responsible for
243        // the ordinary/uniform data of their own existential/interface-type sub-objects.
244        //
245        if (bindingRangeInfo.bindingType != slang::BindingType::ExistentialValue)
246            continue;
247
248        // Each sub-object range represents a single "leaf" field, but might be nested
249        // under zero or more outer arrays, such that the number of existential values
250        // in the same range can be one or more.
251        //
252        auto count = bindingRangeInfo.count;
253
254        // We are not concerned with the case where the existential value(s) in the range
255        // git into the payload part of the leaf field.
256        //
257        // In the case where the value didn't fit, the Slang layout strategy would have
258        // considered the requirements of the value as a "pending" allocation, and would
259        // allocate storage for the ordinary/uniform part of that pending allocation inside
260        // of the parent object's type layout.
261        //
262        // Here we assume that the Slang reflection API can provide us with a single byte
263        // offset and stride for the location of the pending data allocation in the specialized
264        // type layout, which will store the values for this sub-object range.
265        //
266        // TODO: The reflection API functions we are assuming here haven't been implemented
267        // yet, so the functions being called here are stubs.
268        //
269        // TODO: It might not be that a single sub-object range can reliably map to a single
270        // contiguous array with a single stride; we need to carefully consider what the layout
271        // logic does for complex cases with multiple layers of nested arrays and structures.
272        //
273        size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
274        size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
275
276        // If the range doesn't actually need/use the "pending" allocation at all, then
277        // we need to detect that case and skip such ranges.
278        //
279        // TODO: This should probably be handled on a per-object basis by caching a "does it fit?"
280        // bit as part of the information for bound sub-objects, given that we already
281        // compute the "does it fit?" status as part of `setObject()`.
282        //
283        if (subObjectRangePendingDataOffset == 0)
284            continue;
285
286        for (Slang::Index i = 0; i < count; ++i)
287        {
288            auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i];
289
290            ShaderObjectLayoutImpl* subObjectLayout = subObject->getLayout();
291
292            auto subObjectOffset =
293                subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride;
294
295            auto subObjectDest = (char*)dest + subObjectOffset;
296
297            subObject->_writeOrdinaryData(
298                subObjectDest,
299                destSize - subObjectOffset,
300                subObjectLayout);
301        }
302    }
303    return SLANG_OK;
304}
305
306Result ShaderObjectImpl::_ensureOrdinaryDataBufferCreatedIfNeeded(
307    DeviceImpl* device,
308    ShaderObjectLayoutImpl* layout)
309{
310    auto ordinaryDataSize = layout->getTotalOrdinaryDataSize();
311    if (ordinaryDataSize == 0)
312        return SLANG_OK;
313
314    // If we have already created a buffer to hold ordinary data, then we should
315    // simply re-use that buffer rather than re-create it.
316    if (!m_ordinaryDataBuffer)
317    {
318        ComPtr<IBufferResource> bufferResourcePtr;
319        IBufferResource::Desc bufferDesc = {};
320        bufferDesc.type = IResource::Type::Buffer;
321        bufferDesc.sizeInBytes = ordinaryDataSize;
322        bufferDesc.defaultState = ResourceState::ConstantBuffer;
323        bufferDesc.allowedStates =
324            ResourceStateSet(ResourceState::ConstantBuffer, ResourceState::CopyDestination);
325        bufferDesc.memoryType = MemoryType::Upload;
326        SLANG_RETURN_ON_FAIL(
327            device->createBufferResource(bufferDesc, nullptr, bufferResourcePtr.writeRef()));
328        m_ordinaryDataBuffer = static_cast<BufferResourceImpl*>(bufferResourcePtr.get());
329    }
330
331    if (m_isConstantBufferDirty)
332    {
333        // Once the buffer is allocated, we can use `_writeOrdinaryData` to fill it in.
334        //
335        // Note that `_writeOrdinaryData` is potentially recursive in the case
336        // where this object contains interface/existential-type fields, so we
337        // don't need or want to inline it into this call site.
338        //
339
340        MemoryRange range = {0, ordinaryDataSize};
341        void* ordinaryData;
342        SLANG_RETURN_ON_FAIL(m_ordinaryDataBuffer->map(&range, &ordinaryData));
343        auto result = _writeOrdinaryData(ordinaryData, ordinaryDataSize, layout);
344        m_ordinaryDataBuffer->unmap(&range);
345        m_isConstantBufferDirty = false;
346        return result;
347    }
348    return SLANG_OK;
349}
350
351Result ShaderObjectImpl::_bindOrdinaryDataBufferIfNeeded(
352    BindingContext* context,
353    BindingOffset& ioOffset,
354    ShaderObjectLayoutImpl* layout)
355{
356    // We start by ensuring that the buffer is created, if it is needed.
357    //
358    SLANG_RETURN_ON_FAIL(_ensureOrdinaryDataBufferCreatedIfNeeded(context->device, layout));
359
360    // If we did indeed need/create a buffer, then we must bind it
361    // into root binding state.
362    //
363    if (m_ordinaryDataBuffer)
364    {
365        context->setBuffer(m_ordinaryDataBuffer->m_buffer.get(), ioOffset.buffer);
366        ioOffset.buffer++;
367    }
368
369    return SLANG_OK;
370}
371
372void ShaderObjectImpl::writeOrdinaryDataIntoArgumentBuffer(
373    slang::TypeLayoutReflection* argumentBufferTypeLayout,
374    slang::TypeLayoutReflection* defaultTypeLayout,
375    uint8_t* argumentBuffer,
376    uint8_t* srcData)
377{
378    // If we are pure data, just copy it over from srcData.
379    if (defaultTypeLayout->getCategoryCount() == 1)
380    {
381        switch (defaultTypeLayout->getCategoryByIndex(0))
382        {
383        case slang::ParameterCategory::Uniform:
384            // Just copy the uniform data
385            memcpy(argumentBuffer, srcData, defaultTypeLayout->getSize());
386            break;
387        }
388        return;
389    }
390
391    for (unsigned int i = 0; i < argumentBufferTypeLayout->getFieldCount(); i++)
392    {
393        auto argumentBufferField = argumentBufferTypeLayout->getFieldByIndex(i);
394        auto defaultLayoutField = defaultTypeLayout->getFieldByIndex(i);
395        // If the field is mixed type, recurse.
396        writeOrdinaryDataIntoArgumentBuffer(
397            argumentBufferField->getTypeLayout(),
398            defaultLayoutField->getTypeLayout(),
399            argumentBuffer + argumentBufferField->getOffset(),
400            srcData + defaultLayoutField->getOffset());
401    }
402}
403
404BufferResourceImpl* ShaderObjectImpl::_ensureArgumentBufferUpToDate(
405    BindingContext* context,
406    DeviceImpl* device,
407    ShaderObjectLayoutImpl* layout)
408{
409    auto typeLayout = layout->getParameterBlockTypeLayout();
410
411    // If we have already created a buffer to hold the parmaeter block, then we should
412    // simply re-use that buffer rather than re-create it.
413    if (!m_argumentBuffer)
414    {
415        ComPtr<IBufferResource> bufferResourcePtr;
416        IBufferResource::Desc bufferDesc = {};
417        bufferDesc.type = IResource::Type::Buffer;
418        bufferDesc.sizeInBytes = typeLayout->getSize();
419        bufferDesc.defaultState = ResourceState::ConstantBuffer;
420        bufferDesc.allowedStates =
421            ResourceStateSet(ResourceState::ConstantBuffer, ResourceState::CopyDestination);
422        bufferDesc.memoryType = MemoryType::Upload;
423        SLANG_RETURN_NULL_ON_FAIL(
424            device->createBufferResource(bufferDesc, nullptr, bufferResourcePtr.writeRef()));
425        m_argumentBuffer = static_cast<BufferResourceImpl*>(bufferResourcePtr.get());
426    }
427
428    if (m_isArgumentBufferDirty)
429    {
430        // Once the buffer is allocated, we can fill it in with the uniform data
431        // and resource bindings we have tracked, using `typeLayout` to obtain
432        // the offsets for each field.
433        //
434        auto dataSize = typeLayout->getSize();
435        MemoryRange range = {0, dataSize};
436        void* argumentData;
437        SLANG_RETURN_NULL_ON_FAIL(m_argumentBuffer->map(&range, &argumentData));
438
439        // For parameter blocks, all the fields are flattened as ordinary data, so the size of the
440        // m_data must be equal to the size of the argument buffer, we just need to copy the data
441        // from m_data to argumentData, the only thing we need to specially handle is the parameter
442        // block and constant buffer, which will be a represented as device pointer in the argument
443        // buffer, we have to set the address of the argument buffer of nested parameter block to
444        // the corresponding offset in the argument buffer
445        SLANG_ASSERT(m_data.getCount() == dataSize);
446        memcpy(argumentData, m_data.getBuffer(), dataSize);
447
448        // Special handle the parameter block and constant buffer
449        for (uint32_t i = 0; i < typeLayout->getFieldCount(); i++)
450        {
451            auto field = typeLayout->getFieldByIndex(i);
452            auto kind = field->getTypeLayout()->getKind();
453            switch (kind)
454            {
455            case slang::TypeReflection::Kind::ConstantBuffer:
456            case slang::TypeReflection::Kind::ParameterBlock:
457                {
458                    // set address of argument buffer of nested parameter block to corresponding
459                    // offset in argument buffer
460                    auto offset = field->getOffset();
461                    uint32_t bindingRangeIndex = typeLayout->getFieldBindingRangeOffset(i);
462                    auto bindingRange = layout->getBindingRange(bindingRangeIndex);
463                    auto subObjectIndex = bindingRange.subObjectIndex;
464                    auto subObject = m_objects[subObjectIndex];
465                    BufferResourceImpl* argumentBufferPtr =
466                        subObject->_ensureArgumentBufferUpToDate(
467                            context,
468                            device,
469                            subObject->getLayout());
470                    if (argumentBufferPtr)
471                    {
472                        uint8_t* argumentBuffer = (uint8_t*)argumentData + offset;
473                        gfx::DeviceAddress bufferAddr = argumentBufferPtr->getDeviceAddress();
474                        memcpy(argumentBuffer, &bufferAddr, sizeof(bufferAddr));
475
476                        MTL::Resource const* resource[] = {argumentBufferPtr->m_buffer.get()};
477                        // Nested parameter block and constant buffer is also bindless resource, we
478                        // need to inform Metal to hazard track the resource
479                        context->useResources(
480                            resource,
481                            1,
482                            MTL::ResourceUsageWrite | MTL::ResourceUsageRead);
483                    }
484                    break;
485                }
486            default:
487                break;
488            }
489        }
490
491        // Handle bindless resources
492        List<MTL::Resource const*> resources;
493        for (uint32_t i = 0; i < m_buffers.getCount(); i++)
494        {
495            if (m_buffers[i])
496            {
497                MTL::Buffer* mtlBuffer = m_buffers[i]->m_buffer->m_buffer.get();
498                resources.add(mtlBuffer);
499            }
500        }
501
502        for (uint32_t i = 0; i < m_textures.getCount(); i++)
503        {
504            if (m_textures[i])
505            {
506                MTL::Texture* mtlTexture = m_textures[i]->m_texture->m_texture.get();
507                resources.add(mtlTexture);
508            }
509        }
510        // It's important to call useResources because Metal will not automatically do the hazard
511        // tracking for bindless resources, we have to call useResources to inform Metal to track
512        // the resources.
513        context->useResources(
514            resources.getBuffer(),
515            resources.getCount(),
516            MTL::ResourceUsageWrite | MTL::ResourceUsageRead);
517
518        m_argumentBuffer->unmap(&range);
519        m_isArgumentBufferDirty = false;
520    }
521
522    return m_argumentBuffer.get();
523}
524
525Result ShaderObjectImpl::bindAsParameterBlock(
526    BindingContext* context,
527    BindingOffset const& inOffset,
528    ShaderObjectLayoutImpl* layout)
529{
530    if (!context->device->m_hasArgumentBufferTier2)
531        return SLANG_FAIL;
532
533    auto argumentBuffer = _ensureArgumentBufferUpToDate(context, context->device, layout);
534
535    if (m_argumentBuffer)
536    {
537        context->setBuffer(m_argumentBuffer->m_buffer.get(), inOffset.buffer);
538    }
539    return SLANG_OK;
540}
541
542Result ShaderObjectImpl::bindAsConstantBuffer(
543    BindingContext* context,
544    BindingOffset const& inOffset,
545    ShaderObjectLayoutImpl* layout)
546{
547    // When binding a `ConstantBuffer<X>` we need to first bind a constant
548    // buffer for any "ordinary" data in `X`, and then bind the remaining
549    // resources and sub-objects.
550    //
551    BindingOffset offset = inOffset;
552    SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(context, /*inout*/ offset, layout));
553
554    // Once the ordinary data buffer is bound, we can move on to binding
555    // the rest of the state, which can use logic shared with the case
556    // for interface-type sub-object ranges.
557    //
558    // Note that this call will use the `inOffset` value instead of the offset
559    // modified by `_bindOrindaryDataBufferIfNeeded', because the indexOffset in
560    // the binding range should already take care of the offset due to the default
561    // cbuffer.
562    //
563    SLANG_RETURN_ON_FAIL(bindAsValue(context, inOffset, layout));
564
565    return SLANG_OK;
566}
567
568Result ShaderObjectImpl::bindAsValue(
569    BindingContext* context,
570    BindingOffset const& offset,
571    ShaderObjectLayoutImpl* layout)
572{
573    // We start by iterating over the binding ranges in this type, isolating
574    // just those ranges that represent buffers, textures, and samplers.
575    // In each loop we will bind the values stored for those binding ranges
576    // to the correct metal resource indices (based on the `registerOffset` field
577    // stored in the bindinge range).
578
579    for (auto bindingRangeIndex : layout->getBufferRanges())
580    {
581        auto const& bindingRange = layout->getBindingRange(bindingRangeIndex);
582        auto count = (uint32_t)bindingRange.count;
583        auto baseIndex = (uint32_t)bindingRange.baseIndex;
584        auto registerOffset = bindingRange.registerOffset + offset.buffer;
585        for (uint32_t i = 0; i < count; ++i)
586        {
587            auto buffer = m_buffers[baseIndex + i];
588            context->setBuffer(
589                buffer ? buffer->m_buffer->m_buffer.get() : nullptr,
590                registerOffset + i);
591        }
592    }
593
594    for (auto bindingRangeIndex : layout->getTextureRanges())
595    {
596        auto const& bindingRange = layout->getBindingRange(bindingRangeIndex);
597        auto count = (uint32_t)bindingRange.count;
598        auto baseIndex = (uint32_t)bindingRange.baseIndex;
599        auto registerOffset = bindingRange.registerOffset + offset.texture;
600        for (uint32_t i = 0; i < count; ++i)
601        {
602            auto texture = m_textures[baseIndex + i];
603            context->setTexture(
604                texture ? texture->m_textureView.get() : nullptr,
605                registerOffset + i);
606        }
607    }
608
609    for (auto bindingRangeIndex : layout->getSamplerRanges())
610    {
611        auto const& bindingRange = layout->getBindingRange(bindingRangeIndex);
612        auto count = (uint32_t)bindingRange.count;
613        auto baseIndex = (uint32_t)bindingRange.baseIndex;
614        auto registerOffset = bindingRange.registerOffset + offset.sampler;
615        for (uint32_t i = 0; i < count; ++i)
616        {
617            auto sampler = m_samplers[baseIndex + i];
618            context->setSampler(
619                sampler ? sampler->m_samplerState.get() : nullptr,
620                registerOffset + i);
621        }
622    }
623
624    // Once all the simple binding ranges are dealt with, we will bind
625    // all of the sub-objects in sub-object ranges.
626    //
627    for (auto const& subObjectRange : layout->getSubObjectRanges())
628    {
629        auto subObjectLayout = subObjectRange.layout;
630        auto const& bindingRange = layout->getBindingRange(subObjectRange.bindingRangeIndex);
631        Index count = bindingRange.count;
632        Index subObjectIndex = bindingRange.subObjectIndex;
633
634        // The starting offset for a sub-object range was computed
635        // from Slang reflection information, so we can apply it here.
636        //
637        BindingOffset rangeOffset = offset;
638        rangeOffset += subObjectRange.offset;
639
640        // Similarly, the "stride" between consecutive objects in
641        // the range was also pre-computed.
642        //
643        BindingOffset rangeStride = subObjectRange.stride;
644
645        switch (bindingRange.bindingType)
646        {
647        case slang::BindingType::ConstantBuffer:
648            {
649                BindingOffset objOffset = rangeOffset;
650                for (Index i = 0; i < count; ++i)
651                {
652                    auto subObject = m_objects[subObjectIndex + i];
653
654                    // Unsurprisingly, we bind each object in the range as
655                    // a constant buffer.
656                    //
657                    SLANG_RETURN_ON_FAIL(
658                        subObject->bindAsConstantBuffer(context, objOffset, subObjectLayout));
659
660                    objOffset += rangeStride;
661                }
662                break;
663            }
664        case slang::BindingType::ParameterBlock:
665            {
666                BindingOffset objOffset = rangeOffset;
667                for (Index i = 0; i < count; ++i)
668                {
669                    auto subObject = m_objects[subObjectIndex + i];
670                    SLANG_RETURN_ON_FAIL(
671                        subObject->bindAsParameterBlock(context, objOffset, subObjectLayout));
672                    objOffset += rangeStride;
673                }
674            }
675            break;
676
677#if 0
678        case slang::BindingType::ExistentialValue:
679            // We can only bind information for existential-typed sub-object
680            // ranges if we have a static type that we are able to specialize to.
681            //
682            if (subObjectLayout)
683            {
684                // The data for objects in this range will always be bound into
685                // the "pending" allocation for the parent block/buffer/object.
686                // As a result, the offset for the first object in the range
687                // will come from the `pending` part of the range's offset.
688                //
689                SimpleBindingOffset objOffset = rangeOffset.pending;
690                SimpleBindingOffset objStride = rangeStride.pending;
691
692                for (Index i = 0; i < count; ++i)
693                {
694                    auto subObject = m_objects[subObjectIndex + i];
695                    subObject->bindAsValue(context, BindingOffset(objOffset), subObjectLayout);
696
697                    objOffset += objStride;
698                }
699            }
700            break;
701#endif
702
703        default:
704            break;
705        }
706    }
707
708    return SLANG_OK;
709}
710
711Result RootShaderObjectImpl::create(
712    IDevice* device,
713    RootShaderObjectLayoutImpl* layout,
714    RootShaderObjectImpl** outShaderObject)
715{
716    RefPtr<RootShaderObjectImpl> object = new RootShaderObjectImpl();
717    SLANG_RETURN_ON_FAIL(object->init(device, layout));
718
719    returnRefPtrMove(outShaderObject, object);
720    return SLANG_OK;
721}
722
723Result RootShaderObjectImpl::collectSpecializationArgs(ExtendedShaderObjectTypeList& args)
724{
725    SLANG_RETURN_ON_FAIL(ShaderObjectImpl::collectSpecializationArgs(args));
726    for (auto& entryPoint : m_entryPoints)
727    {
728        SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args));
729    }
730    return SLANG_OK;
731}
732
733Result RootShaderObjectImpl::bindAsRoot(BindingContext* context, RootShaderObjectLayoutImpl* layout)
734{
735    // When binding an entire root shader object, we need to deal with
736    // the way that specialization might have allocated space for "pending"
737    // parameter data after all the primary parameters.
738    //
739    // We start by initializing an offset that will store zeros for the
740    // primary data, an the computed offset from the specialized layout
741    // for pending data.
742    //
743    BindingOffset offset;
744#if 0
745    offset.pending = layout->getPendingDataOffset();
746#endif
747
748    // Note: We could *almost* call `bindAsConstantBuffer()` here to bind
749    // the state of the root object itself, but there is an important
750    // detail that means we can't:
751    //
752    // The `_bindOrdinaryDataBufferIfNeeded` operation automatically
753    // increments the offset parameter if it binds a buffer, so that
754    // subsequently bindings will be adjusted. However, the reflection
755    // information computed for root shader parameters is absolute rather
756    // than relative to the default constant buffer (if any).
757    //
758    // TODO: Quite technically, the ordinary data buffer for the global
759    // scope is *not* guaranteed to be at offset zero, so this logic should
760    // really be querying an appropriate absolute offset from `layout`.
761    //
762#if 0
763    BindingOffset ordinaryDataBufferOffset = offset;
764    SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(context, /*inout*/ ordinaryDataBufferOffset, layout));
765#endif
766    SLANG_RETURN_ON_FAIL(bindAsValue(context, offset, layout));
767
768    // Once the state stored in the root shader object itself has been bound,
769    // we turn our attention to the entry points and their parameters.
770    //
771    auto entryPointCount = m_entryPoints.getCount();
772    for (Index i = 0; i < entryPointCount; ++i)
773    {
774        auto entryPoint = m_entryPoints[i];
775        auto const& entryPointInfo = layout->getEntryPoint(i);
776
777        // Each entry point will be bound at some offset relative to where
778        // the root shader parameters start.
779        //
780        BindingOffset entryPointOffset = offset;
781        entryPointOffset += entryPointInfo.offset;
782
783        // An entry point can simply be bound as a constant buffer, because
784        // the absolute offsets as are used for the global scope do not apply
785        // (because entry points don't need to deal with explicit bindings).
786        //
787        SLANG_RETURN_ON_FAIL(
788            entryPoint->bindAsConstantBuffer(context, entryPointOffset, entryPointInfo.layout));
789    }
790
791    return SLANG_OK;
792}
793
794Result RootShaderObjectImpl::init(IDevice* device, RootShaderObjectLayoutImpl* layout)
795{
796    SLANG_RETURN_ON_FAIL(Super::init(device, layout));
797    m_entryPoints.clear();
798    for (auto entryPointInfo : layout->getEntryPoints())
799    {
800        RefPtr<ShaderObjectImpl> entryPoint;
801        SLANG_RETURN_ON_FAIL(
802            ShaderObjectImpl::create(device, entryPointInfo.layout, entryPoint.writeRef()));
803        m_entryPoints.add(entryPoint);
804    }
805
806    return SLANG_OK;
807}
808
809} // namespace metal
810} // namespace gfx