summaryrefslogtreecommitdiffstats
path: root/examples/shader-object/main.cpp
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2021-02-05 14:36:07 -0800
committerGitHub <noreply@github.com>2021-02-05 14:36:07 -0800
commitdf7548ef62c02b9ab1cc5addecaa6b6c150f2750 (patch)
tree17081a8d5de3fd3292043aae6761d0c8960e6783 /examples/shader-object/main.cpp
parent5fbaccfc1d4ac7d17d528de894d1f276e41d9ce1 (diff)
Shader-Object example (#1694)
Diffstat (limited to 'examples/shader-object/main.cpp')
-rw-r--r--examples/shader-object/main.cpp230
1 files changed, 230 insertions, 0 deletions
diff --git a/examples/shader-object/main.cpp b/examples/shader-object/main.cpp
new file mode 100644
index 000000000..8a5bc8373
--- /dev/null
+++ b/examples/shader-object/main.cpp
@@ -0,0 +1,230 @@
+// main.cpp
+
+// This file provides the application code for the `shader-object` example.
+//
+
+// This example uses the Slang gfx layer to target different APIs.
+// The goal is to demonstrate how the Shader Object model implemented in `gfx` layer
+// simplifies shader specialization and parameter binding when using `interface` typed
+// shader parameters.
+//
+#include <slang.h>
+#include <slang-com-ptr.h>
+using Slang::ComPtr;
+
+#include "gfx/render.h"
+#include "gfx-util/shader-cursor.h"
+#include "source/core/slang-basic.h"
+
+using namespace gfx;
+
+// Helper function for print out diagnostic messages output by Slang compiler.
+void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob)
+{
+ if (diagnosticsBlob != nullptr)
+ {
+ printf("%s", (const char*)diagnosticsBlob->getBufferPointer());
+ }
+}
+
+// Loads the shader code defined in `shader-object.slang` for use by the `gfx` layer.
+//
+Result loadShaderProgram(
+ gfx::IRenderer* renderer,
+ ComPtr<gfx::IShaderProgram>& outShaderProgram,
+ slang::ProgramLayout*& slangReflection)
+{
+ // We need to obatin a compilation session (`slang::ISession`) that will provide
+ // a scope to all the compilation and loading of code we do.
+ //
+ // Our example application uses the `gfx` graphics API abstraction layer, which already
+ // creates a Slang compilation session for us, so we just grab and use it here.
+ ComPtr<slang::ISession> slangSession;
+ SLANG_RETURN_ON_FAIL(renderer->getSlangSession(slangSession.writeRef()));
+
+ // Once the session has been obtained, we can start loading code into it.
+ //
+ // The simplest way to load code is by calling `loadModule` with the name of a Slang
+ // module. A call to `loadModule("MyStuff")` will behave more or less as if you
+ // wrote:
+ //
+ // import MyStuff;
+ //
+ // In a Slang shader file. The compiler will use its search paths to try to locate
+ // `MyModule.slang`, then compile and load that file. If a matching module had
+ // already been loaded previously, that would be used directly.
+ //
+ // Note: The only interesting wrinkle here is that our file is named `shader-object` with
+ // a hyphen in it, so the name is not directly usable as an identifier in Slang code.
+ // Instead, when trying to import this module in the context of Slang code, a user
+ // needs to replace the hyphens with underscores:
+ //
+ // import shader_object;
+ //
+ ComPtr<slang::IBlob> diagnosticsBlob;
+ slang::IModule* module = slangSession->loadModule("shader-object", diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ if(!module)
+ return SLANG_FAIL;
+
+ // Loading the `shader-object` module will compile and check all the shader code in it,
+ // including the shader entry points we want to use. Now that the module is loaded
+ // we can look up those entry points by name.
+ //
+ // Note: If you are using this `loadModule` approach to load your shader code it is
+ // important to tag your entry point functions with the `[shader("...")]` attribute
+ // (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there
+ // is no umambiguous way for the compiler to know which functions represent entry
+ // points when it parses your code via `loadModule()`.
+ //
+ char const* computeEntryPointName = "computeMain";
+ ComPtr<slang::IEntryPoint> computeEntryPoint;
+ SLANG_RETURN_ON_FAIL(
+ module->findEntryPointByName(computeEntryPointName, computeEntryPoint.writeRef()));
+
+ // At this point we have a few different Slang API objects that represent
+ // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`.
+ //
+ // A single Slang module could contain many different entry points (e.g.,
+ // four vertex entry points, three fragment entry points, and two compute
+ // shaders), and before we try to generate output code for our target API
+ // we need to identify which entry points we plan to use together.
+ //
+ // Modules and entry points are both examples of *component types* in the
+ // Slang API. The API also provides a way to build a *composite* out of
+ // other pieces, and that is what we are going to do with our module
+ // and entry points.
+ //
+ Slang::List<slang::IComponentType*> componentTypes;
+ componentTypes.add(module);
+ componentTypes.add(computeEntryPoint);
+
+ // Actually creating the composite component type is a single operation
+ // on the Slang session, but the operation could potentially fail if
+ // something about the composite was invalid (e.g., you are trying to
+ // combine multiple copies of the same module), so we need to deal
+ // with the possibility of diagnostic output.
+ //
+ ComPtr<slang::IComponentType> composedProgram;
+ SlangResult result = slangSession->createCompositeComponentType(
+ componentTypes.getBuffer(),
+ componentTypes.getCount(),
+ composedProgram.writeRef(),
+ diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+ slangReflection = composedProgram->getLayout();
+
+ // At this point, `composedProgram` represents the shader program
+ // we want to run, and the compute shader there have been checked.
+ // We can create a `gfx::IShaderProgram` object from `composedProgram`
+ // so it may be used by the graphics layer.
+ gfx::IShaderProgram::Desc programDesc = {};
+ programDesc.pipelineType = gfx::PipelineType::Compute;
+ programDesc.slangProgram = composedProgram.get();
+
+ auto shaderProgram = renderer->createProgram(programDesc);
+
+ outShaderProgram = shaderProgram;
+ return SLANG_OK;
+}
+
+// Main body of the example.
+int main()
+{
+ // Creates a `gfx` renderer, which provides the main interface for
+ // interacting with the graphics API.
+ Slang::ComPtr<gfx::IRenderer> renderer;
+ IRenderer::Desc rendererDesc = {};
+ rendererDesc.rendererType = RendererType::CUDA;
+ SLANG_RETURN_ON_FAIL(gfxCreateRenderer(&rendererDesc, nullptr, renderer.writeRef()));
+
+ // Now we can load the shader code.
+ // A `gfx::IShaderProgram` object for use in the `gfx` layer.
+ ComPtr<gfx::IShaderProgram> shaderProgram;
+ // A composed `IComponentType` that gives us reflection info on the shader code.
+ slang::ProgramLayout* slangReflection;
+ SLANG_RETURN_ON_FAIL(loadShaderProgram(renderer, shaderProgram, slangReflection));
+
+ // Create a pipelien state with the loaded shader.
+ gfx::ComputePipelineStateDesc pipelineDesc = {};
+ pipelineDesc.program = shaderProgram.get();
+ ComPtr<gfx::IPipelineState> pipelineState;
+ SLANG_RETURN_ON_FAIL(
+ renderer->createComputePipelineState(pipelineDesc, pipelineState.writeRef()));
+
+ // Create and initiate our input/output buffer.
+ const int numberCount = 4;
+ float initialData[] = {0.0f, 1.0f, 2.0f, 3.0f};
+ IBufferResource::Desc bufferDesc = {};
+ bufferDesc.sizeInBytes = numberCount * sizeof(float);
+ bufferDesc.format = gfx::Format::Unknown;
+ bufferDesc.elementSize = sizeof(float);
+ bufferDesc.bindFlags = gfx::IResource::BindFlag::NonPixelShaderResource |
+ gfx::IResource::BindFlag::UnorderedAccess;
+ bufferDesc.cpuAccessFlags = IResource::AccessFlag::Write | IResource::AccessFlag::Read;
+
+ ComPtr<gfx::IBufferResource> numbersBuffer;
+ SLANG_RETURN_ON_FAIL(renderer->createBufferResource(
+ gfx::IResource::Usage::UnorderedAccess,
+ bufferDesc,
+ (void*)initialData,
+ numbersBuffer.writeRef()));
+
+ // Create a resource view for the buffer.
+ ComPtr<gfx::IResourceView> bufferView;
+ gfx::IResourceView::Desc viewDesc = {};
+ viewDesc.type = gfx::IResourceView::Type::UnorderedAccess;
+ viewDesc.format = gfx::Format::Unknown;
+ SLANG_RETURN_ON_FAIL(renderer->createBufferView(numbersBuffer, viewDesc, bufferView.writeRef()));
+
+ // Now comes the interesting part: binding the shader parameter for the
+ // compute kernel that we about to launch. We would like to construct
+ // a shader object that represents a `f(x)=x+1` transformation and apply
+ // it to the numbers in `numbersBuffer`.
+ // To start, we create a root shader object that represents the root level
+ // scope of the shader parameters.
+ ComPtr<gfx::IShaderObject> rootObject;
+ SLANG_RETURN_ON_FAIL(renderer->createRootShaderObject(shaderProgram, rootObject.writeRef()));
+ // We can set parameters directly with `rootObject`, but that requires us to use
+ // the Slang reflection API to obtain the proper offsets into the root object for each parameter.
+ // We implemented these logic in the `ShaderCursor` helper class, which simplifies the user
+ // code to find shader parameters. Here we demonstrate how to set parameters with `ShaderCursor`.
+ gfx::ShaderCursor entryPointCursor(rootObject->getEntryPoint(0)); // get a cursor the the first entry-point.
+ // Bind buffer view to the entry point.
+ entryPointCursor.getPath("buffer").setResource(bufferView);
+
+ // Next, we create a shader object that represents the transformer we want to use.
+ // To do so, we first need to lookup for the `AddTransformer` type defined in the shader code.
+ ComPtr<gfx::IShaderObject> transformer;
+ ComPtr<gfx::IShaderObjectLayout> transformerObjectLayout;
+ slang::TypeLayoutReflection* addTransformerTypeLayout = slangReflection->getTypeLayout(
+ slangReflection->findTypeByName("AddTransformer"));
+
+ // Now we can use this type to create a shader object that can be bound to the root object.
+ SLANG_RETURN_ON_FAIL(renderer->createShaderObjectLayout(
+ addTransformerTypeLayout, transformerObjectLayout.writeRef()));
+ SLANG_RETURN_ON_FAIL(
+ renderer->createShaderObject(transformerObjectLayout, transformer.writeRef()));
+ // Set the `c` field of the `AddTransformer`.
+ float c = 1.0f;
+ gfx::ShaderCursor(transformer).getPath("c").setData(&c, sizeof(float));
+
+ // Now the transformer object is ready, we can bind it to root object.
+ entryPointCursor.getPath("transformer").setObject(transformer);
+
+ // We have set up all required parameters in entry-point object, now it is time
+ // to bind the pipeline and root object and launch the kernel.
+ renderer->setPipelineState(pipelineState);
+ SLANG_RETURN_ON_FAIL(renderer->bindRootShaderObject(gfx::PipelineType::Compute, rootObject));
+ renderer->dispatchCompute(1, 1, 1);
+
+ // Read back the results.
+ renderer->waitForGpu();
+ float* result = (float*)renderer->map(numbersBuffer, gfx::MapFlavor::HostRead);
+ for (int i = 0; i < numberCount; i++)
+ printf("%f\n", result[i]);
+ renderer->unmap(numbersBuffer);
+
+ return SLANG_OK;
+}