diff options
| author | Tim Foley <tfoley@nvidia.com> | 2017-06-09 11:34:21 -0700 |
|---|---|---|
| committer | Tim Foley <tfoley@nvidia.com> | 2017-06-09 13:44:59 -0700 |
| commit | fcf83dbf9effab3bd98bad2b83b2468b7eb05cfd (patch) | |
| tree | 41047c94883b86ec085a81597391ce3ef557cd43 /tools/render-test | |
| parent | 52e8d4b9a27ab0060f874c3a63ab531847be35c0 (diff) | |
Initial import of code.
Diffstat (limited to 'tools/render-test')
| -rw-r--r-- | tools/render-test/README.md | 4 | ||||
| -rw-r--r-- | tools/render-test/main.cpp | 369 | ||||
| -rw-r--r-- | tools/render-test/options.cpp | 95 | ||||
| -rw-r--r-- | tools/render-test/options.h | 41 | ||||
| -rw-r--r-- | tools/render-test/render-d3d11.cpp | 900 | ||||
| -rw-r--r-- | tools/render-test/render-d3d11.h | 10 | ||||
| -rw-r--r-- | tools/render-test/render-gl.cpp | 242 | ||||
| -rw-r--r-- | tools/render-test/render-gl.h | 10 | ||||
| -rw-r--r-- | tools/render-test/render-test.vcxproj | 170 | ||||
| -rw-r--r-- | tools/render-test/render-test.vcxproj.filters | 54 | ||||
| -rw-r--r-- | tools/render-test/render.h | 118 | ||||
| -rw-r--r-- | tools/render-test/slang-support.cpp | 84 | ||||
| -rw-r--r-- | tools/render-test/slang-support.h | 12 | ||||
| -rw-r--r-- | tools/render-test/window.h | 10 |
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 |
