summaryrefslogtreecommitdiffstats
path: root/tools/render-test
diff options
context:
space:
mode:
authorTim Foley <tfoley@nvidia.com>2017-06-09 11:34:21 -0700
committerTim Foley <tfoley@nvidia.com>2017-06-09 13:44:59 -0700
commitfcf83dbf9effab3bd98bad2b83b2468b7eb05cfd (patch)
tree41047c94883b86ec085a81597391ce3ef557cd43 /tools/render-test
parent52e8d4b9a27ab0060f874c3a63ab531847be35c0 (diff)
Initial import of code.
Diffstat (limited to 'tools/render-test')
-rw-r--r--tools/render-test/README.md4
-rw-r--r--tools/render-test/main.cpp369
-rw-r--r--tools/render-test/options.cpp95
-rw-r--r--tools/render-test/options.h41
-rw-r--r--tools/render-test/render-d3d11.cpp900
-rw-r--r--tools/render-test/render-d3d11.h10
-rw-r--r--tools/render-test/render-gl.cpp242
-rw-r--r--tools/render-test/render-gl.h10
-rw-r--r--tools/render-test/render-test.vcxproj170
-rw-r--r--tools/render-test/render-test.vcxproj.filters54
-rw-r--r--tools/render-test/render.h118
-rw-r--r--tools/render-test/slang-support.cpp84
-rw-r--r--tools/render-test/slang-support.h12
-rw-r--r--tools/render-test/window.h10
14 files changed, 2119 insertions, 0 deletions
diff --git a/tools/render-test/README.md b/tools/render-test/README.md
new file mode 100644
index 000000000..6d0d9111f
--- /dev/null
+++ b/tools/render-test/README.md
@@ -0,0 +1,4 @@
+Render Test
+===========
+
+This is a simple tool for running end-to-end tests that render with Spire, so that we can validate that it generates correct code.
diff --git a/tools/render-test/main.cpp b/tools/render-test/main.cpp
new file mode 100644
index 000000000..e444a8387
--- /dev/null
+++ b/tools/render-test/main.cpp
@@ -0,0 +1,369 @@
+// main.cpp
+
+#include "options.h"
+#include "render.h"
+#include "render-d3d11.h"
+#include "render-gl.h"
+#include "slang-support.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+#undef WIN32_LEAN_AND_MEAN
+#undef NOMINMAX
+
+namespace renderer_test {
+
+//
+
+
+int gWindowWidth = 1024;
+int gWindowHeight = 768;
+
+//
+// For the purposes of a small example, we will define the vertex data for a
+// single triangle directly in the source file. It should be easy to extend
+// this example to load data from an external source, if desired.
+//
+
+struct Vertex
+{
+ float position[3];
+ float color[3];
+};
+
+static const int kVertexCount = 3;
+static const Vertex kVertexData[kVertexCount] = {
+ { { 0, 0, 0.5 }, {1, 0, 0} },
+ { { 0, 1, 0.5 }, {0, 0, 1} },
+ { { 1, 0, 0.5 }, {0, 1, 0} },
+};
+
+
+// Global variables for state to be used for rendering...
+
+uintptr_t gConstantBufferSize;
+
+Buffer* gConstantBuffer;
+InputLayout* gInputLayout;
+Buffer* gVertexBuffer;
+ShaderProgram* gShaderProgram;
+
+// Entry point name to use for vertex/fragment shader
+static char const* vertexEntryPointName = "vertexMain";
+static char const* fragmentEntryPointName = "fragmentMain";
+
+// "Profile" to use when compiling for HLSL targets
+// TODO: does this belong here?
+static char const* vertexProfileName = "vs_4_0";
+static char const* fragmentProfileName = "ps_4_0";
+
+Error initializeShaders(
+ ShaderCompiler* shaderCompiler)
+{
+ // Read in the source code
+ char const* sourcePath = gOptions.sourcePath;
+ FILE* sourceFile = fopen(sourcePath, "rb");
+ if( !sourceFile )
+ {
+ fprintf(stderr, "error: failed to open '%s' for reading\n", sourcePath);
+ exit(1);
+ }
+ fseek(sourceFile, 0, SEEK_END);
+ size_t sourceSize = ftell(sourceFile);
+ fseek(sourceFile, 0, SEEK_SET);
+ char* sourceText = (char*) malloc(sourceSize + 1);
+ if( !sourceText )
+ {
+ fprintf(stderr, "error: out of memory");
+ exit(1);
+ }
+ fread(sourceText, sourceSize, 1, sourceFile);
+ fclose(sourceFile);
+ sourceText[sourceSize] = 0;
+
+ ShaderCompileRequest::SourceInfo sourceInfo;
+ sourceInfo.path = sourcePath;
+ sourceInfo.text = sourceText;
+
+ ShaderCompileRequest compileRequest;
+ compileRequest.source = sourceInfo;
+ compileRequest.vertexShader.source = sourceInfo;
+ compileRequest.vertexShader.name = vertexEntryPointName;
+ compileRequest.vertexShader.profile = vertexProfileName;
+ compileRequest.fragmentShader.source = sourceInfo;
+ compileRequest.fragmentShader.name = fragmentEntryPointName;
+ compileRequest.fragmentShader.profile = fragmentProfileName;
+
+ gShaderProgram = shaderCompiler->compileProgram(compileRequest);
+ if( !gShaderProgram )
+ {
+ return Error::Unexpected;
+ }
+
+ return Error::None;
+}
+
+//
+// At initialization time, we are going to load and compile our Slang shader
+// code, and then create the D3D11 API objects we need for rendering.
+//
+Error initializeInner(
+ Renderer* renderer,
+ ShaderCompiler* shaderCompiler)
+{
+ Error err = Error::None;
+
+ err = initializeShaders(shaderCompiler);
+ if(err != Error::None) return err;
+
+
+ // Do other initialization that doesn't depend on the source language.
+
+ // TODO(tfoley): use each API's reflection interface to query the constant-buffer size needed
+ gConstantBufferSize = 16 * sizeof(float);
+
+ BufferDesc constantBufferDesc;
+ constantBufferDesc.size = gConstantBufferSize;
+ constantBufferDesc.flavor = BufferFlavor::Constant;
+
+ gConstantBuffer = renderer->createBuffer(constantBufferDesc);
+ if(!gConstantBuffer)
+ return Error::Unexpected;
+
+ // Input Assembler (IA)
+
+ InputElementDesc inputElements[] = {
+ { "A", 0, Format::RGB_Float32, offsetof(Vertex, position) },
+ { "A", 1, Format::RGB_Float32, offsetof(Vertex, color) },
+ };
+
+ gInputLayout = renderer->createInputLayout(&inputElements[0], 2);
+ if(!gInputLayout)
+ return Error::Unexpected;
+
+ BufferDesc vertexBufferDesc;
+ vertexBufferDesc.size = kVertexCount * sizeof(Vertex);
+ vertexBufferDesc.flavor = BufferFlavor::Vertex;
+ vertexBufferDesc.initData = &kVertexData[0];
+
+ gVertexBuffer = renderer->createBuffer(vertexBufferDesc);
+ if(!gVertexBuffer)
+ return Error::Unexpected;
+
+ return Error::None;
+}
+
+void renderFrameInner(
+ Renderer* renderer)
+{
+ auto mappedData = renderer->map(gConstantBuffer, MapFlavor::WriteDiscard);
+ if(mappedData)
+ {
+ static const float kIdentity[] =
+ { 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1 };
+ memcpy(mappedData, kIdentity, sizeof(kIdentity));
+
+ renderer->unmap(gConstantBuffer);
+ }
+
+ // Input Assembler (IA)
+
+ renderer->setInputLayout(gInputLayout);
+ renderer->setPrimitiveTopology(PrimitiveTopology::TriangleList);
+
+ renderer->setVertexBuffer(0, gVertexBuffer, sizeof(Vertex));
+
+ // Vertex Shader (VS)
+ // Pixel Shader (PS)
+
+ renderer->setShaderProgram(gShaderProgram);
+ renderer->setConstantBuffer(0, gConstantBuffer);
+
+ //
+
+ renderer->draw(3);
+}
+
+void finalize()
+{
+}
+
+
+
+//
+// We use a bare-minimum window procedure to get things up and running.
+//
+
+static LRESULT CALLBACK windowProc(
+ HWND windowHandle,
+ UINT message,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ switch (message)
+ {
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ }
+
+ return DefWindowProcW(windowHandle, message, wParam, lParam);
+}
+
+
+} // renderer_test
+
+
+//
+
+int main(
+ int argc,
+ char** argv)
+{
+ using namespace renderer_test;
+
+ // Parse command-line options
+ parseOptions(&argc, argv);
+
+
+ // Do initial window-creation stuff here, rather than in the renderer-specific files
+
+ HINSTANCE instance = GetModuleHandleA(0);
+ int showCommand = SW_SHOW;
+
+ // First we register a window class.
+
+ WNDCLASSEXW windowClassDesc;
+ windowClassDesc.cbSize = sizeof(windowClassDesc);
+ windowClassDesc.style = CS_OWNDC | CS_HREDRAW | CS_VREDRAW;
+ windowClassDesc.lpfnWndProc = &windowProc;
+ windowClassDesc.cbClsExtra = 0;
+ windowClassDesc.cbWndExtra = 0;
+ windowClassDesc.hInstance = instance;
+ windowClassDesc.hIcon = 0;
+ windowClassDesc.hCursor = 0;
+ windowClassDesc.hbrBackground = 0;
+ windowClassDesc.lpszMenuName = 0;
+ windowClassDesc.lpszClassName = L"HelloWorld";
+ windowClassDesc.hIconSm = 0;
+ ATOM windowClassAtom = RegisterClassExW(&windowClassDesc);
+ if(!windowClassAtom)
+ {
+ fprintf(stderr, "error: failed to register window class\n");
+ return 1;
+ }
+
+ // Next, we create a window using that window class.
+
+ DWORD windowExtendedStyle = 0;
+ DWORD windowStyle = 0;
+ LPWSTR windowName = L"Slang Hello World";
+ HWND windowHandle = CreateWindowExW(
+ windowExtendedStyle,
+ (LPWSTR)windowClassAtom,
+ windowName,
+ windowStyle,
+ 0, 0, // x, y
+ gWindowWidth, gWindowHeight,
+ NULL, // parent
+ NULL, // menu
+ instance,
+ NULL);
+ if(!windowHandle)
+ {
+ fprintf(stderr, "error: failed to create window\n");
+ return 1;
+ }
+
+
+ Renderer* renderer = nullptr;
+ switch( gOptions.mode )
+ {
+ case Mode::Slang:
+ case Mode::HLSL:
+ renderer = createD3D11Renderer();
+ break;
+
+ case Mode::GLSLCrossCompile:
+ renderer = createGLRenderer();
+ break;
+
+ default:
+ fprintf(stderr, "error: unexpected\n");
+ exit(1);
+ break;
+ }
+
+ renderer->initialize(windowHandle);
+
+ auto shaderCompiler = renderer->getShaderCompiler();
+ switch( gOptions.mode )
+ {
+ case Mode::Slang:
+ shaderCompiler = createSlangShaderCompiler(shaderCompiler, SLANG_HLSL);
+ break;
+
+ case Mode::GLSLCrossCompile:
+ shaderCompiler = createSlangShaderCompiler(shaderCompiler, SLANG_GLSL);
+ break;
+
+ default:
+ break;
+ }
+
+ Error err = initializeInner(renderer, shaderCompiler);
+ if( err != Error::None )
+ {
+ exit(1);
+ }
+
+
+ // Once initialization is all complete, we show the window...
+ ShowWindow(windowHandle, showCommand);
+
+ // ... and enter the event loop:
+ for(;;)
+ {
+ MSG message;
+
+ int result = PeekMessageW(&message, NULL, 0, 0, PM_REMOVE);
+ if (result != 0)
+ {
+ if (message.message == WM_QUIT)
+ {
+ return (int)message.wParam;
+ }
+
+ TranslateMessage(&message);
+ DispatchMessageW(&message);
+ }
+ else
+ {
+ // Whenver we don't have Windows events to process,
+ // we render a frame.
+
+ renderer->clearFrame();
+
+ renderFrameInner(renderer);
+
+ // If we are in a mode where output is requested, we need to snapshot the back buffer here
+ if( gOptions.outputPath )
+ {
+ renderer->captureScreenShot(gOptions.outputPath);
+ return 0;
+ }
+
+ renderer->presentFrame();
+ }
+ }
+
+ return 0;
+}
+
diff --git a/tools/render-test/options.cpp b/tools/render-test/options.cpp
new file mode 100644
index 000000000..9cfbb81fb
--- /dev/null
+++ b/tools/render-test/options.cpp
@@ -0,0 +1,95 @@
+// options.cpp
+
+#include "options.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace renderer_test {
+
+Options gOptions;
+
+void parseOptions(int* argc, char** argv)
+{
+ int argCount = *argc;
+ char const* const* argCursor = argv;
+ char const* const* argEnd = argCursor + argCount;
+
+ char const** writeCursor = (char const**) argv;
+
+ // first argument is the application name
+ if( argCursor != argEnd )
+ {
+ gOptions.appName = *argCursor++;
+ }
+
+ // now iterate over arguments to collect options
+ while(argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ if( arg[0] != '-' )
+ {
+ *writeCursor++ = arg;
+ continue;
+ }
+
+ if( strcmp(arg, "--") == 0 )
+ {
+ while(argCursor != argEnd)
+ {
+ char const* arg = *argCursor++;
+ *writeCursor++ = arg;
+ }
+ break;
+ }
+ else if( strcmp(arg, "-o") == 0 )
+ {
+ if( argCursor == argEnd )
+ {
+ fprintf(stderr, "expected argument for '%s' option\n", arg);
+ exit(1);
+ }
+ gOptions.outputPath = *argCursor++;
+ }
+ else if( strcmp(arg, "-hlsl") == 0 )
+ {
+ gOptions.mode = Mode::HLSL;
+ }
+ else if( strcmp(arg, "-slang") == 0 )
+ {
+ gOptions.mode = Mode::Slang;
+ }
+ else if( strcmp(arg, "-glsl-cross") == 0 )
+ {
+ gOptions.mode = Mode::GLSLCrossCompile;
+ }
+ else
+ {
+ fprintf(stderr, "unknown option '%s'\n", arg);
+ exit(1);
+ }
+ }
+
+ // any arguments left over were positional arguments
+ argCount = (int)(writeCursor - argv);
+ argCursor = argv;
+ argEnd = argCursor + argCount;
+
+ // first positional argument is source shader path
+ if( argCursor != argEnd )
+ {
+ gOptions.sourcePath = *argCursor++;
+ }
+
+ // any remaining arguments represent an error
+ if(argCursor != argEnd)
+ {
+ fprintf(stderr, "unexpected arguments\n");
+ exit(1);
+ }
+
+ *argc = 0;
+}
+
+} // renderer_test
diff --git a/tools/render-test/options.h b/tools/render-test/options.h
new file mode 100644
index 000000000..ffb07bc93
--- /dev/null
+++ b/tools/render-test/options.h
@@ -0,0 +1,41 @@
+// options.h
+#pragma once
+
+#include <stdint.h>
+
+namespace renderer_test {
+
+typedef intptr_t Int;
+typedef uintptr_t UInt;
+
+enum class Mode
+{
+ Slang,
+ HLSL,
+ GLSLCrossCompile,
+};
+
+struct Options
+{
+ char const* appName = "render-test";
+ char const* sourcePath = nullptr;
+ char const* outputPath = nullptr;
+ Mode mode = Mode::Slang;
+};
+
+extern Options gOptions;
+
+extern int gWindowWidth;
+extern int gWindowHeight;
+
+
+void parseOptions(int* argc, char** argv);
+
+enum class Error
+{
+ None = 0,
+ InvalidParam,
+ Unexpected,
+};
+
+} // renderer_test
diff --git a/tools/render-test/render-d3d11.cpp b/tools/render-test/render-d3d11.cpp
new file mode 100644
index 000000000..ab981bd45
--- /dev/null
+++ b/tools/render-test/render-d3d11.cpp
@@ -0,0 +1,900 @@
+// render-d3d11.cpp
+#include "render-d3d11.h"
+
+#include "options.h"
+#include "render.h"
+
+// In order to use the Slang API, we need to include its header
+
+#include <Slang.h>
+
+#define STB_IMAGE_WRITE_IMPLEMENTATION
+#include "external/stb/stb_image_write.h"
+
+// We will be rendering with Direct3D 11, so we need to include
+// the Windows and D3D11 headers
+
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+#undef WIN32_LEAN_AND_MEAN
+#undef NOMINMAX
+
+#include <d3d11_2.h>
+#include <d3dcompiler.h>
+
+// We will use the C standard library just for printing error messages.
+#include <stdio.h>
+
+#ifdef _MSC_VER
+#include <stddef.h>
+#if (_MSC_VER < 1900)
+#define snprintf sprintf_s
+#endif
+#endif
+//
+
+namespace renderer_test {
+
+//
+
+
+//
+
+// Global variabels for the various D3D11 API objects to be used for rendering
+ID3D11Buffer* dxConstantBuffer;
+ID3D11InputLayout* dxInputLayout;
+ID3D11Buffer* dxVertexBuffer;
+ID3D11VertexShader* dxVertexShader;
+ID3D11PixelShader* dxPixelShader;
+
+// The Slang compiler currently generates HLSL source, so we'll need a utility
+// routine (defined later) to translate that into D3D11 shader bytecode.
+ID3DBlob* compileHLSLShader(
+ char const* sourcePath,
+ char const* source,
+ char const* entryPointName,
+ char const* dxProfileName);
+
+static char const* vertexEntryPointName = "vertexMain";
+static char const* fragmentEntryPointName = "fragmentMain";
+
+static char const* vertexProfileName = "vs_4_0";
+static char const* fragmentProfileName = "ps_4_0";
+
+ID3DBlob* gVertexShaderBlob;
+ID3DBlob* gPixelShaderBlob;
+
+// Initialization when using HLSL for shaders
+HRESULT initializeHLSLInner(ID3D11Device* dxDevice, char const* sourcePath, char const* sourceText)
+{
+ // Compile the generated HLSL code
+ gVertexShaderBlob = compileHLSLShader(sourcePath, sourceText, vertexEntryPointName, vertexProfileName);
+ if(!gVertexShaderBlob) return E_FAIL;
+
+ gPixelShaderBlob = compileHLSLShader(sourcePath, sourceText, fragmentEntryPointName, fragmentProfileName);
+ if(!gPixelShaderBlob) return E_FAIL;
+
+
+ return S_OK;
+}
+
+// Initialization when using HLSL for shaders
+HRESULT initializeHLSL(ID3D11Device* dxDevice, char const* sourceText)
+{
+ HRESULT hr = initializeHLSLInner(dxDevice, gOptions.sourcePath, sourceText);
+ if(FAILED(hr))
+ return hr;
+
+ // TODO: any reflection stuff to do here?
+
+ return S_OK;
+}
+
+// Initialization when using Slang for shaders
+HRESULT initializeSlang(ID3D11Device* dxDevice, char const* sourceText)
+{
+ //
+ // First, we will load and compile our Slang source code.
+ //
+
+ // The argument here is an optional directory where the Slang compiler
+ // can cache files to speed up compilation of many kernels.
+ SlangSession* slangSession = spCreateSession(NULL);
+
+ // A compile request represents a single invocation of the compiler,
+ // to process some inputs and produce outputs (or errors).
+ SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession);
+
+ // Instruct Slang to generate code as HLSL
+ spSetCodeGenTarget(slangRequest, SLANG_HLSL);
+
+ int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr);
+
+ spAddTranslationUnitSourceString(slangRequest, translationUnitIndex, gOptions.sourcePath, sourceText);
+
+ spAddTranslationUnitEntryPoint(slangRequest, translationUnitIndex, vertexEntryPointName, spFindProfile(slangSession, vertexProfileName));
+ spAddTranslationUnitEntryPoint(slangRequest, translationUnitIndex, fragmentEntryPointName, spFindProfile(slangSession, fragmentProfileName));
+
+ int compileErr = spCompile(slangRequest);
+ if(auto diagnostics = spGetDiagnosticOutput(slangRequest))
+ {
+ OutputDebugStringA(diagnostics);
+ fprintf(stderr, "%s", diagnostics);
+ }
+ if(compileErr)
+ {
+ return E_FAIL;
+ }
+
+ char const* translatedCode = spGetTranslationUnitSource(slangRequest, translationUnitIndex);
+
+ // Compile the generated HLSL code
+ HRESULT hr = initializeHLSLInner(dxDevice, "slangGeneratedCode", translatedCode);
+ if(FAILED(hr))
+ return hr;
+
+ // We clean up the Slang compilation context and result *after*
+ // we have done the HLSL-to-bytecode compilation, because Slang
+ // owns the memory allocation for the generated HLSL, and will
+ // free it when we destroy the compilation result.
+ spDestroyCompileRequest(slangRequest);
+ spDestroySession(slangSession);
+
+ return S_OK;
+}
+
+#if 0
+
+//
+// At initialization time, we are going to load and compile our Slang shader
+// code, and then create the D3D11 API objects we need for rendering.
+//
+HRESULT initializeInner( ID3D11Device* dxDevice )
+{
+ HRESULT hr = S_OK;
+
+ // Read in the source code
+ char const* sourcePath = gOptions.sourcePath;
+ FILE* sourceFile = fopen(sourcePath, "rb");
+ if( !sourceFile )
+ {
+ fprintf(stderr, "error: failed to open '%s' for reading\n", sourcePath);
+ exit(1);
+ }
+ fseek(sourceFile, 0, SEEK_END);
+ size_t sourceSize = ftell(sourceFile);
+ fseek(sourceFile, 0, SEEK_SET);
+ char* sourceText = (char*) malloc(sourceSize + 1);
+ if( !sourceText )
+ {
+ fprintf(stderr, "error: out of memory");
+ exit(1);
+ }
+ fread(sourceText, sourceSize, 1, sourceFile);
+ fclose(sourceFile);
+ sourceText[sourceSize] = 0;
+
+ switch( gOptions.mode )
+ {
+ case Mode::HLSL:
+ hr = initializeHLSL(dxDevice, sourceText);
+ break;
+
+ case Mode::Slang:
+ hr = initializeSlang(dxDevice, sourceText);
+ break;
+
+ default:
+ hr = E_FAIL;
+ break;
+ }
+ if( FAILED(hr) )
+ {
+ return hr;
+ }
+
+ // Do other initialization that doesn't depend on the source language.
+
+ // TODO(tfoley): use each API's reflection interface to query the constant-buffer size needed
+ gConstantBufferSize = 16 * sizeof(float);
+
+
+ D3D11_BUFFER_DESC dxConstantBufferDesc = { 0 };
+ dxConstantBufferDesc.ByteWidth = gConstantBufferSize;
+ dxConstantBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
+ dxConstantBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ dxConstantBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+
+ hr = dxDevice->CreateBuffer(
+ &dxConstantBufferDesc,
+ NULL,
+ &dxConstantBuffer);
+ if(FAILED(hr)) return hr;
+
+
+ // Input Assembler (IA)
+
+ // In Slang-generated HLSL, all vertex shader inputs have a semantic
+ // like: `A0`, `A1`, `A2`, etc., rather than trying to do by-name
+ // matching. The user is thus responsibile for ensuring that the
+ // order of their "input element descs" here matches the order
+ // in which inputs are declared in the shader code.
+ D3D11_INPUT_ELEMENT_DESC dxInputElements[] = {
+ {"A", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, position), D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ {"A", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, offsetof(Vertex, color), D3D11_INPUT_PER_VERTEX_DATA, 0 },
+ };
+ hr = dxDevice->CreateInputLayout(
+ &dxInputElements[0],
+ 2,
+ gVertexShaderBlob->GetBufferPointer(),
+ gVertexShaderBlob->GetBufferSize(),
+ &dxInputLayout);
+ if(FAILED(hr)) return hr;
+
+ D3D11_BUFFER_DESC dxVertexBufferDesc = { 0 };
+ dxVertexBufferDesc.ByteWidth = kVertexCount * sizeof(Vertex);
+ dxVertexBufferDesc.Usage = D3D11_USAGE_IMMUTABLE;
+ dxVertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
+
+ D3D11_SUBRESOURCE_DATA dxVertexBufferInitData = { 0 };
+ dxVertexBufferInitData.pSysMem = &kVertexData[0];
+
+ hr = dxDevice->CreateBuffer(
+ &dxVertexBufferDesc,
+ &dxVertexBufferInitData,
+ &dxVertexBuffer);
+ if(FAILED(hr)) return hr;
+
+ // Vertex Shader (VS)
+
+ hr = dxDevice->CreateVertexShader(
+ gVertexShaderBlob->GetBufferPointer(),
+ gVertexShaderBlob->GetBufferSize(),
+ NULL,
+ &dxVertexShader);
+ gVertexShaderBlob->Release();
+ if(FAILED(hr)) return hr;
+
+ // Pixel Shader (PS)
+
+ hr = dxDevice->CreatePixelShader(
+ gPixelShaderBlob->GetBufferPointer(),
+ gPixelShaderBlob->GetBufferSize(),
+ NULL,
+ &dxPixelShader);
+ gPixelShaderBlob->Release();
+ if(FAILED(hr)) return hr;
+
+ return S_OK;
+}
+
+void renderFrameInner(ID3D11DeviceContext* dxContext)
+{
+ // We update our constant buffer per-frame, just for the purposes
+ // of the example, but we don't actually load different data
+ // per-frame (we always use an identity projection).
+ D3D11_MAPPED_SUBRESOURCE mapped;
+ HRESULT hr = dxContext->Map(dxConstantBuffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped);
+ if(!FAILED(hr))
+ {
+ float* data = (float*) mapped.pData;
+
+ static const float kIdentity[] =
+ { 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1 };
+ memcpy(data, kIdentity, sizeof(kIdentity));
+
+ dxContext->Unmap(dxConstantBuffer, 0);
+ }
+
+ // Input Assembler (IA)
+
+ dxContext->IASetInputLayout(dxInputLayout);
+ dxContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
+
+ UINT dxVertexStride = sizeof(Vertex);
+ UINT dxVertexBufferOffset = 0;
+ dxContext->IASetVertexBuffers(0, 1, &dxVertexBuffer, &dxVertexStride, &dxVertexBufferOffset);
+
+ // Vertex Shader (VS)
+
+ dxContext->VSSetShader(dxVertexShader, NULL, 0);
+ dxContext->VSSetConstantBuffers(0, 1, &dxConstantBuffer);
+
+ // Pixel Shader (PS)
+
+ dxContext->PSSetShader(dxPixelShader, NULL, 0);
+ dxContext->VSSetConstantBuffers(0, 1, &dxConstantBuffer);
+
+ //
+
+ dxContext->Draw(3, 0);
+}
+
+void finalize()
+{
+}
+
+#endif
+
+//
+// Definition of the HLSL-to-bytecode compilation logic.
+//
+ID3DBlob* compileHLSLShader(
+ char const* sourcePath,
+ char const* source,
+ char const* entryPointName,
+ char const* dxProfileName )
+{
+ // Rather than statically link against the `d3dcompile` library, we
+ // dynamically load it.
+ //
+ // Note: A more realistic application would compile from HLSL text to D3D
+ // shader bytecode as part of an offline process, rather than doing it
+ // on-the-fly like this
+ //
+ static pD3DCompile D3DCompile_ = nullptr;
+ if( !D3DCompile_ )
+ {
+ // TODO(tfoley): maybe want to search for one of a few versions of the DLL
+ HMODULE d3dcompiler = LoadLibraryA("d3dcompiler_47.dll");
+ if(!d3dcompiler)
+ {
+ fprintf(stderr, "error: failed load 'd3dcompiler_47.dll'\n");
+ exit(1);
+ }
+
+ D3DCompile_ = (pD3DCompile)GetProcAddress(d3dcompiler, "D3DCompile");
+ if( !D3DCompile_ )
+ {
+ fprintf(stderr, "error: failed load symbol 'D3DCompile'\n");
+ exit(1);
+ }
+ }
+
+ // For this example, we turn on debug output, and turn off all
+ // optimization. A real application would only use these flags
+ // when shader debugging is needed.
+ UINT flags = 0;
+ flags |= D3DCOMPILE_DEBUG;
+ flags |= D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_SKIP_OPTIMIZATION;
+
+ // The `D3DCompile` entry point takes a bunch of parameters, but we
+ // don't really need most of them for Slang-generated code.
+ ID3DBlob* dxShaderBlob = nullptr;
+ ID3DBlob* dxErrorBlob = nullptr;
+ HRESULT hr = D3DCompile_(
+ source,
+ strlen(source),
+ sourcePath,
+ nullptr,
+ nullptr,
+ entryPointName,
+ dxProfileName,
+ flags,
+ 0,
+ &dxShaderBlob,
+ &dxErrorBlob);
+
+ // If the HLSL-to-bytecode compilation produced any diagnostic messages
+ // then we will print them out (whether or not the compilation failed).
+ if( dxErrorBlob )
+ {
+ OutputDebugStringA(
+ (char const*)dxErrorBlob->GetBufferPointer());
+ dxErrorBlob->Release();
+ }
+
+ if( FAILED(hr) )
+ {
+ return nullptr;
+ }
+
+ return dxShaderBlob;
+}
+
+
+
+
+// Capture a texture to a file
+
+static HRESULT captureTextureToFile(
+ ID3D11Device* dxDevice,
+ ID3D11DeviceContext* dxContext,
+ ID3D11Texture2D* dxTexture,
+ char const* outputPath)
+{
+ if(!dxContext) return E_INVALIDARG;
+ if(!dxTexture) return E_INVALIDARG;
+
+ D3D11_TEXTURE2D_DESC dxTextureDesc;
+ dxTexture->GetDesc(&dxTextureDesc);
+
+ // Don't bother supporing MSAA for right now
+ if(dxTextureDesc.SampleDesc.Count > 1)
+ return E_INVALIDARG;
+
+ HRESULT hr = S_OK;
+ ID3D11Texture2D* dxStagingTexture = nullptr;
+
+ if( dxTextureDesc.Usage == D3D11_USAGE_STAGING && (dxTextureDesc.CPUAccessFlags & D3D11_CPU_ACCESS_READ) )
+ {
+ dxStagingTexture = dxTexture;
+ dxStagingTexture->AddRef();
+ }
+ else
+ {
+ // Modify the descriptor to give us a staging texture
+ dxTextureDesc.BindFlags = 0;
+ dxTextureDesc.MiscFlags &= D3D11_RESOURCE_MISC_TEXTURECUBE;
+ dxTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
+ dxTextureDesc.Usage = D3D11_USAGE_STAGING;
+
+ hr = dxDevice->CreateTexture2D(&dxTextureDesc, 0, &dxStagingTexture);
+ if(FAILED(hr))
+ return hr;
+
+ dxContext->CopyResource(dxStagingTexture, dxTexture);
+ }
+
+ // Now just read back texels from the staging textures
+
+ D3D11_MAPPED_SUBRESOURCE dxMappedResource;
+ hr = dxContext->Map(dxStagingTexture, 0, D3D11_MAP_READ, 0, &dxMappedResource);
+ if(FAILED(hr))
+ return hr;
+
+ int stbResult = stbi_write_png(
+ outputPath,
+ dxTextureDesc.Width,
+ dxTextureDesc.Height,
+ 4,
+ dxMappedResource.pData,
+ dxMappedResource.RowPitch);
+ if( !stbResult )
+ {
+ return E_UNEXPECTED;
+ }
+
+ dxContext->Unmap(dxStagingTexture, 0);
+
+ dxStagingTexture->Release();
+
+ return S_OK;
+}
+
+//
+
+class D3D11Renderer : public Renderer, public ShaderCompiler
+{
+public:
+ IDXGISwapChain* dxSwapChain = NULL;
+ ID3D11Device* dxDevice = NULL;
+ ID3D11DeviceContext* dxImmediateContext = NULL;
+ ID3D11Texture2D* dxBackBufferTexture = NULL;
+ ID3D11RenderTargetView* dxBackBufferRTV = NULL;
+
+ virtual void initialize(void* inWindowHandle) override
+ {
+ auto windowHandle = (HWND) inWindowHandle;
+
+ // Rather than statically link against D3D, we load it dynamically.
+
+ HMODULE d3d11 = LoadLibraryA("d3d11.dll");
+ if(!d3d11)
+ {
+ fprintf(stderr, "error: failed load 'd3d11.dll'\n");
+ exit(1);
+ }
+
+ PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN D3D11CreateDeviceAndSwapChain_ =
+ (PFN_D3D11_CREATE_DEVICE_AND_SWAP_CHAIN)GetProcAddress(
+ d3d11,
+ "D3D11CreateDeviceAndSwapChain");
+ if(!D3D11CreateDeviceAndSwapChain_)
+ {
+ fprintf(stderr,
+ "error: failed load symbol 'D3D11CreateDeviceAndSwapChain'\n");
+ exit(1);
+ }
+
+ // We create our device in debug mode, just so that we can check that the
+ // example doesn't trigger warnings.
+ UINT deviceFlags = 0;
+ deviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
+
+ // We will ask for the highest feature level that can be supported.
+
+ D3D_FEATURE_LEVEL featureLevels[] = {
+ D3D_FEATURE_LEVEL_11_1,
+ D3D_FEATURE_LEVEL_11_0,
+ D3D_FEATURE_LEVEL_10_1,
+ D3D_FEATURE_LEVEL_10_0,
+ D3D_FEATURE_LEVEL_9_3,
+ D3D_FEATURE_LEVEL_9_2,
+ D3D_FEATURE_LEVEL_9_1,
+ };
+ D3D_FEATURE_LEVEL dxFeatureLevel = D3D_FEATURE_LEVEL_9_1;
+
+ // Our swap chain uses RGBA8 with sRGB, with double buffering.
+
+ DXGI_SWAP_CHAIN_DESC dxSwapChainDesc = { 0 };
+ dxSwapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
+ dxSwapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
+ dxSwapChainDesc.SampleDesc.Count = 1;
+ dxSwapChainDesc.SampleDesc.Quality = 0;
+ dxSwapChainDesc.BufferCount = 2;
+ dxSwapChainDesc.OutputWindow = windowHandle;
+ dxSwapChainDesc.Windowed = TRUE;
+ dxSwapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
+ dxSwapChainDesc.Flags = 0;
+
+ // On a machine that does not have an up-to-date version of D3D installed,
+ // the `D3D11CreateDeviceAndSwapChain` call will fail with `E_INVALIDARG`
+ // if you ask for featuer level 11_1. The workaround is to call
+ // `D3D11CreateDeviceAndSwapChain` up to twice: the first time with 11_1
+ // at the start of the list of requested feature levels, and the second
+ // time without it.
+
+ HRESULT hr = S_OK;
+ for( int ii = 0; ii < 2; ++ii )
+ {
+ hr = D3D11CreateDeviceAndSwapChain_(
+ NULL, // adapter (use default)
+ D3D_DRIVER_TYPE_HARDWARE,
+ NULL, // software
+ deviceFlags,
+ &featureLevels[ii],
+ (sizeof(featureLevels) / sizeof(featureLevels[0])) - 1,
+ D3D11_SDK_VERSION,
+ &dxSwapChainDesc,
+ &dxSwapChain,
+ &dxDevice,
+ &dxFeatureLevel,
+ &dxImmediateContext);
+
+ // Failures with `E_INVALIDARG` might be due to feature level 11_1
+ // not being supported. Other failures are real, though.
+ if( hr != E_INVALIDARG )
+ break;
+ }
+ if( FAILED(hr) )
+ {
+ exit(1);
+ }
+
+ // After we've created the swap chain, we can request a pointer to the
+ // back buffer as a D3D11 texture, and create a render-target view from it.
+
+ static const IID kIID_ID3D11Texture2D = {
+ 0x6f15aaf2, 0xd208, 0x4e89, 0x9a, 0xb4, 0x48,
+ 0x95, 0x35, 0xd3, 0x4f, 0x9c };
+ dxSwapChain->GetBuffer(
+ 0,
+ kIID_ID3D11Texture2D,
+ (void**)&dxBackBufferTexture);
+
+ dxDevice->CreateRenderTargetView(
+ dxBackBufferTexture,
+ NULL,
+ &dxBackBufferRTV);
+
+ // We immediately bind the back-buffer render target view, and we aren't
+ // going to switch. We don't bother with a depth buffer.
+ dxImmediateContext->OMSetRenderTargets(
+ 1,
+ &dxBackBufferRTV,
+ NULL);
+
+ // Similarly, we are going to set up a viewport once, and then never
+ // switch, since this is a simple test app.
+ D3D11_VIEWPORT dxViewport;
+ dxViewport.TopLeftX = 0;
+ dxViewport.TopLeftY = 0;
+ dxViewport.Width = (float) gWindowWidth;
+ dxViewport.Height = (float) gWindowHeight;
+ dxViewport.MaxDepth = 1; // TODO(tfoley): use reversed depth
+ dxViewport.MinDepth = 0;
+ dxImmediateContext->RSSetViewports(1, &dxViewport);
+ }
+
+ virtual void clearFrame() override
+ {
+ static const float kClearColor[] = { 0.25, 0.25, 0.25, 1.0 };
+ dxImmediateContext->ClearRenderTargetView(
+ dxBackBufferRTV,
+ kClearColor);
+ }
+
+ virtual void presentFrame() override
+ {
+ dxSwapChain->Present(0, 0);
+ }
+
+ virtual void captureScreenShot(char const* outputPath) override
+ {
+ HRESULT hr = captureTextureToFile(
+ dxDevice,
+ dxImmediateContext,
+ dxBackBufferTexture,
+ outputPath);
+ if( FAILED(hr) )
+ {
+ fprintf(stderr, "error: could not capture screenshot to '%s'\n", outputPath);
+ exit(1);
+ }
+ }
+
+ virtual ShaderCompiler* getShaderCompiler() override
+ {
+ return this;
+ }
+
+ virtual Buffer* createBuffer(BufferDesc const& desc) override
+ {
+ D3D11_BUFFER_DESC dxBufferDesc = { 0 };
+ dxBufferDesc.ByteWidth = desc.size;
+
+ switch( desc.flavor )
+ {
+ case BufferFlavor::Constant:
+ dxBufferDesc.Usage = D3D11_USAGE_DYNAMIC;
+ dxBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
+ dxBufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
+ break;
+
+ case BufferFlavor::Vertex:
+ break;
+
+ default:
+ return nullptr;
+ }
+
+ D3D11_SUBRESOURCE_DATA dxInitData = { 0 };
+ dxInitData.pSysMem = desc.initData;
+
+
+ ID3D11Buffer* dxBuffer = nullptr;
+ HRESULT hr = dxDevice->CreateBuffer(
+ &dxBufferDesc,
+ desc.initData ? &dxInitData : nullptr,
+ &dxBuffer);
+ if(FAILED(hr)) return nullptr;
+
+ return (Buffer*) dxBuffer;
+ }
+
+ static DXGI_FORMAT mapFormat(Format format)
+ {
+ switch( format )
+ {
+ case Format::RGB_Float32:
+ return DXGI_FORMAT_R32G32B32_FLOAT;
+
+ default:
+ return DXGI_FORMAT_UNKNOWN;
+ }
+ }
+
+ virtual InputLayout* createInputLayout(InputElementDesc const* inputElements, UInt inputElementCount) override
+ {
+ D3D11_INPUT_ELEMENT_DESC dxInputElements[16] = {};
+
+ char hlslBuffer[1024];
+ char* hlslCursor = &hlslBuffer[0];
+
+ hlslCursor += sprintf(hlslCursor, "float4 main(\n");
+
+ for( UInt ii = 0; ii < inputElementCount; ++ii )
+ {
+ dxInputElements[ii].SemanticName = inputElements[ii].semanticName;
+ dxInputElements[ii].SemanticIndex = inputElements[ii].semanticIndex;
+ dxInputElements[ii].Format = mapFormat(inputElements[ii].format);
+ dxInputElements[ii].InputSlot = 0;
+ dxInputElements[ii].AlignedByteOffset = inputElements[ii].offset;
+ dxInputElements[ii].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
+ dxInputElements[ii].InstanceDataStepRate = 0;
+
+ if(ii != 0)
+ {
+ hlslCursor+= sprintf(hlslCursor, ",\n");
+ }
+
+ char const* typeName = "Uknown";
+ switch(inputElements[ii].format)
+ {
+ case Format::RGB_Float32:
+ typeName = "float3";
+ break;
+
+ default:
+ return nullptr;
+ }
+
+ hlslCursor+= sprintf(hlslCursor, "%s a%d : %s%d", typeName, ii,
+ inputElements[ii].semanticName,
+ inputElements[ii].semanticIndex);
+ }
+
+ hlslCursor += sprintf(hlslCursor, "\n) : SV_Position { return 0; }");
+
+ auto dxVertexShaderBlob = compileHLSLShader("inputLayout", hlslBuffer, "main", "vs_4_0");
+ if(!dxVertexShaderBlob)
+ return nullptr;
+
+ ID3D11InputLayout* dxInputLayout = nullptr;
+ HRESULT hr = dxDevice->CreateInputLayout(
+ &dxInputElements[0],
+ inputElementCount,
+ dxVertexShaderBlob->GetBufferPointer(),
+ dxVertexShaderBlob->GetBufferSize(),
+ &dxInputLayout);
+
+ dxVertexShaderBlob->Release();
+
+ if(FAILED(hr))
+ return nullptr;
+
+ return (InputLayout*) dxInputLayout;
+ }
+
+ virtual void* map(Buffer* buffer, MapFlavor flavor) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ auto dxBuffer = (ID3D11Buffer*) buffer;
+
+ D3D11_MAP dxMapFlavor;
+ switch( flavor )
+ {
+ case MapFlavor::WriteDiscard:
+ dxMapFlavor = D3D11_MAP_WRITE_DISCARD;
+ break;
+
+ default:
+ return nullptr;
+ }
+
+ // We update our constant buffer per-frame, just for the purposes
+ // of the example, but we don't actually load different data
+ // per-frame (we always use an identity projection).
+ D3D11_MAPPED_SUBRESOURCE dxMapped;
+ HRESULT hr = dxContext->Map(dxBuffer, 0, dxMapFlavor, 0, &dxMapped);
+ if(FAILED(hr))
+ return nullptr;
+
+ return dxMapped.pData;
+ }
+
+ virtual void unmap(Buffer* buffer) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ auto dxBuffer = (ID3D11Buffer*) buffer;
+
+ dxContext->Unmap(dxBuffer, 0);
+ }
+
+ virtual void setInputLayout(InputLayout* inputLayout) override
+ {
+ auto dxContext = dxImmediateContext;
+ auto dxInputLayout = (ID3D11InputLayout*) inputLayout;
+
+ dxContext->IASetInputLayout(dxInputLayout);
+ }
+
+ virtual void setPrimitiveTopology(PrimitiveTopology topology) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ D3D11_PRIMITIVE_TOPOLOGY dxTopology;
+ switch( topology )
+ {
+ case PrimitiveTopology::TriangleList:
+ dxTopology = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
+ break;
+
+ default:
+ return;
+ }
+
+ dxContext->IASetPrimitiveTopology(dxTopology);
+ }
+
+ virtual void setVertexBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* strides, UInt const* offsets) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ static const int kMaxVertexBuffers = 16;
+
+ UINT dxVertexStrides[kMaxVertexBuffers];
+ UINT dxVertexOffsets[kMaxVertexBuffers];
+
+ for( UInt ii = 0; ii < slotCount; ++ii )
+ {
+ dxVertexStrides[ii] = strides[ii];
+ dxVertexOffsets[ii] = offsets[ii];
+ }
+
+ auto dxVertexBuffers = (ID3D11Buffer* const*) buffers;
+
+ dxContext->IASetVertexBuffers(startSlot, slotCount, &dxVertexBuffers[0], &dxVertexStrides[0], &dxVertexOffsets[0]);
+ }
+
+ virtual void setShaderProgram(ShaderProgram* inProgram) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ auto program = (D3D11ShaderProgram*) inProgram;
+
+ dxContext->VSSetShader(program->dxVertexShader, NULL, 0);
+ dxContext->PSSetShader(program->dxPixelShader, NULL, 0);
+ }
+
+ virtual void setConstantBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* offsets) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ // TODO: actually use those offsets
+
+ auto dxConstantBuffers = (ID3D11Buffer* const*) buffers;
+
+ dxContext->VSSetConstantBuffers(startSlot, slotCount, &dxConstantBuffers[0]);
+ dxContext->VSSetConstantBuffers(startSlot, slotCount, &dxConstantBuffers[0]);
+ }
+
+
+ virtual void draw(UInt vertexCount, UInt startVertex) override
+ {
+ auto dxContext = dxImmediateContext;
+
+ dxContext->Draw(vertexCount, startVertex);
+ }
+
+
+ // ShaderCompiler interface
+
+ struct D3D11ShaderProgram
+ {
+ ID3D11VertexShader* dxVertexShader;
+ ID3D11PixelShader* dxPixelShader;
+ };
+
+ virtual ShaderProgram* compileProgram(ShaderCompileRequest const& request) override
+ {
+ auto dxVertexShaderBlob = compileHLSLShader(request.source.path, request.source.text, request.vertexShader .name, request.vertexShader .profile);
+ if(!dxVertexShaderBlob) return nullptr;
+
+ auto dxFragmentShaderBlob = compileHLSLShader(request.source.path, request.source.text, request.fragmentShader .name, request.fragmentShader .profile);
+ if(!dxFragmentShaderBlob) return nullptr;
+
+ ID3D11VertexShader* dxVertexShader;
+ ID3D11PixelShader* dxPixelShader;
+
+ HRESULT vsResult = dxDevice->CreateVertexShader( dxVertexShaderBlob ->GetBufferPointer(), dxVertexShaderBlob ->GetBufferSize(), nullptr, &dxVertexShader);
+ HRESULT psResult = dxDevice->CreatePixelShader( dxFragmentShaderBlob->GetBufferPointer(), dxFragmentShaderBlob->GetBufferSize(), nullptr, &dxPixelShader);
+
+ dxVertexShaderBlob ->Release();
+ dxFragmentShaderBlob->Release();
+
+ if(FAILED(vsResult)) return nullptr;
+ if(FAILED(psResult)) return nullptr;
+
+ D3D11ShaderProgram* shaderProgram = new D3D11ShaderProgram();
+ shaderProgram->dxVertexShader = dxVertexShader;
+ shaderProgram->dxPixelShader = dxPixelShader;
+ return (ShaderProgram*) shaderProgram;
+ }
+};
+
+
+
+Renderer* createD3D11Renderer()
+{
+ return new D3D11Renderer();
+}
+
+} // renderer_test
diff --git a/tools/render-test/render-d3d11.h b/tools/render-test/render-d3d11.h
new file mode 100644
index 000000000..59142731d
--- /dev/null
+++ b/tools/render-test/render-d3d11.h
@@ -0,0 +1,10 @@
+// render-d3d11.h
+#pragma once
+
+namespace renderer_test {
+
+class Renderer;
+
+Renderer* createD3D11Renderer();
+
+} // renderer_test
diff --git a/tools/render-test/render-gl.cpp b/tools/render-test/render-gl.cpp
new file mode 100644
index 000000000..545dd2e44
--- /dev/null
+++ b/tools/render-test/render-gl.cpp
@@ -0,0 +1,242 @@
+// render-gl.cpp
+#include "render-gl.h"
+
+#include "options.h"
+#include "render.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+
+// TODO(tfoley): eventually we should be able to run these
+// tests on non-Windows targets to confirm that cross-compilation
+// at least *works* on those platforms...
+#define WIN32_LEAN_AND_MEAN
+#define NOMINMAX
+#include <Windows.h>
+#undef WIN32_LEAN_AND_MEAN
+#undef NOMINMAX
+
+#ifdef _MSC_VER
+#include <stddef.h>
+#if (_MSC_VER < 1900)
+#define snprintf sprintf_s
+#endif
+#endif
+
+#pragma comment(lib, "opengl32")
+
+#include <GL/GL.h>
+#include "external/glext.h"
+
+// We define an "X-macro" for mapping over loadable OpenGL
+// extension entry point that we will use, so that we can
+// easily write generic code to iterate over them.
+#define MAP_GL_EXTENSION_FUNCS(F) \
+ F(glCreateProgram, PFNGLCREATEPROGRAMPROC) \
+ F(glCreateShader, PFNGLCREATESHADERPROC) \
+ F(glShaderSource, PFNGLSHADERSOURCEPROC) \
+ F(glCompileShader, PFNGLCOMPILESHADERPROC) \
+ F(glGetShaderiv, PFNGLGETSHADERIVPROC) \
+ F(glDeleteShader, PFNGLDELETESHADERPROC) \
+ F(glAttachShader, PFNGLATTACHSHADERPROC) \
+ F(glLinkProgram, PFNGLLINKPROGRAMPROC) \
+ F(glGetProgramiv, PFNGLGETPROGRAMIVPROC) \
+ F(glGetProgramInfoLog, PFNGLGETPROGRAMINFOLOGPROC) \
+ F(glDeleteProgram, PFNGLDELETEPROGRAMPROC) \
+ F(glGetShaderInfoLog, PFNGLGETSHADERINFOLOGPROC) \
+ /* emtty */
+
+namespace renderer_test {
+
+class GLRenderer : public Renderer, public ShaderCompiler
+{
+public:
+ HDC deviceContext;
+ HGLRC glContext;
+
+ // Declre a function pointer for each OpenGL
+ // extension function we need to load
+
+#define DECLARE_GL_EXTENSION_FUNC(NAME, TYPE) TYPE NAME;
+ MAP_GL_EXTENSION_FUNCS(DECLARE_GL_EXTENSION_FUNC)
+#undef DECLARE_GL_EXTENSION_FUNC
+
+ // Renderer interface
+
+ virtual void initialize(void* inWindowHandle) override
+ {
+ auto windowHandle = (HWND) inWindowHandle;
+
+ deviceContext = GetDC(windowHandle);
+
+ PIXELFORMATDESCRIPTOR pixelFormatDesc = { sizeof(PIXELFORMATDESCRIPTOR) };
+ pixelFormatDesc.nVersion = 1;
+ pixelFormatDesc.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
+ pixelFormatDesc.iPixelType = PFD_TYPE_RGBA;
+ pixelFormatDesc.cColorBits = 32;
+ pixelFormatDesc.cDepthBits = 24;
+ pixelFormatDesc.cStencilBits = 8;
+ pixelFormatDesc.iLayerType = PFD_MAIN_PLANE;
+
+ int pixelFormatIndex = ChoosePixelFormat(deviceContext, &pixelFormatDesc);
+ SetPixelFormat(deviceContext, pixelFormatIndex, &pixelFormatDesc);
+
+ glContext = wglCreateContext(deviceContext);
+ wglMakeCurrent(deviceContext, glContext);
+
+ auto renderer = glGetString(GL_RENDERER);
+ auto extensions = glGetString(GL_EXTENSIONS);
+
+ // Load ech of our etension functions by name
+
+ #define LOAD_GL_EXTENSION_FUNC(NAME, TYPE) NAME = (TYPE) wglGetProcAddress(#NAME);
+ MAP_GL_EXTENSION_FUNCS(LOAD_GL_EXTENSION_FUNC)
+ #undef LOAD_GL_EXTENSION_FUNC
+ }
+
+ virtual void clearFrame() override
+ {
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
+ }
+
+ virtual void presentFrame() override
+ {
+ glFlush();
+ SwapBuffers(deviceContext);
+ }
+
+ virtual void captureScreenShot(char const* outputPath) override
+ {
+
+ }
+
+ virtual ShaderCompiler* getShaderCompiler() override
+ {
+ return this;
+ }
+
+ virtual Buffer* createBuffer(BufferDesc const& desc) override
+ {
+ return nullptr;
+ }
+
+ virtual InputLayout* createInputLayout(InputElementDesc const* inputElements, UInt inputElementCount) override
+ {
+ return nullptr;
+ }
+
+ virtual void* map(Buffer* buffer, MapFlavor flavor) override
+ {
+ return nullptr;
+ }
+
+ virtual void unmap(Buffer* buffer) override
+ {
+ }
+
+ virtual void setInputLayout(InputLayout* inputLayout) override
+ {
+ }
+
+ virtual void setPrimitiveTopology(PrimitiveTopology topology) override
+ {
+ }
+
+ virtual void setVertexBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* strides, UInt const* offsets) override
+ {
+ }
+
+ virtual void setShaderProgram(ShaderProgram* program) override
+ {
+ }
+
+ virtual void setConstantBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* offsets) override
+ {
+ }
+
+
+ virtual void draw(UInt vertexCount, UInt startVertex = 0) override
+ {
+ }
+
+ // ShaderCompiler interface
+
+ virtual ShaderProgram* compileProgram(ShaderCompileRequest const& request) override
+ {
+ auto programID = glCreateProgram();
+
+ auto vertexShaderID = loadShader(GL_VERTEX_SHADER, request.vertexShader .source.text);
+ auto fragmentShaderID = loadShader(GL_FRAGMENT_SHADER, request.fragmentShader.source.text);
+
+ glAttachShader(programID, vertexShaderID);
+ glAttachShader(programID, fragmentShaderID);
+
+ glLinkProgram(programID);
+
+ glDeleteShader(vertexShaderID);
+ glDeleteShader(fragmentShaderID);
+
+ GLint success = GL_FALSE;
+ glGetProgramiv(programID, GL_LINK_STATUS, &success);
+ if( !success )
+ {
+ int maxSize = 0;
+ glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &maxSize);
+
+ auto infoBuffer = (char*) malloc(maxSize);
+
+ int infoSize = 0;
+ glGetProgramInfoLog(programID, maxSize, &infoSize, infoBuffer);
+ if( infoSize > 0 )
+ {
+ fprintf(stderr, "%s", infoBuffer);
+ OutputDebugStringA(infoBuffer);
+ }
+
+ glDeleteProgram(programID);
+ return 0;
+ }
+
+ return (ShaderProgram*) (uintptr_t) programID;
+ }
+
+ GLuint loadShader(GLenum stage, char const* source)
+ {
+ auto shaderID = glCreateShader(stage);
+
+ glShaderSource(shaderID, 1, &source, nullptr);
+ glCompileShader(shaderID);
+
+ GLint success = GL_FALSE;
+ glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success);
+ if( !success )
+ {
+ int maxSize = 0;
+ glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &maxSize);
+
+ auto infoBuffer = (char*) malloc(maxSize);
+
+ int infoSize = 0;
+ glGetShaderInfoLog(shaderID, maxSize, &infoSize, infoBuffer);
+ if( infoSize > 0 )
+ {
+ fprintf(stderr, "%s", infoBuffer);
+ OutputDebugStringA(infoBuffer);
+ }
+
+ glDeleteShader(shaderID);
+ return 0;
+ }
+
+ return shaderID;
+ }
+};
+
+
+
+Renderer* createGLRenderer()
+{
+ return new GLRenderer();
+}
+
+} // renderer_test
diff --git a/tools/render-test/render-gl.h b/tools/render-test/render-gl.h
new file mode 100644
index 000000000..4e6de970c
--- /dev/null
+++ b/tools/render-test/render-gl.h
@@ -0,0 +1,10 @@
+// render-d3d11.h
+#pragma once
+
+namespace renderer_test {
+
+class Renderer;
+
+Renderer* createGLRenderer();
+
+} // renderer_test
diff --git a/tools/render-test/render-test.vcxproj b/tools/render-test/render-test.vcxproj
new file mode 100644
index 000000000..dfe610681
--- /dev/null
+++ b/tools/render-test/render-test.vcxproj
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{96610759-07B9-4EEB-A974-5C634A2E742B}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>rendertest</RootNamespace>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_WINDOWS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp" />
+ <ClCompile Include="options.cpp" />
+ <ClCompile Include="render-d3d11.cpp" />
+ <ClCompile Include="render-gl.cpp" />
+ <ClCompile Include="slang-support.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="options.h" />
+ <ClInclude Include="render-d3d11.h" />
+ <ClInclude Include="render-gl.h" />
+ <ClInclude Include="render.h" />
+ <ClInclude Include="slang-support.h" />
+ <ClInclude Include="window.h" />
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/tools/render-test/render-test.vcxproj.filters b/tools/render-test/render-test.vcxproj.filters
new file mode 100644
index 000000000..6e0ff295a
--- /dev/null
+++ b/tools/render-test/render-test.vcxproj.filters
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="render-d3d11.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="options.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="render-gl.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="slang-support.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+ <ItemGroup>
+ <ClInclude Include="options.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="render-d3d11.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="render-gl.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="render.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="window.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="slang-support.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/tools/render-test/render.h b/tools/render-test/render.h
new file mode 100644
index 000000000..a30bf98f9
--- /dev/null
+++ b/tools/render-test/render.h
@@ -0,0 +1,118 @@
+// render.h
+#pragma once
+
+#include "options.h"
+#include "window.h"
+
+namespace renderer_test {
+
+typedef struct Buffer Buffer;
+typedef struct InputLayout InputLayout;
+typedef struct ShaderProgram ShaderProgram;
+
+struct ShaderCompileRequest
+{
+ struct SourceInfo
+ {
+ char const* path;
+ char const* text;
+ };
+
+ struct EntryPoint
+ {
+ char const* name;
+ char const* profile;
+
+ SourceInfo source;
+ };
+
+ SourceInfo source;
+ EntryPoint vertexShader;
+ EntryPoint fragmentShader;
+};
+
+class ShaderCompiler
+{
+public:
+ virtual ShaderProgram* compileProgram(ShaderCompileRequest const& request) = 0;
+};
+
+enum class Format
+{
+ Unknown,
+ RGB_Float32,
+};
+
+enum class BufferFlavor
+{
+ Constant,
+ Vertex,
+};
+
+struct BufferDesc
+{
+ UInt size = 0;
+ BufferFlavor flavor = BufferFlavor::Constant;
+ void const* initData = nullptr;
+};
+
+struct InputElementDesc
+{
+ char const* semanticName;
+ UInt semanticIndex;
+ Format format;
+ UInt offset;
+};
+
+enum class MapFlavor
+{
+ WriteDiscard,
+};
+
+enum class PrimitiveTopology
+{
+ TriangleList,
+};
+
+class Renderer
+{
+public:
+ virtual void initialize(void* inWindowHandle) = 0;
+
+ virtual void clearFrame() = 0;
+ virtual void presentFrame() = 0;
+
+ virtual void captureScreenShot(char const* outputPath) = 0;
+
+ virtual Buffer* createBuffer(BufferDesc const& desc) = 0;
+
+ virtual InputLayout* createInputLayout(InputElementDesc const* inputElements, UInt inputElementCount) = 0;
+
+ virtual ShaderCompiler* getShaderCompiler() = 0;
+
+ virtual void* map(Buffer* buffer, MapFlavor flavor) = 0;
+ virtual void unmap(Buffer* buffer) = 0;
+
+ virtual void setInputLayout(InputLayout* inputLayout) = 0;
+ virtual void setPrimitiveTopology(PrimitiveTopology topology) = 0;
+
+ virtual void setVertexBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* strides, UInt const* offsets) = 0;
+
+ inline void setVertexBuffer(UInt slot, Buffer* buffer, UInt stride, UInt offset = 0)
+ {
+ setVertexBuffers(slot, 1, &buffer, &stride, &offset);
+ }
+
+ virtual void setShaderProgram(ShaderProgram* program) = 0;
+
+ virtual void setConstantBuffers(UInt startSlot, UInt slotCount, Buffer* const* buffers, UInt const* offsets) = 0;
+
+ inline void setConstantBuffer(UInt slot, Buffer* buffer, UInt offset = 0)
+ {
+ setConstantBuffers(slot, 1, &buffer, &offset);
+ }
+
+ virtual void draw(UInt vertexCount, UInt startVertex = 0) = 0;
+};
+
+} // renderer_test
diff --git a/tools/render-test/slang-support.cpp b/tools/render-test/slang-support.cpp
new file mode 100644
index 000000000..5aafc562e
--- /dev/null
+++ b/tools/render-test/slang-support.cpp
@@ -0,0 +1,84 @@
+// slang-support.cpp
+
+#define SLANG_INCLUDE_IMPLEMENTATION
+
+#include "slang-support.h"
+
+#include <stdio.h>
+
+namespace renderer_test {
+
+struct SlangShaderCompilerWrapper : public ShaderCompiler
+{
+ ShaderCompiler* innerCompiler;
+ SlangCompileTarget target;
+
+ virtual ShaderProgram* compileProgram(ShaderCompileRequest const& request) override
+ {
+ SlangSession* slangSession = spCreateSession(NULL);
+ SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession);
+
+ spSetCodeGenTarget(slangRequest, target);
+
+ int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr);
+
+ spAddTranslationUnitSourceString(slangRequest, translationUnitIndex, request.source.path, request.source.text);
+
+ int vertexEntryPoint = spAddTranslationUnitEntryPoint(slangRequest, translationUnitIndex, request.vertexShader.name, spFindProfile(slangSession, request.vertexShader.profile));
+ int fragmentEntryPoint = spAddTranslationUnitEntryPoint(slangRequest, translationUnitIndex, request.fragmentShader.name, spFindProfile(slangSession, request.fragmentShader.profile));
+
+ int compileErr = spCompile(slangRequest);
+ if(auto diagnostics = spGetDiagnosticOutput(slangRequest))
+ {
+ // TODO(tfoley): re-enable when I get a logging solution in place
+// OutputDebugStringA(diagnostics);
+ fprintf(stderr, "%s", diagnostics);
+ }
+ if(compileErr)
+ {
+ return nullptr;
+ }
+
+ char const* translatedCode = spGetTranslationUnitSource(slangRequest, translationUnitIndex);
+ char const* vertexCode = spGetEntryPointSource(slangRequest, translationUnitIndex, vertexEntryPoint);
+ char const* fragmentCode = spGetEntryPointSource(slangRequest, translationUnitIndex, fragmentEntryPoint);
+
+ ShaderCompileRequest innerRequest = request;
+ innerRequest.source.text = translatedCode;
+ innerRequest.vertexShader.source.text = vertexCode;
+ innerRequest.fragmentShader.source.text = fragmentCode;
+
+
+ auto result = innerCompiler->compileProgram(innerRequest);
+
+ // We clean up the Slang compilation context and result *after*
+ // we have run the downstream compiler, because Slang
+ // owns the memory allocation for the generated text, and will
+ // free it when we destroy the compilation result.
+ spDestroyCompileRequest(slangRequest);
+ spDestroySession(slangSession);
+
+ return result;
+ }
+};
+
+ShaderCompiler* createSlangShaderCompiler(ShaderCompiler* innerCompiler, SlangCompileTarget target)
+{
+ auto result = new SlangShaderCompilerWrapper();
+ result->innerCompiler = innerCompiler;
+ result->target = target;
+
+ return result;
+
+}
+
+
+} // renderer_test
+
+//
+// In order to actually use Slang in our application, we need to link in its
+// implementation. The easiest way to accomplish this is by directly inlcuding
+// the (concatenated) Slang source code into our app.
+//
+
+#include <slang.h>
diff --git a/tools/render-test/slang-support.h b/tools/render-test/slang-support.h
new file mode 100644
index 000000000..a191fbfef
--- /dev/null
+++ b/tools/render-test/slang-support.h
@@ -0,0 +1,12 @@
+// slang-support.h
+#pragma once
+
+#include "render.h"
+
+#include <slang.h>
+
+namespace renderer_test {
+
+ShaderCompiler* createSlangShaderCompiler(ShaderCompiler* innerCompiler, SlangCompileTarget target);
+
+} // renderer_test
diff --git a/tools/render-test/window.h b/tools/render-test/window.h
new file mode 100644
index 000000000..5d0a89ee4
--- /dev/null
+++ b/tools/render-test/window.h
@@ -0,0 +1,10 @@
+// window.h
+#pragma once
+
+namespace renderer_test {
+
+typedef struct Window Window;
+
+Window* createWindow();
+
+} // renderer_test