yum-mirror/slang
Making it easier to work with shaders
git clone https://git.yummers.dev/yum-mirror/slang
5b9931456
master
1// main.cpp 2 3#include <stdio.h> 4 5// This file implements an extremely simple example of loading and 6// executing a Slang shader program on the CPU. 7// 8// More information about generation C++ or CPU code can be found in docs/cpu-target.md 9// 10// NOTE! This test will only run on a system correctly where slang can find a suitable 11// C++ compiler - such as clang/gcc/visual studio 12// 13// The comments in the file will attempt to explain concepts as 14// they are introduced. 15// 16// Of course, in order to use the Slang API, we need to include 17// its header. We have set up the build options for this project 18// so that it is as simple as: 19#include "slang.h" 20 21// Allows use of ComPtr - which we can use to scope any 'com-like' pointers easily 22#include "slang-com-ptr.h" 23// Provides macros for handling SlangResult values easily 24#include "slang-com-helper.h" 25 26// This includes a useful small function for setting up the prelude (described more further below). 27#include "../../source/core/slang-test-tool-util.h" 28#include "examples/example-base/example-base.h" 29 30// Slang namespace is used for elements support code (like core) which we use here 31// for ComPtr<> and TestToolUtil 32using namespace Slang ; 33 34// Slang source is converted into C++ code which is compiled by a backend compiler. 35// That process uses a 'prelude' which defines types and functions that are needed 36// for everything else to work. 37// 38// We include the prelude here, so we can directly use the types as were used by the 39// compiled code. It is not necessary to include the prelude, as long as memory is 40// laid out in the manner that the generated slang code expects. 41#define SLANG_PRELUDE_NAMESPACE CPPPrelude 42#include "../../prelude/slang-cpp-types.h" 43 44static const ExampleResources resourceBase ("cpu-hello-world" ); 45 46struct UniformState ; 47 48static SlangResult _innerMain (int argc ,char ** argv ) 49{ 50TestBase testBase ; 51testBase .parseOption (argc ,argv ); 52 53// First, we need to create a "session" for interacting with the Slang 54// compiler. This scopes all of our application's interactions 55// with the Slang library. At the moment, creating a session causes 56// Slang to load and validate its standard library, so this is a 57// somewhat heavy-weight operation. When possible, an application 58// should try to re-use the same session across multiple compiles. 59// 60// NOTE that we use attach instead of setting via assignment, as assignment will increase 61// the refcount. spCreateSession returns a IGlobalSession with a refcount of 1. 62ComPtr < slang::IGlobalSession > slangSession ; 63 64SLANG_RETURN_ON_FAIL (slang::createGlobalSession (slangSession .writeRef ())); 65 66// As touched on earlier, in order to generate the final executable code, 67// the slang code is converted into C++, and that C++ needs a 'prelude' which 68// is just definitions that the generated code needed to work correctly. 69// There is a simple default definition of a prelude provided in the prelude 70// directory called 'slang-cpp-prelude.h'. 71// 72// We need to tell slang either the contents of the prelude, or suitable include/s 73// that will work. The actual API call to set the prelude is `setPrelude` 74// and this just sets for a specific language a bit of text placed before generated code. 75// 76// Most downstream C++ compilers work on files. In that case slang may generate temporary 77// files that contain the generated code. Typically the generated files will not be in the 78// same directory as the original source so handling includes becomes awkward. The mechanism 79// used here is for the prelude code to be an *absolute* path to the 'slang-cpp-prelude.h' - 80// which means this will work wherever the generated code is, and allows accessing other files 81// via relative paths. 82// 83// Look at the source to TestToolUtil::setSessionDefaultPreludeFromExePath to see what's 84// involed. 85TestToolUtil ::setSessionDefaultPreludeFromExePath (argv [0 ],slangSession ); 86 87 slang::SessionDesc sessionDesc = {}; 88 slang::TargetDesc targetDesc = {}; 89targetDesc .format = SLANG_SHADER_HOST_CALLABLE ; 90targetDesc .flags = SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM ; 91 92sessionDesc .targets = & targetDesc ; 93sessionDesc .targetCount = 1 ; 94 95ComPtr < slang::ISession > session ; 96SLANG_RETURN_ON_FAIL (slangSession -> createSession (sessionDesc ,session .writeRef ())); 97 98 slang::IModule * slangModule = nullptr ; 99 { 100ComPtr < slang::IBlob > diagnosticBlob ; 101Slang ::String path = resourceBase .resolveResource ("shader.slang" ); 102slangModule = session -> loadModule (path .getBuffer (),diagnosticBlob .writeRef ()); 103diagnoseIfNeeded (diagnosticBlob ); 104if (!slangModule ) 105return -1 ; 106 } 107 108ComPtr < slang::IEntryPoint > entryPoint ; 109slangModule -> findEntryPointByName ("computeMain" ,entryPoint .writeRef ()); 110 111Slang ::List < slang::IComponentType *> componentTypes ; 112componentTypes .add (slangModule ); 113componentTypes .add (entryPoint ); 114 115ComPtr < slang::IComponentType > composedProgram ; 116 { 117ComPtr < slang::IBlob > diagnosticsBlob ; 118SlangResult result = session -> createCompositeComponentType ( 119componentTypes .getBuffer (), 120componentTypes .getCount (), 121composedProgram .writeRef (), 122diagnosticsBlob .writeRef ()); 123diagnoseIfNeeded (diagnosticsBlob ); 124SLANG_RETURN_ON_FAIL (result ); 125 } 126 127// Get the 'shared library' (note that this doesn't necessarily have to be implemented as a 128// shared library it's just an interface to executable code). 129ComPtr < ISlangSharedLibrary > sharedLibrary ; 130 { 131ComPtr < slang::IBlob > diagnosticsBlob ; 132SlangResult result = composedProgram -> getEntryPointHostCallable ( 1330 , 1340 , 135sharedLibrary .writeRef (), 136diagnosticsBlob .writeRef ()); 137diagnoseIfNeeded (diagnosticsBlob ); 138SLANG_RETURN_ON_FAIL (result ); 139if (testBase .isTestMode ()) 140 { 141testBase .printEntrypointHashes (1 ,1 ,composedProgram ); 142 } 143 } 144// Once we have the sharedLibrary, we no longer need the request 145// unless we want to use reflection, to for example workout how 'UniformState' and 146// 'UniformEntryPointParams' are laid out at runtime. We don't do that here - as we hard code 147// the structures. 148 149// Get the function we are going to execute 150const char entryPointName []= "computeMain" ; 151CPPPrelude ::ComputeFunc func = 152 (CPPPrelude ::ComputeFunc )sharedLibrary -> findFuncByName (entryPointName ); 153if (!func ) 154 { 155return SLANG_FAIL ; 156 } 157 158// Define the uniform state structure that is *specific* to our shader defined in shader.slang 159// That the layout of the structure can be determined through reflection, or can be inferred 160// from the original slang source. Look at the documentation in docs/cpu-target.md which 161// describes how different resources map. The order of the resources is in the order that they 162// are defined in the source. 163struct UniformState 164 { 165CPPPrelude ::RWStructuredBuffer < float > ioBuffer ; 166 }; 167 168// the uniformState will be passed as a pointer to the CPU code 169UniformState uniformState ; 170 171// The contents of the buffer are modified, so we'll copy it 172const float startBufferContents []= {2.0f ,-10.0f ,-3.0f ,5.0f }; 173float bufferContents [SLANG_COUNT_OF (startBufferContents )]; 174memcpy (bufferContents ,startBufferContents ,sizeof (startBufferContents )); 175 176// Set up the ioBuffer such that it uses bufferContents. It is important to set the .count 177// such that bounds checking can be performed in the kernel. 178uniformState .ioBuffer .data = bufferContents ; 179uniformState .ioBuffer .count = SLANG_COUNT_OF (bufferContents ); 180 181// In shader.slang, then entry point is attributed with `[numthreads(4, 1, 1)]` meaning each 182// group consists of 4 'thread' in x. Our input buffer is 4 wide, and we index the input array 183// via `SV_DispatchThreadID` so we only need to run a single group to execute over all of the 4 184// elements here. The group range from { 0, 0, 0 } -> { 1, 1, 1 } means it will execute over the 185// single group { 0, 0, 0 }. 186 187const CPPPrelude ::uint3 startGroupID = {0 ,0 ,0 }; 188const CPPPrelude ::uint3 endGroupID = {1 ,1 ,1 }; 189 190CPPPrelude ::ComputeVaryingInput varyingInput ; 191varyingInput .startGroupID = startGroupID ; 192varyingInput .endGroupID = endGroupID ; 193 194// We don't have any entry point parameters so that's passed as NULL 195// We need to cast our definition of the uniform state to the undefined CPPPrelude::UniformState 196// as that type is just a name to indicate what kind of thing needs to be passed in. 197func (& varyingInput ,NULL ,& uniformState ); 198 199// bufferContents holds the output 200 201// Print out the values before the computation 202printf ("Before:\n" ); 203for (float v :startBufferContents ) 204 { 205printf ("%f, " ,v ); 206 } 207printf ("\n" ); 208 209// Print out the values the the kernel produced 210printf ("After: \n" ); 211for (float v :bufferContents ) 212 { 213printf ("%f, " ,v ); 214 } 215printf ("\n" ); 216 217return SLANG_OK ; 218} 219 220int exampleMain (int argc ,char ** argv ) 221{ 222return SLANG_SUCCEEDED (_innerMain (argc ,argv )) ?0 :-1 ; 223}