yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
ba8132345
master
1// render-test-main.cpp 2 3#define _CRT_SECURE_NO_WARNINGS 1 4 5#include "../../source/core/slang-test-tool-util.h" 6#include "../source/core/slang-io.h" 7#include "../source/core/slang-std-writers.h" 8#include "../source/core/slang-string-util.h" 9#include "core/slang-token-reader.h" 10#include "options.h" 11#include "png-serialize-util.h" 12#include "shader-input-layout.h" 13#include "shader-renderer-util.h" 14#include "slang-support.h" 15#include "slang-test-device-cache.h" 16#include "window.h" 17 18#if defined(_WIN32 ) 19#include <d3d12.h> 20#include <windows.h> 21#pragma comment(lib, "advapi32") 22#endif 23 24#include <slang-rhi.h> 25#include <slang-rhi/acceleration-structure-utils.h> 26#include <slang-rhi/shader-cursor.h> 27#include <stdio.h> 28#include <stdlib.h> 29#define ENABLE_RENDERDOC_INTEGRATION 0 30 31#if ENABLE_RENDERDOC_INTEGRATION 32#include "external/renderdoc_app.h" 33 34#include <windows.h> 35#endif 36 37#if defined(_WIN32 ) 38// Check if Windows Developer mode is enabled 39// Developer mode is required for D3D12 experimental features 40static bool isWindowsDeveloperModeEnabled () 41{ 42// Helper function to check a specific registry key with detailed logging 43auto checkRegistryKey = [](HKEY rootKey , 44const char * rootName , 45const char * path , 46const char * valueName , 47bool try32BitView = false)-> bool 48 { 49HKEY key ; 50DWORD accessFlags = KEY_READ | (try32BitView ?KEY_WOW64_32KEY :KEY_WOW64_64KEY ); 51LONG ret = RegOpenKeyExA (rootKey ,path ,0 ,accessFlags ,& key ); 52 53if (ret != ERROR_SUCCESS ) 54 { 55return false; 56 } 57 58DWORD value = 0 ; 59DWORD size = sizeof (DWORD ); 60DWORD type = REG_DWORD ; 61 62ret = RegQueryValueExA (key ,valueName ,nullptr ,& type , (LPBYTE )& value ,& size ); 63 64RegCloseKey (key ); 65 66if (ret != ERROR_SUCCESS || type != REG_DWORD ) 67 { 68return false; 69 } 70 71return value == 1 ; 72 }; 73 74// Method 1: Check multiple registry locations with different views 75struct RegistryLocation 76 { 77HKEY rootKey ; 78const char * rootName ; 79const char * path ; 80const char * valueName ; 81 }; 82 83RegistryLocation locations []= { 84// Windows 10+ main location 85 {HKEY_LOCAL_MACHINE , 86"HKLM" , 87"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock" , 88"AllowDevelopmentWithoutDevLicense" }, 89 90// Alternative user location 91 {HKEY_CURRENT_USER , 92"HKCU" , 93"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock" , 94"AllowDevelopmentWithoutDevLicense" }, 95 96// Windows 11 alternative 97 {HKEY_LOCAL_MACHINE , 98"HKLM" , 99"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\AppModelUnlock" , 100"AllowAllTrustedApps" }, 101 102// Policy location 103 {HKEY_LOCAL_MACHINE , 104"HKLM" , 105"SOFTWARE\\Policies\\Microsoft\\Windows\\Appx" , 106"AllowDevelopmentWithoutDevLicense" }, 107 108// Additional alternative locations 109 {HKEY_CURRENT_USER , 110"HKCU" , 111"SOFTWARE\\Policies\\Microsoft\\Windows\\Appx" , 112"AllowDevelopmentWithoutDevLicense" }, 113 }; 114 115for (const auto & location :locations ) 116 { 117// Try 64-bit view first 118if (checkRegistryKey ( 119location .rootKey , 120location .rootName , 121location .path , 122location .valueName , 123 false)) 124 { 125return true; 126 } 127 128// Try 32-bit view as fallback 129if (checkRegistryKey ( 130location .rootKey , 131location .rootName , 132location .path , 133location .valueName , 134 true)) 135 { 136return true; 137 } 138 } 139 140printf ("*** Developer Mode NOT DETECTED ***\n" ); 141printf ("To enable Developer Mode:\n" ); 142printf ("1. Open Windows Settings (Windows key + I)\n" ); 143printf ("2. Go to 'System' -> 'For developers'\n" ); 144printf ("3. Turn on 'Developer Mode'\n" ); 145printf ("4. Restart the application\n" ); 146printf ("==========================================\n" ); 147 148return false; 149} 150#endif 151 152namespace renderer_test 153{ 154 155using Slang ::Result ; 156 157int gWindowWidth = 1024 ; 158int gWindowHeight = 768 ; 159 160// 161// For the purposes of a small example, we will define the vertex data for a 162// single triangle directly in the source file. It should be easy to extend 163// this example to load data from an external source, if desired. 164// 165 166struct Vertex 167{ 168float position [3 ]; 169float color [3 ]; 170float uv [2 ]; 171float customData0 [4 ]; 172float customData1 [4 ]; 173float customData2 [4 ]; 174float customData3 [4 ]; 175}; 176 177static const Vertex kVertexData []= { 178 {{0 ,0 ,0.5 }, {1 ,0 ,0 }, {0 ,0 }, {1 ,2 ,3 ,4 }, {5 ,6 ,7 ,8 }, {9 ,10 ,11 ,12 }, {13 ,14 ,15 ,16 }}, 179 {{0 ,1 ,0.5 }, {0 ,0 ,1 }, {1 ,0 }, {1 ,2 ,3 ,4 }, {5 ,6 ,7 ,8 }, {9 ,10 ,11 ,12 }, {13 ,14 ,15 ,16 }}, 180 {{1 ,0 ,0.5 }, {0 ,1 ,0 }, {1 ,1 }, {1 ,2 ,3 ,4 }, {5 ,6 ,7 ,8 }, {9 ,10 ,11 ,12 }, {13 ,14 ,15 ,16 }}, 181}; 182static const int kVertexCount = SLANG_COUNT_OF (kVertexData ); 183 184using namespace Slang ; 185 186static void _outputProfileTime (uint64_t startTicks ,uint64_t endTicks ) 187{ 188WriterHelper out = StdWriters ::getOut (); 189double time = double (endTicks - startTicks ) /Process ::getClockFrequency (); 190out ."profile-time=%g\n" ,time ); 191} 192 193class ProgramVars ; 194 195struct ShaderOutputPlan 196{ 197struct Item 198 { 199ComPtr < IResource > resource ; 200 slang::TypeLayoutReflection * typeLayout = nullptr ; 201 }; 202 203List < Item > items ; 204}; 205 206// A context for hodling resources allocated for a test. 207struct TestResourceContext 208{ 209List < ComPtr < IResource >> resources ; 210}; 211 212class RenderTestApp 213{ 214public : 215Result update (); 216 217// At initialization time, we are going to load and compile our Slang shader 218// code, and then create the API objects we need for rendering. 219Result initialize ( 220SlangSession * session , 221IDevice * device , 222const Options & options , 223const ShaderCompilerUtil ::Input & input ); 224void finalize (); 225 226Result applyBinding (IShaderObject * rootObject ); 227void setProjectionMatrix (IShaderObject * rootObject ); 228Result writeBindingOutput (const String & fileName ); 229 230Result writeScreen (const String & filename ); 231 232protected : 233/// Called in initialize 234Result _initializeShaders ( 235SlangSession * session , 236IDevice * device , 237Options ::ShaderProgramType shaderType , 238const ShaderCompilerUtil ::Input & input ); 239void _initializeRenderPass (); 240void _initializeAccelerationStructure (); 241 242uint64_t m_startTicks ; 243 244// variables for state to be used for rendering... 245uintptr_t m_constantBufferSize ; 246 247IDevice * m_device ; 248ComPtr < ICommandQueue > m_queue ; 249ComPtr < IInputLayout > m_inputLayout ; 250ComPtr < IBuffer > m_vertexBuffer ; 251ComPtr < IShaderProgram > m_shaderProgram ; 252ComPtr < IPipeline > m_pipeline ; 253ComPtr < IShaderTable > m_shaderTable ; 254ComPtr < ITexture > m_depthBuffer ; 255ComPtr < ITextureView > m_depthBufferView ; 256ComPtr < ITexture > m_colorBuffer ; 257ComPtr < ITextureView > m_colorBufferView ; 258 259ComPtr < IBuffer > m_blasBuffer ; 260ComPtr < IAccelerationStructure > m_bottomLevelAccelerationStructure ; 261ComPtr < IBuffer > m_tlasBuffer ; 262ComPtr < IAccelerationStructure > m_topLevelAccelerationStructure ; 263 264ShaderCompilerUtil ::OutputAndLayout m_compilationOutput ; 265 266ShaderInputLayout m_shaderInputLayout ;///< The binding layout 267 268Options m_options ; 269 270ShaderOutputPlan m_outputPlan ; 271TestResourceContext m_resourceContext ; 272}; 273 274struct AssignValsFromLayoutContext 275{ 276IDevice * device ; 277 slang::IComponentType * slangComponent ; 278ShaderOutputPlan & outputPlan ; 279TestResourceContext & resourceContext ; 280IAccelerationStructure * accelerationStructure ; 281 282AssignValsFromLayoutContext ( 283IDevice * device , 284 slang::IComponentType * slangComponent , 285ShaderOutputPlan & outputPlan , 286TestResourceContext & resourceContext , 287IAccelerationStructure * accelerationStructure ) 288 :device (device ) 289 ,slangComponent (slangComponent ) 290 ,outputPlan (outputPlan ) 291 ,resourceContext (resourceContext ) 292 ,accelerationStructure (accelerationStructure ) 293 { 294 } 295 296 slang::ProgramLayout * slangReflection () {return slangComponent -> getLayout (); } 297 slang::ISession * slangSession () {return slangComponent -> getSession (); } 298 299void maybeAddOutput ( 300ShaderCursor const & dstCursor , 301ShaderInputLayout ::Val * srcVal , 302IResource * resource ) 303 { 304if (srcVal -> isOutput ) 305 { 306ShaderOutputPlan ::Item item ; 307item .resource = resource ; 308item .typeLayout = dstCursor .getTypeLayout (); 309outputPlan .items .add (item ); 310 } 311 } 312 313SlangResult assignData (ShaderCursor const & dstCursor ,ShaderInputLayout ::DataVal * srcVal ) 314 { 315const size_t bufferSize = srcVal -> bufferData .getCount ()* sizeof (uint32_t ); 316 317ShaderCursor dataCursor = dstCursor ; 318switch (dataCursor .getTypeLayout ()-> getKind ()) 319 { 320case slang::TypeReflection ::Kind ::ConstantBuffer : 321case slang::TypeReflection ::Kind ::ParameterBlock : 322dataCursor = dataCursor .getDereferenced (); 323break ; 324 325default : 326break ; 327 } 328 329SLANG_RETURN_ON_FAIL (dataCursor .setData (srcVal -> bufferData .getBuffer (),bufferSize )); 330return SLANG_OK ; 331 } 332 333SlangResult assignBuffer (ShaderCursor const & dstCursor ,ShaderInputLayout ::BufferVal * srcVal ) 334 { 335const InputBufferDesc & srcBuffer = srcVal -> bufferDesc ; 336auto & bufferData = srcVal -> bufferData ; 337const size_t bufferSize = Math ::Max ( 338 (size_t )bufferData .getCount ()* sizeof (uint32_t ), 339 (size_t )(srcBuffer .elementCount * srcBuffer .stride )); 340bufferData .reserve (bufferSize /sizeof (uint32_t )); 341for (size_t i = bufferData .getCount ();i < bufferSize /sizeof (uint32_t );i ++ ) 342bufferData .add (0 ); 343 344ComPtr < IBuffer > bufferResource ; 345 346SLANG_RETURN_ON_FAIL (ShaderRendererUtil ::createBuffer ( 347srcBuffer , 348/*entry.isOutput,*/ bufferSize , 349bufferData .getBuffer (), 350device , 351bufferResource )); 352 353if ((dstCursor .getTypeLayout ()-> getType ()-> getKind ()== 354 slang::TypeReflection ::Kind ::Scalar && 355dstCursor .getTypeLayout ()-> getType ()-> getScalarType ()== 356 slang::TypeReflection ::ScalarType ::UInt64 )|| 357dstCursor .getTypeLayout ()-> getType ()-> getKind ()== slang::TypeReflection ::Kind ::Pointer ) 358 { 359// dstCursor is pointer to an ordinary uniform data field, 360// we should write bufferResource as a pointer. 361uint64_t addr = bufferResource -> getDeviceAddress (); 362dstCursor .setData (& addr ,sizeof (addr )); 363resourceContext .resources .add (ComPtr < IResource > (bufferResource .get ())); 364maybeAddOutput (dstCursor ,srcVal ,bufferResource ); 365return SLANG_OK ; 366 } 367 368ComPtr < IBuffer > counterResource ; 369const auto explicitCounterCursor = dstCursor .getExplicitCounter (); 370if (srcBuffer .counter != ~0u ) 371 { 372if (explicitCounterCursor .isValid ()) 373 { 374// If this cursor has a full buffer object associated with the 375// resource, then assign to that. 376ShaderInputLayout ::BufferVal counterVal ; 377counterVal .bufferData .add (srcBuffer .counter ); 378assignBuffer (explicitCounterCursor ,& counterVal ); 379 } 380else 381 { 382// Otherwise, this API (D3D) must be handling the buffer object 383// specially, in which case create the buffer resource to pass 384// into `createBufferView` 385const InputBufferDesc & counterBufferDesc { 386InputBufferType ::StorageBuffer , 387sizeof (uint32_t ), 3881 , 389Format ::Undefined , 390 }; 391SLANG_RETURN_ON_FAIL (ShaderRendererUtil ::createBuffer ( 392counterBufferDesc , 393sizeof (srcBuffer .counter ), 394& srcBuffer .counter , 395device , 396counterResource )); 397 } 398 } 399else if (explicitCounterCursor .isValid ()) 400 { 401// If we know we require a counter for this resource but haven't 402// been given one, error 403return SLANG_E_INVALID_ARG ; 404 } 405 406if (counterResource ) 407 { 408dstCursor .setBinding (Binding (bufferResource ,counterResource )); 409 } 410else 411 { 412dstCursor .setBinding (bufferResource ); 413 } 414maybeAddOutput (dstCursor ,srcVal ,bufferResource ); 415 416return SLANG_OK ; 417 } 418 419SlangResult assignCombinedTextureSampler ( 420ShaderCursor const & dstCursor , 421ShaderInputLayout ::CombinedTextureSamplerVal * srcVal ) 422 { 423auto & textureEntry = srcVal -> textureVal ; 424auto & samplerEntry = srcVal -> samplerVal ; 425 426ComPtr < ITexture > texture ; 427SLANG_RETURN_ON_FAIL (ShaderRendererUtil ::generateTexture ( 428textureEntry -> textureDesc , 429ResourceState ::ShaderResource , 430device , 431texture )); 432 433auto sampler = _createSampler (device ,samplerEntry -> samplerDesc ); 434 435dstCursor .setBinding (Binding (texture ,sampler )); 436maybeAddOutput (dstCursor ,srcVal ,texture ); 437 438return SLANG_OK ; 439 } 440 441SlangResult assignTexture (ShaderCursor const & dstCursor ,ShaderInputLayout ::TextureVal * srcVal ) 442 { 443ComPtr < ITexture > texture ; 444ResourceState defaultState = srcVal -> textureDesc .isRWTexture 445 ?ResourceState ::UnorderedAccess 446 :ResourceState ::ShaderResource ; 447 448SLANG_RETURN_ON_FAIL (ShaderRendererUtil ::generateTexture ( 449srcVal -> textureDesc , 450defaultState , 451device , 452texture )); 453 454dstCursor .setBinding (texture ); 455maybeAddOutput (dstCursor ,srcVal ,texture ); 456return SLANG_OK ; 457 } 458 459SlangResult assignSampler (ShaderCursor const & dstCursor ,ShaderInputLayout ::SamplerVal * srcVal ) 460 { 461auto sampler = _createSampler (device ,srcVal -> samplerDesc ); 462 463dstCursor .setBinding (sampler ); 464return SLANG_OK ; 465 } 466 467SlangResult assignAggregate (ShaderCursor const & dstCursor ,ShaderInputLayout ::AggVal * srcVal ) 468 { 469Index fieldCount = srcVal -> fields .getCount (); 470for (Index fieldIndex = 0 ;fieldIndex < fieldCount ;++ fieldIndex ) 471 { 472auto & field = srcVal -> fields [fieldIndex ]; 473 474if (field .name .getLength ()== 0 ) 475 { 476// If no name was given, assume by-indexing matching is requested 477auto fieldCursor = dstCursor .getElement ((uint32_t )fieldIndex ); 478if (!fieldCursor .isValid ()) 479 { 480StdWriters ::getError ()."error: could not find shader parameter at index %d\n" , 482 (int )fieldIndex ); 483return SLANG_E_INVALID_ARG ; 484 } 485SLANG_RETURN_ON_FAIL (assign (fieldCursor ,field .val )); 486 } 487else 488 { 489auto fieldCursor = dstCursor .getPath (field .name .getBuffer ()); 490if (!fieldCursor .isValid ()) 491 { 492StdWriters ::getError ()."error: could not find shader parameter matching '%s'\n" , 494field .name .begin ()); 495return SLANG_E_INVALID_ARG ; 496 } 497SLANG_RETURN_ON_FAIL (assign (fieldCursor ,field .val )); 498 } 499 } 500return SLANG_OK ; 501 } 502 503SlangResult assignObject (ShaderCursor const & dstCursor ,ShaderInputLayout ::ObjectVal * srcVal ) 504 { 505auto typeName = srcVal -> typeName ; 506 slang::TypeReflection * slangType = nullptr ; 507if (typeName .getLength ()!= 0 ) 508 { 509// If the input line specified the name of the type 510// to allocate, then we use it directly. 511// 512slangType = slangReflection ()-> findTypeByName (typeName .getBuffer ()); 513 } 514else 515 { 516// if the user did not specify what type to allocate, 517// then we will infer the type from the type of the 518// value pointed to by `entryCursor`. 519// 520auto slangTypeLayout = dstCursor .getTypeLayout (); 521switch (slangTypeLayout -> getKind ()) 522 { 523default : 524break ; 525 526case slang::TypeReflection ::Kind ::ConstantBuffer : 527case slang::TypeReflection ::Kind ::ParameterBlock : 528// If the cursor is pointing at a constant buffer 529// or parameter block, then we assume the user 530// actually means to allocate an object based on 531// the element type of the block. 532// 533slangTypeLayout = slangTypeLayout -> getElementTypeLayout (); 534break ; 535 } 536slangType = slangTypeLayout -> getType (); 537 } 538 539ComPtr < IShaderObject > shaderObject ; 540device -> createShaderObject ( 541slangSession (), 542slangType , 543ShaderObjectContainerType ::None , 544shaderObject .writeRef ()); 545 546SLANG_RETURN_ON_FAIL (assign (ShaderCursor (shaderObject ),srcVal -> contentVal )); 547shaderObject -> finalize (); 548dstCursor .setObject (shaderObject ); 549return SLANG_OK ; 550 } 551 552SlangResult assignValWithSpecializationArg ( 553ShaderCursor const & dstCursor , 554ShaderInputLayout ::SpecializeVal * srcVal ) 555 { 556assign (dstCursor ,srcVal -> contentVal ); 557List < slang::SpecializationArg > args ; 558for (auto & typeName :srcVal -> typeArgs ) 559 { 560auto slangType = slangReflection ()-> findTypeByName (typeName .getBuffer ()); 561if (!slangType ) 562 { 563StdWriters ::getError ()."error: could not find shader type '%s'\n" , 565typeName .getBuffer ()); 566return SLANG_E_INVALID_ARG ; 567 } 568args .add (slang::SpecializationArg ::fromType (slangType )); 569 } 570return dstCursor .setSpecializationArgs (args .getBuffer (), (uint32_t )args .getCount ()); 571 } 572 573SlangResult assignArray (ShaderCursor const & dstCursor ,ShaderInputLayout ::ArrayVal * srcVal ) 574 { 575Index elementCounter = 0 ; 576for (auto elementVal :srcVal -> vals ) 577 { 578Index elementIndex = elementCounter ++ ; 579SLANG_RETURN_ON_FAIL (assign (dstCursor [elementIndex ],elementVal )); 580 } 581return SLANG_OK ; 582 } 583 584SlangResult assignAccelerationStructure ( 585ShaderCursor const & dstCursor , 586ShaderInputLayout ::AccelerationStructureVal * srcVal ) 587 { 588dstCursor .setBinding (accelerationStructure ); 589return SLANG_OK ; 590 } 591 592SlangResult assign (ShaderCursor const & dstCursor ,ShaderInputLayout ::ValPtr const & srcVal ) 593 { 594auto & entryCursor = dstCursor ; 595switch (srcVal -> kind ) 596 { 597case ShaderInputType ::UniformData : 598return assignData (dstCursor , (ShaderInputLayout ::DataVal * )srcVal .Ptr ()); 599 600case ShaderInputType ::Buffer : 601return assignBuffer (dstCursor , (ShaderInputLayout ::BufferVal * )srcVal .Ptr ()); 602 603case ShaderInputType ::CombinedTextureSampler : 604return assignCombinedTextureSampler ( 605dstCursor , 606 (ShaderInputLayout ::CombinedTextureSamplerVal * )srcVal .Ptr ()); 607 608case ShaderInputType ::Texture : 609return assignTexture (dstCursor , (ShaderInputLayout ::TextureVal * )srcVal .Ptr ()); 610 611case ShaderInputType ::Sampler : 612return assignSampler (dstCursor , (ShaderInputLayout ::SamplerVal * )srcVal .Ptr ()); 613 614case ShaderInputType ::Object : 615return assignObject (dstCursor , (ShaderInputLayout ::ObjectVal * )srcVal .Ptr ()); 616 617case ShaderInputType ::Specialize : 618return assignValWithSpecializationArg ( 619dstCursor , 620 (ShaderInputLayout ::SpecializeVal * )srcVal .Ptr ()); 621 622case ShaderInputType ::Aggregate : 623return assignAggregate (dstCursor , (ShaderInputLayout ::AggVal * )srcVal .Ptr ()); 624 625case ShaderInputType ::Array : 626return assignArray (dstCursor , (ShaderInputLayout ::ArrayVal * )srcVal .Ptr ()); 627 628case ShaderInputType ::AccelerationStructure : 629return assignAccelerationStructure ( 630dstCursor , 631 (ShaderInputLayout ::AccelerationStructureVal * )srcVal .Ptr ()); 632default : 633assert (!"Unhandled type" ); 634return SLANG_FAIL ; 635 } 636 } 637}; 638 639static SlangResult _assignVarsFromLayout ( 640IDevice * device , 641 slang::IComponentType * slangComponent , 642IShaderObject * shaderObject , 643ShaderInputLayout const & layout , 644ShaderOutputPlan & ioOutputPlan , 645TestResourceContext & ioResourceContext , 646IAccelerationStructure * accelerationStructure ) 647{ 648AssignValsFromLayoutContext 649context (device ,slangComponent ,ioOutputPlan ,ioResourceContext ,accelerationStructure ); 650ShaderCursor rootCursor = ShaderCursor (shaderObject ); 651return context .assign (rootCursor ,layout .rootVal ); 652} 653 654Result RenderTestApp ::applyBinding (IShaderObject * rootObject ) 655{ 656return _assignVarsFromLayout ( 657m_device , 658m_compilationOutput .output .slangProgram , 659rootObject , 660m_compilationOutput .layout , 661m_outputPlan , 662m_resourceContext , 663m_topLevelAccelerationStructure ); 664} 665 666SlangResult RenderTestApp ::initialize ( 667SlangSession * session , 668IDevice * device , 669const Options & options , 670const ShaderCompilerUtil ::Input & input ) 671{ 672m_options = options ; 673 674// We begin by compiling the shader file and entry points that specified via the options. 675// 676SLANG_RETURN_ON_FAIL (ShaderCompilerUtil ::compileWithLayout ( 677device -> getSlangSession ()-> getGlobalSession (), 678options , 679input , 680m_compilationOutput )); 681m_shaderInputLayout = m_compilationOutput .layout ; 682 683// Once the shaders have been compiled we load them via the underlying API. 684// 685ComPtr < ISlangBlob > outDiagnostics ; 686auto result = device -> createShaderProgram ( 687m_compilationOutput .output .desc , 688m_shaderProgram .writeRef (), 689outDiagnostics .writeRef ()); 690 691// If there was a failure creating a program, we can't continue 692// Special case SLANG_E_NOT_AVAILABLE error code to make it a failure, 693// as it is also used to indicate an attempt setup something failed gracefully (because it 694// couldn't be supported) but that's not this. 695if (SLANG_FAILED (result )) 696 { 697result = (result == SLANG_E_NOT_AVAILABLE ) ?SLANG_FAIL :result ; 698return result ; 699 } 700 701m_device = device ; 702 703_initializeRenderPass (); 704_initializeAccelerationStructure (); 705 706 { 707switch (m_options .shaderType ) 708 { 709default : 710assert (!"unexpected test shader type" ); 711return SLANG_FAIL ; 712 713case Options ::ShaderProgramType ::Compute : 714 { 715ComputePipelineDesc desc ; 716desc .program = m_shaderProgram ; 717 718m_pipeline = device -> createComputePipeline (desc ); 719 } 720break ; 721 722case Options ::ShaderProgramType ::Graphics : 723case Options ::ShaderProgramType ::GraphicsCompute : 724 { 725// TODO: We should conceivably be able to match up the "available" vertex 726// attributes, as defined by the vertex stream(s) on the model being 727// renderer, with the "required" vertex attributes as defiend on the 728// shader. 729// 730// For now we just create a fixed input layout for all graphics tests 731// since at present they all draw the same single triangle with a 732// fixed/known set of attributes. 733// 734const InputElementDesc inputElements []= { 735 {"A" ,0 ,Format ::RGB32Float , offsetof(Vertex ,position )}, 736 {"A" ,1 ,Format ::RGB32Float , offsetof(Vertex ,color )}, 737 {"A" ,2 ,Format ::RG32Float , offsetof(Vertex ,uv )}, 738 {"A" ,3 ,Format ::RGBA32Float , offsetof(Vertex ,customData0 )}, 739 {"A" ,4 ,Format ::RGBA32Float , offsetof(Vertex ,customData1 )}, 740 {"A" ,5 ,Format ::RGBA32Float , offsetof(Vertex ,customData2 )}, 741 {"A" ,6 ,Format ::RGBA32Float , offsetof(Vertex ,customData3 )}, 742 }; 743 744ComPtr < IInputLayout > inputLayout ; 745SLANG_RETURN_ON_FAIL (device -> createInputLayout ( 746sizeof (Vertex ), 747inputElements , 748SLANG_COUNT_OF (inputElements ), 749inputLayout .writeRef ())); 750 751BufferDesc vertexBufferDesc ; 752vertexBufferDesc .size = kVertexCount * sizeof (Vertex ); 753vertexBufferDesc .memoryType = MemoryType ::DeviceLocal ; 754vertexBufferDesc .usage = BufferUsage ::VertexBuffer ; 755vertexBufferDesc .defaultState = ResourceState ::VertexBuffer ; 756 757SLANG_RETURN_ON_FAIL ( 758device -> createBuffer (vertexBufferDesc ,kVertexData ,m_vertexBuffer .writeRef ())); 759 760ColorTargetDesc colorTarget ; 761colorTarget .format = Format ::RGBA8Unorm ; 762RenderPipelineDesc desc ; 763desc .program = m_shaderProgram ; 764desc .inputLayout = inputLayout ; 765desc .targets = & colorTarget ; 766desc .targetCount = 1 ; 767desc .depthStencil .format = Format ::D32Float ; 768m_pipeline = device -> createRenderPipeline (desc ); 769 } 770break ; 771 772case Options ::ShaderProgramType ::GraphicsMeshCompute : 773case Options ::ShaderProgramType ::GraphicsTaskMeshCompute : 774 { 775ColorTargetDesc colorTarget ; 776colorTarget .format = Format ::RGBA8Unorm ; 777RenderPipelineDesc desc ; 778desc .program = m_shaderProgram ; 779desc .targets = & colorTarget ; 780desc .targetCount = 1 ; 781desc .depthStencil .format = Format ::D32Float ; 782m_pipeline = device -> createRenderPipeline (desc ); 783 } 784break ; 785 786case Options ::ShaderProgramType ::RayTracing : 787 { 788RayTracingPipelineDesc desc ; 789desc .program = m_shaderProgram ; 790 791m_pipeline = device -> createRayTracingPipeline (desc ); 792 793const char * raygenNames []= {"raygenMain" }; 794 795// We don't define a miss shader for this test. OptiX allows 796// passing nullptr to indicate no miss shader, but something in 797// slang-rhi assumes that the miss shader always has a name. To 798// work around that, use a dummy name. 799const char * missNames []= {"missNull" }; 800 801ShaderTableDesc shaderTableDesc = {}; 802shaderTableDesc .program = m_shaderProgram ; 803shaderTableDesc .rayGenShaderCount = 1 ; 804shaderTableDesc .rayGenShaderEntryPointNames = raygenNames ; 805shaderTableDesc .missShaderCount = 1 ; 806shaderTableDesc .missShaderEntryPointNames = missNames ; 807SLANG_RETURN_ON_FAIL ( 808device -> createShaderTable (shaderTableDesc ,m_shaderTable .writeRef ())); 809 } 810break ; 811 } 812 } 813// If success must have a pipeline state 814return m_pipeline ?SLANG_OK :SLANG_FAIL ; 815} 816 817Result RenderTestApp ::_initializeShaders ( 818SlangSession * session , 819IDevice * device , 820Options ::ShaderProgramType shaderType , 821const ShaderCompilerUtil ::Input & input ) 822{ 823SLANG_RETURN_ON_FAIL (ShaderCompilerUtil ::compileWithLayout ( 824device -> getSlangSession ()-> getGlobalSession (), 825m_options , 826input , 827m_compilationOutput )); 828m_shaderInputLayout = m_compilationOutput .layout ; 829m_shaderProgram = device -> createShaderProgram (m_compilationOutput .output .desc ); 830return m_shaderProgram ?SLANG_OK :SLANG_FAIL ; 831} 832 833void RenderTestApp ::_initializeRenderPass () 834{ 835m_queue = m_device -> getQueue (QueueType ::Graphics ); 836SLANG_ASSERT (m_queue ); 837 838 rhi::TextureDesc depthBufferDesc ; 839depthBufferDesc .type = TextureType ::Texture2D ; 840depthBufferDesc .size .width = gWindowWidth ; 841depthBufferDesc .size .height = gWindowHeight ; 842depthBufferDesc .size .depth = 1 ; 843depthBufferDesc .mipCount = 1 ; 844depthBufferDesc .format = Format ::D32Float ; 845depthBufferDesc .usage = TextureUsage ::DepthStencil ; 846depthBufferDesc .defaultState = ResourceState ::DepthWrite ; 847m_depthBuffer = m_device -> createTexture (depthBufferDesc ,nullptr ); 848SLANG_ASSERT (m_depthBuffer ); 849m_depthBufferView = m_device -> createTextureView (m_depthBuffer , {}); 850SLANG_ASSERT (m_depthBufferView ); 851 852 rhi::TextureDesc colorBufferDesc ; 853colorBufferDesc .type = TextureType ::Texture2D ; 854colorBufferDesc .size .width = gWindowWidth ; 855colorBufferDesc .size .height = gWindowHeight ; 856colorBufferDesc .size .depth = 1 ; 857colorBufferDesc .mipCount = 1 ; 858colorBufferDesc .format = Format ::RGBA8Unorm ; 859colorBufferDesc .usage = TextureUsage ::RenderTarget |TextureUsage ::CopySource ; 860colorBufferDesc .defaultState = ResourceState ::RenderTarget ; 861m_colorBuffer = m_device -> createTexture (colorBufferDesc ,nullptr ); 862SLANG_ASSERT (m_colorBuffer ); 863m_colorBufferView = m_device -> createTextureView (m_colorBuffer , {}); 864SLANG_ASSERT (m_colorBufferView ); 865} 866 867void RenderTestApp ::_initializeAccelerationStructure () 868{ 869if (!m_device -> hasFeature ("ray-tracing" )) 870return ; 871BufferDesc vertexBufferDesc = {}; 872vertexBufferDesc .size = kVertexCount * sizeof (Vertex ); 873vertexBufferDesc .usage = BufferUsage ::AccelerationStructureBuildInput ; 874vertexBufferDesc .defaultState = ResourceState ::AccelerationStructureBuildInput ; 875ComPtr < IBuffer > vertexBuffer = m_device -> createBuffer (vertexBufferDesc ,& kVertexData [0 ]); 876 877BufferDesc transformBufferDesc = {}; 878transformBufferDesc .size = sizeof (float )* 12 ; 879transformBufferDesc .usage = BufferUsage ::AccelerationStructureBuildInput ; 880transformBufferDesc .defaultState = ResourceState ::AccelerationStructureBuildInput ; 881float transformData [12 ]= 882 {1.0f ,0.0f ,0.0f ,0.0f ,0.0f ,1.0f ,0.0f ,0.0f ,0.0f ,0.0f ,1.0f ,0.0f }; 883ComPtr < IBuffer > transformBuffer = m_device -> createBuffer (transformBufferDesc ,& transformData ); 884 885// Build bottom level acceleration structure. 886 { 887AccelerationStructureBuildInput buildInput = {}; 888buildInput .type = AccelerationStructureBuildInputType ::Triangles ; 889buildInput .triangles .vertexBuffers [0 ]= vertexBuffer ; 890buildInput .triangles .vertexBufferCount = 1 ; 891buildInput .triangles .vertexFormat = Format ::RGB32Float ; 892buildInput .triangles .vertexCount = kVertexCount ; 893buildInput .triangles .vertexStride = sizeof (Vertex ); 894buildInput .triangles .preTransformBuffer = transformBuffer ; 895buildInput .triangles .flags = AccelerationStructureGeometryFlags ::Opaque ; 896AccelerationStructureBuildDesc buildDesc = {}; 897buildDesc .inputs = & buildInput ; 898buildDesc .inputCount = 1 ; 899buildDesc .flags = AccelerationStructureBuildFlags ::AllowCompaction ; 900 901// Query buffer size for acceleration structure build. 902AccelerationStructureSizes accelerationStructureSizes = {}; 903m_device -> getAccelerationStructureSizes (buildDesc ,& accelerationStructureSizes ); 904 905BufferDesc scratchBufferDesc = {}; 906scratchBufferDesc .usage = BufferUsage ::UnorderedAccess ; 907scratchBufferDesc .defaultState = ResourceState ::UnorderedAccess ; 908scratchBufferDesc .size = accelerationStructureSizes .scratchSize ; 909ComPtr < IBuffer > scratchBuffer = m_device -> createBuffer (scratchBufferDesc ); 910 911ComPtr < IQueryPool > compactedSizeQuery ; 912QueryPoolDesc queryPoolDesc = {}; 913queryPoolDesc .count = 1 ; 914queryPoolDesc .type = QueryType ::AccelerationStructureCompactedSize ; 915m_device -> createQueryPool (queryPoolDesc ,compactedSizeQuery .writeRef ()); 916 917// Build acceleration structure. 918ComPtr < IAccelerationStructure > draftAS ; 919AccelerationStructureDesc draftDesc = {}; 920draftDesc .size = accelerationStructureSizes .accelerationStructureSize ; 921m_device -> createAccelerationStructure (draftDesc ,draftAS .writeRef ()); 922 923compactedSizeQuery -> reset (); 924 925auto encoder = m_queue -> createCommandEncoder (); 926AccelerationStructureQueryDesc compactedSizeQueryDesc = {}; 927compactedSizeQueryDesc .queryPool = compactedSizeQuery ; 928compactedSizeQueryDesc .queryType = QueryType ::AccelerationStructureCompactedSize ; 929encoder -> buildAccelerationStructure ( 930buildDesc , 931draftAS , 932nullptr , 933scratchBuffer , 9341 , 935& compactedSizeQueryDesc ); 936m_queue -> submit (encoder -> finish ()); 937m_queue -> waitOnHost (); 938 939uint64_t compactedSize = 0 ; 940compactedSizeQuery -> getResult (0 ,1 ,& compactedSize ); 941AccelerationStructureDesc finalDesc ; 942finalDesc .size = compactedSize ; 943m_device -> createAccelerationStructure ( 944finalDesc , 945m_bottomLevelAccelerationStructure .writeRef ()); 946 947encoder = m_queue -> createCommandEncoder (); 948encoder -> copyAccelerationStructure ( 949m_bottomLevelAccelerationStructure , 950draftAS , 951AccelerationStructureCopyMode ::Compact ); 952m_queue -> submit (encoder -> finish ()); 953m_queue -> waitOnHost (); 954 } 955 956// Build top level acceleration structure. 957 { 958AccelerationStructureInstanceDescType nativeInstanceDescType = 959getAccelerationStructureInstanceDescType (m_device ); 960 rhi::Size nativeInstanceDescSize = 961getAccelerationStructureInstanceDescSize (nativeInstanceDescType ); 962 963List < AccelerationStructureInstanceDescGeneric > genericInstanceDescs ; 964genericInstanceDescs .setCount (1 ); 965genericInstanceDescs [0 ].accelerationStructure = 966m_bottomLevelAccelerationStructure -> getHandle (); 967genericInstanceDescs [0 ].flags = 968AccelerationStructureInstanceFlags ::TriangleFacingCullDisable ; 969genericInstanceDescs [0 ].instanceContributionToHitGroupIndex = 0 ; 970genericInstanceDescs [0 ].instanceID = 0 ; 971genericInstanceDescs [0 ].instanceMask = 0xFF ; 972float transformMatrix []= 973 {1.0f ,0.0f ,0.0f ,0.0f ,0.0f ,1.0f ,0.0f ,0.0f ,0.0f ,0.0f ,1.0f ,0.0f }; 974memcpy (& genericInstanceDescs [0 ].transform [0 ][0 ],transformMatrix ,sizeof (float )* 12 ); 975 976List < unsigned char > nativeInstanceDescs ; 977nativeInstanceDescs .setCount (genericInstanceDescs .getCount ()* nativeInstanceDescSize ); 978convertAccelerationStructureInstanceDescs ( 979genericInstanceDescs .getCount (), 980nativeInstanceDescType , 981nativeInstanceDescs .getBuffer (), 982nativeInstanceDescSize , 983genericInstanceDescs .getBuffer (), 984sizeof (AccelerationStructureInstanceDescGeneric )); 985 986BufferDesc instanceBufferDesc = {}; 987instanceBufferDesc .size = nativeInstanceDescs .getCount (); 988instanceBufferDesc .usage = BufferUsage ::AccelerationStructureBuildInput ; 989instanceBufferDesc .defaultState = ResourceState ::AccelerationStructureBuildInput ; 990ComPtr < IBuffer > instanceBuffer = 991m_device -> createBuffer (instanceBufferDesc ,nativeInstanceDescs .getBuffer ()); 992 993AccelerationStructureBuildInput buildInput = {}; 994buildInput .type = AccelerationStructureBuildInputType ::Instances ; 995buildInput .instances .instanceBuffer = instanceBuffer ; 996buildInput .instances .instanceCount = 1 ; 997buildInput .instances .instanceStride = nativeInstanceDescSize ; 998AccelerationStructureBuildDesc buildDesc = {}; 999buildDesc .inputs = & buildInput ; 1000buildDesc .inputCount = 1 ; 1001 1002// Query buffer size for acceleration structure build. 1003AccelerationStructureSizes accelerationStructureSizes = {}; 1004m_device -> getAccelerationStructureSizes (buildDesc ,& accelerationStructureSizes ); 1005 1006BufferDesc scratchBufferDesc = {}; 1007scratchBufferDesc .usage = BufferUsage ::UnorderedAccess ; 1008scratchBufferDesc .defaultState = ResourceState ::UnorderedAccess ; 1009scratchBufferDesc .size = (size_t )accelerationStructureSizes .scratchSize ; 1010ComPtr < IBuffer > scratchBuffer = m_device -> createBuffer (scratchBufferDesc ); 1011 1012AccelerationStructureDesc createDesc = {}; 1013createDesc .size = accelerationStructureSizes .accelerationStructureSize ; 1014m_device -> createAccelerationStructure ( 1015createDesc , 1016m_topLevelAccelerationStructure .writeRef ()); 1017 1018auto encoder = m_queue -> createCommandEncoder (); 1019encoder -> buildAccelerationStructure ( 1020buildDesc , 1021m_topLevelAccelerationStructure , 1022nullptr , 1023scratchBuffer , 10240 , 1025nullptr ); 1026m_queue -> submit (encoder -> finish ()); 1027m_queue -> waitOnHost (); 1028 } 1029} 1030 1031void RenderTestApp ::setProjectionMatrix (IShaderObject * rootObject ) 1032{ 1033float kIdentity [16 ]= 1034 {1.f ,0.f ,0.f ,0.f ,0.f ,1.f ,0.f ,0.f ,0.f ,0.f ,1.f ,0.f ,0.f ,0.f ,0.f ,1.f }; 1035auto info = m_device -> getInfo (); 1036ShaderCursor (rootObject ) 1037 .getField ("Uniforms" ) 1038 .getDereferenced () 1039 .setData (kIdentity ,sizeof (kIdentity )); 1040} 1041 1042void RenderTestApp ::finalize () 1043{ 1044m_compilationOutput .output .reset (); 1045} 1046 1047Result RenderTestApp ::writeBindingOutput (const String & fileName ) 1048{ 1049// Wait until everything is complete 1050m_queue -> waitOnHost (); 1051 1052FILE * f = fopen (fileName .getBuffer (),"wb" ); 1053if (!f ) 1054 { 1055return SLANG_FAIL ; 1056 } 1057FileWriter writer (f ,WriterFlags (0 )); 1058 1059for (auto outputItem :m_outputPlan .items ) 1060 { 1061auto resource = outputItem .resource ; 1062IBuffer * buffer = nullptr ; 1063resource -> queryInterface (IBuffer ::getTypeGuid (), (void ** )& buffer ); 1064if (buffer ) 1065 { 1066const BufferDesc & bufferDesc = buffer -> getDesc (); 1067const size_t bufferSize = bufferDesc .size ; 1068 1069ComPtr < ISlangBlob > blob ; 1070m_device -> readBuffer (buffer ,0 ,bufferSize ,blob .writeRef ()); 1071buffer -> release (); 1072 1073if (!blob ) 1074 { 1075return SLANG_FAIL ; 1076 } 1077const SlangResult res = ShaderInputLayout ::writeBinding ( 1078m_options .outputUsingType ?outputItem .typeLayout 1079 :nullptr ,// TODO: always output using type 1080blob -> getBufferPointer (), 1081bufferSize , 1082& writer ); 1083SLANG_RETURN_ON_FAIL (res ); 1084 } 1085else 1086 { 1087auto typeName = outputItem .typeLayout -> getName (); 1088printf ("invalid output type '%s'.\n" ,typeName ?typeName :"UNKNOWN" ); 1089 } 1090 } 1091return SLANG_OK ; 1092} 1093 1094Result RenderTestApp ::writeScreen (const String & filename ) 1095{ 1096 rhi::SubresourceLayout layout ; 1097ComPtr < ISlangBlob > blob ; 1098SLANG_RETURN_ON_FAIL (m_device -> readTexture (m_colorBuffer ,0 ,0 ,blob .writeRef (),& layout )); 1099return PngSerializeUtil ::write ( 1100filename .getBuffer (), 1101blob , 1102layout .size .width , 1103layout .size .height , 1104layout .rowPitch ); 1105} 1106 1107Result RenderTestApp ::update () 1108{ 1109auto encoder = m_queue -> createCommandEncoder (); 1110if (m_options .shaderType == Options ::ShaderProgramType ::Compute ) 1111 { 1112auto passEncoder = encoder -> beginComputePass (); 1113auto rootObject = 1114passEncoder -> bindPipeline (static_cast < IComputePipeline *> (m_pipeline .get ())); 1115applyBinding (rootObject ); 1116passEncoder -> dispatchCompute ( 1117m_options .computeDispatchSize [0 ], 1118m_options .computeDispatchSize [1 ], 1119m_options .computeDispatchSize [2 ]); 1120passEncoder -> end (); 1121 } 1122else if (m_options .shaderType == Options ::ShaderProgramType ::RayTracing ) 1123 { 1124auto passEncoder = encoder -> beginRayTracingPass (); 1125auto rootObject = passEncoder -> bindPipeline ( 1126static_cast < IRayTracingPipeline *> (m_pipeline .get ()), 1127m_shaderTable ); 1128applyBinding (rootObject ); 1129passEncoder -> dispatchRays ( 11300 , 1131m_options .computeDispatchSize [0 ], 1132m_options .computeDispatchSize [1 ], 1133m_options .computeDispatchSize [2 ]); 1134passEncoder -> end (); 1135 } 1136else 1137 { 1138RenderPassColorAttachment colorAttachment = {}; 1139colorAttachment .view = m_colorBufferView ; 1140colorAttachment .loadOp = LoadOp ::Clear ; 1141colorAttachment .storeOp = StoreOp ::Store ; 1142RenderPassDepthStencilAttachment depthStencilAttachment = {}; 1143depthStencilAttachment .view = m_depthBufferView ; 1144depthStencilAttachment .depthLoadOp = LoadOp ::Clear ; 1145depthStencilAttachment .depthStoreOp = StoreOp ::Store ; 1146RenderPassDesc renderPass = {}; 1147renderPass .colorAttachments = & colorAttachment ; 1148renderPass .colorAttachmentCount = 1 ; 1149renderPass .depthStencilAttachment = & depthStencilAttachment ; 1150 1151auto passEncoder = encoder -> beginRenderPass (renderPass ); 1152auto rootObject = 1153passEncoder -> bindPipeline (static_cast < IRenderPipeline *> (m_pipeline .get ())); 1154applyBinding (rootObject ); 1155setProjectionMatrix (rootObject ); 1156 1157RenderState state ; 1158state .viewports [0 ]= Viewport ::fromSize (gWindowWidth ,gWindowHeight ); 1159state .viewportCount = 1 ; 1160state .scissorRects [0 ]= ScissorRect ::fromSize (gWindowWidth ,gWindowHeight ); 1161state .scissorRectCount = 1 ; 1162 1163if (m_options .shaderType == Options ::ShaderProgramType ::GraphicsMeshCompute || 1164m_options .shaderType == Options ::ShaderProgramType ::GraphicsTaskMeshCompute ) 1165 { 1166passEncoder -> setRenderState (state ); 1167passEncoder -> drawMeshTasks ( 1168m_options .computeDispatchSize [0 ], 1169m_options .computeDispatchSize [1 ], 1170m_options .computeDispatchSize [2 ]); 1171 } 1172else 1173 { 1174state .vertexBuffers [0 ]= m_vertexBuffer ; 1175state .vertexBufferCount = 1 ; 1176passEncoder -> setRenderState (state ); 1177DrawArguments args ; 1178args .vertexCount = 3 ; 1179passEncoder -> draw (args ); 1180 } 1181passEncoder -> end (); 1182 } 1183m_startTicks = Process ::getClockTick (); 1184m_queue -> submit (encoder -> finish ()); 1185m_queue -> waitOnHost (); 1186 1187// If we are in a mode where output is requested, we need to snapshot the back buffer here 1188if (m_options .outputPath .getLength ()|| m_options .performanceProfile ) 1189 { 1190// Wait until everything is complete 1191 1192if (m_options .performanceProfile ) 1193 { 1194#if 0 1195// It might not be enough on some APIs to 'waitForGpu' to mean the computation has completed. Let's lock an output 1196// buffer to be sure 1197if (m_bindingState -> outputBindings .getCount ()> 0 ) 1198 { 1199const auto & binding = m_bindingState -> outputBindings [0 ]; 1200auto i = binding .entryIndex ; 1201const auto & layoutBinding = m_shaderInputLayout .entries [i ]; 1202 1203assert (layoutBinding .isOutput ); 1204 1205if (binding .resource && binding .resource -> isBuffer ()) 1206 { 1207BufferResource * bufferResource = static_cast < BufferResource *> (binding .resource .Ptr ()); 1208const size_t bufferSize = bufferResource -> getDesc ().size ; 1209unsigned int * ptr = (unsigned int * )m_renderer -> map (bufferResource ,MapFlavor ::HostRead ); 1210if (!ptr ) 1211 { 1212return SLANG_FAIL ; 1213 } 1214m_renderer -> unmap (bufferResource ); 1215 } 1216 } 1217#endif 1218 1219// Note we don't do the same with screen rendering -> as that will do a lot of work, 1220// which may swamp any computation so can only really profile compute shaders at the 1221// moment 1222 1223const uint64_t endTicks = Process ::getClockTick (); 1224 1225_outputProfileTime (m_startTicks ,endTicks ); 1226 } 1227 1228if (m_options .outputPath .getLength ()) 1229 { 1230if (m_options .shaderType == Options ::ShaderProgramType ::Compute || 1231m_options .shaderType == Options ::ShaderProgramType ::GraphicsCompute || 1232m_options .shaderType == Options ::ShaderProgramType ::GraphicsMeshCompute || 1233m_options .shaderType == Options ::ShaderProgramType ::GraphicsTaskMeshCompute || 1234m_options .shaderType == Options ::ShaderProgramType ::RayTracing ) 1235 { 1236SLANG_RETURN_ON_FAIL (writeBindingOutput (m_options .outputPath )); 1237 } 1238else 1239 { 1240SlangResult res = writeScreen (m_options .outputPath ); 1241if (SLANG_FAILED (res )) 1242 { 1243fprintf (stderr ,"ERROR: failed to write screen capture to file\n" ); 1244return res ; 1245 } 1246 } 1247 } 1248return SLANG_OK ; 1249 } 1250return SLANG_OK ; 1251} 1252 1253 1254static SlangResult _setSessionPrelude ( 1255const Options & options , 1256const char * exePath , 1257SlangSession * session ) 1258{ 1259// Let's see if we need to set up special prelude for HLSL 1260if (options .nvapiExtnSlot .getLength ()) 1261 { 1262#if !SLANG_WINDOWS_FAMILY 1263// NVAPI is currently only available on Windows 1264return SLANG_E_NOT_AVAILABLE ; 1265#else 1266// We want to set the path to NVAPI 1267String rootPath ; 1268SLANG_RETURN_ON_FAIL (TestToolUtil ::getRootPath (exePath ,rootPath )); 1269String includePath ; 1270SLANG_RETURN_ON_FAIL ( 1271TestToolUtil ::getIncludePath (rootPath ,"external/nvapi/nvHLSLExtns.h" ,includePath )) 1272 1273StringBuilder buf ; 1274// We have to choose a slot that NVAPI will use. 1275buf <<"#define NV_SHADER_EXTN_SLOT " <<options .nvapiExtnSlot <<"\n" ; 1276 1277// Include the NVAPI header 1278buf <<"#include " ; 1279StringEscapeUtil ::appendQuoted ( 1280StringEscapeUtil ::getHandler (StringEscapeUtil ::Style ::Cpp ), 1281includePath .getUnownedSlice (), 1282buf ); 1283buf <<"\n\n" ; 1284 1285session -> setLanguagePrelude (SLANG_SOURCE_LANGUAGE_HLSL ,buf .getBuffer ()); 1286#endif 1287 } 1288else 1289 { 1290session -> setLanguagePrelude (SLANG_SOURCE_LANGUAGE_HLSL ,"" ); 1291 } 1292 1293return SLANG_OK ; 1294} 1295 1296}// namespace renderer_test 1297 1298#if ENABLE_RENDERDOC_INTEGRATION 1299static RENDERDOC_API_1_1_2 * rdoc_api = NULL ; 1300static void initializeRenderDoc () 1301{ 1302if (HMODULE mod = GetModuleHandleA ("renderdoc.dll" )) 1303 { 1304pRENDERDOC_GetAPI RENDERDOC_GetAPI = 1305 (pRENDERDOC_GetAPI )GetProcAddress (mod ,"RENDERDOC_GetAPI" ); 1306int ret = RENDERDOC_GetAPI (eRENDERDOC_API_Version_1_1_2 , (void ** )& rdoc_api ); 1307assert (ret == 1 ); 1308 } 1309} 1310static void renderDocBeginFrame () 1311{ 1312if (rdoc_api ) 1313rdoc_api -> StartFrameCapture (nullptr ,nullptr ); 1314} 1315static void renderDocEndFrame () 1316{ 1317if (rdoc_api ) 1318rdoc_api -> EndFrameCapture (nullptr ,nullptr ); 1319_fgetchar (); 1320} 1321#else 1322static void initializeRenderDoc () {} 1323static void renderDocBeginFrame () {} 1324static void renderDocEndFrame () {} 1325#endif 1326 1327static SlangResult _innerMain ( 1328Slang ::StdWriters * stdWriters , 1329SlangSession * session , 1330int argcIn , 1331const char * const * argvIn ) 1332{ 1333using namespace renderer_test ; 1334using namespace Slang ; 1335 1336initializeRenderDoc (); 1337 1338StdWriters ::setSingleton (stdWriters ); 1339 1340Options options ; 1341 1342// Parse command-line options 1343SLANG_RETURN_ON_FAIL (Options ::parse (argcIn ,argvIn ,StdWriters ::getError (),options )); 1344if (options .deviceType == DeviceType ::Default ) 1345 { 1346return SLANG_OK ; 1347 } 1348 1349ShaderCompilerUtil ::Input input ; 1350 1351input .profile = "" ; 1352input .target = SLANG_TARGET_NONE ; 1353 1354SlangSourceLanguage nativeLanguage = SLANG_SOURCE_LANGUAGE_UNKNOWN ; 1355SlangPassThrough slangPassThrough = SLANG_PASS_THROUGH_NONE ; 1356char const * profileName = "" ; 1357switch (options .deviceType ) 1358 { 1359case DeviceType ::D3D11 : 1360input .target = SLANG_DXBC ; 1361input .profile = "sm_5_0" ; 1362nativeLanguage = SLANG_SOURCE_LANGUAGE_HLSL ; 1363slangPassThrough = SLANG_PASS_THROUGH_FXC ; 1364 1365break ; 1366 1367case DeviceType ::D3D12 : 1368input .target = SLANG_DXIL ; 1369input .profile = "sm_6_5" ; 1370nativeLanguage = SLANG_SOURCE_LANGUAGE_HLSL ; 1371slangPassThrough = SLANG_PASS_THROUGH_DXC ; 1372 1373if (options .useDXBC ) 1374 { 1375input .target = SLANG_DXBC ; 1376input .profile = "sm_5_0" ; 1377slangPassThrough = SLANG_PASS_THROUGH_FXC ; 1378 } 1379break ; 1380 1381case DeviceType ::Vulkan : 1382input .target = SLANG_SPIRV ; 1383input .profile = "" ; 1384nativeLanguage = SLANG_SOURCE_LANGUAGE_GLSL ; 1385slangPassThrough = SLANG_PASS_THROUGH_GLSLANG ; 1386break ; 1387case DeviceType ::Metal : 1388input .target = SLANG_METAL_LIB ; 1389input .profile = "" ; 1390nativeLanguage = SLANG_SOURCE_LANGUAGE_METAL ; 1391slangPassThrough = SLANG_PASS_THROUGH_METAL ; 1392break ; 1393case DeviceType ::CPU : 1394input .target = SLANG_SHADER_HOST_CALLABLE ; 1395input .profile = "" ; 1396nativeLanguage = SLANG_SOURCE_LANGUAGE_CPP ; 1397slangPassThrough = SLANG_PASS_THROUGH_GENERIC_C_CPP ; 1398break ; 1399case DeviceType ::CUDA : 1400input .target = SLANG_PTX ; 1401input .profile = "" ; 1402nativeLanguage = SLANG_SOURCE_LANGUAGE_CUDA ; 1403slangPassThrough = SLANG_PASS_THROUGH_NVRTC ; 1404break ; 1405case DeviceType ::WGPU : 1406input .target = SLANG_WGSL ; 1407input .profile = "" ; 1408nativeLanguage = SLANG_SOURCE_LANGUAGE_WGSL ; 1409slangPassThrough = SLANG_PASS_THROUGH_NONE ; 1410break ; 1411 1412default : 1413fprintf (stderr ,"error: unexpected\n" ); 1414return SLANG_FAIL ; 1415 } 1416 1417switch (options .inputLanguageID ) 1418 { 1419case Options ::InputLanguageID ::Slang : 1420input .sourceLanguage = SLANG_SOURCE_LANGUAGE_SLANG ; 1421input .passThrough = SLANG_PASS_THROUGH_NONE ; 1422break ; 1423 1424case Options ::InputLanguageID ::Native : 1425input .sourceLanguage = nativeLanguage ; 1426input .passThrough = slangPassThrough ; 1427break ; 1428 1429default : 1430break ; 1431 } 1432 1433if (options .sourceLanguage != SLANG_SOURCE_LANGUAGE_UNKNOWN ) 1434 { 1435input .sourceLanguage = options .sourceLanguage ; 1436 1437if (input .sourceLanguage == SLANG_SOURCE_LANGUAGE_C || 1438input .sourceLanguage == SLANG_SOURCE_LANGUAGE_CPP ) 1439 { 1440input .passThrough = SLANG_PASS_THROUGH_GENERIC_C_CPP ; 1441 } 1442 } 1443 1444static renderer_test::CoreToRHIDebugBridge debugCallback ; 1445debugCallback .setCoreCallback (stdWriters -> getDebugCallback ()); 1446 1447// Use the profile name set on options if set 1448input .profile = options .profileName .getLength () ?options .profileName :input .profile ; 1449 1450StringBuilder rendererName ; 1451auto info = rendererName <<"[" <<getRHI ()-> getDeviceTypeName (options .deviceType ) <<"] " ; 1452 1453if (options .onlyStartup ) 1454 { 1455switch (options .deviceType ) 1456 { 1457case DeviceType ::CUDA : 1458 { 1459#if RENDER_TEST_CUDA 1460if (SLANG_FAILED ( 1461spSessionCheckPassThroughSupport (session ,SLANG_PASS_THROUGH_NVRTC ))) 1462return SLANG_FAIL ; 1463#else 1464return SLANG_FAIL ; 1465#endif 1466 } 1467case DeviceType ::CPU : 1468 { 1469// As long as we have CPU, then this should work 1470return spSessionCheckPassThroughSupport (session ,SLANG_PASS_THROUGH_GENERIC_C_CPP ); 1471 } 1472default : 1473break ; 1474 } 1475 } 1476 1477Index nvapiExtnSlot = -1 ; 1478 1479// Let's see if we need to set up special prelude for HLSL 1480if (options .nvapiExtnSlot .getLength ()&& options .nvapiExtnSlot [0 ]== 'u' ) 1481 { 1482// 1483Slang ::Int value ; 1484UnownedStringSlice slice = options .nvapiExtnSlot .getUnownedSlice (); 1485UnownedStringSlice indexText (slice .begin ()+ 1 ,slice .end ()); 1486if (SLANG_SUCCEEDED (StringUtil ::parseInt (indexText ,value ))) 1487 { 1488nvapiExtnSlot = Index (value ); 1489 } 1490 } 1491 1492// If can't set up a necessary prelude make not available (which will lead to the test being 1493// ignored) 1494if (SLANG_FAILED (_setSessionPrelude (options ,argvIn [0 ],session ))) 1495 { 1496return SLANG_E_NOT_AVAILABLE ; 1497 } 1498 1499CachedDeviceWrapper deviceWrapper ; 1500 { 1501DeviceDesc desc = {}; 1502desc .deviceType = options .deviceType ; 1503 1504desc .enableValidation = options .enableDebugLayers ; 1505desc .debugCallback = & debugCallback ; 1506 1507desc .slang .lineDirectiveMode = SLANG_LINE_DIRECTIVE_MODE_NONE ; 1508if (options .generateSPIRVDirectly ) 1509desc .slang .targetFlags = SLANG_TARGET_FLAG_GENERATE_SPIRV_DIRECTLY ; 1510else 1511desc .slang .targetFlags = 0 ; 1512 1513List < const char *> requiredFeatureList ; 1514for (auto & name :options .renderFeatures ) 1515requiredFeatureList .add (name .getBuffer ()); 1516 1517desc .requiredFeatures = requiredFeatureList .getBuffer (); 1518desc .requiredFeatureCount = (int )requiredFeatureList .getCount (); 1519 1520#if defined(_WIN32 ) 1521// When the experimental feature is enabled, things become unstable. 1522// It is enabled only when requested. 1523D3D12ExperimentalFeaturesDesc experimentalFD = {}; 1524UUID features [1 ]= {D3D12ExperimentalShaderModels }; 1525experimentalFD .featureCount = 1 ; 1526experimentalFD .featureIIDs = features ; 1527experimentalFD .configurationStructs = nullptr ; 1528experimentalFD .configurationStructSizes = nullptr ; 1529 1530if (options .dx12Experimental ) 1531 { 1532// Check if Windows Developer mode is enabled 1533if (!isWindowsDeveloperModeEnabled ()) 1534 { 1535return SLANG_E_NOT_AVAILABLE ; 1536 } 1537desc .next = & experimentalFD ; 1538 } 1539#endif 1540 1541// Look for args going to slang 1542 { 1543const auto & args = options .downstreamArgs .getArgsByName ("slang" ); 1544for (const auto & arg :args ) 1545 { 1546if (arg .value == "-matrix-layout-column-major" ) 1547 { 1548desc .slang .defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR ; 1549break ; 1550 } 1551 } 1552 } 1553 1554desc .nvapiExtUavSlot = uint32_t (nvapiExtnSlot ); 1555desc .slang .slangGlobalSession = session ; 1556desc .slang .targetProfile = options .profileName .getBuffer (); 1557 { 1558if (options .enableDebugLayers ) 1559 { 1560getRHI ()-> enableDebugLayers (); 1561 } 1562Slang ::ComPtr < rhi::IDevice > rhiDevice ; 1563SlangResult res ; 1564if (options .cacheRhiDevice ) 1565 { 1566res = DeviceCache ::acquireDevice (desc ,rhiDevice .writeRef ()); 1567if (SLANG_FAILED (res )) 1568 { 1569rhiDevice = nullptr ; 1570 } 1571 } 1572else 1573 { 1574res = rhi::getRHI ()-> createDevice (desc ,rhiDevice .writeRef ()); 1575if (SLANG_FAILED (res )) 1576 { 1577rhiDevice = nullptr ; 1578 } 1579 } 1580 1581// Check result for both cached and non-cached paths 1582if (SLANG_FAILED (res )|| !rhiDevice ) 1583 { 1584// We need to be careful here about SLANG_E_NOT_AVAILABLE. This return value means 1585// that the renderer couldn't be created because it required *features* that were 1586// *not available*. It does not mean the renderer in general couldn't be 1587// constructed. 1588// 1589// Returning SLANG_E_NOT_AVAILABLE will lead to the test infrastructure ignoring 1590// this test. 1591// 1592// We also don't want to output the 'Unable to create renderer' error, as this isn't 1593// an error. 1594if (res == SLANG_E_NOT_AVAILABLE ) 1595 { 1596return res ; 1597 } 1598if (!options .onlyStartup ) 1599 { 1600fprintf (stderr ,"Unable to create renderer %s\n" ,rendererName .getBuffer ()); 1601 } 1602return res ; 1603 } 1604SLANG_ASSERT (rhiDevice ); 1605deviceWrapper = CachedDeviceWrapper (rhiDevice ); 1606 } 1607 1608for (const auto & feature :requiredFeatureList ) 1609 { 1610// If doesn't have required feature... we have to give up 1611if (!deviceWrapper -> hasFeature (feature )) 1612 { 1613return SLANG_E_NOT_AVAILABLE ; 1614 } 1615 } 1616 } 1617 1618// Print adapter info after device creation but before any other operations 1619if (options .showAdapterInfo ) 1620 { 1621auto info = deviceWrapper -> getInfo (); 1622auto out = stdWriters -> getOut (); 1623out ."Using graphics adapter: %s\n" ,info .adapterName ); 1624 } 1625 1626// If the only test is we can startup, then we are done 1627if (options .onlyStartup ) 1628 { 1629return SLANG_OK ; 1630 } 1631 1632 { 1633RenderTestApp app ; 1634renderDocBeginFrame (); 1635SLANG_RETURN_ON_FAIL (app .initialize (session ,deviceWrapper .get (),options ,input )); 1636app .update (); 1637renderDocEndFrame (); 1638app .finalize (); 1639 } 1640 1641return SLANG_OK ; 1642} 1643 1644SLANG_TEST_TOOL_API void cleanDeviceCache () 1645{ 1646DeviceCache ::cleanCache (); 1647} 1648 1649SLANG_TEST_TOOL_API SlangResult innerMain ( 1650Slang ::StdWriters * stdWriters , 1651SlangSession * sharedSession , 1652int inArgc , 1653const char * const * inArgv ) 1654{ 1655using namespace Slang ; 1656 1657// Assume we will used the shared session 1658ComPtr < slang::IGlobalSession > session (sharedSession ); 1659 1660// The sharedSession always has a pre-loaded core module. 1661// This differed test checks if the command line has an option to setup the core module. 1662// If so we *don't* use the sharedSession, and create a new session without the core module just 1663// for this compilation. 1664if (TestToolUtil ::hasDeferredCoreModule (Index (inArgc - 1 ),inArgv + 1 )) 1665 { 1666SLANG_RETURN_ON_FAIL ( 1667slang_createGlobalSessionWithoutCoreModule (SLANG_API_VERSION ,session .writeRef ())); 1668 } 1669 1670SlangResult res = SLANG_FAIL ; 1671try 1672 { 1673res = _innerMain (stdWriters ,session ,inArgc ,inArgv ); 1674 } 1675catch (const Slang ::Exception & exception ) 1676 { 1677stdWriters -> getOut ().put (exception .Message .getUnownedSlice ()); 1678return SLANG_FAIL ; 1679 } 1680catch (...) 1681 { 1682stdWriters -> getOut ().put (UnownedStringSlice ::fromLiteral ("Unhandled exception" )); 1683return SLANG_FAIL ; 1684 } 1685 1686return res ; 1687} 1688 1689int main (int argc ,char ** argv ) 1690{ 1691using namespace Slang ; 1692SlangSession * session = spCreateSession (nullptr ); 1693 1694TestToolUtil ::setSessionDefaultPreludeFromExePath (argv [0 ],session ); 1695 1696auto stdWriters = StdWriters ::initDefaultSingleton (); 1697 1698SlangResult res = innerMain (stdWriters ,session ,argc ,argv ); 1699spDestroySession (session ); 1700 1701 slang::shutdown (); 1702return (int )TestToolUtil ::getReturnCode (res ); 1703}