yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
43d0c2100
master
1#if 0 2// Duplicated: this is identical to slang-rhi\tests\test-shader-cache.cpp 3// This file uses gfx-specific shader cache functionality (IShaderCache) 4// that has been replaced with a different caching system (IPersistentCache) in slang-rhi. 5 6#include "core/slang-basic.h" 7#include "core/slang-file-system.h" 8#include "core/slang-io.h" 9#include "core/slang-process.h" 10#include "core/slang-string-util.h" 11#include "gfx-test-texture-util.h" 12#include "gfx-test-util.h" 13#include "slang-gfx.h" 14#include "slang-rhi/shader-cursor.h" 15#include "unit-test/slang-unit-test.h" 16 17using namespace gfx ; 18using namespace Slang ; 19 20namespace gfx_test 21{ 22// Base class for shader cache tests. 23// Slang currently does not allow reloading shaders from modified sources. 24// Because of this, the tests recreate a GFX device for each test step, 25// allowing to modify shader sources in between. 26struct ShaderCacheTest 27{ 28UnitTestContext * context ; 29Slang ::RenderApiFlag ::Enum api ; 30 31String testDirectory ; 32String cacheDirectory ; 33 34ComPtr < ISlangMutableFileSystem > diskFileSystem ; 35 36IDevice ::ShaderCacheDesc shaderCacheDesc = {}; 37 38ComPtr < IDevice > device ; 39ComPtr < IShaderCache > shaderCache ; 40ComPtr < IPipelineState > pipelineState ; 41ComPtr < IBufferResource > bufferResource ; 42ComPtr < IResourceView > bufferView ; 43 44String computeShaderA = String ( 45R"( 46[shader("compute")] 47[numthreads(4, 1, 1)] 48void main( 49uint3 sv_dispatchThreadID : SV_DispatchThreadID, 50uniform RWStructuredBuffer<float> buffer) 51{ 52var input = buffer[sv_dispatchThreadID.x]; 53buffer[sv_dispatchThreadID.x] = input + 1.0f; 54} 55)" ); 56 57String computeShaderB = String ( 58R"( 59[shader("compute")] 60[numthreads(4, 1, 1)] 61void main( 62uint3 sv_dispatchThreadID : SV_DispatchThreadID, 63uniform RWStructuredBuffer<float> buffer) 64{ 65var input = buffer[sv_dispatchThreadID.x]; 66buffer[sv_dispatchThreadID.x] = input + 2.0f; 67} 68)" ); 69 70String computeShaderC = String ( 71R"( 72[shader("compute")] 73[numthreads(4, 1, 1)] 74void main( 75uint3 sv_dispatchThreadID : SV_DispatchThreadID, 76uniform RWStructuredBuffer<float> buffer) 77{ 78var input = buffer[sv_dispatchThreadID.x]; 79buffer[sv_dispatchThreadID.x] = input + 3.0f; 80} 81)" ); 82 83 84void removeDirectory (const String & directory ) 85 { 86auto osFileSystem = OSFileSystem ::getMutableSingleton (); 87 88struct Context 89 { 90ISlangMutableFileSystem * fileSystem ; 91const String & directory ; 92 }context {osFileSystem ,directory }; 93 94osFileSystem -> enumeratePathContents ( 95directory .getBuffer (), 96 [](SlangPathType pathType ,const char * fileName ,void * userData ) 97 { 98struct Context * context = static_cast < Context *> (userData ); 99if (pathType == SlangPathType ::SLANG_PATH_TYPE_FILE ) 100 { 101String path = Path ::simplify (context -> directory + "/" + fileName ); 102context -> fileSystem -> remove (path .getBuffer ()); 103 } 104 }, 105& context ); 106 107osFileSystem -> remove (directory .getBuffer ()); 108 } 109 110void writeShader (const String & source ,const String & fileName ) 111 { 112diskFileSystem -> saveFile (fileName .getBuffer (),source .getBuffer (),source .getLength ()); 113 } 114 115void init (UnitTestContext * context ,Slang ::RenderApiFlag ::Enum api ) 116 { 117this -> context = context ; 118this -> api = api ; 119testDirectory = Path ::simplify ( 120Path ::getParentDirectory (Path ::getExecutablePath ())+ "/shader-cache-test" + 121String (Process ::getId ())); 122cacheDirectory = Path ::simplify (testDirectory + "/cache" + String (Process ::getId ())); 123 124// Cleanup if there are stale files from a previously aborted test. 125removeDirectory (cacheDirectory ); 126removeDirectory (testDirectory ); 127 128Path ::createDirectory (testDirectory ); 129diskFileSystem = new RelativeFileSystem (OSFileSystem ::getMutableSingleton (),testDirectory ); 130shaderCacheDesc .shaderCachePath = cacheDirectory .getBuffer (); 131 } 132 133void cleanup () 134 { 135removeDirectory (cacheDirectory ); 136removeDirectory (testDirectory ); 137 } 138 139template < typename Func > 140void runStep (Func func ) 141 { 142List < const char *> additionalSearchPaths ; 143additionalSearchPaths .add (testDirectory .getBuffer ()); 144 145runTestImpl ( 146 [this ,func ](IDevice * device ,UnitTestContext * ctx ) 147 { 148this -> device = device ; 149SLANG_CHECK_ABORT (SLANG_SUCCEEDED (device -> queryInterface ( 150SLANG_UUID_IShaderCache , 151 (void ** )this -> shaderCache .writeRef ()))); 152func (); 153this -> device = nullptr ; 154this -> shaderCache = nullptr ; 155 }, 156context , 157api , 158additionalSearchPaths , 159shaderCacheDesc ); 160 } 161 162void createComputeResources () 163 { 164const int numberCount = 4 ; 165float initialData []= {0.0f ,1.0f ,2.0f ,3.0f }; 166IBufferResource ::Desc bufferDesc = {}; 167bufferDesc .sizeInBytes = numberCount * sizeof (float ); 168bufferDesc .format = Format ::Unknown ; 169bufferDesc .elementSize = sizeof (float ); 170bufferDesc .allowedStates = ResourceStateSet ( 171ResourceState ::ShaderResource , 172ResourceState ::UnorderedAccess , 173ResourceState ::CopyDestination , 174ResourceState ::CopySource ); 175bufferDesc .defaultState = ResourceState ::UnorderedAccess ; 176bufferDesc .memoryType = MemoryType ::DeviceLocal ; 177 178GFX_CHECK_CALL_ABORT (device -> createBufferResource ( 179bufferDesc , 180 (void * )initialData , 181bufferResource .writeRef ())); 182 183IResourceView ::Desc viewDesc = {}; 184viewDesc .type = IResourceView ::Type ::UnorderedAccess ; 185viewDesc .format = Format ::Unknown ; 186GFX_CHECK_CALL_ABORT ( 187device -> createBufferView (bufferResource ,nullptr ,viewDesc ,bufferView .writeRef ())); 188 } 189 190void freeComputeResources () 191 { 192bufferResource = nullptr ; 193bufferView = nullptr ; 194pipelineState = nullptr ; 195 } 196 197void createComputePipeline (const char * moduleName ,const char * entryPointName ) 198 { 199ComPtr < IShaderProgram > shaderProgram ; 200 slang::ProgramLayout * slangReflection ; 201GFX_CHECK_CALL_ABORT ( 202loadComputeProgram (device ,shaderProgram ,moduleName ,entryPointName ,slangReflection )); 203 204ComputePipelineStateDesc pipelineDesc = {}; 205pipelineDesc .program = shaderProgram .get (); 206GFX_CHECK_CALL_ABORT ( 207device -> createComputePipelineState (pipelineDesc ,pipelineState .writeRef ())); 208 } 209 210void createComputePipeline (Slang ::String shaderSource ) 211 { 212ComPtr < IShaderProgram > shaderProgram ; 213GFX_CHECK_CALL_ABORT (loadComputeProgramFromSource (device ,shaderProgram ,shaderSource )); 214 215ComputePipelineStateDesc pipelineDesc = {}; 216pipelineDesc .program = shaderProgram .get (); 217GFX_CHECK_CALL_ABORT ( 218device -> createComputePipelineState (pipelineDesc ,pipelineState .writeRef ())); 219 } 220 221void dispatchComputePipeline () 222 { 223ComPtr < ITransientResourceHeap > transientHeap ; 224ITransientResourceHeap ::Desc transientHeapDesc = {}; 225transientHeapDesc .constantBufferSize = 4096 ; 226GFX_CHECK_CALL_ABORT ( 227device -> createTransientResourceHeap (transientHeapDesc ,transientHeap .writeRef ())); 228 229ICommandQueue ::Desc queueDesc = {ICommandQueue ::QueueType ::Graphics }; 230auto queue = device -> createCommandQueue (queueDesc ); 231 232auto commandBuffer = transientHeap -> createCommandBuffer (); 233auto encoder = commandBuffer -> encodeComputeCommands (); 234 235auto rootObject = encoder -> bindPipeline (pipelineState ); 236 237// Bind buffer view to the entry point. 238ShaderCursor entryPointCursor (rootObject -> getEntryPoint (0 )); 239entryPointCursor .getPath ("buffer" ).setResource (bufferView ); 240 241encoder -> dispatchCompute (4 ,1 ,1 ); 242encoder -> endEncoding (); 243commandBuffer -> close (); 244queue -> executeCommandBuffer (commandBuffer ); 245queue -> waitOnHost (); 246 } 247 248bool checkOutput (const List < float >& expectedOutput ) 249 { 250ComPtr < ISlangBlob > bufferBlob ; 251device -> readBufferResource (bufferResource ,0 ,4 * sizeof (float ),bufferBlob .writeRef ()); 252SLANG_CHECK_ABORT ( 253bufferBlob && bufferBlob -> getBufferSize ()== expectedOutput .getCount ()* sizeof (float )); 254return ::memcmp ( 255bufferBlob -> getBufferPointer (), 256expectedOutput .getBuffer (), 257bufferBlob -> getBufferSize ())== 0 ; 258 } 259 260bool runComputePipeline ( 261const char * moduleName , 262const char * entryPointName , 263const List < float >& expectedOutput ) 264 { 265createComputeResources (); 266createComputePipeline (moduleName ,entryPointName ); 267dispatchComputePipeline (); 268bool hasExpectedOutput = checkOutput (expectedOutput ); 269SLANG_CHECK (hasExpectedOutput ); 270freeComputeResources (); 271return hasExpectedOutput ; 272 } 273 274bool runComputePipeline (Slang ::String shaderSource ,const List < float >& expectedOutput ) 275 { 276createComputeResources (); 277createComputePipeline (shaderSource ); 278dispatchComputePipeline (); 279bool hasExpectedOutput = checkOutput (expectedOutput ); 280SLANG_CHECK (hasExpectedOutput ); 281freeComputeResources (); 282return hasExpectedOutput ; 283 } 284 285ShaderCacheStats getStats () 286 { 287SLANG_ASSERT (shaderCache ); 288ShaderCacheStats stats ; 289shaderCache -> getShaderCacheStats (& stats ); 290return stats ; 291 } 292 293void run (UnitTestContext * context ,Slang ::RenderApiFlag ::Enum api ) 294 { 295init (context ,api ); 296runTests (); 297cleanup (); 298 } 299 300virtual void runTests ()= 0 ; 301}; 302 303// Basic shader cache test using 3 different shader files stored on disk. 304struct ShaderCacheSourceFile :ShaderCacheTest 305{ 306void runTests () 307 { 308// Write shader source files. 309writeShader (computeShaderA ,"shader-cache-tmp-a.slang" ); 310writeShader (computeShaderB ,"shader-cache-tmp-b.slang" ); 311writeShader (computeShaderC ,"shader-cache-tmp-c.slang" ); 312 313// Cache is cold and we expect 3 misses. 314runStep ( 315 [this ]() 316 { 317SLANG_CHECK (runComputePipeline ("shader-cache-tmp-a" ,"main" , {1.f ,2.f ,3.f ,4.f })); 318SLANG_CHECK (runComputePipeline ("shader-cache-tmp-b" ,"main" , {2.f ,3.f ,4.f ,5.f })); 319SLANG_CHECK (runComputePipeline ("shader-cache-tmp-c" ,"main" , {3.f ,4.f ,5.f ,6.f })); 320 321SLANG_CHECK (getStats ().missCount == 3 ); 322SLANG_CHECK (getStats ().hitCount == 0 ); 323SLANG_CHECK (getStats ().entryCount == 3 ); 324 }); 325 326// Cache is hot and we expect 3 hits. 327runStep ( 328 [this ]() 329 { 330SLANG_CHECK (runComputePipeline ("shader-cache-tmp-a" ,"main" , {1.f ,2.f ,3.f ,4.f })); 331SLANG_CHECK (runComputePipeline ("shader-cache-tmp-b" ,"main" , {2.f ,3.f ,4.f ,5.f })); 332SLANG_CHECK (runComputePipeline ("shader-cache-tmp-c" ,"main" , {3.f ,4.f ,5.f ,6.f })); 333 334SLANG_CHECK (getStats ().missCount == 0 ); 335SLANG_CHECK (getStats ().hitCount == 3 ); 336SLANG_CHECK (getStats ().entryCount == 3 ); 337 }); 338 339// Write shader source files, all rotated by one. 340writeShader (computeShaderA ,"shader-cache-tmp-b.slang" ); 341writeShader (computeShaderB ,"shader-cache-tmp-c.slang" ); 342writeShader (computeShaderC ,"shader-cache-tmp-a.slang" ); 343 344// Cache is cold again and we expect 3 misses. 345runStep ( 346 [this ]() 347 { 348SLANG_CHECK (runComputePipeline ("shader-cache-tmp-b" ,"main" , {1.f ,2.f ,3.f ,4.f })); 349SLANG_CHECK (runComputePipeline ("shader-cache-tmp-c" ,"main" , {2.f ,3.f ,4.f ,5.f })); 350SLANG_CHECK (runComputePipeline ("shader-cache-tmp-a" ,"main" , {3.f ,4.f ,5.f ,6.f })); 351 352SLANG_CHECK (getStats ().missCount == 3 ); 353SLANG_CHECK (getStats ().hitCount == 0 ); 354SLANG_CHECK (getStats ().entryCount == 6 ); 355 }); 356 357// Cache is hot again and we expect 3 hits. 358runStep ( 359 [this ]() 360 { 361SLANG_CHECK (runComputePipeline ("shader-cache-tmp-b" ,"main" , {1.f ,2.f ,3.f ,4.f })); 362SLANG_CHECK (runComputePipeline ("shader-cache-tmp-c" ,"main" , {2.f ,3.f ,4.f ,5.f })); 363SLANG_CHECK (runComputePipeline ("shader-cache-tmp-a" ,"main" , {3.f ,4.f ,5.f ,6.f })); 364 365SLANG_CHECK (getStats ().missCount == 0 ); 366SLANG_CHECK (getStats ().hitCount == 3 ); 367SLANG_CHECK (getStats ().entryCount == 6 ); 368 }); 369 } 370}; 371 372// Test caching of shaders that are compiled from source strings instead of files. 373struct ShaderCacheTestSourceString :ShaderCacheTest 374{ 375void runTests () 376 { 377// Cache is cold and we expect 3 misses. 378runStep ( 379 [this ]() 380 { 381SLANG_CHECK (runComputePipeline (computeShaderA , {1.f ,2.f ,3.f ,4.f })); 382SLANG_CHECK (runComputePipeline (computeShaderB , {2.f ,3.f ,4.f ,5.f })); 383SLANG_CHECK (runComputePipeline (computeShaderC , {3.f ,4.f ,5.f ,6.f })); 384 385SLANG_CHECK (getStats ().missCount == 3 ); 386SLANG_CHECK (getStats ().hitCount == 0 ); 387SLANG_CHECK (getStats ().entryCount == 3 ); 388 }); 389 390// Cache is hot and we expect 3 hits. 391runStep ( 392 [this ]() 393 { 394SLANG_CHECK (runComputePipeline (computeShaderA , {1.f ,2.f ,3.f ,4.f })); 395SLANG_CHECK (runComputePipeline (computeShaderB , {2.f ,3.f ,4.f ,5.f })); 396SLANG_CHECK (runComputePipeline (computeShaderC , {3.f ,4.f ,5.f ,6.f })); 397 398SLANG_CHECK (getStats ().missCount == 0 ); 399SLANG_CHECK (getStats ().hitCount == 3 ); 400SLANG_CHECK (getStats ().entryCount == 3 ); 401 }); 402 } 403}; 404 405// Test one shader file on disk with multiple entry points. 406struct ShaderCacheTestEntryPoint :ShaderCacheTest 407{ 408void runTests () 409 { 410// Cache is cold and we expect 3 misses, one for each entry point. 411runStep ( 412 [this ]() 413 { 414SLANG_CHECK (runComputePipeline ( 415"shader-cache-multiple-entry-points" , 416"computeA" , 417 {1.f ,2.f ,3.f ,4.f })); 418SLANG_CHECK (runComputePipeline ( 419"shader-cache-multiple-entry-points" , 420"computeB" , 421 {2.f ,3.f ,4.f ,5.f })); 422SLANG_CHECK (runComputePipeline ( 423"shader-cache-multiple-entry-points" , 424"computeC" , 425 {3.f ,4.f ,5.f ,6.f })); 426 427SLANG_CHECK (getStats ().missCount == 3 ); 428SLANG_CHECK (getStats ().hitCount == 0 ); 429SLANG_CHECK (getStats ().entryCount == 3 ); 430 }); 431 432// Cache is hot and we expect 3 hits. 433runStep ( 434 [this ]() 435 { 436SLANG_CHECK (runComputePipeline ( 437"shader-cache-multiple-entry-points" , 438"computeA" , 439 {1.f ,2.f ,3.f ,4.f })); 440SLANG_CHECK (runComputePipeline ( 441"shader-cache-multiple-entry-points" , 442"computeB" , 443 {2.f ,3.f ,4.f ,5.f })); 444SLANG_CHECK (runComputePipeline ( 445"shader-cache-multiple-entry-points" , 446"computeC" , 447 {3.f ,4.f ,5.f ,6.f })); 448 449SLANG_CHECK (getStats ().missCount == 0 ); 450SLANG_CHECK (getStats ().hitCount == 3 ); 451SLANG_CHECK (getStats ().entryCount == 3 ); 452 }); 453 } 454}; 455 456// Test cache invalidation due to an import/include file being changed on disk. 457struct ShaderCacheTestImportInclude :ShaderCacheTest 458{ 459String importedContentsA = String ( 460R"( 461public void processElement(RWStructuredBuffer<float> buffer, uint index) 462{ 463var input = buffer[index]; 464buffer[index] = input + 1.0f; 465} 466)" ); 467 468String importedContentsB = String ( 469R"( 470public void processElement(RWStructuredBuffer<float> buffer, uint index) 471{ 472var input = buffer[index]; 473buffer[index] = input + 2.0f; 474} 475)" ); 476 477String importFile = String ( 478R"( 479import shader_cache_tmp_imported; 480481 [shader("compute")] 482[numthreads(4, 1, 1)] 483void main( 484uint3 sv_dispatchThreadID : SV_DispatchThreadID, 485uniform RWStructuredBuffer<float> buffer) 486{ 487processElement(buffer, sv_dispatchThreadID.x); 488} 489)" ); 490 491String includeFile = String ( 492R"( 493#include "shader-cache-tmp-imported.slang" 494 495[shader("compute")] 496[numthreads(4, 1, 1)] 497void main( 498uint3 sv_dispatchThreadID : SV_DispatchThreadID, 499uniform RWStructuredBuffer<float> buffer) 500{ 501processElement(buffer, sv_dispatchThreadID.x); 502})" ); 503 504void runTests () 505 { 506// Write shader source files. 507writeShader (importedContentsA ,"shader-cache-tmp-imported.slang" ); 508writeShader (importFile ,"shader-cache-tmp-import.slang" ); 509writeShader (includeFile ,"shader-cache-tmp-include.slang" ); 510 511// Cache is cold and we expect 2 misses. 512runStep ( 513 [this ]() 514 { 515SLANG_CHECK ( 516runComputePipeline ("shader-cache-tmp-import" ,"main" , {1.f ,2.f ,3.f ,4.f })); 517SLANG_CHECK ( 518runComputePipeline ("shader-cache-tmp-include" ,"main" , {1.f ,2.f ,3.f ,4.f })); 519 520SLANG_CHECK (getStats ().missCount == 2 ); 521SLANG_CHECK (getStats ().hitCount == 0 ); 522SLANG_CHECK (getStats ().entryCount == 2 ); 523 }); 524 525// Cache is hot and we expect 2 hits. 526runStep ( 527 [this ]() 528 { 529SLANG_CHECK ( 530runComputePipeline ("shader-cache-tmp-import" ,"main" , {1.f ,2.f ,3.f ,4.f })); 531SLANG_CHECK ( 532runComputePipeline ("shader-cache-tmp-include" ,"main" , {1.f ,2.f ,3.f ,4.f })); 533 534SLANG_CHECK (getStats ().missCount == 0 ); 535SLANG_CHECK (getStats ().hitCount == 2 ); 536SLANG_CHECK (getStats ().entryCount == 2 ); 537 }); 538 539// Change content of imported/included shader file. 540writeShader (importedContentsB ,"shader-cache-tmp-imported.slang" ); 541 542// Cache is cold and we expect 2 misses. 543runStep ( 544 [this ]() 545 { 546SLANG_CHECK ( 547runComputePipeline ("shader-cache-tmp-import" ,"main" , {2.f ,3.f ,4.f ,5.f })); 548SLANG_CHECK ( 549runComputePipeline ("shader-cache-tmp-include" ,"main" , {2.f ,3.f ,4.f ,5.f })); 550 551SLANG_CHECK (getStats ().missCount == 2 ); 552SLANG_CHECK (getStats ().hitCount == 0 ); 553SLANG_CHECK (getStats ().entryCount == 4 ); 554 }); 555 556// Cache is hot and we expect 2 hits. 557runStep ( 558 [this ]() 559 { 560SLANG_CHECK ( 561runComputePipeline ("shader-cache-tmp-import" ,"main" , {2.f ,3.f ,4.f ,5.f })); 562SLANG_CHECK ( 563runComputePipeline ("shader-cache-tmp-include" ,"main" , {2.f ,3.f ,4.f ,5.f })); 564 565SLANG_CHECK (getStats ().missCount == 0 ); 566SLANG_CHECK (getStats ().hitCount == 2 ); 567SLANG_CHECK (getStats ().entryCount == 4 ); 568 }); 569 } 570}; 571 572// One shader featuring multiple kinds of shader objects that can be bound. 573struct ShaderCacheTestSpecialization :ShaderCacheTest 574{ 575 slang::ProgramLayout * slangReflection ; 576 577void createComputePipeline () 578 { 579ComPtr < IShaderProgram > shaderProgram ; 580 581GFX_CHECK_CALL_ABORT (loadComputeProgram ( 582device , 583shaderProgram , 584"shader-cache-specialization" , 585"computeMain" , 586slangReflection )); 587 588ComputePipelineStateDesc pipelineDesc = {}; 589pipelineDesc .program = shaderProgram .get (); 590GFX_CHECK_CALL_ABORT ( 591device -> createComputePipelineState (pipelineDesc ,pipelineState .writeRef ())); 592 } 593 594void dispatchComputePipeline (const char * transformerTypeName ) 595 { 596Slang ::ComPtr < ITransientResourceHeap > transientHeap ; 597ITransientResourceHeap ::Desc transientHeapDesc = {}; 598transientHeapDesc .constantBufferSize = 4096 ; 599GFX_CHECK_CALL_ABORT ( 600device -> createTransientResourceHeap (transientHeapDesc ,transientHeap .writeRef ())); 601 602ICommandQueue ::Desc queueDesc = {ICommandQueue ::QueueType ::Graphics }; 603auto queue = device -> createCommandQueue (queueDesc ); 604 605auto commandBuffer = transientHeap -> createCommandBuffer (); 606auto encoder = commandBuffer -> encodeComputeCommands (); 607 608auto rootObject = encoder -> bindPipeline (pipelineState ); 609 610Slang ::ComPtr < IShaderObject > transformer ; 611 slang::TypeReflection * transformerType = 612slangReflection -> findTypeByName (transformerTypeName ); 613GFX_CHECK_CALL_ABORT (device -> createShaderObject ( 614transformerType , 615ShaderObjectContainerType ::None , 616transformer .writeRef ())); 617 618float c = 5.f ; 619ShaderCursor (transformer ).getPath ("c" ).setData (& c ,sizeof (float )); 620 621ShaderCursor entryPointCursor (rootObject -> getEntryPoint (0 )); 622entryPointCursor .getPath ("buffer" ).setResource (bufferView ); 623entryPointCursor .getPath ("transformer" ).setObject (transformer ); 624 625encoder -> dispatchCompute (1 ,1 ,1 ); 626encoder -> endEncoding (); 627commandBuffer -> close (); 628queue -> executeCommandBuffer (commandBuffer ); 629queue -> waitOnHost (); 630 } 631 632bool runComputePipeline (const char * transformerTypeName ,const List < float >& expectedOutput ) 633 { 634createComputeResources (); 635createComputePipeline (); 636dispatchComputePipeline (transformerTypeName ); 637bool hasExpectedOutput = checkOutput (expectedOutput ); 638SLANG_CHECK (hasExpectedOutput ); 639freeComputeResources (); 640return hasExpectedOutput ; 641 } 642 643void runTests () 644 { 645// Cache is cold and we expect 2 misses. 646runStep ( 647 [this ]() 648 { 649SLANG_CHECK (runComputePipeline ("AddTransformer" , {5.f ,6.f ,7.f ,8.f })); 650SLANG_CHECK (runComputePipeline ("MulTransformer" , {0.f ,5.f ,10.f ,15.f })); 651 652SLANG_CHECK (getStats ().missCount == 2 ); 653SLANG_CHECK (getStats ().hitCount == 0 ); 654SLANG_CHECK (getStats ().entryCount == 2 ); 655 }); 656 657// Cache is hot and we expect 2 hits. 658runStep ( 659 [this ]() 660 { 661SLANG_CHECK (runComputePipeline ("AddTransformer" , {5.f ,6.f ,7.f ,8.f })); 662SLANG_CHECK (runComputePipeline ("MulTransformer" , {0.f ,5.f ,10.f ,15.f })); 663 664SLANG_CHECK (getStats ().missCount == 0 ); 665SLANG_CHECK (getStats ().hitCount == 2 ); 666SLANG_CHECK (getStats ().entryCount == 2 ); 667 }); 668 } 669}; 670 671struct ShaderCacheTestEviction :ShaderCacheTest 672{ 673void runTests () 674 { 675shaderCacheDesc .maxEntryCount = 2 ; 676 677// Load shader A & B. Cache is cold and we expect 2 misses. 678runStep ( 679 [this ]() 680 { 681SLANG_CHECK (runComputePipeline (computeShaderA , {1.f ,2.f ,3.f ,4.f })); 682SLANG_CHECK (runComputePipeline (computeShaderB , {2.f ,3.f ,4.f ,5.f })); 683 684SLANG_CHECK (getStats ().missCount == 2 ); 685SLANG_CHECK (getStats ().hitCount == 0 ); 686SLANG_CHECK (getStats ().entryCount == 2 ); 687 }); 688 689// Load shader A & B. Cache is hot and we expect 2 hits. 690runStep ( 691 [this ]() 692 { 693SLANG_CHECK (runComputePipeline (computeShaderA , {1.f ,2.f ,3.f ,4.f })); 694SLANG_CHECK (runComputePipeline (computeShaderB , {2.f ,3.f ,4.f ,5.f })); 695 696SLANG_CHECK (getStats ().missCount == 0 ); 697SLANG_CHECK (getStats ().hitCount == 2 ); 698SLANG_CHECK (getStats ().entryCount == 2 ); 699 }); 700 701// Load shader C. Cache is cold and we expect 1 miss. 702// This will evict the least frequently used entry (shader A). 703// We expect 2 entries in the cache (shader B & C). 704runStep ( 705 [this ]() 706 { 707SLANG_CHECK (runComputePipeline (computeShaderC , {3.f ,4.f ,5.f ,6.f })); 708 709SLANG_CHECK (getStats ().missCount == 1 ); 710SLANG_CHECK (getStats ().hitCount == 0 ); 711SLANG_CHECK (getStats ().entryCount == 2 ); 712 }); 713 714// Load shader C. Cache is hot and we expect 1 hit. 715runStep ( 716 [this ]() 717 { 718SLANG_CHECK (runComputePipeline (computeShaderC , {3.f ,4.f ,5.f ,6.f })); 719 720SLANG_CHECK (getStats ().missCount == 0 ); 721SLANG_CHECK (getStats ().hitCount == 1 ); 722SLANG_CHECK (getStats ().entryCount == 2 ); 723 }); 724 725// Load shader B. Cache is hot and we expect 1 hit. 726runStep ( 727 [this ]() 728 { 729SLANG_CHECK (runComputePipeline (computeShaderB , {2.f ,3.f ,4.f ,5.f })); 730 731SLANG_CHECK (getStats ().missCount == 0 ); 732SLANG_CHECK (getStats ().hitCount == 1 ); 733SLANG_CHECK (getStats ().entryCount == 2 ); 734 }); 735 736// Load shader A. Cache is cold and we expect 1 miss. 737runStep ( 738 [this ]() 739 { 740SLANG_CHECK (runComputePipeline (computeShaderA , {1.f ,2.f ,3.f ,4.f })); 741 742SLANG_CHECK (getStats ().missCount == 1 ); 743SLANG_CHECK (getStats ().hitCount == 0 ); 744SLANG_CHECK (getStats ().entryCount == 2 ); 745 }); 746 } 747}; 748 749// Similar to ShaderCacheTestEntryPoint but with a source file containing a vertex and fragment 750// shader. 751struct ShaderCacheTestGraphics :ShaderCacheTest 752{ 753struct Vertex 754 { 755float position [3 ]; 756 }; 757 758static const int kWidth = 256 ; 759static const int kHeight = 256 ; 760static const Format format = Format ::R32G32B32A32_FLOAT ; 761 762ComPtr < IBufferResource > vertexBuffer ; 763ComPtr < ITextureResource > colorBuffer ; 764ComPtr < IInputLayout > inputLayout ; 765ComPtr < IFramebufferLayout > framebufferLayout ; 766ComPtr < IRenderPassLayout > renderPass ; 767ComPtr < IFramebuffer > framebuffer ; 768 769ComPtr < IBufferResource > createVertexBuffer (IDevice * device ) 770 { 771const Vertex vertices []= { 772 {0 ,0 ,0.5 }, 773 {1 ,0 ,0.5 }, 774 {0 ,1 ,0.5 }, 775 }; 776 777IBufferResource ::Desc vertexBufferDesc ; 778vertexBufferDesc .type = IResource ::Type ::Buffer ; 779vertexBufferDesc .sizeInBytes = sizeof (vertices ); 780vertexBufferDesc .defaultState = ResourceState ::VertexBuffer ; 781vertexBufferDesc .allowedStates = ResourceState ::VertexBuffer ; 782ComPtr < IBufferResource > vertexBuffer = 783device -> createBufferResource (vertexBufferDesc ,vertices ); 784SLANG_CHECK_ABORT (vertexBuffer != nullptr ); 785return vertexBuffer ; 786 } 787 788ComPtr < ITextureResource > createColorBuffer (IDevice * device ) 789 { 790 gfx::ITextureResource ::Desc colorBufferDesc ; 791colorBufferDesc .type = IResource ::Type ::Texture2D ; 792colorBufferDesc .size .width = kWidth ; 793colorBufferDesc .size .height = kHeight ; 794colorBufferDesc .size .depth = 1 ; 795colorBufferDesc .numMipLevels = 1 ; 796colorBufferDesc .format = format ; 797colorBufferDesc .defaultState = ResourceState ::RenderTarget ; 798colorBufferDesc .allowedStates = {ResourceState ::RenderTarget ,ResourceState ::CopySource }; 799ComPtr < ITextureResource > colorBuffer = 800device -> createTextureResource (colorBufferDesc ,nullptr ); 801SLANG_CHECK_ABORT (colorBuffer != nullptr ); 802return colorBuffer ; 803 } 804 805void createGraphicsResources () 806 { 807VertexStreamDesc vertexStreams []= { 808 {sizeof (Vertex ),InputSlotClass ::PerVertex ,0 }, 809 }; 810 811InputElementDesc inputElements []= { 812// Vertex buffer data 813 {"POSITION" ,0 ,Format ::R32G32B32_FLOAT , offsetof(Vertex ,position ),0 }, 814 }; 815IInputLayout ::Desc inputLayoutDesc = {}; 816inputLayoutDesc .inputElementCount = SLANG_COUNT_OF (inputElements ); 817inputLayoutDesc .inputElements = inputElements ; 818inputLayoutDesc .vertexStreamCount = SLANG_COUNT_OF (vertexStreams ); 819inputLayoutDesc .vertexStreams = vertexStreams ; 820inputLayout = device -> createInputLayout (inputLayoutDesc ); 821SLANG_CHECK_ABORT (inputLayout != nullptr ); 822 823vertexBuffer = createVertexBuffer (device ); 824colorBuffer = createColorBuffer (device ); 825 826IFramebufferLayout ::TargetLayout targetLayout ; 827targetLayout .format = format ; 828targetLayout .sampleCount = 1 ; 829 830IFramebufferLayout ::Desc framebufferLayoutDesc ; 831framebufferLayoutDesc .renderTargetCount = 1 ; 832framebufferLayoutDesc .renderTargets = & targetLayout ; 833framebufferLayout = device -> createFramebufferLayout (framebufferLayoutDesc ); 834SLANG_CHECK_ABORT (framebufferLayout != nullptr ); 835 836IRenderPassLayout ::Desc renderPassDesc = {}; 837renderPassDesc .framebufferLayout = framebufferLayout ; 838renderPassDesc .renderTargetCount = 1 ; 839IRenderPassLayout ::TargetAccessDesc renderTargetAccess = {}; 840renderTargetAccess .loadOp = IRenderPassLayout ::TargetLoadOp ::Clear ; 841renderTargetAccess .storeOp = IRenderPassLayout ::TargetStoreOp ::Store ; 842renderTargetAccess .initialState = ResourceState ::RenderTarget ; 843renderTargetAccess .finalState = ResourceState ::CopySource ; 844renderPassDesc .renderTargetAccess = & renderTargetAccess ; 845GFX_CHECK_CALL_ABORT (device -> createRenderPassLayout (renderPassDesc ,renderPass .writeRef ())); 846 847 gfx::IResourceView ::Desc colorBufferViewDesc ; 848memset (& colorBufferViewDesc ,0 ,sizeof (colorBufferViewDesc )); 849colorBufferViewDesc .format = format ; 850colorBufferViewDesc .renderTarget .shape = gfx::IResource ::Type ::Texture2D ; 851colorBufferViewDesc .type = gfx::IResourceView ::Type ::RenderTarget ; 852auto rtv = device -> createTextureView (colorBuffer ,colorBufferViewDesc ); 853 854 gfx::IFramebuffer ::Desc framebufferDesc ; 855framebufferDesc .renderTargetCount = 1 ; 856framebufferDesc .depthStencilView = nullptr ; 857framebufferDesc .renderTargetViews = rtv .readRef (); 858framebufferDesc .layout = framebufferLayout ; 859GFX_CHECK_CALL_ABORT (device -> createFramebuffer (framebufferDesc ,framebuffer .writeRef ())); 860 } 861 862void freeGraphicsResources () 863 { 864inputLayout = nullptr ; 865framebufferLayout = nullptr ; 866renderPass = nullptr ; 867framebuffer = nullptr ; 868vertexBuffer = nullptr ; 869colorBuffer = nullptr ; 870pipelineState = nullptr ; 871 } 872 873void createGraphicsPipeline () 874 { 875ComPtr < IShaderProgram > shaderProgram ; 876 slang::ProgramLayout * slangReflection ; 877GFX_CHECK_CALL_ABORT (loadGraphicsProgram ( 878device , 879shaderProgram , 880"shader-cache-graphics" , 881"vertexMain" , 882"fragmentMain" , 883slangReflection )); 884 885GraphicsPipelineStateDesc pipelineDesc = {}; 886pipelineDesc .program = shaderProgram .get (); 887pipelineDesc .inputLayout = inputLayout ; 888pipelineDesc .framebufferLayout = framebufferLayout ; 889pipelineDesc .depthStencil .depthTestEnable = false; 890pipelineDesc .depthStencil .depthWriteEnable = false; 891GFX_CHECK_CALL_ABORT ( 892device -> createGraphicsPipelineState (pipelineDesc ,pipelineState .writeRef ())); 893 } 894 895void dispatchGraphicsPipeline () 896 { 897ComPtr < ITransientResourceHeap > transientHeap ; 898ITransientResourceHeap ::Desc transientHeapDesc = {}; 899transientHeapDesc .constantBufferSize = 4096 ; 900GFX_CHECK_CALL_ABORT ( 901device -> createTransientResourceHeap (transientHeapDesc ,transientHeap .writeRef ())); 902 903ICommandQueue ::Desc queueDesc = {ICommandQueue ::QueueType ::Graphics }; 904auto queue = device -> createCommandQueue (queueDesc ); 905auto commandBuffer = transientHeap -> createCommandBuffer (); 906 907auto encoder = commandBuffer -> encodeRenderCommands (renderPass ,framebuffer ); 908auto rootObject = encoder -> bindPipeline (pipelineState ); 909 910 gfx::Viewport viewport = {}; 911viewport .maxZ = 1.0f ; 912viewport .extentX = (float )kWidth ; 913viewport .extentY = (float )kHeight ; 914encoder -> setViewportAndScissor (viewport ); 915 916encoder -> setVertexBuffer (0 ,vertexBuffer ); 917encoder -> setPrimitiveTopology (PrimitiveTopology ::TriangleList ); 918 919encoder -> draw (3 ); 920encoder -> endEncoding (); 921commandBuffer -> close (); 922queue -> executeCommandBuffer (commandBuffer ); 923queue -> waitOnHost (); 924 } 925 926void runGraphicsPipeline () 927 { 928createGraphicsResources (); 929createGraphicsPipeline (); 930dispatchGraphicsPipeline (); 931freeGraphicsResources (); 932 } 933 934void runTests () 935 { 936// Cache is cold and we expect 2 misses (2 entry points). 937runStep ( 938 [this ]() 939 { 940runGraphicsPipeline (); 941 942SLANG_CHECK (getStats ().missCount == 2 ); 943SLANG_CHECK (getStats ().hitCount == 0 ); 944SLANG_CHECK (getStats ().entryCount == 2 ); 945 }); 946 947// Cache is hot and we expect 2 hits. 948runStep ( 949 [this ]() 950 { 951runGraphicsPipeline (); 952 953SLANG_CHECK (getStats ().missCount == 0 ); 954SLANG_CHECK (getStats ().hitCount == 2 ); 955SLANG_CHECK (getStats ().entryCount == 2 ); 956 }); 957 } 958}; 959 960// Similar to ShaderCacheTestGraphics but with two separate shader files for the vertex and fragment 961// shaders. 962struct ShaderCacheTestGraphicsSplit :ShaderCacheTestGraphics 963{ 964void createGraphicsPipeline () 965 { 966ComPtr < slang::ISession > slangSession ; 967GFX_CHECK_CALL_ABORT (device -> getSlangSession (slangSession .writeRef ())); 968 slang::IModule * vertexModule = slangSession -> loadModule ("shader-cache-graphics-vertex" ); 969SLANG_CHECK_ABORT (vertexModule ); 970 slang::IModule * fragmentModule = slangSession -> loadModule ("shader-cache-graphics-fragment" ); 971SLANG_CHECK_ABORT (fragmentModule ); 972 973ComPtr < slang::IEntryPoint > vertexEntryPoint ; 974GFX_CHECK_CALL_ABORT ( 975vertexModule -> findEntryPointByName ("main" ,vertexEntryPoint .writeRef ())); 976 977ComPtr < slang::IEntryPoint > fragmentEntryPoint ; 978GFX_CHECK_CALL_ABORT ( 979fragmentModule -> findEntryPointByName ("main" ,fragmentEntryPoint .writeRef ())); 980 981Slang ::List < slang::IComponentType *> componentTypes ; 982componentTypes .add (vertexModule ); 983componentTypes .add (fragmentModule ); 984 985Slang ::ComPtr < slang::IComponentType > composedProgram ; 986GFX_CHECK_CALL_ABORT (slangSession -> createCompositeComponentType ( 987componentTypes .getBuffer (), 988componentTypes .getCount (), 989composedProgram .writeRef ())); 990 991 slang::ProgramLayout * slangReflection = composedProgram -> getLayout (); 992 993Slang ::List < slang::IComponentType *> entryPoints ; 994entryPoints .add (vertexEntryPoint ); 995entryPoints .add (fragmentEntryPoint ); 996 997 gfx::IShaderProgram ::Desc programDesc = {}; 998programDesc .slangGlobalScope = composedProgram .get (); 999programDesc .linkingStyle = gfx::IShaderProgram ::LinkingStyle ::SeparateEntryPointCompilation ; 1000programDesc .entryPointCount = 2 ; 1001programDesc .slangEntryPoints = entryPoints .getBuffer (); 1002 1003ComPtr < IShaderProgram > shaderProgram = device -> createProgram (programDesc ); 1004 1005GraphicsPipelineStateDesc pipelineDesc = {}; 1006pipelineDesc .program = shaderProgram .get (); 1007pipelineDesc .inputLayout = inputLayout ; 1008pipelineDesc .framebufferLayout = framebufferLayout ; 1009pipelineDesc .depthStencil .depthTestEnable = false; 1010pipelineDesc .depthStencil .depthWriteEnable = false; 1011GFX_CHECK_CALL_ABORT ( 1012device -> createGraphicsPipelineState (pipelineDesc ,pipelineState .writeRef ())); 1013 } 1014 1015void runGraphicsPipeline () 1016 { 1017createGraphicsResources (); 1018createGraphicsPipeline (); 1019dispatchGraphicsPipeline (); 1020freeGraphicsResources (); 1021 } 1022 1023void runTests () 1024 { 1025// Cache is cold and we expect 2 misses (2 entry points). 1026runStep ( 1027 [this ]() 1028 { 1029runGraphicsPipeline (); 1030 1031SLANG_CHECK (getStats ().missCount == 2 ); 1032SLANG_CHECK (getStats ().hitCount == 0 ); 1033SLANG_CHECK (getStats ().entryCount == 2 ); 1034 }); 1035 1036// Cache is hot and we expect 2 hits. 1037runStep ( 1038 [this ]() 1039 { 1040runGraphicsPipeline (); 1041 1042SLANG_CHECK (getStats ().missCount == 0 ); 1043SLANG_CHECK (getStats ().hitCount == 2 ); 1044SLANG_CHECK (getStats ().entryCount == 2 ); 1045 }); 1046 } 1047}; 1048 1049template < typename T > 1050void runTest (UnitTestContext * context ,Slang ::RenderApiFlag ::Enum api ) 1051{ 1052T test ; 1053test .run (context ,api ); 1054} 1055 1056SLANG_UNIT_TEST (shaderCacheSourceFileD3D12 ) 1057{ 1058runTest < ShaderCacheSourceFile > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1059} 1060 1061SLANG_UNIT_TEST (shaderCacheSourceFileVulkan ) 1062{ 1063runTest < ShaderCacheSourceFile > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1064} 1065 1066SLANG_UNIT_TEST (shaderCacheSourceStringD3D12 ) 1067{ 1068runTest < ShaderCacheTestSourceString > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1069} 1070 1071SLANG_UNIT_TEST (shaderCacheSourceStringVulkan ) 1072{ 1073runTest < ShaderCacheTestSourceString > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1074} 1075 1076SLANG_UNIT_TEST (shaderCacheEntryPointD3D12 ) 1077{ 1078runTest < ShaderCacheTestEntryPoint > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1079} 1080 1081SLANG_UNIT_TEST (shaderCacheEntryPointVulkan ) 1082{ 1083runTest < ShaderCacheTestEntryPoint > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1084} 1085 1086SLANG_UNIT_TEST (shaderCacheImportIncludeD3D12 ) 1087{ 1088runTest < ShaderCacheTestImportInclude > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1089} 1090 1091SLANG_UNIT_TEST (shaderCacheImportIncludeVulkan ) 1092{ 1093runTest < ShaderCacheTestImportInclude > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1094} 1095 1096SLANG_UNIT_TEST (shaderCacheSpecializationD3D12 ) 1097{ 1098runTest < ShaderCacheTestSpecialization > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1099} 1100 1101SLANG_UNIT_TEST (shaderCacheSpecializationVulkan ) 1102{ 1103runTest < ShaderCacheTestSpecialization > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1104} 1105 1106SLANG_UNIT_TEST (shaderCacheEvictionD3D12 ) 1107{ 1108runTest < ShaderCacheTestEviction > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1109} 1110 1111SLANG_UNIT_TEST (shaderCacheEvictionVulkan ) 1112{ 1113runTest < ShaderCacheTestEviction > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1114} 1115 1116SLANG_UNIT_TEST (shaderCacheGraphicsD3D12 ) 1117{ 1118runTest < ShaderCacheTestGraphics > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1119} 1120 1121SLANG_UNIT_TEST (shaderCacheGraphicsVulkan ) 1122{ 1123runTest < ShaderCacheTestGraphics > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1124} 1125 1126SLANG_UNIT_TEST (shaderCacheGraphicsSplitD3D12 ) 1127{ 1128runTest < ShaderCacheTestGraphicsSplit > (unitTestContext ,Slang ::RenderApiFlag ::D3D12 ); 1129} 1130 1131SLANG_UNIT_TEST (shaderCacheGraphicsSplitVulkan ) 1132{ 1133runTest < ShaderCacheTestGraphicsSplit > (unitTestContext ,Slang ::RenderApiFlag ::Vulkan ); 1134} 1135}// namespace gfx_test 1136 1137#endif