summaryrefslogtreecommitdiffstats
path: root/examples/shader-toy/main.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2020-12-07 09:29:37 -0800
committerGitHub <noreply@github.com>2020-12-07 09:29:37 -0800
commitd7ce74a3f7f941ffba5a9fa73b0d7d559897d6e7 (patch)
treeb3dd4145646ac08e001528a64b2e5e91a4fbdacc /examples/shader-toy/main.cpp
parente98c32f5eb1c6e880ed69a135e798b3c43f77d93 (diff)
"Shader Toy" example and related fixes (#1629)
* "Shader Toy" example and related fixes This change introduces a new `shader-toy` example program that is primarily designed to show how Slang's features for type-based encapsulation and modularity can be applied to modularity for effects along the lines of those from `shadertoy.com`. The Example ----------- The example is being checked in with an example "toy" effect that I hastily put together, so that it would not be encumbered with any IP concerns. I wrote the effect using the shadertoy.com editor, so I can be sure it is valid GLSL. During bringup of the application I used a pre-existing and larger effect for testing, so some of the support code that was added is not being used at present. The big-picture idea here is to have an exmaple that shows how to modularize things using Slang interfaces and generics, and then to use the Slang compiler API to manage the compilation, composition, specialization, and linking steps. For better or worse this leads to the sequence of API calls involved being much longer than what was in something like the `hello-world` example. Future Work (Example) --------------------- There is a lot of room for improvement and expansion here, so this should be viewed as a checkpoint of work in progress rather than something I'm claiming as a finalized demonstration of all we'd like to achieve. Areas for future work include: * We need to copy the integration of "Dear, IMGUI" that was already done for the `model-viewer` example so that this example can have a UI. * Now that the compilation flow is broken into all these additional steps, it should be possible to have the application load multiple effects as distinct modules, and then provide a UI for switching between them. The chosen effect module would be used to specialize the top-level shader(s) before kernel generation. * The checked-in logic includes a compute shader that can execute an effect, but that hasn't been tested nor has it been wired up to any kind of UI. We should have a way to switch between multiple execution methods, with a goal of eventually including CPU execution. * The "GLSL compatibility" code needs a lot of improvements before it is likely to be usable for a nontrivial number of shaders. Some of that work is waiting on Slang compiler fixes, though. * We should consider allowing the individual "toy" effects to define their own uniform parameters and expose those via a UI and reflection. The catch in this case is not that this would be difficult to do, but that it would be a semantic change to how shader toy effects currently work. The Compiler Fixes ------------------ Doing this work exposed a few bugs in Slang, and this change includes fixes for the ones that were quick to address. We already had logic in `slang-check-shader.cpp` that was validating the entry points in a compile request - either by checking the explicitly-listed entry points, or by scanning for `[shader("...")]` attributes. The problem is that the routine that did that checking was not being invoked on all compiles. The logic that handled entry points was only being run for manual compiles using `SlangCompileRequest`, while anything using `import` or `loadModule` would ignore entry points. I refactored the relevant code into a subroutine that will be invoked in all compilation scenarios. There were already `TODO` comments in `SpecializedComponentType` which made the point about how a specialized entry point like `myShader<YourType>` would need to properly show that it has dependencies on both the module that defines `myShader` *and* the module that defines `YourType`, while only the former was being handled at present. I went ahead and implemented the logic to scan the generic arguments for a specialized compoment type in order to determine what module(s) the arguments depend on (both type arguments and witness tables). With that change, using `IComponentType::link` on a specialized component will properly pull in the module(s) that the generic arguments come from. In `slang-ir-legalize-types.cpp` we could run into assertion failures in debug builds because of code trying to legalize layout `IRAttr`s for fields or parameters with types that need legalization. In practice it is safe to skip these layout attributes, because legalization of the fields/parameters they pertain to would result in creation of entirely new layout attributes, and the old ones would then be unreferenced. Future Work (Fixes) ------------------- There are other compiler bugs that this work exposed, but which this change does not address. These will need to be resolved as part of subsequent changes: * Slang allows for default-initialization of variables of a generic type. That is, given `<T : ISomething>` a user is allowed to declare `T x = {};` and the Slang front-end does not complain. Instead, this leads to an internal compiler error during IR lowering. * The Slang `__init()` feature probably needs to be upgraded to a properly supported feature, and we probably need a way to make implementing default-initialization an easy thing (e.g., any `struct` type that has initial-value expressions for all its fields should automatically and implicitly satsify an `init();` requirement declared in an interface) * Iniside an `__init()` definition, code has mutable access to members of the enclosing type, but for some reason the front-end is incorrectly treating `this` as immutable in those contexts. As a result you can write to `someField` but not `this.someField`. * User-defined operator overloads flat out don't work (which isn't surprising given that no clients have decided to use them yet, and we have no test coverage for them). This is actually due to the shadowing rules being used for lookup right now, so a fix for this issue is going to have far-reaching consequences around what overloads are visible where (and anything that impacts overload resolution is a big can of worms, including around performance). * fixup: test case had missing main function
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)