yum-mirror/slang

Making it easier to work with shaders

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

Gangzheng TongConvert gfx unit tests and examples to use slang-rhi (#7577)43d0c2100

master
10.1 KiB239 linesraw
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(
30    IDevice* device,
31    ComPtr<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.
39    ComPtr<slang::ISession> slangSession;
40    slangSession = 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    //
61    ComPtr<slang::IBlob> diagnosticsBlob;
62    Slang::String path = resourceBase.resolveResource("shader-object.slang");
63    slang::IModule* module = slangSession->loadModule(path.getBuffer(), diagnosticsBlob.writeRef());
64    diagnoseIfNeeded(diagnosticsBlob);
65    if (!module)
66        return 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    //
78    char const* computeEntryPointName = "computeMain";
79    ComPtr<slang::IEntryPoint> computeEntryPoint;
80    SLANG_RETURN_ON_FAIL(
81        module->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    //
96    Slang::List<slang::IComponentType*> componentTypes;
97    componentTypes.add(module);
98    componentTypes.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    //
106    ComPtr<slang::IComponentType> composedProgram;
107    SlangResult result = slangSession->createCompositeComponentType(
108        componentTypes.getBuffer(),
109        componentTypes.getCount(),
110        composedProgram.writeRef(),
111        diagnosticsBlob.writeRef());
112    diagnoseIfNeeded(diagnosticsBlob);
113    SLANG_RETURN_ON_FAIL(result);
114    if (testBase.isTestMode())
115    {
116        testBase.printEntrypointHashes(1, 1, composedProgram);
117    }
118
119    slangReflection = 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.
125    ShaderProgramDesc programDesc = {};
126    programDesc.slangGlobalScope = composedProgram.get();
127
128    auto shaderProgram = device->createShaderProgram(programDesc);
129
130    outShaderProgram = shaderProgram;
131    return SLANG_OK;
132}
133
134// Main body of the example.
135int exampleMain(int argc, char** argv)
136{
137    testBase.parseOption(argc, argv);
138
139    // Creates a `slang-rhi` renderer, which provides the main interface for
140    // interacting with the graphics API.
141    Slang::ComPtr<IDevice> device;
142    DeviceDesc deviceDesc = {};
143    device = getRHI()->createDevice(deviceDesc);
144    if (!device)
145        return SLANG_FAIL;
146
147    // Now we can load the shader code.
148    // A `IShaderProgram` object for use in the `slang-rhi` layer.
149    ComPtr<IShaderProgram> shaderProgram;
150    // A composed `IComponentType` that gives us reflection info on the shader code.
151    slang::ProgramLayout* slangReflection;
152    SLANG_RETURN_ON_FAIL(loadShaderProgram(device, shaderProgram, slangReflection));
153
154    // Create a pipeline state with the loaded shader.
155    ComputePipelineDesc pipelineDesc = {};
156    pipelineDesc.program = shaderProgram.get();
157    ComPtr<IComputePipeline> pipelineState;
158    pipelineState = device->createComputePipeline(pipelineDesc);
159    if (!pipelineState)
160        return SLANG_FAIL;
161
162    // Create and initiate our input/output buffer.
163    const int numberCount = 4;
164    float initialData[] = {0.0f, 1.0f, 2.0f, 3.0f};
165    BufferDesc bufferDesc = {};
166    bufferDesc.size = numberCount * sizeof(float);
167    bufferDesc.format = Format::Undefined;
168    bufferDesc.elementSize = sizeof(float);
169    bufferDesc.usage = BufferUsage::ShaderResource | BufferUsage::UnorderedAccess |
170                       BufferUsage::CopyDestination | BufferUsage::CopySource;
171    bufferDesc.defaultState = ResourceState::UnorderedAccess;
172    bufferDesc.memoryType = MemoryType::DeviceLocal;
173
174    ComPtr<IBuffer> numbersBuffer;
175    numbersBuffer = device->createBuffer(bufferDesc, (void*)initialData);
176    if (!numbersBuffer)
177        return 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    {
182        auto queue = device->getQueue(QueueType::Graphics);
183
184        auto commandEncoder = queue->createCommandEncoder();
185        auto 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.
193        auto 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 =
199            slangReflection->findTypeByName("AddTransformer");
200
201        // Now we can use this type to create a shader object that can be bound to the root object.
202        ComPtr<IShaderObject> transformer;
203        transformer =
204            device->createShaderObject(addTransformerType, ShaderObjectContainerType::None);
205        if (!transformer)
206            return SLANG_FAIL;
207
208        // Set the `c` field of the `AddTransformer`.
209        float c = 1.0f;
210        ShaderCursor(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`.
217        ShaderCursor entryPointCursor(
218            rootObject->getEntryPoint(0)); // get a cursor the the first entry-point.
219        // Bind buffer to the entry point.
220        entryPointCursor.getPath("buffer").setBinding(numbersBuffer);
221
222        // Bind the previously created transformer object to root object.
223        entryPointCursor.getPath("transformer").setObject(transformer);
224
225        encoder->dispatchCompute(1, 1, 1);
226        encoder->end();
227        queue->submit(commandEncoder->finish());
228        queue->waitOnHost();
229    }
230    // Read back the results.
231    ComPtr<ISlangBlob> resultBlob;
232    SLANG_RETURN_ON_FAIL(
233        device->readBuffer(numbersBuffer, 0, numberCount * sizeof(float), resultBlob.writeRef()));
234    auto result = reinterpret_cast<const float*>(resultBlob->getBufferPointer());
235    for (int i = 0; i < numberCount; i++)
236        printf("%f\n", result[i]);
237
238    return SLANG_OK;
239}