yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
b118451e3
master
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 ( 15IDevice * device , 16ShaderObjectLayoutImpl * layout , 17ShaderObjectImpl ** outShaderObject ) 18{ 19auto object = RefPtr < ShaderObjectImpl > (new ShaderObjectImpl ()); 20SLANG_RETURN_ON_FAIL (object -> init (device ,layout )); 21 22returnRefPtrMove (outShaderObject ,object ); 23return SLANG_OK ; 24} 25 26SLANG_NO_THROW Result SLANG_MCALL 27ShaderObjectImpl ::setData (ShaderOffset const & inOffset ,void const * data ,size_t inSize ) 28{ 29Index offset = inOffset .uniformOffset ; 30Index size = inSize ; 31 32char * dest = m_data .getBuffer (); 33Index 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// 39if (offset < 0 ) 40 { 41size += offset ; 42offset = 0 ; 43 } 44if ((offset + size ) >=availableSize ) 45 { 46size = availableSize - offset ; 47 } 48 49memcpy (dest + offset ,data ,size ); 50 51m_isConstantBufferDirty = true; 52 53return SLANG_OK ; 54} 55 56SLANG_NO_THROW Result SLANG_MCALL 57ShaderObjectImpl ::setResource (ShaderOffset const & offset ,IResourceView * resourceView ) 58{ 59if (offset .bindingRangeIndex < 0 ) 60return SLANG_E_INVALID_ARG ; 61auto layout = getLayout (); 62if (offset .bindingRangeIndex >=layout -> getBindingRangeCount ()) 63return SLANG_E_INVALID_ARG ; 64auto & bindingRange = layout -> getBindingRange (offset .bindingRangeIndex ); 65 66auto resourceViewImpl = static_cast < ResourceViewImpl *> (resourceView ); 67if (D3DUtil ::isUAVBinding (bindingRange .bindingType )) 68 { 69SLANG_ASSERT (resourceViewImpl -> m_type == ResourceViewImpl ::Type ::UAV ); 70m_uavs [bindingRange .baseIndex + offset .bindingArrayIndex ]= 71static_cast < UnorderedAccessViewImpl *> (resourceView ); 72 } 73else 74 { 75SLANG_ASSERT (resourceViewImpl -> m_type == ResourceViewImpl ::Type ::SRV ); 76m_srvs [bindingRange .baseIndex + offset .bindingArrayIndex ]= 77static_cast < ShaderResourceViewImpl *> (resourceView ); 78 } 79return SLANG_OK ; 80} 81 82SLANG_NO_THROW Result SLANG_MCALL 83ShaderObjectImpl ::setSampler (ShaderOffset const & offset ,ISamplerState * sampler ) 84{ 85if (offset .bindingRangeIndex < 0 ) 86return SLANG_E_INVALID_ARG ; 87auto layout = getLayout (); 88if (offset .bindingRangeIndex >=layout -> getBindingRangeCount ()) 89return SLANG_E_INVALID_ARG ; 90auto & bindingRange = layout -> getBindingRange (offset .bindingRangeIndex ); 91 92m_samplers [bindingRange .baseIndex + offset .bindingArrayIndex ]= 93static_cast < SamplerStateImpl *> (sampler ); 94return SLANG_OK ; 95} 96 97Result ShaderObjectImpl ::init (IDevice * device ,ShaderObjectLayoutImpl * layout ) 98{ 99m_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// 110size_t uniformSize = layout -> getElementTypeLayout ()-> getSize (); 111if (uniformSize ) 112 { 113m_data .setCount (uniformSize ); 114memset (m_data .getBuffer (),0 ,uniformSize ); 115 } 116 117m_srvs .setCount (layout -> getSRVCount ()); 118m_samplers .setCount (layout -> getSamplerCount ()); 119m_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// 124Index subObjectCount = layout -> getSubObjectCount (); 125m_objects .setCount (subObjectCount ); 126 127for (auto subObjectRangeInfo :layout -> getSubObjectRanges ()) 128 { 129auto 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// 137if (!subObjectLayout ) 138continue ; 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 144auto & bindingRangeInfo = layout -> getBindingRange (subObjectRangeInfo .bindingRangeIndex ); 145for (Index i = 0 ;i < bindingRangeInfo .count ;++ i ) 146 { 147RefPtr < ShaderObjectImpl > subObject ; 148SLANG_RETURN_ON_FAIL ( 149ShaderObjectImpl ::create (device ,subObjectLayout ,subObject .writeRef ())); 150m_objects [bindingRangeInfo .subObjectIndex + i ]= subObject ; 151 } 152 } 153 154return SLANG_OK ; 155} 156 157Result ShaderObjectImpl ::_writeOrdinaryData ( 158void * dest , 159size_t destSize , 160ShaderObjectLayoutImpl * specializedLayout ) 161{ 162// We start by simply writing in the ordinary data contained directly in this object. 163// 164auto src = m_data .getBuffer (); 165auto srcSize = size_t (m_data .getCount ()); 166SLANG_ASSERT (srcSize <=destSize ); 167memcpy (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// 183Index subObjectRangeCounter = 0 ; 184for (auto const & subObjectRangeInfo :specializedLayout -> getSubObjectRanges ()) 185 { 186Index subObjectRangeIndex = subObjectRangeCounter ++ ; 187auto const & bindingRangeInfo = 188specializedLayout -> 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// 194if (bindingRangeInfo .bindingType != slang::BindingType ::ExistentialValue ) 195continue ; 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// 201auto 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// 222size_t subObjectRangePendingDataOffset = subObjectRangeInfo .offset .pendingOrdinaryData ; 223size_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// 232if (subObjectRangePendingDataOffset == 0 ) 233continue ; 234 235for (Slang ::Index i = 0 ;i < count ;++ i ) 236 { 237auto subObject = m_objects [bindingRangeInfo .subObjectIndex + i ]; 238 239RefPtr < ShaderObjectLayoutImpl > subObjectLayout ; 240SLANG_RETURN_ON_FAIL (subObject -> _getSpecializedLayout (subObjectLayout .writeRef ())); 241 242auto subObjectOffset = 243subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride ; 244 245auto subObjectDest = (char * )dest + subObjectOffset ; 246 247subObject -> _writeOrdinaryData ( 248subObjectDest , 249destSize - subObjectOffset , 250subObjectLayout ); 251 } 252 } 253 254return SLANG_OK ; 255} 256 257Result ShaderObjectImpl ::_ensureOrdinaryDataBufferCreatedIfNeeded ( 258DeviceImpl * device , 259ShaderObjectLayoutImpl * specializedLayout ) 260{ 261auto specializedOrdinaryDataSize = specializedLayout -> getTotalOrdinaryDataSize (); 262if (specializedOrdinaryDataSize == 0 ) 263return 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. 267if (!m_ordinaryDataBuffer ) 268 { 269ComPtr < IBufferResource > bufferResourcePtr ; 270IBufferResource ::Desc bufferDesc = {}; 271bufferDesc .type = IResource ::Type ::Buffer ; 272bufferDesc .sizeInBytes = specializedOrdinaryDataSize ; 273bufferDesc .defaultState = ResourceState ::ConstantBuffer ; 274bufferDesc .allowedStates = 275ResourceStateSet (ResourceState ::ConstantBuffer ,ResourceState ::CopyDestination ); 276bufferDesc .memoryType = MemoryType ::Upload ; 277SLANG_RETURN_ON_FAIL ( 278device -> createBufferResource (bufferDesc ,nullptr ,bufferResourcePtr .writeRef ())); 279m_ordinaryDataBuffer = static_cast < BufferResourceImpl *> (bufferResourcePtr .get ()); 280 } 281 282if (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 291auto ordinaryData = device -> map (m_ordinaryDataBuffer , gfx::MapFlavor ::WriteDiscard ); 292auto result = 293_writeOrdinaryData (ordinaryData ,specializedOrdinaryDataSize ,specializedLayout ); 294device -> unmap (m_ordinaryDataBuffer ,0 ,specializedOrdinaryDataSize ); 295m_isConstantBufferDirty = false; 296return result ; 297 } 298return SLANG_OK ; 299} 300 301Result ShaderObjectImpl ::_bindOrdinaryDataBufferIfNeeded ( 302BindingContext * context , 303BindingOffset & ioOffset , 304ShaderObjectLayoutImpl * specializedLayout ) 305{ 306// We start by ensuring that the buffer is created, if it is needed. 307// 308SLANG_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// 314if (m_ordinaryDataBuffer ) 315 { 316context -> setCBV (ioOffset .cbv ,m_ordinaryDataBuffer -> m_buffer ); 317ioOffset .cbv ++ ; 318 } 319 320return SLANG_OK ; 321} 322 323Result ShaderObjectImpl ::bindAsConstantBuffer ( 324BindingContext * context , 325BindingOffset const & inOffset , 326ShaderObjectLayoutImpl * 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// 332BindingOffset offset = inOffset ; 333SLANG_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// 345SLANG_RETURN_ON_FAIL (bindAsValue (context ,inOffset ,specializedLayout )); 346 347return SLANG_OK ; 348} 349 350Result ShaderObjectImpl ::bindAsValue ( 351BindingContext * context , 352BindingOffset const & offset , 353ShaderObjectLayoutImpl * 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 373for (auto bindingRangeIndex :specializedLayout -> getSRVRanges ()) 374 { 375auto const & bindingRange = specializedLayout -> getBindingRange (bindingRangeIndex ); 376auto count = (uint32_t )bindingRange .count ; 377auto baseIndex = (uint32_t )bindingRange .baseIndex ; 378auto registerOffset = bindingRange .registerOffset + offset .srv ; 379for (uint32_t i = 0 ;i < count ;++ i ) 380 { 381auto srv = m_srvs [baseIndex + i ]; 382context -> setSRV (registerOffset + i ,srv ?srv -> m_srv :nullptr ); 383 } 384 } 385 386for (auto bindingRangeIndex :specializedLayout -> getUAVRanges ()) 387 { 388auto const & bindingRange = specializedLayout -> getBindingRange (bindingRangeIndex ); 389auto count = (uint32_t )bindingRange .count ; 390auto baseIndex = (uint32_t )bindingRange .baseIndex ; 391auto registerOffset = bindingRange .registerOffset + offset .uav ; 392for (uint32_t i = 0 ;i < count ;++ i ) 393 { 394auto uav = m_uavs [baseIndex + i ]; 395context -> setUAV (registerOffset + i ,uav ?uav -> m_uav :nullptr ); 396 } 397 } 398 399for (auto bindingRangeIndex :specializedLayout -> getSamplerRanges ()) 400 { 401auto const & bindingRange = specializedLayout -> getBindingRange (bindingRangeIndex ); 402auto count = (uint32_t )bindingRange .count ; 403auto baseIndex = (uint32_t )bindingRange .baseIndex ; 404auto registerOffset = bindingRange .registerOffset + offset .sampler ; 405for (uint32_t i = 0 ;i < count ;++ i ) 406 { 407auto sampler = m_samplers [baseIndex + i ]; 408context -> 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// 415for (auto const & subObjectRange :specializedLayout -> getSubObjectRanges ()) 416 { 417auto subObjectLayout = subObjectRange .layout ; 418auto const & bindingRange = 419specializedLayout -> getBindingRange (subObjectRange .bindingRangeIndex ); 420Index count = bindingRange .count ; 421Index 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// 426BindingOffset rangeOffset = offset ; 427rangeOffset += subObjectRange .offset ; 428 429// Similarly, the "stride" between consecutive objects in 430// the range was also pre-computed. 431// 432BindingOffset rangeStride = subObjectRange .stride ; 433 434switch (bindingRange .bindingType ) 435 { 436// For D3D11-compatible compilation targets, the Slang compiler 437// treats the `ConstantBuffer<T>` and `ParameterBlock<T>` types the same. 438// 439case slang::BindingType ::ConstantBuffer : 440case slang::BindingType ::ParameterBlock : 441 { 442BindingOffset objOffset = rangeOffset ; 443for (Index i = 0 ;i < count ;++ i ) 444 { 445auto subObject = m_objects [subObjectIndex + i ]; 446 447// Unsurprisingly, we bind each object in the range as 448// a constant buffer. 449// 450subObject -> bindAsConstantBuffer (context ,objOffset ,subObjectLayout ); 451 452objOffset += rangeStride ; 453 } 454 } 455break ; 456 457case 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// 461if (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// 468SimpleBindingOffset objOffset = rangeOffset .pending ; 469SimpleBindingOffset objStride = rangeStride .pending ; 470 471for (Index i = 0 ;i < count ;++ i ) 472 { 473auto subObject = m_objects [subObjectIndex + i ]; 474subObject -> bindAsValue (context ,BindingOffset (objOffset ),subObjectLayout ); 475 476objOffset += objStride ; 477 } 478 } 479break ; 480 481default : 482break ; 483 } 484 } 485 486return SLANG_OK ; 487} 488 489Result ShaderObjectImpl ::_getSpecializedLayout (ShaderObjectLayoutImpl ** outLayout ) 490{ 491if (!m_specializedLayout ) 492 { 493SLANG_RETURN_ON_FAIL (_createSpecializedLayout (m_specializedLayout .writeRef ())); 494 } 495returnRefPtr (outLayout ,m_specializedLayout ); 496return SLANG_OK ; 497} 498 499Result ShaderObjectImpl ::_createSpecializedLayout (ShaderObjectLayoutImpl ** outLayout ) 500{ 501ExtendedShaderObjectType extendedType ; 502SLANG_RETURN_ON_FAIL (getSpecializedShaderObjectType (& extendedType )); 503 504auto renderer = getRenderer (); 505RefPtr < ShaderObjectLayoutImpl > layout ; 506SLANG_RETURN_ON_FAIL (renderer -> getShaderObjectLayout ( 507m_layout -> m_slangSession , 508extendedType .slangType , 509m_layout -> getContainerType (), 510 (ShaderObjectLayoutBase ** )layout .writeRef ())); 511 512returnRefPtrMove (outLayout ,layout ); 513return SLANG_OK ; 514} 515 516Result RootShaderObjectImpl ::create ( 517IDevice * device , 518RootShaderObjectLayoutImpl * layout , 519RootShaderObjectImpl ** outShaderObject ) 520{ 521RefPtr < RootShaderObjectImpl > object = new RootShaderObjectImpl (); 522SLANG_RETURN_ON_FAIL (object -> init (device ,layout )); 523 524returnRefPtrMove (outShaderObject ,object ); 525return SLANG_OK ; 526} 527 528Result RootShaderObjectImpl ::collectSpecializationArgs (ExtendedShaderObjectTypeList & args ) 529{ 530SLANG_RETURN_ON_FAIL (ShaderObjectImpl ::collectSpecializationArgs (args )); 531for (auto & entryPoint :m_entryPoints ) 532 { 533SLANG_RETURN_ON_FAIL (entryPoint -> collectSpecializationArgs (args )); 534 } 535return SLANG_OK ; 536} 537 538Result RootShaderObjectImpl ::bindAsRoot ( 539BindingContext * context , 540RootShaderObjectLayoutImpl * 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// 550BindingOffset offset ; 551offset .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// 567BindingOffset ordinaryDataBufferOffset = offset ; 568SLANG_RETURN_ON_FAIL (_bindOrdinaryDataBufferIfNeeded ( 569context , 570/*inout*/ ordinaryDataBufferOffset , 571specializedLayout )); 572SLANG_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// 577auto entryPointCount = m_entryPoints .getCount (); 578for (Index i = 0 ;i < entryPointCount ;++ i ) 579 { 580auto entryPoint = m_entryPoints [i ]; 581auto 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// 586BindingOffset entryPointOffset = offset ; 587entryPointOffset += 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// 593SLANG_RETURN_ON_FAIL ( 594entryPoint -> bindAsConstantBuffer (context ,entryPointOffset ,entryPointInfo .layout )); 595 } 596 597return SLANG_OK ; 598} 599 600Result RootShaderObjectImpl ::init (IDevice * device ,RootShaderObjectLayoutImpl * layout ) 601{ 602SLANG_RETURN_ON_FAIL (Super ::init (device ,layout )); 603 604for (auto entryPointInfo :layout -> getEntryPoints ()) 605 { 606RefPtr < ShaderObjectImpl > entryPoint ; 607SLANG_RETURN_ON_FAIL ( 608ShaderObjectImpl ::create (device ,entryPointInfo .layout ,entryPoint .writeRef ())); 609m_entryPoints .add (entryPoint ); 610 } 611 612return SLANG_OK ; 613} 614 615Result RootShaderObjectImpl ::_createSpecializedLayout (ShaderObjectLayoutImpl ** outLayout ) 616{ 617ExtendedShaderObjectTypeList specializationArgs ; 618SLANG_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// 657ComPtr < slang::IComponentType > specializedComponentType ; 658ComPtr < slang::IBlob > diagnosticBlob ; 659auto result = getLayout ()-> getSlangProgram ()-> specialize ( 660specializationArgs .components .getArrayView ().getBuffer (), 661specializationArgs .getCount (), 662specializedComponentType .writeRef (), 663diagnosticBlob .writeRef ()); 664 665// TODO: print diagnostic message via debug output interface. 666 667if (result != SLANG_OK ) 668return result ; 669 670auto slangSpecializedLayout = specializedComponentType -> getLayout (); 671RefPtr < RootShaderObjectLayoutImpl > specializedLayout ; 672RootShaderObjectLayoutImpl ::create ( 673getRenderer (), 674specializedComponentType , 675slangSpecializedLayout , 676specializedLayout .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// 685auto entryPointCount = m_entryPoints .getCount (); 686for (Index i = 0 ;i < entryPointCount ;++ i ) 687 { 688auto entryPointInfo = specializedLayout -> getEntryPoint (i ); 689auto entryPointVars = m_entryPoints [i ]; 690 691entryPointVars -> m_specializedLayout = entryPointInfo .layout ; 692 } 693 694returnRefPtrMove (outLayout ,specializedLayout ); 695return SLANG_OK ; 696} 697}// namespace d3d11 698}// namespace gfx