yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
43d0c2100
master
1// main.cpp 2 3#include "../../source/core/slang-io.h" 4#include "GFSDK_Aftermath.h" 5#include "GFSDK_Aftermath_GpuCrashDump.h" 6#include "core/slang-basic.h" 7#include "examples/example-base/example-base.h" 8#include "platform/window.h" 9#include "slang-com-ptr.h" 10#include "slang.h" 11 12#include <slang-rhi.h> 13#include <slang-rhi/shader-cursor.h> 14 15using namespace rhi ; 16using namespace Slang ; 17 18static const ExampleResources resourceBase ("nv-aftermath-example" ); 19 20// This example is based on the "triangle" sample. 21// 22// This examples purpose is to show how to use the aftermath SDK to capture 23// a crash dump. 24// 25// * [nsight aftermath](https://developer.nvidia.com/nsight-aftermath) 26// 27// In addition it uses obfuscation and source maps to allow source level 28// debugging via aftermath even with obfuscation. 29// 30// * [obfuscation](https://github.com/shader-slang/slang/blob/master/docs/user-guide/a1-03-obfuscation.md) 31// * [source map](https://github.com/source-map/source-map-spec) 32 33struct Vertex 34{ 35float position [3 ]; 36float color [3 ]; 37}; 38 39static const int kVertexCount = 3 ; 40static const Vertex kVertexData [kVertexCount ]= { 41 {{0 ,0 ,0.5 }, {1 ,0 ,0 }}, 42 {{0 ,1 ,0.5 }, {0 ,0 ,1 }}, 43 {{1 ,0 ,0.5 }, {0 ,1 ,0 }}, 44}; 45 46struct AftermathCrashExample :public WindowedAppBase 47{ 48void diagnoseIfNeeded (slang::IBlob * diagnosticsBlob ); 49 50Result loadShaderProgram (IDevice * device ,IShaderProgram ** outProgram ); 51 52virtual void renderFrame (ITexture * texture )override ; 53 54void onAftermathCrash (const void * data ,const uint32_t dataSizeInBytes ); 55 56void onAftermathDebugInfo (const void * pGpuCrashDump ,const uint32_t gpuCrashDumpSize ); 57 58void onAftermathCrashDescription (PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription description ); 59 60void onAftermathMarker (const void * pMarker ,void ** resolvedMarkerData ,uint32_t * markerSize ); 61 62ComPtr < IRenderPipeline > m_renderPipeline ; 63ComPtr < IBuffer > m_vertexBuffer ; 64 65/// A counter such that we can make aftermath dump file names unique 66 std::atomic < int > m_uniqueId = 0 ; 67 68Slang ::Result initialize () 69 { 70// Defer shader debug information callbacks until an actual GPU crash dump 71// is generated. Increases memory footprint. 72const uint32_t aftermathFeatureFlags = 73GFSDK_Aftermath_GpuCrashDumpFeatureFlags_DeferDebugInfoCallbacks ; 74 75// As per docs must be called before any device is created 76GFSDK_Aftermath_EnableGpuCrashDumps ( 77GFSDK_Aftermath_Version_API , 78GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_DX | 79GFSDK_Aftermath_GpuCrashDumpWatchedApiFlags_Vulkan , 80aftermathFeatureFlags , 81_crashCallback , 82_debugInfoCallback , 83_crashDescriptionCallback , 84_markerCallback , 85this ); 86 87SLANG_RETURN_ON_FAIL (initializeBase ("autodiff-texture" ,1024 ,768 ,DeviceType ::Default )); 88 89 90// We will create objects needed to configure the "input assembler" 91// (IA) stage of the pipeline. 92// 93// First, we create an input layout: 94// 95InputElementDesc inputElements []= { 96 {"POSITION" ,0 ,Format ::RGB32Float , offsetof(Vertex ,position )}, 97 {"COLOR" ,0 ,Format ::RGB32Float , offsetof(Vertex ,color )}, 98 }; 99auto inputLayout = gDevice -> createInputLayout (sizeof (Vertex ),& inputElements [0 ],2 ); 100if (!inputLayout ) 101return SLANG_FAIL ; 102 103// Next we allocate a vertex buffer for our pre-initialized 104// vertex data. 105// 106BufferDesc vertexBufferDesc ; 107vertexBufferDesc .size = kVertexCount * sizeof (Vertex ); 108vertexBufferDesc .usage = BufferUsage ::VertexBuffer ; 109vertexBufferDesc .defaultState = ResourceState ::VertexBuffer ; 110m_vertexBuffer = gDevice -> createBuffer (vertexBufferDesc ,& kVertexData [0 ]); 111if (!m_vertexBuffer ) 112return SLANG_FAIL ; 113 114// Now we will use our `loadShaderProgram` function to load 115// the code from `shaders.slang` into the graphics API. 116// 117ComPtr < IShaderProgram > shaderProgram ; 118SLANG_RETURN_ON_FAIL (loadShaderProgram (device ,shaderProgram .writeRef ())); 119 120// Following the D3D12/Vulkan style of API, we need a pipeline state object 121// (PSO) to encapsulate the configuration of the overall graphics pipeline. 122// 123ColorTargetDesc colorTarget ; 124colorTarget .format = Format ::RGBA8Unorm ; 125RenderPipelineDesc desc ; 126desc .inputLayout = inputLayout ; 127desc .program = shaderProgram ; 128desc .targetCount = 1 ; 129desc .targets = & colorTarget ; 130desc .depthStencil .depthTestEnable = false; 131desc .depthStencil .depthWriteEnable = false; 132desc .primitiveTopology = PrimitiveTopology ::TriangleList ; 133auto pipelineState = gDevice -> createRenderPipeline (desc ); 134if (!pipelineState ) 135return SLANG_FAIL ; 136 137m_renderPipeline = pipelineState ; 138 139return SLANG_OK ; 140 } 141}; 142 143void AftermathCrashExample ::diagnoseIfNeeded (slang::IBlob * diagnosticsBlob ) 144{ 145if (diagnosticsBlob != nullptr ) 146 { 147printf ("%s" , (const char * )diagnosticsBlob -> getBufferPointer ()); 148 } 149} 150 151void AftermathCrashExample ::onAftermathCrash (const void * data ,const uint32_t dataSizeInBytes ) 152{ 153// NOTE! This method can be called from *any* thread. 154const auto id = m_uniqueId ++ ; 155 156// Dump out as a file 157Slang ::StringBuilder filename ; 158filename <<"aftermath-dump-" <<id <<".bin" ; 159 160File ::writeAllBytes (filename ,data ,dataSizeInBytes ); 161 162// SLANG_BREAKPOINT(0); 163} 164 165void AftermathCrashExample ::onAftermathDebugInfo ( 166const void * gpuCrashDump , 167const uint32_t gpuCrashDumpSize ) 168{ 169const auto id = m_uniqueId ++ ; 170 171// Dump out as a file 172Slang ::StringBuilder filename ; 173filename <<"aftermath-debug-info-" <<id <<".bin" ; 174 175File ::writeAllBytes (filename ,gpuCrashDump ,gpuCrashDumpSize ); 176} 177 178void AftermathCrashExample ::onAftermathCrashDescription ( 179PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription description ) 180{ 181// Ignore for now 182} 183 184void AftermathCrashExample ::onAftermathMarker ( 185const void * marker , 186void ** resolvedMarkerData , 187uint32_t * markerSize ) 188{ 189// Ignore for now 190} 191 192struct FileSystemEntry 193{ 194SlangPathType type ;///< The type of the entry 195String path ;///< The path to the entr 196}; 197 198struct CompileProduct 199{ 200String fileName ;///< The filename to write the compile product out to 201ComPtr < ISlangBlob > blob ;///< A blob holding the products contents 202}; 203 204/* Currently the mechanism to access the contents of a compilation that might consist of many 205products is through representing the contents as a "file system". 206 207The file system is just a somewhat convenient/simple in memory representation of the compilation 208products. 209 210This function transverses the file system and adds everything found into outEntries. 211*/ 212static SlangResult _findFileSystemContents ( 213ISlangFileSystemExt * fileSystem , 214const char * rootPath , 215List < FileSystemEntry >& outEntries ) 216{ 217 { 218SlangPathType type ; 219SLANG_RETURN_ON_FAIL (fileSystem -> getPathType (rootPath ,& type )); 220outEntries .add (FileSystemEntry {type ,rootPath }); 221 } 222 223// A context used to hold state, when using enumeratePathContents 224struct Context 225 { 226List < FileSystemEntry >& entries ;// The entries to be accumulated to 227String path ;// The path being enumerated 228 }; 229 230for (Index i = outEntries .getCount ()- 1 ;i < outEntries .getCount ();++ i ) 231 { 232const auto & entry = outEntries [i ]; 233 234// If it's a directory we want to traverse it's contents 235if (entry .type == SLANG_PATH_TYPE_DIRECTORY ) 236 { 237Context context {outEntries ,entry .path }; 238 239fileSystem -> enumeratePathContents ( 240entry .path .getBuffer (), 241 [](SlangPathType pathType ,const char * name ,void * userData )-> void 242 { 243Context * context = reinterpret_cast < Context *> (userData ); 244 245const String path = Path ::simplify (Path ::combine (context -> path ,name )); 246 247context -> entries .add ({pathType ,path }); 248 }, 249& context ); 250 } 251 } 252 253return SLANG_OK ; 254} 255 256/* This function takes a compile results file system, and finds items that should be written out. 257 258This is somewhat complicated because the names of products from different compilations might have 259the same names. So a "prefix" is passed in, and for files that don't have unique names, they are 260uniqified via the prefix. 261 262The same product may appear in multiple compilations, for example obfuscated source maps so a 263product is not added if there is already a product with the same name */ 264static SlangResult _addCompileProducts ( 265ISlangFileSystemExt * fileSystem , 266const char * prefix , 267List < CompileProduct >& ioProducts ) 268{ 269List < FileSystemEntry > fileSystemEntries ; 270SLANG_RETURN_ON_FAIL (_findFileSystemContents (fileSystem ,"." ,fileSystemEntries )); 271 272for (const auto & fileSystemEntry :fileSystemEntries ) 273 { 274if (fileSystemEntry .type != SLANG_PATH_TYPE_FILE ) 275 { 276continue ; 277 } 278 279const auto ext = Path ::getPathExt (fileSystemEntry .path ); 280 281String outFileName ; 282 283// Some filenames need special handling, and their names are already unique 284// Others will be the same between differen fileSystem that represent the 285// compilation products. 286// 287// Source maps that are obfuscated are unique. 288 { 289String inFileName = Path ::getFileNameWithoutExt (fileSystemEntry .path ); 290 291// If it's an obfuscated source map, it's name is already unique (it includes the hash) 292const bool isUniqueName = 293 (ext == toSlice ("map" )&& inFileName .endsWith (toSlice ("-obfuscated" ))); 294 295StringBuilder buf ; 296// If it's not a uniquename make it unique via the prefix 297if (!isUniqueName ) 298 { 299// Uniquify with the prefix 300buf <<prefix <<"-" ; 301 } 302 303buf <<inFileName <<"." <<ext ; 304outFileName = buf ; 305 } 306 307// If we have an output filename 308if (outFileName .getLength ()) 309 { 310// And that filename isn't already used 311if (ioProducts .findFirstIndex ( 312 [& ](const CompileProduct & product )-> bool 313 {return product .fileName == outFileName ; })< 0 ) 314 { 315ComPtr < ISlangBlob > blob ; 316SLANG_RETURN_ON_FAIL ( 317fileSystem -> loadFile (fileSystemEntry .path .getBuffer (),blob .writeRef ())); 318 319// Add to the results 320ioProducts .add (CompileProduct {outFileName ,blob }); 321 } 322 } 323 } 324 325return SLANG_OK ; 326} 327 328Result AftermathCrashExample ::loadShaderProgram (IDevice * device ,IShaderProgram ** outProgram ) 329{ 330ComPtr < slang::ISession > slangSession ; 331slangSession = gDevice -> getSlangSession (); 332 333// This is a little bit of a work around. 334// 335// We want to set some options that are only available 336// via processCommandLineArguments, but we need a request to be able to set them up 337// The setting actually sets the parameters on the Linkage, so they will be used for the later 338// actual compilation 339 { 340ComPtr < slang::ICompileRequest > request ; 341 342SLANG_RETURN_ON_FAIL (slangSession -> createCompileRequest (request .writeRef ())); 343 344// Turn on obfuscation 345// 346// Turns on source map as the line directive, this will lead to an "emit source map" 347// and no #line directives in generated source. 348// 349// It isn't necessary to use the "source-map" line directive mode, and just use 350// #line directives, and have source locations to obfuscated source file directly embedded. 351// 352// To do this replace the line below with 353// 354// ``` 355// const char* args[] = { "-obfuscate" }; 356// ``` 357const char * args []= {"-obfuscate" ,"-line-directive-mode" ,"source-map" }; 358 359request -> processCommandLineArguments (args ,SLANG_COUNT_OF (args )); 360 361// Enable debug info 362request -> setDebugInfoLevel (SLANG_DEBUG_INFO_LEVEL_MAXIMAL ); 363 } 364 365ComPtr < slang::IBlob > diagnosticsBlob ; 366Slang ::String path = resourceBase .resolveResource ("shaders.slang" ); 367 slang::IModule * module = slangSession -> loadModule (path .getBuffer (),diagnosticsBlob .writeRef ()); 368diagnoseIfNeeded (diagnosticsBlob ); 369if (!module ) 370return SLANG_FAIL ; 371 372// Find the entry points 373ComPtr < slang::IEntryPoint > vertexEntryPoint ; 374SLANG_RETURN_ON_FAIL (module -> findEntryPointByName ("vertexMain" ,vertexEntryPoint .writeRef ())); 375// 376ComPtr < slang::IEntryPoint > fragmentEntryPoint ; 377SLANG_RETURN_ON_FAIL ( 378module -> findEntryPointByName ("fragmentMain" ,fragmentEntryPoint .writeRef ())); 379 380// At this point we have a few different Slang API objects that represent 381// pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. 382// 383// A single Slang module could contain many different entry points (e.g., 384// four vertex entry points, three fragment entry points, and two compute 385// shaders), and before we try to generate output code for our target API 386// we need to identify which entry points we plan to use together. 387// 388// Modules and entry points are both examples of *component types* in the 389// Slang API. The API also provides a way to build a *composite* out of 390// other pieces, and that is what we are going to do with our module 391// and entry points. 392// 393Slang ::List < slang::IComponentType *> componentTypes ; 394componentTypes .add (module ); 395 396// Later on when we go to extract compiled kernel code for our vertex 397// and fragment shaders, we will need to make use of their order within 398// the composition, so we will record the relative ordering of the entry 399// points here as we add them. 400int entryPointCount = 0 ; 401int vertexEntryPointIndex = entryPointCount ++ ; 402componentTypes .add (vertexEntryPoint ); 403 404int fragmentEntryPointIndex = entryPointCount ++ ; 405componentTypes .add (fragmentEntryPoint ); 406 407// Actually creating the composite component type is a single operation 408// on the Slang session, but the operation could potentially fail if 409// something about the composite was invalid (e.g., you are trying to 410// combine multiple copies of the same module), so we need to deal 411// with the possibility of diagnostic output. 412// 413ComPtr < slang::IComponentType > linkedProgram ; 414SlangResult result = slangSession -> createCompositeComponentType ( 415componentTypes .getBuffer (), 416componentTypes .getCount (), 417linkedProgram .writeRef (), 418diagnosticsBlob .writeRef ()); 419diagnoseIfNeeded (diagnosticsBlob ); 420SLANG_RETURN_ON_FAIL (result ); 421 422const Index targetIndex = 0 ; 423 424// Trigger compilation by requesting the code. 425// Normally gfx would compile as needed. 426 { 427ComPtr < ISlangBlob > code ; 428ComPtr < ISlangBlob > diagnostics ; 429 430SLANG_RETURN_ON_FAIL (linkedProgram -> getEntryPointCode ( 431vertexEntryPointIndex , 432targetIndex , 433code .writeRef (), 434diagnostics .writeRef ())); 435SLANG_RETURN_ON_FAIL (linkedProgram -> getEntryPointCode ( 436fragmentEntryPointIndex , 437targetIndex , 438code .writeRef (), 439diagnostics .writeRef ())); 440 } 441 442 { 443// We want to find all the compilation products. In particular we want to get the emit 444// source map, and the obfuscated source maps 445 446List < CompileProduct > compileProducts ; 447 448// The current mechanism for getting access to compilation products other than result 449// blob/diagnostics is to return it as a compilation result "file system". 450 451ComPtr < ISlangMutableFileSystem > vertexFileSystem ; 452SLANG_RETURN_ON_FAIL (linkedProgram -> getResultAsFileSystem ( 453vertexEntryPointIndex , 454targetIndex , 455vertexFileSystem .writeRef ())); 456 457ComPtr < ISlangMutableFileSystem > fragmentFileSystem ; 458SLANG_RETURN_ON_FAIL (linkedProgram -> getResultAsFileSystem ( 459fragmentEntryPointIndex , 460targetIndex , 461fragmentFileSystem .writeRef ())); 462 463// Add the contents of the compile result file systems into compileProducts 464// Some products might appear in both file systems, so compileProducts is just the unique 465// products. Additionally because some products may have the same name, we pass in a 466// "prefix" to make the products name unique. 467SLANG_RETURN_ON_FAIL (_addCompileProducts (vertexFileSystem ,"vertex" ,compileProducts )); 468SLANG_RETURN_ON_FAIL (_addCompileProducts (fragmentFileSystem ,"fragment" ,compileProducts )); 469 470// Now write all of the products out 471for (const auto & product :compileProducts ) 472 { 473SLANG_RETURN_ON_FAIL (File ::writeAllBytes ( 474product .fileName , 475product .blob -> getBufferPointer (), 476product .blob -> getBufferSize ())); 477 } 478 } 479 480// Once we've described the particular composition of entry points 481// that we want to compile, we defer to the graphics API layer 482// to extract compiled kernel code and load it into the API-specific 483// program representation. 484// 485ShaderProgramDesc programDesc = {}; 486programDesc .slangGlobalScope = linkedProgram ; 487SLANG_RETURN_ON_FAIL (gDevice -> createShaderProgram (programDesc ,outProgram )); 488 489return SLANG_OK ; 490} 491 492static void GFSDK_AFTERMATH_CALL 493_crashCallback (const void * gpuCrashDump ,const uint32_t gpuCrashDumpSize ,void * userData ) 494{ 495reinterpret_cast < AftermathCrashExample *> (userData )-> onAftermathCrash ( 496gpuCrashDump , 497gpuCrashDumpSize ); 498} 499 500static void GFSDK_AFTERMATH_CALL 501_debugInfoCallback (const void * gpuCrashDump ,const uint32_t gpuCrashDumpSize ,void * userData ) 502{ 503reinterpret_cast < AftermathCrashExample *> (userData )-> onAftermathDebugInfo ( 504gpuCrashDump , 505gpuCrashDumpSize ); 506} 507 508static void GFSDK_AFTERMATH_CALL _crashDescriptionCallback ( 509PFN_GFSDK_Aftermath_AddGpuCrashDumpDescription addDescription , 510void * userData ) 511{ 512reinterpret_cast < AftermathCrashExample *> (userData )-> onAftermathCrashDescription (addDescription ); 513} 514 515static void GFSDK_AFTERMATH_CALL _markerCallback ( 516const void * marker , 517void * pUserData , 518void ** resolvedMarkerData , 519uint32_t * markerSize ) 520{ 521reinterpret_cast < AftermathCrashExample *> (pUserData )-> onAftermathMarker ( 522marker , 523resolvedMarkerData , 524markerSize ); 525} 526 527void AftermathCrashExample ::renderFrame (ITexture * texture ) 528{ 529auto commandEncoder = gQueue -> createCommandEncoder (); 530 531ComPtr < ITextureView > textureView = gDevice -> createTextureView (texture , {}); 532RenderPassColorAttachment colorAttachment = {}; 533colorAttachment .view = textureView ; 534colorAttachment .loadOp = LoadOp ::Clear ; 535 536RenderPassDesc renderPass = {}; 537renderPass .colorAttachments = & colorAttachment ; 538renderPass .colorAttachmentCount = 1 ; 539 540auto renderEncoder = commandEncoder -> beginRenderPass (renderPass ); 541 542RenderState renderState = {}; 543renderState .viewports [0 ]= Viewport ::fromSize (windowWidth ,windowHeight ); 544renderState .viewportCount = 1 ; 545renderState .scissorRects [0 ]= ScissorRect ::fromSize (windowWidth ,windowHeight ); 546renderState .scissorRectCount = 1 ; 547 548auto rootObject = renderEncoder -> bindPipeline (m_renderPipeline ); 549ShaderCursor rootCursor (rootObject ); 550 551rootCursor ["Uniforms" ]["modelViewProjection" ].setData (kIdentity ,sizeof (float )* 16 ); 552 553// We are going to extra efforts to create a shader that we know will time 554// out because we *want* a GPU "crash", such we can capture via nsight aftermath. 555// The failCount is just a number that is large enough to make things take too long. 556int32_t failCount = 0x3fffffff ; 557rootCursor ["Uniforms" ]["failCount" ].setData (& failCount ,sizeof (failCount )); 558 559// We also need to set up a few pieces of fixed-function pipeline 560// state that are not bound by the pipeline state above. 561// 562renderState .vertexBuffers [0 ]= m_vertexBuffer ; 563renderState .vertexBufferCount = 1 ; 564renderEncoder -> setRenderState (renderState ); 565 566// Finally, we are ready to issue a draw call for a single triangle. 567// 568DrawArguments drawArgs = {}; 569drawArgs .vertexCount = 3 ; 570renderEncoder -> draw (drawArgs ); 571 572renderEncoder -> end (); 573gQueue -> submit (commandEncoder -> finish ()); 574 575if (!isTestMode ()) 576 { 577// With that, we are done drawing for one frame, and ready for the next. 578// 579gSurface ()-> present (); 580 } 581 582// If the id changes means we have a capture and so can quit. 583// On D3D11, the first present *doesn't* appear to crash. 584if (m_uniqueId != 0 ) 585 { 586 platform::Application ::quit (); 587 } 588} 589 590// This macro instantiates an appropriate main function to 591// run the application defined above. 592EXAMPLE_MAIN (innerMain < AftermathCrashExample > )