yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Anders LeinoAdd backtraces to examples (#5973)5b9931456

master
9.1 KiB223 linesraw
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{
50    TestBase testBase;
51    testBase.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.
62    ComPtr<slang::IGlobalSession> slangSession;
63
64    SLANG_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.
85    TestToolUtil::setSessionDefaultPreludeFromExePath(argv[0], slangSession);
86
87    slang::SessionDesc sessionDesc = {};
88    slang::TargetDesc targetDesc = {};
89    targetDesc.format = SLANG_SHADER_HOST_CALLABLE;
90    targetDesc.flags = SLANG_TARGET_FLAG_GENERATE_WHOLE_PROGRAM;
91
92    sessionDesc.targets = &targetDesc;
93    sessionDesc.targetCount = 1;
94
95    ComPtr<slang::ISession> session;
96    SLANG_RETURN_ON_FAIL(slangSession->createSession(sessionDesc, session.writeRef()));
97
98    slang::IModule* slangModule = nullptr;
99    {
100        ComPtr<slang::IBlob> diagnosticBlob;
101        Slang::String path = resourceBase.resolveResource("shader.slang");
102        slangModule = session->loadModule(path.getBuffer(), diagnosticBlob.writeRef());
103        diagnoseIfNeeded(diagnosticBlob);
104        if (!slangModule)
105            return -1;
106    }
107
108    ComPtr<slang::IEntryPoint> entryPoint;
109    slangModule->findEntryPointByName("computeMain", entryPoint.writeRef());
110
111    Slang::List<slang::IComponentType*> componentTypes;
112    componentTypes.add(slangModule);
113    componentTypes.add(entryPoint);
114
115    ComPtr<slang::IComponentType> composedProgram;
116    {
117        ComPtr<slang::IBlob> diagnosticsBlob;
118        SlangResult result = session->createCompositeComponentType(
119            componentTypes.getBuffer(),
120            componentTypes.getCount(),
121            composedProgram.writeRef(),
122            diagnosticsBlob.writeRef());
123        diagnoseIfNeeded(diagnosticsBlob);
124        SLANG_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).
129    ComPtr<ISlangSharedLibrary> sharedLibrary;
130    {
131        ComPtr<slang::IBlob> diagnosticsBlob;
132        SlangResult result = composedProgram->getEntryPointHostCallable(
133            0,
134            0,
135            sharedLibrary.writeRef(),
136            diagnosticsBlob.writeRef());
137        diagnoseIfNeeded(diagnosticsBlob);
138        SLANG_RETURN_ON_FAIL(result);
139        if (testBase.isTestMode())
140        {
141            testBase.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
150    const char entryPointName[] = "computeMain";
151    CPPPrelude::ComputeFunc func =
152        (CPPPrelude::ComputeFunc)sharedLibrary->findFuncByName(entryPointName);
153    if (!func)
154    {
155        return 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.
163    struct UniformState
164    {
165        CPPPrelude::RWStructuredBuffer<float> ioBuffer;
166    };
167
168    // the uniformState will be passed as a pointer to the CPU code
169    UniformState uniformState;
170
171    // The contents of the buffer are modified, so we'll copy it
172    const float startBufferContents[] = {2.0f, -10.0f, -3.0f, 5.0f};
173    float bufferContents[SLANG_COUNT_OF(startBufferContents)];
174    memcpy(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.
178    uniformState.ioBuffer.data = bufferContents;
179    uniformState.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
187    const CPPPrelude::uint3 startGroupID = {0, 0, 0};
188    const CPPPrelude::uint3 endGroupID = {1, 1, 1};
189
190    CPPPrelude::ComputeVaryingInput varyingInput;
191    varyingInput.startGroupID = startGroupID;
192    varyingInput.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.
197    func(&varyingInput, NULL, &uniformState);
198
199    // bufferContents holds the output
200
201    // Print out the values before the computation
202    printf("Before:\n");
203    for (float v : startBufferContents)
204    {
205        printf("%f, ", v);
206    }
207    printf("\n");
208
209    // Print out the values the the kernel produced
210    printf("After: \n");
211    for (float v : bufferContents)
212    {
213        printf("%f, ", v);
214    }
215    printf("\n");
216
217    return SLANG_OK;
218}
219
220int exampleMain(int argc, char** argv)
221{
222    return SLANG_SUCCEEDED(_innerMain(argc, argv)) ? 0 : -1;
223}