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
27.0 KiB698 linesraw
1// d3d11-shader-object.cpp
2#include "d3d11-shader-object.h"
3
4#include "d3d11-device.h"
5
6namespace gfx
7{
8
9using namespace Slang;
10
11namespace d3d11
12{
13
14Result ShaderObjectImpl::create(
15    IDevice* device,
16    ShaderObjectLayoutImpl* layout,
17    ShaderObjectImpl** outShaderObject)
18{
19    auto object = RefPtr<ShaderObjectImpl>(new ShaderObjectImpl());
20    SLANG_RETURN_ON_FAIL(object->init(device, layout));
21
22    returnRefPtrMove(outShaderObject, object);
23    return SLANG_OK;
24}
25
26SLANG_NO_THROW Result SLANG_MCALL
27ShaderObjectImpl::setData(ShaderOffset const& inOffset, void const* data, size_t inSize)
28{
29    Index offset = inOffset.uniformOffset;
30    Index size = inSize;
31
32    char* dest = m_data.getBuffer();
33    Index availableSize = m_data.getCount();
34
35    // TODO: We really should bounds-check access rather than silently ignoring sets
36    // that are too large, but we have several test cases that set more data than
37    // an object actually stores on several targets...
38    //
39    if (offset < 0)
40    {
41        size += offset;
42        offset = 0;
43    }
44    if ((offset + size) >= availableSize)
45    {
46        size = availableSize - offset;
47    }
48
49    memcpy(dest + offset, data, size);
50
51    m_isConstantBufferDirty = true;
52
53    return SLANG_OK;
54}
55
56SLANG_NO_THROW Result SLANG_MCALL
57ShaderObjectImpl::setResource(ShaderOffset const& offset, IResourceView* resourceView)
58{
59    if (offset.bindingRangeIndex < 0)
60        return SLANG_E_INVALID_ARG;
61    auto layout = getLayout();
62    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
63        return SLANG_E_INVALID_ARG;
64    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
65
66    auto resourceViewImpl = static_cast<ResourceViewImpl*>(resourceView);
67    if (D3DUtil::isUAVBinding(bindingRange.bindingType))
68    {
69        SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::Type::UAV);
70        m_uavs[bindingRange.baseIndex + offset.bindingArrayIndex] =
71            static_cast<UnorderedAccessViewImpl*>(resourceView);
72    }
73    else
74    {
75        SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::Type::SRV);
76        m_srvs[bindingRange.baseIndex + offset.bindingArrayIndex] =
77            static_cast<ShaderResourceViewImpl*>(resourceView);
78    }
79    return SLANG_OK;
80}
81
82SLANG_NO_THROW Result SLANG_MCALL
83ShaderObjectImpl::setSampler(ShaderOffset const& offset, ISamplerState* sampler)
84{
85    if (offset.bindingRangeIndex < 0)
86        return SLANG_E_INVALID_ARG;
87    auto layout = getLayout();
88    if (offset.bindingRangeIndex >= layout->getBindingRangeCount())
89        return SLANG_E_INVALID_ARG;
90    auto& bindingRange = layout->getBindingRange(offset.bindingRangeIndex);
91
92    m_samplers[bindingRange.baseIndex + offset.bindingArrayIndex] =
93        static_cast<SamplerStateImpl*>(sampler);
94    return SLANG_OK;
95}
96
97Result ShaderObjectImpl::init(IDevice* device, ShaderObjectLayoutImpl* layout)
98{
99    m_layout = layout;
100
101    // If the layout tells us that there is any uniform data,
102    // then we will allocate a CPU memory buffer to hold that data
103    // while it is being set from the host.
104    //
105    // Once the user is done setting the parameters/fields of this
106    // shader object, we will produce a GPU-memory version of the
107    // uniform data (which includes values from this object and
108    // any existential-type sub-objects).
109    //
110    size_t uniformSize = layout->getElementTypeLayout()->getSize();
111    if (uniformSize)
112    {
113        m_data.setCount(uniformSize);
114        memset(m_data.getBuffer(), 0, uniformSize);
115    }
116
117    m_srvs.setCount(layout->getSRVCount());
118    m_samplers.setCount(layout->getSamplerCount());
119    m_uavs.setCount(layout->getUAVCount());
120
121    // If the layout specifies that we have any sub-objects, then
122    // we need to size the array to account for them.
123    //
124    Index subObjectCount = layout->getSubObjectCount();
125    m_objects.setCount(subObjectCount);
126
127    for (auto subObjectRangeInfo : layout->getSubObjectRanges())
128    {
129        auto subObjectLayout = subObjectRangeInfo.layout;
130
131        // In the case where the sub-object range represents an
132        // existential-type leaf field (e.g., an `IBar`), we
133        // cannot pre-allocate the object(s) to go into that
134        // range, since we can't possibly know what to allocate
135        // at this point.
136        //
137        if (!subObjectLayout)
138            continue;
139        //
140        // Otherwise, we will allocate a sub-object to fill
141        // in each entry in this range, based on the layout
142        // information we already have.
143
144        auto& bindingRangeInfo = layout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
145        for (Index i = 0; i < bindingRangeInfo.count; ++i)
146        {
147            RefPtr<ShaderObjectImpl> subObject;
148            SLANG_RETURN_ON_FAIL(
149                ShaderObjectImpl::create(device, subObjectLayout, subObject.writeRef()));
150            m_objects[bindingRangeInfo.subObjectIndex + i] = subObject;
151        }
152    }
153
154    return SLANG_OK;
155}
156
157Result ShaderObjectImpl::_writeOrdinaryData(
158    void* dest,
159    size_t destSize,
160    ShaderObjectLayoutImpl* specializedLayout)
161{
162    // We start by simply writing in the ordinary data contained directly in this object.
163    //
164    auto src = m_data.getBuffer();
165    auto srcSize = size_t(m_data.getCount());
166    SLANG_ASSERT(srcSize <= destSize);
167    memcpy(dest, src, srcSize);
168
169    // In the case where this object has any sub-objects of
170    // existential/interface type, we need to recurse on those objects
171    // that need to write their state into an appropriate "pending" allocation.
172    //
173    // Note: Any values that could fit into the "payload" included
174    // in the existential-type field itself will have already been
175    // written as part of `setObject()`. This loop only needs to handle
176    // those sub-objects that do not "fit."
177    //
178    // An implementers looking at this code might wonder if things could be changed
179    // so that *all* writes related to sub-objects for interface-type fields could
180    // be handled in this one location, rather than having some in `setObject()` and
181    // others handled here.
182    //
183    Index subObjectRangeCounter = 0;
184    for (auto const& subObjectRangeInfo : specializedLayout->getSubObjectRanges())
185    {
186        Index subObjectRangeIndex = subObjectRangeCounter++;
187        auto const& bindingRangeInfo =
188            specializedLayout->getBindingRange(subObjectRangeInfo.bindingRangeIndex);
189
190        // We only need to handle sub-object ranges for interface/existential-type fields,
191        // because fields of constant-buffer or parameter-block type are responsible for
192        // the ordinary/uniform data of their own existential/interface-type sub-objects.
193        //
194        if (bindingRangeInfo.bindingType != slang::BindingType::ExistentialValue)
195            continue;
196
197        // Each sub-object range represents a single "leaf" field, but might be nested
198        // under zero or more outer arrays, such that the number of existential values
199        // in the same range can be one or more.
200        //
201        auto count = bindingRangeInfo.count;
202
203        // We are not concerned with the case where the existential value(s) in the range
204        // git into the payload part of the leaf field.
205        //
206        // In the case where the value didn't fit, the Slang layout strategy would have
207        // considered the requirements of the value as a "pending" allocation, and would
208        // allocate storage for the ordinary/uniform part of that pending allocation inside
209        // of the parent object's type layout.
210        //
211        // Here we assume that the Slang reflection API can provide us with a single byte
212        // offset and stride for the location of the pending data allocation in the specialized
213        // type layout, which will store the values for this sub-object range.
214        //
215        // TODO: The reflection API functions we are assuming here haven't been implemented
216        // yet, so the functions being called here are stubs.
217        //
218        // TODO: It might not be that a single sub-object range can reliably map to a single
219        // contiguous array with a single stride; we need to carefully consider what the layout
220        // logic does for complex cases with multiple layers of nested arrays and structures.
221        //
222        size_t subObjectRangePendingDataOffset = subObjectRangeInfo.offset.pendingOrdinaryData;
223        size_t subObjectRangePendingDataStride = subObjectRangeInfo.stride.pendingOrdinaryData;
224
225        // If the range doesn't actually need/use the "pending" allocation at all, then
226        // we need to detect that case and skip such ranges.
227        //
228        // TODO: This should probably be handled on a per-object basis by caching a "does it fit?"
229        // bit as part of the information for bound sub-objects, given that we already
230        // compute the "does it fit?" status as part of `setObject()`.
231        //
232        if (subObjectRangePendingDataOffset == 0)
233            continue;
234
235        for (Slang::Index i = 0; i < count; ++i)
236        {
237            auto subObject = m_objects[bindingRangeInfo.subObjectIndex + i];
238
239            RefPtr<ShaderObjectLayoutImpl> subObjectLayout;
240            SLANG_RETURN_ON_FAIL(subObject->_getSpecializedLayout(subObjectLayout.writeRef()));
241
242            auto subObjectOffset =
243                subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride;
244
245            auto subObjectDest = (char*)dest + subObjectOffset;
246
247            subObject->_writeOrdinaryData(
248                subObjectDest,
249                destSize - subObjectOffset,
250                subObjectLayout);
251        }
252    }
253
254    return SLANG_OK;
255}
256
257Result ShaderObjectImpl::_ensureOrdinaryDataBufferCreatedIfNeeded(
258    DeviceImpl* device,
259    ShaderObjectLayoutImpl* specializedLayout)
260{
261    auto specializedOrdinaryDataSize = specializedLayout->getTotalOrdinaryDataSize();
262    if (specializedOrdinaryDataSize == 0)
263        return SLANG_OK;
264
265    // If we have already created a buffer to hold ordinary data, then we should
266    // simply re-use that buffer rather than re-create it.
267    if (!m_ordinaryDataBuffer)
268    {
269        ComPtr<IBufferResource> bufferResourcePtr;
270        IBufferResource::Desc bufferDesc = {};
271        bufferDesc.type = IResource::Type::Buffer;
272        bufferDesc.sizeInBytes = specializedOrdinaryDataSize;
273        bufferDesc.defaultState = ResourceState::ConstantBuffer;
274        bufferDesc.allowedStates =
275            ResourceStateSet(ResourceState::ConstantBuffer, ResourceState::CopyDestination);
276        bufferDesc.memoryType = MemoryType::Upload;
277        SLANG_RETURN_ON_FAIL(
278            device->createBufferResource(bufferDesc, nullptr, bufferResourcePtr.writeRef()));
279        m_ordinaryDataBuffer = static_cast<BufferResourceImpl*>(bufferResourcePtr.get());
280    }
281
282    if (m_isConstantBufferDirty)
283    {
284        // Once the buffer is allocated, we can use `_writeOrdinaryData` to fill it in.
285        //
286        // Note that `_writeOrdinaryData` is potentially recursive in the case
287        // where this object contains interface/existential-type fields, so we
288        // don't need or want to inline it into this call site.
289        //
290
291        auto ordinaryData = device->map(m_ordinaryDataBuffer, gfx::MapFlavor::WriteDiscard);
292        auto result =
293            _writeOrdinaryData(ordinaryData, specializedOrdinaryDataSize, specializedLayout);
294        device->unmap(m_ordinaryDataBuffer, 0, specializedOrdinaryDataSize);
295        m_isConstantBufferDirty = false;
296        return result;
297    }
298    return SLANG_OK;
299}
300
301Result ShaderObjectImpl::_bindOrdinaryDataBufferIfNeeded(
302    BindingContext* context,
303    BindingOffset& ioOffset,
304    ShaderObjectLayoutImpl* specializedLayout)
305{
306    // We start by ensuring that the buffer is created, if it is needed.
307    //
308    SLANG_RETURN_ON_FAIL(
309        _ensureOrdinaryDataBufferCreatedIfNeeded(context->device, specializedLayout));
310
311    // If we did indeed need/create a buffer, then we must bind it
312    // into root binding state.
313    //
314    if (m_ordinaryDataBuffer)
315    {
316        context->setCBV(ioOffset.cbv, m_ordinaryDataBuffer->m_buffer);
317        ioOffset.cbv++;
318    }
319
320    return SLANG_OK;
321}
322
323Result ShaderObjectImpl::bindAsConstantBuffer(
324    BindingContext* context,
325    BindingOffset const& inOffset,
326    ShaderObjectLayoutImpl* specializedLayout)
327{
328    // When binding a `ConstantBuffer<X>` we need to first bind a constant
329    // buffer for any "ordinary" data in `X`, and then bind the remaining
330    // resources and sub-objects.
331    //
332    BindingOffset offset = inOffset;
333    SLANG_RETURN_ON_FAIL(
334        _bindOrdinaryDataBufferIfNeeded(context, /*inout*/ offset, specializedLayout));
335
336    // Once the ordinary data buffer is bound, we can move on to binding
337    // the rest of the state, which can use logic shared with the case
338    // for interface-type sub-object ranges.
339    //
340    // Note that this call will use the `inOffset` value instead of the offset
341    // modified by `_bindOrindaryDataBufferIfNeeded', because the indexOffset in
342    // the binding range should already take care of the offset due to the default
343    // cbuffer.
344    //
345    SLANG_RETURN_ON_FAIL(bindAsValue(context, inOffset, specializedLayout));
346
347    return SLANG_OK;
348}
349
350Result ShaderObjectImpl::bindAsValue(
351    BindingContext* context,
352    BindingOffset const& offset,
353    ShaderObjectLayoutImpl* specializedLayout)
354{
355    // We start by iterating over the binding ranges in this type, isolating
356    // just those ranges that represent SRVs, UAVs, and samplers.
357    // In each loop we will bind the values stored for those binding ranges
358    // to the correct D3D11 register (based on the `registerOffset` field
359    // stored in the bindinge range).
360    //
361    // TODO: These loops could be optimized if we stored parallel arrays
362    // for things like `m_srvs` so that we directly store an array of
363    // `ID3D11ShaderResourceView*` where each entry matches the `gfx`-level
364    // object that was bound (or holds null if nothing is bound).
365    // In that case, we could perform a single `setSRVs()` call for each
366    // binding range.
367    //
368    // TODO: More ambitiously, if the Slang layout algorithm could be modified
369    // so that non-sub-object binding ranges are guaranteed to be contiguous
370    // then a *single* `setSRVs()` call could set all of the SRVs for an object
371    // at once.
372
373    for (auto bindingRangeIndex : specializedLayout->getSRVRanges())
374    {
375        auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
376        auto count = (uint32_t)bindingRange.count;
377        auto baseIndex = (uint32_t)bindingRange.baseIndex;
378        auto registerOffset = bindingRange.registerOffset + offset.srv;
379        for (uint32_t i = 0; i < count; ++i)
380        {
381            auto srv = m_srvs[baseIndex + i];
382            context->setSRV(registerOffset + i, srv ? srv->m_srv : nullptr);
383        }
384    }
385
386    for (auto bindingRangeIndex : specializedLayout->getUAVRanges())
387    {
388        auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
389        auto count = (uint32_t)bindingRange.count;
390        auto baseIndex = (uint32_t)bindingRange.baseIndex;
391        auto registerOffset = bindingRange.registerOffset + offset.uav;
392        for (uint32_t i = 0; i < count; ++i)
393        {
394            auto uav = m_uavs[baseIndex + i];
395            context->setUAV(registerOffset + i, uav ? uav->m_uav : nullptr);
396        }
397    }
398
399    for (auto bindingRangeIndex : specializedLayout->getSamplerRanges())
400    {
401        auto const& bindingRange = specializedLayout->getBindingRange(bindingRangeIndex);
402        auto count = (uint32_t)bindingRange.count;
403        auto baseIndex = (uint32_t)bindingRange.baseIndex;
404        auto registerOffset = bindingRange.registerOffset + offset.sampler;
405        for (uint32_t i = 0; i < count; ++i)
406        {
407            auto sampler = m_samplers[baseIndex + i];
408            context->setSampler(registerOffset + i, sampler ? sampler->m_sampler.get() : nullptr);
409        }
410    }
411
412    // Once all the simple binding ranges are dealt with, we will bind
413    // all of the sub-objects in sub-object ranges.
414    //
415    for (auto const& subObjectRange : specializedLayout->getSubObjectRanges())
416    {
417        auto subObjectLayout = subObjectRange.layout;
418        auto const& bindingRange =
419            specializedLayout->getBindingRange(subObjectRange.bindingRangeIndex);
420        Index count = bindingRange.count;
421        Index subObjectIndex = bindingRange.subObjectIndex;
422
423        // The starting offset for a sub-object range was computed
424        // from Slang reflection information, so we can apply it here.
425        //
426        BindingOffset rangeOffset = offset;
427        rangeOffset += subObjectRange.offset;
428
429        // Similarly, the "stride" between consecutive objects in
430        // the range was also pre-computed.
431        //
432        BindingOffset rangeStride = subObjectRange.stride;
433
434        switch (bindingRange.bindingType)
435        {
436            // For D3D11-compatible compilation targets, the Slang compiler
437            // treats the `ConstantBuffer<T>` and `ParameterBlock<T>` types the same.
438            //
439        case slang::BindingType::ConstantBuffer:
440        case slang::BindingType::ParameterBlock:
441            {
442                BindingOffset objOffset = rangeOffset;
443                for (Index i = 0; i < count; ++i)
444                {
445                    auto subObject = m_objects[subObjectIndex + i];
446
447                    // Unsurprisingly, we bind each object in the range as
448                    // a constant buffer.
449                    //
450                    subObject->bindAsConstantBuffer(context, objOffset, subObjectLayout);
451
452                    objOffset += rangeStride;
453                }
454            }
455            break;
456
457        case slang::BindingType::ExistentialValue:
458            // We can only bind information for existential-typed sub-object
459            // ranges if we have a static type that we are able to specialize to.
460            //
461            if (subObjectLayout)
462            {
463                // The data for objects in this range will always be bound into
464                // the "pending" allocation for the parent block/buffer/object.
465                // As a result, the offset for the first object in the range
466                // will come from the `pending` part of the range's offset.
467                //
468                SimpleBindingOffset objOffset = rangeOffset.pending;
469                SimpleBindingOffset objStride = rangeStride.pending;
470
471                for (Index i = 0; i < count; ++i)
472                {
473                    auto subObject = m_objects[subObjectIndex + i];
474                    subObject->bindAsValue(context, BindingOffset(objOffset), subObjectLayout);
475
476                    objOffset += objStride;
477                }
478            }
479            break;
480
481        default:
482            break;
483        }
484    }
485
486    return SLANG_OK;
487}
488
489Result ShaderObjectImpl::_getSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
490{
491    if (!m_specializedLayout)
492    {
493        SLANG_RETURN_ON_FAIL(_createSpecializedLayout(m_specializedLayout.writeRef()));
494    }
495    returnRefPtr(outLayout, m_specializedLayout);
496    return SLANG_OK;
497}
498
499Result ShaderObjectImpl::_createSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
500{
501    ExtendedShaderObjectType extendedType;
502    SLANG_RETURN_ON_FAIL(getSpecializedShaderObjectType(&extendedType));
503
504    auto renderer = getRenderer();
505    RefPtr<ShaderObjectLayoutImpl> layout;
506    SLANG_RETURN_ON_FAIL(renderer->getShaderObjectLayout(
507        m_layout->m_slangSession,
508        extendedType.slangType,
509        m_layout->getContainerType(),
510        (ShaderObjectLayoutBase**)layout.writeRef()));
511
512    returnRefPtrMove(outLayout, layout);
513    return SLANG_OK;
514}
515
516Result RootShaderObjectImpl::create(
517    IDevice* device,
518    RootShaderObjectLayoutImpl* layout,
519    RootShaderObjectImpl** outShaderObject)
520{
521    RefPtr<RootShaderObjectImpl> object = new RootShaderObjectImpl();
522    SLANG_RETURN_ON_FAIL(object->init(device, layout));
523
524    returnRefPtrMove(outShaderObject, object);
525    return SLANG_OK;
526}
527
528Result RootShaderObjectImpl::collectSpecializationArgs(ExtendedShaderObjectTypeList& args)
529{
530    SLANG_RETURN_ON_FAIL(ShaderObjectImpl::collectSpecializationArgs(args));
531    for (auto& entryPoint : m_entryPoints)
532    {
533        SLANG_RETURN_ON_FAIL(entryPoint->collectSpecializationArgs(args));
534    }
535    return SLANG_OK;
536}
537
538Result RootShaderObjectImpl::bindAsRoot(
539    BindingContext* context,
540    RootShaderObjectLayoutImpl* specializedLayout)
541{
542    // When binding an entire root shader object, we need to deal with
543    // the way that specialization might have allocated space for "pending"
544    // parameter data after all the primary parameters.
545    //
546    // We start by initializing an offset that will store zeros for the
547    // primary data, an the computed offset from the specialized layout
548    // for pending data.
549    //
550    BindingOffset offset;
551    offset.pending = specializedLayout->getPendingDataOffset();
552
553    // Note: We could *almost* call `bindAsConstantBuffer()` here to bind
554    // the state of the root object itself, but there is an important
555    // detail that means we can't:
556    //
557    // The `_bindOrdinaryDataBufferIfNeeded` operation automatically
558    // increments the offset parameter if it binds a buffer, so that
559    // subsequently bindings will be adjusted. However, the reflection
560    // information computed for root shader parameters is absolute rather
561    // than relative to the default constant buffer (if any).
562    //
563    // TODO: Quite technically, the ordinary data buffer for the global
564    // scope is *not* guaranteed to be at offset zero, so this logic should
565    // really be querying an appropriate absolute offset from `specializedLayout`.
566    //
567    BindingOffset ordinaryDataBufferOffset = offset;
568    SLANG_RETURN_ON_FAIL(_bindOrdinaryDataBufferIfNeeded(
569        context,
570        /*inout*/ ordinaryDataBufferOffset,
571        specializedLayout));
572    SLANG_RETURN_ON_FAIL(bindAsValue(context, offset, specializedLayout));
573
574    // Once the state stored in the root shader object itself has been bound,
575    // we turn our attention to the entry points and their parameters.
576    //
577    auto entryPointCount = m_entryPoints.getCount();
578    for (Index i = 0; i < entryPointCount; ++i)
579    {
580        auto entryPoint = m_entryPoints[i];
581        auto const& entryPointInfo = specializedLayout->getEntryPoint(i);
582
583        // Each entry point will be bound at some offset relative to where
584        // the root shader parameters start.
585        //
586        BindingOffset entryPointOffset = offset;
587        entryPointOffset += entryPointInfo.offset;
588
589        // An entry point can simply be bound as a constant buffer, because
590        // the absolute offsets as are used for the global scope do not apply
591        // (because entry points don't need to deal with explicit bindings).
592        //
593        SLANG_RETURN_ON_FAIL(
594            entryPoint->bindAsConstantBuffer(context, entryPointOffset, entryPointInfo.layout));
595    }
596
597    return SLANG_OK;
598}
599
600Result RootShaderObjectImpl::init(IDevice* device, RootShaderObjectLayoutImpl* layout)
601{
602    SLANG_RETURN_ON_FAIL(Super::init(device, layout));
603
604    for (auto entryPointInfo : layout->getEntryPoints())
605    {
606        RefPtr<ShaderObjectImpl> entryPoint;
607        SLANG_RETURN_ON_FAIL(
608            ShaderObjectImpl::create(device, entryPointInfo.layout, entryPoint.writeRef()));
609        m_entryPoints.add(entryPoint);
610    }
611
612    return SLANG_OK;
613}
614
615Result RootShaderObjectImpl::_createSpecializedLayout(ShaderObjectLayoutImpl** outLayout)
616{
617    ExtendedShaderObjectTypeList specializationArgs;
618    SLANG_RETURN_ON_FAIL(collectSpecializationArgs(specializationArgs));
619
620    // Note: There is an important policy decision being made here that we need
621    // to approach carefully.
622    //
623    // We are doing two different things that affect the layout of a program:
624    //
625    // 1. We are *composing* one or more pieces of code (notably the shared global/module
626    //    stuff and the per-entry-point stuff).
627    //
628    // 2. We are *specializing* code that includes generic/existential parameters
629    //    to concrete types/values.
630    //
631    // We need to decide the relative *order* of these two steps, because of how it impacts
632    // layout. The layout for `specialize(compose(A,B), X, Y)` is potentially different
633    // form that of `compose(specialize(A,X), specialize(B,Y))`, even when both are
634    // semantically equivalent programs.
635    //
636    // Right now we are using the first option: we are first generating a full composition
637    // of all the code we plan to use (global scope plus all entry points), and then
638    // specializing it to the concatenated specialization arguments for all of that.
639    //
640    // In some cases, though, this model isn't appropriate. For example, when dealing with
641    // ray-tracing shaders and local root signatures, we really want the parameters of each
642    // entry point (actually, each entry-point *group*) to be allocated distinct storage,
643    // which really means we want to compute something like:
644    //
645    //      SpecializedGlobals = specialize(compose(ModuleA, ModuleB, ...), X, Y, ...)
646    //
647    //      SpecializedEP1 = compose(SpecializedGlobals, specialize(EntryPoint1, T, U, ...))
648    //      SpecializedEP2 = compose(SpecializedGlobals, specialize(EntryPoint2, A, B, ...))
649    //
650    // Note how in this case all entry points agree on the layout for the shared/common
651    // parameters, but their layouts are also independent of one another.
652    //
653    // Furthermore, in this example, loading another entry point into the system would not
654    // require re-computing the layouts (or generated kernel code) for any of the entry points
655    // that had already been loaded (in contrast to a compose-then-specialize approach).
656    //
657    ComPtr<slang::IComponentType> specializedComponentType;
658    ComPtr<slang::IBlob> diagnosticBlob;
659    auto result = getLayout()->getSlangProgram()->specialize(
660        specializationArgs.components.getArrayView().getBuffer(),
661        specializationArgs.getCount(),
662        specializedComponentType.writeRef(),
663        diagnosticBlob.writeRef());
664
665    // TODO: print diagnostic message via debug output interface.
666
667    if (result != SLANG_OK)
668        return result;
669
670    auto slangSpecializedLayout = specializedComponentType->getLayout();
671    RefPtr<RootShaderObjectLayoutImpl> specializedLayout;
672    RootShaderObjectLayoutImpl::create(
673        getRenderer(),
674        specializedComponentType,
675        slangSpecializedLayout,
676        specializedLayout.writeRef());
677
678    // Note: Computing the layout for the specialized program will have also computed
679    // the layouts for the entry points, and we really need to attach that information
680    // to them so that they don't go and try to compute their own specializations.
681    //
682    // TODO: Well, if we move to the specialization model described above then maybe
683    // we *will* want entry points to do their own specialization work...
684    //
685    auto entryPointCount = m_entryPoints.getCount();
686    for (Index i = 0; i < entryPointCount; ++i)
687    {
688        auto entryPointInfo = specializedLayout->getEntryPoint(i);
689        auto entryPointVars = m_entryPoints[i];
690
691        entryPointVars->m_specializedLayout = entryPointInfo.layout;
692    }
693
694    returnRefPtrMove(outLayout, specializedLayout);
695    return SLANG_OK;
696}
697} // namespace d3d11
698} // namespace gfx