summaryrefslogtreecommitdiffstats
path: root/examples/shader-toy/main.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'examples/shader-toy/main.cpp')
-rw-r--r--examples/shader-toy/main.cpp585
1 files changed, 585 insertions, 0 deletions
diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp
new file mode 100644
index 000000000..1efc3c745
--- /dev/null
+++ b/examples/shader-toy/main.cpp
@@ -0,0 +1,585 @@
+// main.cpp
+
+// This file provides the application code for the `shader-toy` example.
+//
+// Much of the logic here is identical to the simpler `hello-world` example,
+// so we will not spend time commenting those parts that are identical or
+// nearly identical. Readers who want detailed comments on a simpler example
+// using Slang should look there.
+
+// This example uses the Slang C/C++ API, alonmg with its optional type
+// for managing COM-style reference-counted pointers.
+//
+#include <slang.h>
+#include <slang-com-ptr.h>
+using Slang::ComPtr;
+
+// This example uses a graphics API abstraction layer that is implemented inside
+// the Slang codebase for use in our sample programs and test cases. Use of
+// this layer is *not* required or assumed when using the Slang language,
+// compiler, and API.
+//
+#include "gfx/render.h"
+#include "gfx/d3d11/render-d3d11.h"
+#include "gfx/window.h"
+using namespace gfx;
+
+// In order to display a shader toy effect using rasterization-based shader
+// execution we need to render a full-screen triangle. We will define a
+// small helper type that defines the data for such a triangle.
+//
+struct FullScreenTriangle
+{
+ struct Vertex
+ {
+ float position[2];
+ };
+
+ enum { kVertexCount = 3 };
+
+ static const Vertex kVertices[kVertexCount];
+};
+const FullScreenTriangle::Vertex FullScreenTriangle::kVertices[FullScreenTriangle::kVertexCount] =
+{
+ { { -1, -1 } },
+ { { -1, 3 } },
+ { { 3, -1 } },
+};
+
+// The application itself will be encapsulated in a C++ `struct` type
+// so that it can easily scope its state without use of global variables.
+//
+struct ShaderToyApp
+{
+
+// The uniform data used by the shader is defined here as a simple
+// POD ("plain old data") type.
+//
+// Note: This type must match the declaration of `ShaderToyUniforms`
+// in the file `shader-toy.slang`.
+//
+// An application could instead use a shared header file to define
+// this type, or use Slang's reflection capabilities to allocate
+// and set parameters at runtime. For this simple example we did
+// the expedient thing of having distinct Slang and C++ declarations.
+//
+struct Uniforms
+{
+ float iMouse[4];
+ float iResolution[2];
+ float iTime;
+};
+
+// Many Slang API functions return detailed diagnostic information
+// (error messages, warnings, etc.) as a "blob" of data, or return
+// a null blob pointer instead if there were no issues.
+//
+// For convenience, we define a subroutine that will dump the information
+// in a diagnostic blob if one is produced, and skip it otherwise.
+//
+void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob)
+{
+ if( diagnosticsBlob != nullptr )
+ {
+ reportError("%s", (const char*) diagnosticsBlob->getBufferPointer());
+ }
+}
+
+// The main interesting part of the host application code is where we
+// load, compile, inspect, and compose the Slang shader code.
+//
+Result loadShaderProgram(gfx::Renderer* renderer, RefPtr<gfx::ShaderProgram>& outShaderProgram)
+{
+ // The first step in interacting with the Slang API is to create a "global session,"
+ // which represents an instance of the Slang API loaded from the library.
+ //
+ ComPtr<slang::IGlobalSession> slangGlobalSession;
+ SLANG_RETURN_ON_FAIL(slang_createGlobalSession(SLANG_API_VERSION, slangGlobalSession.writeRef()));
+
+ // Next, we need to create a compilation session (`slang::ISession`) that will provide
+ // a scope to all the compilation and loading of code we do.
+ //
+ // In an application like this, which doesn't make use of preprocessor-based specialization,
+ // we can create a single session and use it for the duration of the application.
+ // One important service the session provides is re-use of modules that have already
+ // been compiled, so that if two Slang files `import` the same module, the compiler
+ // will only load and check that module once.
+ //
+ // When creating a session we need to tell it what code generation targets we may
+ // want code generated for. It is valid to have zero or more targets, but many
+ // applications will only want one, corresponding to the graphics API they plan to use.
+ // This application is currently hard-coded to use D3D11, so we set up for compilation
+ // to DX bytecode.
+ //
+ // Note: the `TargetDesc` can also be used to set things like optimization settings
+ // for each target, but this application doesn't care to set any of that stuff.
+ //
+ slang::TargetDesc targetDesc = {};
+ targetDesc.format = SLANG_DXBC;
+ targetDesc.profile = spFindProfile(slangGlobalSession, "sm_4_0");
+
+ // The session can be set up with a few other options, notably:
+ //
+ // * Any search paths that should be used when resolving `import` or `#include` directives.
+ //
+ // * Any preprocessor macros to pre-define when reading in files.
+ //
+ // This application doesn't plan to make heavy use of the preprocessor, and all its
+ // shader files are in the same directory, so we just use the default options (which
+ // will lead to the only search path being the current working directory).
+ //
+ slang::SessionDesc sessionDesc = {};
+ sessionDesc.targetCount = 1;
+ sessionDesc.targets = &targetDesc;
+
+ ComPtr<slang::ISession> slangSession;
+ SLANG_RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, slangSession.writeRef()));
+
+ // Once the session has been created, 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-toy` 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_toy;
+ //
+ ComPtr<slang::IBlob> diagnosticsBlob;
+ slang::IModule* module = slangSession->loadModule("shader-toy", diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ if(!module)
+ return SLANG_FAIL;
+
+ // Loading the `shader-toy` 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* vertexEntryPointName = "vertexMain";
+ char const* fragmentEntryPointName = "fragmentMain";
+ //
+ ComPtr<slang::IEntryPoint> vertexEntryPoint;
+ SLANG_RETURN_ON_FAIL(module->findEntryPointByName(vertexEntryPointName, vertexEntryPoint.writeRef()));
+ //
+ ComPtr<slang::IEntryPoint> fragmentEntryPoint;
+ SLANG_RETURN_ON_FAIL(module->findEntryPointByName(fragmentEntryPointName, fragmentEntryPoint.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.
+ //
+ List<slang::IComponentType*> componentTypes;
+ componentTypes.add(module);
+
+ // Later on when we go to extract compiled kernel code for our vertex
+ // and fragment shaders, we will need to make use of their order within
+ // the composition, so we will record the relative ordering of the entry
+ // points here as we add them.
+ int entryPointCount = 0;
+ int vertexEntryPointIndex = entryPointCount++;
+ componentTypes.add(vertexEntryPoint);
+
+ int fragmentEntryPointIndex = entryPointCount++;
+ componentTypes.add(fragmentEntryPoint);
+
+ // 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);
+
+ // At this point, `composedProgram` represents the shader program
+ // we want to run, and the vertex and fragment shader there have
+ // been checked.
+ //
+ // We could use the Slang reflection API on `composedProgram` at this
+ // point to query things like the locations and offsets of the
+ // various uniform parameters, textures, etc.
+ //
+ // What *cannot* be done yet at this point is actually generating
+ // kernel code, because `composedProgram` includes a generic type
+ // parameter as part of the `fragmentMain` entry point:
+ //
+ // void fragmentMain<T : IShaderToyImageShader>(...)
+ //
+ // Our next task is to load code for a type we'd like to plug in
+ // for `T` there.
+ //
+ // Because Slang supports modular programming, there is no requirement
+ // that a type we want to plug in for `T` has to come from the
+ // same module, and to demonstrate that we will load a different
+ // module to provide the effect type we will plug in.
+ //
+ const char* effectModuleName = "example-effect";
+ const char* effectTypeName = "ExampleEffect";
+ slang::IModule* effectModule = slangSession->loadModule(effectModuleName, diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ if(!module)
+ return SLANG_FAIL;
+
+ // Once we've loaded the code module that defines out effect type,
+ // we can look it up by name using the reflection information on
+ // the module.
+ //
+ // Note: A future version of the Slang API will support enumerating
+ // the types declared in a module so that we do not have to hard-code
+ // the name here.
+ //
+ auto effectType = effectModule->getLayout()->findTypeByName(effectTypeName);
+
+ // Now that we have the `effectType` we want to plug in to our generic
+ // shader, we need to specialize the shader to that type.
+ //
+ // Because a shader program could have zero or more specialization parameters,
+ // we need to build up an array of specialization arguments.
+ //
+ List<slang::SpecializationArg> specializationArgs;
+
+ {
+ // In our case, we only have a single specialization argument we plan
+ // to use, and it is a type argument.
+ //
+ slang::SpecializationArg effectTypeArg;
+ effectTypeArg.kind = slang::SpecializationArg::Kind::Type;
+ effectTypeArg.type = effectType;
+ specializationArgs.add(effectTypeArg);
+ }
+
+ // Specialization of a component type is a single Slang API call, but
+ // we need to deal with the possibility of diagnostic output on failure.
+ // For example, if we tried to specialize the shader program to a
+ // type like `int` that doesn't support the `IShaderToyImageShader` interface,
+ // this is the step where we'd get an error message saying so.
+ //
+ ComPtr<slang::IComponentType> specializedProgram;
+ result = composedProgram->specialize(
+ specializationArgs.getBuffer(),
+ specializationArgs.getCount(),
+ specializedProgram.writeRef(),
+ diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+
+ // At this point we have a specialized shader program that represents our
+ // intention to run the `vertexMain` and `fragmentMain` entry points,
+ // specialized to the `ExampleEffect` type we loaded.
+ //
+ // We can now *link* the program, which ensures that all of the code that
+ // it transitively depends on has been pulled together into a single
+ // component type.
+ //
+ ComPtr<slang::IComponentType> linkedProgram;
+ result = specializedProgram->link(
+ linkedProgram.writeRef(),
+ diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+
+ // Given a linked program (one with no unresolved external references,
+ // and no not-yet-set specialization parameters), we can request kernel
+ // code for the entry points of that program by their indices.
+ //
+ // Because Slang supports a session with multiple active code generation
+ // targets, we also need to specify the index of the target we want
+ // code for, but since we have only a single target in this application,
+ // there isn't actually a choice.
+ //
+ int targetIndex = 0;
+ //
+ // Note: it is possible to get diagnostic messages when generating kernel
+ // code, but this is not a common occurence. Most semantic errors in
+ // user code will be detected at earlier steps in this compilation flow,
+ // but there are certain errors that are currently caught during final
+ // code generation (e.g., when using a function that is specific to one
+ // target, and then requesting kernel code for another target).
+ //
+ ComPtr<ISlangBlob> vertexShaderBlob;
+ result = linkedProgram->getEntryPointCode(vertexEntryPointIndex, targetIndex, vertexShaderBlob.writeRef(), diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+ ComPtr<ISlangBlob> fragmentShaderBlob;
+ result = linkedProgram->getEntryPointCode(fragmentEntryPointIndex, targetIndex, fragmentShaderBlob.writeRef(), diagnosticsBlob.writeRef());
+ diagnoseIfNeeded(diagnosticsBlob);
+ SLANG_RETURN_ON_FAIL(result);
+
+ // Once kernel code has been extracted from Slang, the rest of the logic
+ // here is basically the same as in the `hello-world` example.
+
+ char const* vertexCode = (char const*) vertexShaderBlob->getBufferPointer();
+ char const* vertexCodeEnd = vertexCode + vertexShaderBlob->getBufferSize();
+
+ char const* fragmentCode = (char const*) fragmentShaderBlob->getBufferPointer();
+ char const* fragmentCodeEnd = fragmentCode + fragmentShaderBlob->getBufferSize();
+
+ gfx::ShaderProgram::KernelDesc kernelDescs[] =
+ {
+ { gfx::StageType::Vertex, vertexCode, vertexCodeEnd },
+ { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd },
+ };
+
+ gfx::ShaderProgram::Desc programDesc;
+ programDesc.pipelineType = gfx::PipelineType::Graphics;
+ programDesc.kernels = &kernelDescs[0];
+ programDesc.kernelCount = 2;
+
+ auto shaderProgram = renderer->createProgram(programDesc);
+
+ outShaderProgram = shaderProgram;
+ return SLANG_OK;
+}
+
+int gWindowWidth = 1024;
+int gWindowHeight = 768;
+
+gfx::ApplicationContext* gAppContext;
+gfx::Window* gWindow;
+RefPtr<gfx::Renderer> gRenderer;
+RefPtr<gfx::BufferResource> gConstantBuffer;
+
+RefPtr<gfx::PipelineLayout> gPipelineLayout;
+RefPtr<gfx::PipelineState> gPipelineState;
+RefPtr<gfx::DescriptorSet> gDescriptorSet;
+
+RefPtr<gfx::BufferResource> gVertexBuffer;
+
+Result initialize()
+{
+ WindowDesc windowDesc;
+ windowDesc.title = "Slang Shader Toy";
+ windowDesc.width = gWindowWidth;
+ windowDesc.height = gWindowHeight;
+ windowDesc.eventHandler = &_handleEvent;
+ windowDesc.userData = this;
+ gWindow = createWindow(windowDesc);
+
+ gRenderer = createD3D11Renderer();
+ Renderer::Desc rendererDesc;
+ rendererDesc.width = gWindowWidth;
+ rendererDesc.height = gWindowHeight;
+ {
+ Result res = gRenderer->initialize(rendererDesc, getPlatformWindowHandle(gWindow));
+ if(SLANG_FAILED(res)) return res;
+ }
+
+ int constantBufferSize = sizeof(Uniforms);
+
+ BufferResource::Desc constantBufferDesc;
+ constantBufferDesc.init(constantBufferSize);
+ constantBufferDesc.setDefaults(Resource::Usage::ConstantBuffer);
+ constantBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write;
+
+ gConstantBuffer = gRenderer->createBufferResource(
+ Resource::Usage::ConstantBuffer,
+ constantBufferDesc);
+ if(!gConstantBuffer) return SLANG_FAIL;
+
+ InputElementDesc inputElements[] = {
+ { "POSITION", 0, Format::RG_Float32, offsetof(FullScreenTriangle::Vertex, position) },
+ };
+ auto inputLayout = gRenderer->createInputLayout(
+ &inputElements[0],
+ SLANG_COUNT_OF(inputElements));
+ if(!inputLayout) return SLANG_FAIL;
+
+ BufferResource::Desc vertexBufferDesc;
+ vertexBufferDesc.init(FullScreenTriangle::kVertexCount * sizeof(FullScreenTriangle::Vertex));
+ vertexBufferDesc.setDefaults(Resource::Usage::VertexBuffer);
+ gVertexBuffer = gRenderer->createBufferResource(
+ Resource::Usage::VertexBuffer,
+ vertexBufferDesc,
+ &FullScreenTriangle::kVertices[0]);
+ if(!gVertexBuffer) return SLANG_FAIL;
+
+ RefPtr<ShaderProgram> shaderProgram;
+ SLANG_RETURN_ON_FAIL(loadShaderProgram(gRenderer, shaderProgram));
+
+ DescriptorSetLayout::SlotRangeDesc slotRanges[] =
+ {
+ DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer),
+ };
+ DescriptorSetLayout::Desc descriptorSetLayoutDesc;
+ descriptorSetLayoutDesc.slotRangeCount = 1;
+ descriptorSetLayoutDesc.slotRanges = &slotRanges[0];
+ auto descriptorSetLayout = gRenderer->createDescriptorSetLayout(descriptorSetLayoutDesc);
+ if(!descriptorSetLayout) return SLANG_FAIL;
+
+ PipelineLayout::DescriptorSetDesc descriptorSets[] =
+ {
+ PipelineLayout::DescriptorSetDesc( descriptorSetLayout ),
+ };
+ PipelineLayout::Desc pipelineLayoutDesc;
+ pipelineLayoutDesc.renderTargetCount = 1;
+ pipelineLayoutDesc.descriptorSetCount = 1;
+ pipelineLayoutDesc.descriptorSets = &descriptorSets[0];
+ auto pipelineLayout = gRenderer->createPipelineLayout(pipelineLayoutDesc);
+ if(!pipelineLayout) return SLANG_FAIL;
+
+ gPipelineLayout = pipelineLayout;
+
+ auto descriptorSet = gRenderer->createDescriptorSet(descriptorSetLayout);
+ if(!descriptorSet) return SLANG_FAIL;
+
+ descriptorSet->setConstantBuffer(0, 0, gConstantBuffer);
+
+ gDescriptorSet = descriptorSet;
+
+ GraphicsPipelineStateDesc desc;
+ desc.pipelineLayout = gPipelineLayout;
+ desc.inputLayout = inputLayout;
+ desc.program = shaderProgram;
+ desc.renderTargetCount = 1;
+ auto pipelineState = gRenderer->createGraphicsPipelineState(desc);
+ if(!pipelineState) return SLANG_FAIL;
+
+ gPipelineState = pipelineState;
+
+ showWindow(gWindow);
+
+ return SLANG_OK;
+}
+
+bool wasMouseDown = false;
+bool isMouseDown = false;
+float lastMouseX = 0.0f;
+float lastMouseY = 0.0f;
+float clickMouseX = 0.0f;
+float clickMouseY = 0.0f;
+
+bool firstTime = true;
+uint64_t startTime = 0;
+
+void renderFrame()
+{
+ if( firstTime )
+ {
+ startTime = getCurrentTime();
+ firstTime = false;
+ }
+
+ static const float kClearColor[] = { 0.25, 0.25, 0.25, 1.0 };
+ gRenderer->setClearColor(kClearColor);
+ gRenderer->clearFrame();
+
+ if(Uniforms* uniforms = (Uniforms*) gRenderer->map(gConstantBuffer, MapFlavor::WriteDiscard))
+ {
+ bool isMouseClick = isMouseDown && !wasMouseDown;
+ wasMouseDown = isMouseDown;
+
+ if( isMouseClick )
+ {
+ clickMouseX = lastMouseX;
+ clickMouseY = lastMouseY;
+ }
+
+ uniforms->iMouse[0] = lastMouseX;
+ uniforms->iMouse[1] = lastMouseY;
+ uniforms->iMouse[2] = isMouseDown ? clickMouseX : -clickMouseX;
+ uniforms->iMouse[3] = isMouseClick ? clickMouseY : -clickMouseY;
+ uniforms->iTime = float( double(getCurrentTime() - startTime) / double(getTimerFrequency()) );
+ uniforms->iResolution[0] = float(gWindowWidth);
+ uniforms->iResolution[1] = float(gWindowHeight);
+
+ gRenderer->unmap(gConstantBuffer);
+ }
+
+ gRenderer->setPipelineState(PipelineType::Graphics, gPipelineState);
+ gRenderer->setDescriptorSet(PipelineType::Graphics, gPipelineLayout, 0, gDescriptorSet);
+
+ gRenderer->setVertexBuffer(0, gVertexBuffer, sizeof(FullScreenTriangle::Vertex));
+ gRenderer->setPrimitiveTopology(PrimitiveTopology::TriangleList);
+
+ gRenderer->draw(3);
+
+ gRenderer->presentFrame();
+}
+
+void finalize()
+{
+}
+
+void handleEvent(Event const& event)
+{
+ switch( event.code )
+ {
+ case EventCode::MouseDown:
+ isMouseDown = true;
+ lastMouseX = event.u.mouse.x;
+ lastMouseY = event.u.mouse.y;
+ break;
+
+ case EventCode::MouseMoved:
+ lastMouseX = event.u.mouse.x;
+ lastMouseY = event.u.mouse.y;
+ break;
+
+ case EventCode::MouseUp:
+ isMouseDown = false;
+ lastMouseX = event.u.mouse.x;
+ lastMouseY = event.u.mouse.y;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void _handleEvent(Event const& event)
+{
+ ShaderToyApp* app = (ShaderToyApp*) getUserData(event.window);
+ app->handleEvent(event);
+}
+
+
+};
+
+void innerMain(ApplicationContext* context)
+{
+ ShaderToyApp app;
+
+ if (SLANG_FAILED(app.initialize()))
+ {
+ return exitApplication(context, 1);
+ }
+
+ while(dispatchEvents(context))
+ {
+ app.renderFrame();
+ }
+
+ app.finalize();
+}
+
+GFX_UI_MAIN(innerMain)