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// This file provides the application code for the `shader-object` example. 4// 5 6// This example uses the Slang slang-rhi layer to target different APIs. 7// The goal is to demonstrate how the Shader Object model implemented in `slang-rhi` layer 8// simplifies shader specialization and parameter binding when using `interface` typed 9// shader parameters. 10// 11#include "slang-com-ptr.h" 12#include "slang.h" 13using Slang ::ComPtr ; 14 15#include "core/slang-basic.h" 16#include "examples/example-base/example-base.h" 17#include "slang-rhi.h" 18 19#include <slang-rhi/shader-cursor.h> 20 21using namespace rhi ; 22 23static const ExampleResources resourceBase ("shader-object" ); 24 25static TestBase testBase ; 26 27// Loads the shader code defined in `shader-object.slang` for use by the `slang-rhi` layer. 28// 29Result loadShaderProgram ( 30IDevice * device , 31ComPtr < IShaderProgram >& outShaderProgram , 32 slang::ProgramLayout *& slangReflection ) 33{ 34// We need to obtain a compilation session (`slang::ISession`) that will provide 35// a scope to all the compilation and loading of code we do. 36// 37// Our example application uses the `slang-rhi` graphics API abstraction layer, which already 38// creates a Slang compilation session for us, so we just grab and use it here. 39ComPtr < slang::ISession > slangSession ; 40slangSession = device -> getSlangSession (); 41 42// Once the session has been obtained, we can start loading code into it. 43// 44// The simplest way to load code is by calling `loadModule` with the name of a Slang 45// module. A call to `loadModule("MyStuff")` will behave more or less as if you 46// wrote: 47// 48// import MyStuff; 49// 50// In a Slang shader file. The compiler will use its search paths to try to locate 51// `MyModule.slang`, then compile and load that file. If a matching module had 52// already been loaded previously, that would be used directly. 53// 54// Note: The only interesting wrinkle here is that our file is named `shader-object` with 55// a hyphen in it, so the name is not directly usable as an identifier in Slang code. 56// Instead, when trying to import this module in the context of Slang code, a user 57// needs to replace the hyphens with underscores: 58// 59// import shader_object; 60// 61ComPtr < slang::IBlob > diagnosticsBlob ; 62Slang ::String path = resourceBase .resolveResource ("shader-object.slang" ); 63 slang::IModule * module = slangSession -> loadModule (path .getBuffer (),diagnosticsBlob .writeRef ()); 64diagnoseIfNeeded (diagnosticsBlob ); 65if (!module ) 66return SLANG_FAIL ; 67 68// Loading the `shader-object` module will compile and check all the shader code in it, 69// including the shader entry points we want to use. Now that the module is loaded 70// we can look up those entry points by name. 71// 72// Note: If you are using this `loadModule` approach to load your shader code it is 73// important to tag your entry point functions with the `[shader("...")]` attribute 74// (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there 75// is no umambiguous way for the compiler to know which functions represent entry 76// points when it parses your code via `loadModule()`. 77// 78char const * computeEntryPointName = "computeMain" ; 79ComPtr < slang::IEntryPoint > computeEntryPoint ; 80SLANG_RETURN_ON_FAIL ( 81module -> findEntryPointByName (computeEntryPointName ,computeEntryPoint .writeRef ())); 82 83// At this point we have a few different Slang API objects that represent 84// pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. 85// 86// A single Slang module could contain many different entry points (e.g., 87// four vertex entry points, three fragment entry points, and two compute 88// shaders), and before we try to generate output code for our target API 89// we need to identify which entry points we plan to use together. 90// 91// Modules and entry points are both examples of *component types* in the 92// Slang API. The API also provides a way to build a *composite* out of 93// other pieces, and that is what we are going to do with our module 94// and entry points. 95// 96Slang ::List < slang::IComponentType *> componentTypes ; 97componentTypes .add (module ); 98componentTypes .add (computeEntryPoint ); 99 100// Actually creating the composite component type is a single operation 101// on the Slang session, but the operation could potentially fail if 102// something about the composite was invalid (e.g., you are trying to 103// combine multiple copies of the same module), so we need to deal 104// with the possibility of diagnostic output. 105// 106ComPtr < slang::IComponentType > composedProgram ; 107SlangResult result = slangSession -> createCompositeComponentType ( 108componentTypes .getBuffer (), 109componentTypes .getCount (), 110composedProgram .writeRef (), 111diagnosticsBlob .writeRef ()); 112diagnoseIfNeeded (diagnosticsBlob ); 113SLANG_RETURN_ON_FAIL (result ); 114if (testBase .isTestMode ()) 115 { 116testBase .printEntrypointHashes (1 ,1 ,composedProgram ); 117 } 118 119slangReflection = composedProgram -> getLayout (); 120 121// At this point, `composedProgram` represents the shader program 122// we want to run, and the compute shader there have been checked. 123// We can create a `IShaderProgram` object from `composedProgram` 124// so it may be used by the graphics layer. 125ShaderProgramDesc programDesc = {}; 126programDesc .slangGlobalScope = composedProgram .get (); 127 128auto shaderProgram = device -> createShaderProgram (programDesc ); 129 130outShaderProgram = shaderProgram ; 131return SLANG_OK ; 132} 133 134// Main body of the example. 135int exampleMain (int argc ,char ** argv ) 136{ 137testBase .parseOption (argc ,argv ); 138 139// Creates a `slang-rhi` renderer, which provides the main interface for 140// interacting with the graphics API. 141Slang ::ComPtr < IDevice > device ; 142DeviceDesc deviceDesc = {}; 143device = getRHI ()-> createDevice (deviceDesc ); 144if (!device ) 145return SLANG_FAIL ; 146 147// Now we can load the shader code. 148// A `IShaderProgram` object for use in the `slang-rhi` layer. 149ComPtr < IShaderProgram > shaderProgram ; 150// A composed `IComponentType` that gives us reflection info on the shader code. 151 slang::ProgramLayout * slangReflection ; 152SLANG_RETURN_ON_FAIL (loadShaderProgram (device ,shaderProgram ,slangReflection )); 153 154// Create a pipeline state with the loaded shader. 155ComputePipelineDesc pipelineDesc = {}; 156pipelineDesc .program = shaderProgram .get (); 157ComPtr < IComputePipeline > pipelineState ; 158pipelineState = device -> createComputePipeline (pipelineDesc ); 159if (!pipelineState ) 160return SLANG_FAIL ; 161 162// Create and initiate our input/output buffer. 163const int numberCount = 4 ; 164float initialData []= {0.0f ,1.0f ,2.0f ,3.0f }; 165BufferDesc bufferDesc = {}; 166bufferDesc .size = numberCount * sizeof (float ); 167bufferDesc .format = Format ::Undefined ; 168bufferDesc .elementSize = sizeof (float ); 169bufferDesc .usage = BufferUsage ::ShaderResource |BufferUsage ::UnorderedAccess | 170BufferUsage ::CopyDestination |BufferUsage ::CopySource ; 171bufferDesc .defaultState = ResourceState ::UnorderedAccess ; 172bufferDesc .memoryType = MemoryType ::DeviceLocal ; 173 174ComPtr < IBuffer > numbersBuffer ; 175numbersBuffer = device -> createBuffer (bufferDesc , (void * )initialData ); 176if (!numbersBuffer ) 177return SLANG_FAIL ; 178 179// We have done all the set up work, now it is time to start recording a command buffer for 180// GPU execution. 181 { 182auto queue = device -> getQueue (QueueType ::Graphics ); 183 184auto commandEncoder = queue -> createCommandEncoder (); 185auto encoder = commandEncoder -> beginComputePass (); 186 187// Now comes the interesting part: binding the shader parameter for the 188// compute kernel that we about to launch. We would like to construct 189// a shader object that represents a `f(x)=x+1` transformation and apply 190// it to the numbers in `numbersBuffer`. 191 192// First, obtain a root shader object from command encoder to start parameter binding. 193auto rootObject = encoder -> bindPipeline (pipelineState ); 194 195// Next, we create a shader object that represents the transformer we want to use. 196// To do so, we first need to lookup for the `AddTransformer` type defined in the shader 197// code. 198 slang::TypeReflection * addTransformerType = 199slangReflection -> findTypeByName ("AddTransformer" ); 200 201// Now we can use this type to create a shader object that can be bound to the root object. 202ComPtr < IShaderObject > transformer ; 203transformer = 204device -> createShaderObject (addTransformerType ,ShaderObjectContainerType ::None ); 205if (!transformer ) 206return SLANG_FAIL ; 207 208// Set the `c` field of the `AddTransformer`. 209float c = 1.0f ; 210ShaderCursor (transformer ).getPath ("c" ).setData (& c ,sizeof (float )); 211 212// We can set parameters directly with `rootObject`, but that requires us to use 213// the Slang reflection API to obtain the proper offsets into the root object for each 214// parameter. We implemented these logic in the `ShaderCursor` helper class, which 215// simplifies the user code to find shader parameters. Here we demonstrate how to set 216// parameters with `ShaderCursor`. 217ShaderCursor entryPointCursor ( 218rootObject -> getEntryPoint (0 ));// get a cursor the the first entry-point. 219// Bind buffer to the entry point. 220entryPointCursor .getPath ("buffer" ).setBinding (numbersBuffer ); 221 222// Bind the previously created transformer object to root object. 223entryPointCursor .getPath ("transformer" ).setObject (transformer ); 224 225encoder -> dispatchCompute (1 ,1 ,1 ); 226encoder -> end (); 227queue -> submit (commandEncoder -> finish ()); 228queue -> waitOnHost (); 229 } 230// Read back the results. 231ComPtr < ISlangBlob > resultBlob ; 232SLANG_RETURN_ON_FAIL ( 233device -> readBuffer (numbersBuffer ,0 ,numberCount * sizeof (float ),resultBlob .writeRef ())); 234auto result = reinterpret_cast < const float *> (resultBlob -> getBufferPointer ()); 235for (int i = 0 ;i < numberCount ;i ++ ) 236printf ("%f\n" ,result [i ]); 237 238return SLANG_OK ; 239}