yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
ac0dc491e
master
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 ( 16IDevice * device , 17ShaderObjectLayoutImpl * layout , 18ShaderObjectImpl ** outShaderObject ) 19{ 20auto object = RefPtr < ShaderObjectImpl > (new ShaderObjectImpl ()); 21SLANG_RETURN_ON_FAIL (object -> init (device ,layout )); 22 23returnRefPtrMove (outShaderObject ,object ); 24return 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{ 32Index offset = inOffset .uniformOffset ; 33Index size = inSize ; 34 35char * dest = m_data .getBuffer (); 36Index 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// 42if (offset < 0 ) 43 { 44size += offset ; 45offset = 0 ; 46 } 47if ((offset + size ) >=availableSize ) 48 { 49size = availableSize - offset ; 50 } 51 52memcpy (dest + offset ,data ,size ); 53 54m_isConstantBufferDirty = true; 55m_isArgumentBufferDirty = true; 56return SLANG_OK ; 57} 58 59SLANG_NO_THROW Result SLANG_MCALL 60ShaderObjectImpl ::setResource (ShaderOffset const & offset ,IResourceView * resourceView ) 61{ 62if (offset .bindingRangeIndex < 0 ) 63return SLANG_E_INVALID_ARG ; 64auto layout = getLayout (); 65if (offset .bindingRangeIndex >=layout -> getBindingRangeCount ()) 66return SLANG_E_INVALID_ARG ; 67auto & bindingRange = layout -> getBindingRange (offset .bindingRangeIndex ); 68 69auto resourceViewImpl = static_cast < ResourceViewImpl *> (resourceView ); 70switch (bindingRange .bindingType ) 71 { 72case slang::BindingType ::Texture : 73case slang::BindingType ::MutableTexture : 74SLANG_ASSERT (resourceViewImpl -> m_type == ResourceViewImpl ::ViewType ::Texture ); 75m_textures [bindingRange .baseIndex + offset .bindingArrayIndex ]= 76static_cast < TextureResourceViewImpl *> (resourceView ); 77 78// For parameter blocks, we just need to set the resource ID of the texture to argument 79// buffer 80if (getLayout ()-> isParameterBlock ()) 81 { 82auto resourceId = 83static_cast < TextureResourceViewImpl *> (resourceView )-> m_textureView -> gpuResourceID (); 84setData (offset ,& resourceId ,sizeof (resourceId )); 85 } 86break ; 87case slang::BindingType ::RawBuffer : 88case slang::BindingType ::ConstantBuffer : 89case slang::BindingType ::MutableRawBuffer : 90SLANG_ASSERT (resourceViewImpl -> m_type == ResourceViewImpl ::ViewType ::Buffer ); 91m_buffers [bindingRange .baseIndex + offset .bindingArrayIndex ]= 92static_cast < BufferResourceViewImpl *> (resourceView ); 93 94// For parameter blocks, we just need to set the GPU address of the buffer to argument 95// buffer 96if (getLayout ()-> isParameterBlock ()) 97 { 98DeviceAddress gpuAddress = 99static_cast < BufferResourceViewImpl *> (resourceView )-> m_buffer -> getDeviceAddress (); 100setData (offset ,& gpuAddress ,sizeof (gpuAddress )); 101 } 102break ; 103case slang::BindingType ::TypedBuffer : 104case slang::BindingType ::MutableTypedBuffer : 105SLANG_ASSERT (!"Not implemented" ); 106// SLANG_ASSERT(resourceViewImpl->m_type == ResourceViewImpl::ViewType::TexelBuffer); 107// m_textures[bindingRange.baseIndex + offset.bindingArrayIndex] = 108// static_cast<TextureResourceViewImpl*>(resourceView); 109break ; 110 } 111m_isArgumentBufferDirty = true; 112return SLANG_OK ; 113} 114 115SLANG_NO_THROW Result SLANG_MCALL 116ShaderObjectImpl ::setSampler (ShaderOffset const & offset ,ISamplerState * sampler ) 117{ 118if (offset .bindingRangeIndex < 0 ) 119return SLANG_E_INVALID_ARG ; 120auto layout = getLayout (); 121if (offset .bindingRangeIndex >=layout -> getBindingRangeCount ()) 122return SLANG_E_INVALID_ARG ; 123auto & bindingRange = layout -> getBindingRange (offset .bindingRangeIndex ); 124 125m_samplers [bindingRange .baseIndex + offset .bindingArrayIndex ]= 126static_cast < SamplerStateImpl *> (sampler ); 127 128// For parameter blocks, we just need to set the GPU address of the buffer to argument buffer 129if (layout -> isParameterBlock ()) 130 { 131auto resourceId = static_cast < SamplerStateImpl *> (sampler )-> m_samplerState -> gpuResourceID (); 132setData (offset ,& resourceId ,sizeof (resourceId )); 133 } 134m_isArgumentBufferDirty = true; 135return SLANG_OK ; 136} 137 138Result ShaderObjectImpl ::init (IDevice * device ,ShaderObjectLayoutImpl * layout ) 139{ 140m_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// 151size_t uniformSize = 0 ; 152if (layout -> isParameterBlock ()) 153uniformSize = layout -> getParameterBlockTypeLayout ()-> getSize (); 154else 155uniformSize = layout -> getElementTypeLayout ()-> getSize (); 156 157if (uniformSize ) 158 { 159m_data .setCount (uniformSize ); 160memset (m_data .getBuffer (),0 ,uniformSize ); 161 } 162 163m_buffers .setCount (layout -> getBufferCount ()); 164m_textures .setCount (layout -> getTextureCount ()); 165m_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// 170Index subObjectCount = layout -> getSubObjectCount (); 171m_objects .setCount (subObjectCount ); 172 173for (auto subObjectRangeInfo :layout -> getSubObjectRanges ()) 174 { 175auto 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// 183if (!subObjectLayout ) 184continue ; 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 190auto & bindingRangeInfo = layout -> getBindingRange (subObjectRangeInfo .bindingRangeIndex ); 191for (Index i = 0 ;i < bindingRangeInfo .count ;++ i ) 192 { 193RefPtr < ShaderObjectImpl > subObject ; 194 195if (bindingRangeInfo .bindingType == slang::BindingType ::ParameterBlock || 196bindingRangeInfo .bindingType == slang::BindingType ::ConstantBuffer ) 197subObjectLayout -> setIsParameterBlock (); 198 199SLANG_RETURN_ON_FAIL ( 200ShaderObjectImpl ::create (device ,subObjectLayout ,subObject .writeRef ())); 201m_objects [bindingRangeInfo .subObjectIndex + i ]= subObject ; 202 } 203 } 204m_isArgumentBufferDirty = true; 205return SLANG_OK ; 206} 207 208Result ShaderObjectImpl ::_writeOrdinaryData ( 209void * dest , 210size_t destSize , 211ShaderObjectLayoutImpl * layout ) 212{ 213// We start by simply writing in the ordinary data contained directly in this object. 214// 215auto src = m_data .getBuffer (); 216auto srcSize = size_t (m_data .getCount ()); 217SLANG_ASSERT (srcSize <=destSize ); 218memcpy (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// 234Index subObjectRangeCounter = 0 ; 235for (auto const & subObjectRangeInfo :layout -> getSubObjectRanges ()) 236 { 237Index subObjectRangeIndex = subObjectRangeCounter ++ ; 238auto const & bindingRangeInfo = 239layout -> 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// 245if (bindingRangeInfo .bindingType != slang::BindingType ::ExistentialValue ) 246continue ; 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// 252auto 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// 273size_t subObjectRangePendingDataOffset = subObjectRangeInfo .offset .pendingOrdinaryData ; 274size_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// 283if (subObjectRangePendingDataOffset == 0 ) 284continue ; 285 286for (Slang ::Index i = 0 ;i < count ;++ i ) 287 { 288auto subObject = m_objects [bindingRangeInfo .subObjectIndex + i ]; 289 290ShaderObjectLayoutImpl * subObjectLayout = subObject -> getLayout (); 291 292auto subObjectOffset = 293subObjectRangePendingDataOffset + i * subObjectRangePendingDataStride ; 294 295auto subObjectDest = (char * )dest + subObjectOffset ; 296 297subObject -> _writeOrdinaryData ( 298subObjectDest , 299destSize - subObjectOffset , 300subObjectLayout ); 301 } 302 } 303return SLANG_OK ; 304} 305 306Result ShaderObjectImpl ::_ensureOrdinaryDataBufferCreatedIfNeeded ( 307DeviceImpl * device , 308ShaderObjectLayoutImpl * layout ) 309{ 310auto ordinaryDataSize = layout -> getTotalOrdinaryDataSize (); 311if (ordinaryDataSize == 0 ) 312return 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. 316if (!m_ordinaryDataBuffer ) 317 { 318ComPtr < IBufferResource > bufferResourcePtr ; 319IBufferResource ::Desc bufferDesc = {}; 320bufferDesc .type = IResource ::Type ::Buffer ; 321bufferDesc .sizeInBytes = ordinaryDataSize ; 322bufferDesc .defaultState = ResourceState ::ConstantBuffer ; 323bufferDesc .allowedStates = 324ResourceStateSet (ResourceState ::ConstantBuffer ,ResourceState ::CopyDestination ); 325bufferDesc .memoryType = MemoryType ::Upload ; 326SLANG_RETURN_ON_FAIL ( 327device -> createBufferResource (bufferDesc ,nullptr ,bufferResourcePtr .writeRef ())); 328m_ordinaryDataBuffer = static_cast < BufferResourceImpl *> (bufferResourcePtr .get ()); 329 } 330 331if (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 340MemoryRange range = {0 ,ordinaryDataSize }; 341void * ordinaryData ; 342SLANG_RETURN_ON_FAIL (m_ordinaryDataBuffer -> map (& range ,& ordinaryData )); 343auto result = _writeOrdinaryData (ordinaryData ,ordinaryDataSize ,layout ); 344m_ordinaryDataBuffer -> unmap (& range ); 345m_isConstantBufferDirty = false; 346return result ; 347 } 348return SLANG_OK ; 349} 350 351Result ShaderObjectImpl ::_bindOrdinaryDataBufferIfNeeded ( 352BindingContext * context , 353BindingOffset & ioOffset , 354ShaderObjectLayoutImpl * layout ) 355{ 356// We start by ensuring that the buffer is created, if it is needed. 357// 358SLANG_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// 363if (m_ordinaryDataBuffer ) 364 { 365context -> setBuffer (m_ordinaryDataBuffer -> m_buffer .get (),ioOffset .buffer ); 366ioOffset .buffer ++ ; 367 } 368 369return SLANG_OK ; 370} 371 372void ShaderObjectImpl ::writeOrdinaryDataIntoArgumentBuffer ( 373 slang::TypeLayoutReflection * argumentBufferTypeLayout , 374 slang::TypeLayoutReflection * defaultTypeLayout , 375uint8_t * argumentBuffer , 376uint8_t * srcData ) 377{ 378// If we are pure data, just copy it over from srcData. 379if (defaultTypeLayout -> getCategoryCount ()== 1 ) 380 { 381switch (defaultTypeLayout -> getCategoryByIndex (0 )) 382 { 383case slang::ParameterCategory ::Uniform : 384// Just copy the uniform data 385memcpy (argumentBuffer ,srcData ,defaultTypeLayout -> getSize ()); 386break ; 387 } 388return ; 389 } 390 391for (unsigned int i = 0 ;i < argumentBufferTypeLayout -> getFieldCount ();i ++ ) 392 { 393auto argumentBufferField = argumentBufferTypeLayout -> getFieldByIndex (i ); 394auto defaultLayoutField = defaultTypeLayout -> getFieldByIndex (i ); 395// If the field is mixed type, recurse. 396writeOrdinaryDataIntoArgumentBuffer ( 397argumentBufferField -> getTypeLayout (), 398defaultLayoutField -> getTypeLayout (), 399argumentBuffer + argumentBufferField -> getOffset (), 400srcData + defaultLayoutField -> getOffset ()); 401 } 402} 403 404BufferResourceImpl * ShaderObjectImpl ::_ensureArgumentBufferUpToDate ( 405BindingContext * context , 406DeviceImpl * device , 407ShaderObjectLayoutImpl * layout ) 408{ 409auto 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. 413if (!m_argumentBuffer ) 414 { 415ComPtr < IBufferResource > bufferResourcePtr ; 416IBufferResource ::Desc bufferDesc = {}; 417bufferDesc .type = IResource ::Type ::Buffer ; 418bufferDesc .sizeInBytes = typeLayout -> getSize (); 419bufferDesc .defaultState = ResourceState ::ConstantBuffer ; 420bufferDesc .allowedStates = 421ResourceStateSet (ResourceState ::ConstantBuffer ,ResourceState ::CopyDestination ); 422bufferDesc .memoryType = MemoryType ::Upload ; 423SLANG_RETURN_NULL_ON_FAIL ( 424device -> createBufferResource (bufferDesc ,nullptr ,bufferResourcePtr .writeRef ())); 425m_argumentBuffer = static_cast < BufferResourceImpl *> (bufferResourcePtr .get ()); 426 } 427 428if (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// 434auto dataSize = typeLayout -> getSize (); 435MemoryRange range = {0 ,dataSize }; 436void * argumentData ; 437SLANG_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 445SLANG_ASSERT (m_data .getCount ()== dataSize ); 446memcpy (argumentData ,m_data .getBuffer (),dataSize ); 447 448// Special handle the parameter block and constant buffer 449for (uint32_t i = 0 ;i < typeLayout -> getFieldCount ();i ++ ) 450 { 451auto field = typeLayout -> getFieldByIndex (i ); 452auto kind = field -> getTypeLayout ()-> getKind (); 453switch (kind ) 454 { 455case slang::TypeReflection ::Kind ::ConstantBuffer : 456case slang::TypeReflection ::Kind ::ParameterBlock : 457 { 458// set address of argument buffer of nested parameter block to corresponding 459// offset in argument buffer 460auto offset = field -> getOffset (); 461uint32_t bindingRangeIndex = typeLayout -> getFieldBindingRangeOffset (i ); 462auto bindingRange = layout -> getBindingRange (bindingRangeIndex ); 463auto subObjectIndex = bindingRange .subObjectIndex ; 464auto subObject = m_objects [subObjectIndex ]; 465BufferResourceImpl * argumentBufferPtr = 466subObject -> _ensureArgumentBufferUpToDate ( 467context , 468device , 469subObject -> getLayout ()); 470if (argumentBufferPtr ) 471 { 472uint8_t * argumentBuffer = (uint8_t * )argumentData + offset ; 473 gfx::DeviceAddress bufferAddr = argumentBufferPtr -> getDeviceAddress (); 474memcpy (argumentBuffer ,& bufferAddr ,sizeof (bufferAddr )); 475 476MTL ::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 479context -> useResources ( 480resource , 4811 , 482MTL ::ResourceUsageWrite |MTL ::ResourceUsageRead ); 483 } 484break ; 485 } 486default : 487break ; 488 } 489 } 490 491// Handle bindless resources 492List < MTL ::Resource const *> resources ; 493for (uint32_t i = 0 ;i < m_buffers .getCount ();i ++ ) 494 { 495if (m_buffers [i ]) 496 { 497MTL ::Buffer * mtlBuffer = m_buffers [i ]-> m_buffer -> m_buffer .get (); 498resources .add (mtlBuffer ); 499 } 500 } 501 502for (uint32_t i = 0 ;i < m_textures .getCount ();i ++ ) 503 { 504if (m_textures [i ]) 505 { 506MTL ::Texture * mtlTexture = m_textures [i ]-> m_texture -> m_texture .get (); 507resources .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. 513context -> useResources ( 514resources .getBuffer (), 515resources .getCount (), 516MTL ::ResourceUsageWrite |MTL ::ResourceUsageRead ); 517 518m_argumentBuffer -> unmap (& range ); 519m_isArgumentBufferDirty = false; 520 } 521 522return m_argumentBuffer .get (); 523} 524 525Result ShaderObjectImpl ::bindAsParameterBlock ( 526BindingContext * context , 527BindingOffset const & inOffset , 528ShaderObjectLayoutImpl * layout ) 529{ 530if (!context -> device -> m_hasArgumentBufferTier2 ) 531return SLANG_FAIL ; 532 533auto argumentBuffer = _ensureArgumentBufferUpToDate (context ,context -> device ,layout ); 534 535if (m_argumentBuffer ) 536 { 537context -> setBuffer (m_argumentBuffer -> m_buffer .get (),inOffset .buffer ); 538 } 539return SLANG_OK ; 540} 541 542Result ShaderObjectImpl ::bindAsConstantBuffer ( 543BindingContext * context , 544BindingOffset const & inOffset , 545ShaderObjectLayoutImpl * 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// 551BindingOffset offset = inOffset ; 552SLANG_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// 563SLANG_RETURN_ON_FAIL (bindAsValue (context ,inOffset ,layout )); 564 565return SLANG_OK ; 566} 567 568Result ShaderObjectImpl ::bindAsValue ( 569BindingContext * context , 570BindingOffset const & offset , 571ShaderObjectLayoutImpl * 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 579for (auto bindingRangeIndex :layout -> getBufferRanges ()) 580 { 581auto const & bindingRange = layout -> getBindingRange (bindingRangeIndex ); 582auto count = (uint32_t )bindingRange .count ; 583auto baseIndex = (uint32_t )bindingRange .baseIndex ; 584auto registerOffset = bindingRange .registerOffset + offset .buffer ; 585for (uint32_t i = 0 ;i < count ;++ i ) 586 { 587auto buffer = m_buffers [baseIndex + i ]; 588context -> setBuffer ( 589buffer ?buffer -> m_buffer -> m_buffer .get () :nullptr , 590registerOffset + i ); 591 } 592 } 593 594for (auto bindingRangeIndex :layout -> getTextureRanges ()) 595 { 596auto const & bindingRange = layout -> getBindingRange (bindingRangeIndex ); 597auto count = (uint32_t )bindingRange .count ; 598auto baseIndex = (uint32_t )bindingRange .baseIndex ; 599auto registerOffset = bindingRange .registerOffset + offset .texture ; 600for (uint32_t i = 0 ;i < count ;++ i ) 601 { 602auto texture = m_textures [baseIndex + i ]; 603context -> setTexture ( 604texture ?texture -> m_textureView .get () :nullptr , 605registerOffset + i ); 606 } 607 } 608 609for (auto bindingRangeIndex :layout -> getSamplerRanges ()) 610 { 611auto const & bindingRange = layout -> getBindingRange (bindingRangeIndex ); 612auto count = (uint32_t )bindingRange .count ; 613auto baseIndex = (uint32_t )bindingRange .baseIndex ; 614auto registerOffset = bindingRange .registerOffset + offset .sampler ; 615for (uint32_t i = 0 ;i < count ;++ i ) 616 { 617auto sampler = m_samplers [baseIndex + i ]; 618context -> setSampler ( 619sampler ?sampler -> m_samplerState .get () :nullptr , 620registerOffset + 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// 627for (auto const & subObjectRange :layout -> getSubObjectRanges ()) 628 { 629auto subObjectLayout = subObjectRange .layout ; 630auto const & bindingRange = layout -> getBindingRange (subObjectRange .bindingRangeIndex ); 631Index count = bindingRange .count ; 632Index 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// 637BindingOffset rangeOffset = offset ; 638rangeOffset += subObjectRange .offset ; 639 640// Similarly, the "stride" between consecutive objects in 641// the range was also pre-computed. 642// 643BindingOffset rangeStride = subObjectRange .stride ; 644 645switch (bindingRange .bindingType ) 646 { 647case slang::BindingType ::ConstantBuffer : 648 { 649BindingOffset objOffset = rangeOffset ; 650for (Index i = 0 ;i < count ;++ i ) 651 { 652auto subObject = m_objects [subObjectIndex + i ]; 653 654// Unsurprisingly, we bind each object in the range as 655// a constant buffer. 656// 657SLANG_RETURN_ON_FAIL ( 658subObject -> bindAsConstantBuffer (context ,objOffset ,subObjectLayout )); 659 660objOffset += rangeStride ; 661 } 662break ; 663 } 664case slang::BindingType ::ParameterBlock : 665 { 666BindingOffset objOffset = rangeOffset ; 667for (Index i = 0 ;i < count ;++ i ) 668 { 669auto subObject = m_objects [subObjectIndex + i ]; 670SLANG_RETURN_ON_FAIL ( 671subObject -> bindAsParameterBlock (context ,objOffset ,subObjectLayout )); 672objOffset += rangeStride ; 673 } 674 } 675break ; 676 677#if 0 678case 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// 682if (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// 689SimpleBindingOffset objOffset = rangeOffset .pending ; 690SimpleBindingOffset objStride = rangeStride .pending ; 691 692for (Index i = 0 ;i < count ;++ i ) 693 { 694auto subObject = m_objects [subObjectIndex + i ]; 695subObject -> bindAsValue (context ,BindingOffset (objOffset ),subObjectLayout ); 696 697objOffset += objStride ; 698 } 699 } 700break ; 701#endif 702 703default : 704break ; 705 } 706 } 707 708return SLANG_OK ; 709} 710 711Result RootShaderObjectImpl ::create ( 712IDevice * device , 713RootShaderObjectLayoutImpl * layout , 714RootShaderObjectImpl ** outShaderObject ) 715{ 716RefPtr < RootShaderObjectImpl > object = new RootShaderObjectImpl (); 717SLANG_RETURN_ON_FAIL (object -> init (device ,layout )); 718 719returnRefPtrMove (outShaderObject ,object ); 720return SLANG_OK ; 721} 722 723Result RootShaderObjectImpl ::collectSpecializationArgs (ExtendedShaderObjectTypeList & args ) 724{ 725SLANG_RETURN_ON_FAIL (ShaderObjectImpl ::collectSpecializationArgs (args )); 726for (auto & entryPoint :m_entryPoints ) 727 { 728SLANG_RETURN_ON_FAIL (entryPoint -> collectSpecializationArgs (args )); 729 } 730return 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// 743BindingOffset offset ; 744#if 0 745offset .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 763BindingOffset ordinaryDataBufferOffset = offset ; 764SLANG_RETURN_ON_FAIL (_bindOrdinaryDataBufferIfNeeded (context ,/*inout*/ ordinaryDataBufferOffset ,layout )); 765#endif 766SLANG_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// 771auto entryPointCount = m_entryPoints .getCount (); 772for (Index i = 0 ;i < entryPointCount ;++ i ) 773 { 774auto entryPoint = m_entryPoints [i ]; 775auto 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// 780BindingOffset entryPointOffset = offset ; 781entryPointOffset += 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// 787SLANG_RETURN_ON_FAIL ( 788entryPoint -> bindAsConstantBuffer (context ,entryPointOffset ,entryPointInfo .layout )); 789 } 790 791return SLANG_OK ; 792} 793 794Result RootShaderObjectImpl ::init (IDevice * device ,RootShaderObjectLayoutImpl * layout ) 795{ 796SLANG_RETURN_ON_FAIL (Super ::init (device ,layout )); 797m_entryPoints .clear (); 798for (auto entryPointInfo :layout -> getEntryPoints ()) 799 { 800RefPtr < ShaderObjectImpl > entryPoint ; 801SLANG_RETURN_ON_FAIL ( 802ShaderObjectImpl ::create (device ,entryPointInfo .layout ,entryPoint .writeRef ())); 803m_entryPoints .add (entryPoint ); 804 } 805 806return SLANG_OK ; 807} 808 809}// namespace metal 810}// namespace gfx