summaryrefslogtreecommitdiff
path: root/tools/graphics-app-framework
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2021-01-06 12:58:57 -0800
committerGitHub <noreply@github.com>2021-01-06 12:58:57 -0800
commit92636513abe72d2da0c45f0e2c1235415e0671c3 (patch)
tree234136e9d89006df9d6775e8bcd07e91ae344af7 /tools/graphics-app-framework
parent706d4f91e269d473c963d31792fb2c8320933c9b (diff)
Refactor GUI/Window utils out of gfx library (#1649)
Co-authored-by: Yong He <yhe@nvidia.com>
Diffstat (limited to 'tools/graphics-app-framework')
-rw-r--r--tools/graphics-app-framework/gui.cpp415
-rw-r--r--tools/graphics-app-framework/gui.h28
-rw-r--r--tools/graphics-app-framework/model.cpp566
-rw-r--r--tools/graphics-app-framework/model.h77
-rw-r--r--tools/graphics-app-framework/vector-math.h15
-rw-r--r--tools/graphics-app-framework/window.h122
-rw-r--r--tools/graphics-app-framework/windows/win-window.cpp398
7 files changed, 1621 insertions, 0 deletions
diff --git a/tools/graphics-app-framework/gui.cpp b/tools/graphics-app-framework/gui.cpp
new file mode 100644
index 000000000..8208bd606
--- /dev/null
+++ b/tools/graphics-app-framework/gui.cpp
@@ -0,0 +1,415 @@
+// gui.cpp
+#include "gui.h"
+
+#ifdef _WIN32
+#include <windows.h>
+#include "external/imgui/examples/imgui_impl_win32.h"
+IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
+#endif
+
+
+namespace gfx {
+
+#ifdef _WIN32
+LRESULT CALLBACK guiWindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ LRESULT handled = ImGui_ImplWin32_WndProcHandler(hWnd, msg, wParam, lParam);
+ if(handled) return handled;
+ ImGuiIO& io = ImGui::GetIO();
+
+ switch( msg )
+ {
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ if(io.WantCaptureMouse) handled = 1;
+ break;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ if(io.WantCaptureKeyboard) handled = 1;
+ break;
+ }
+
+ return handled;
+}
+void setNativeWindowHook(Window* window, WNDPROC proc);
+#endif
+
+
+GUI::GUI(Window* window, Renderer* inRenderer)
+ : renderer(inRenderer)
+{
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO();
+
+#ifdef _WIN32
+ ImGui_ImplWin32_Init(getPlatformWindowHandle(window));
+
+ setNativeWindowHook(window, &guiWindowProc);
+#endif
+
+ // Let's do the initialization work required for our graphics API
+ // abstraction layer, so that we can pipe all IMGUI rendering
+ // through the same interface as other work.
+ //
+
+ static const char* shaderCode =
+ "cbuffer U { float4x4 mvp; }; \
+ Texture2D t; \
+ SamplerState s; \
+ struct AssembledVertex { \
+ float2 pos; \
+ float2 uv; \
+ float4 col; \
+ }; \
+ struct CoarseVertex { \
+ float4 col; \
+ float2 uv; \
+ }; \
+ struct VSOutput { \
+ CoarseVertex cv : U; \
+ float4 pos : SV_Position; \
+ }; \
+ void vertexMain( \
+ AssembledVertex i : U, \
+ out VSOutput o) \
+ { \
+ o.cv.col = i.col; \
+ o.cv.uv = i.uv; \
+ o.pos = mul(mvp, \
+ float4(i.pos.xy, 0.f, 1.f)); \
+ } \
+ float4 fragmentMain( \
+ CoarseVertex i : U) \
+ : SV_target \
+ { \
+ return i.col * t.Sample(s, i.uv); \
+ } \
+ ";
+
+ SlangSession* slangSession = spCreateSession(nullptr);
+ SlangCompileRequest* slangRequest = spCreateCompileRequest(slangSession);
+
+ // TODO: These two lines need to change based on what the target graphics API
+ // is, so we need a way for a `Renderer` to pass back its prefeerred code
+ // format and profile name...
+ //
+ int targetIndex = spAddCodeGenTarget(slangRequest, SLANG_DXBC);
+ spSetTargetProfile(slangRequest, targetIndex, spFindProfile(slangSession, "sm_4_0"));
+
+ int translationUnitIndex = spAddTranslationUnit(slangRequest, SLANG_SOURCE_LANGUAGE_SLANG, nullptr);
+ spAddTranslationUnitSourceString(slangRequest, translationUnitIndex, "gui.cpp.slang", shaderCode);
+
+ char const* vertexEntryPointName = "vertexMain";
+ char const* fragmentEntryPointName = "fragmentMain";
+ int vertexIndex = spAddEntryPoint(slangRequest, translationUnitIndex, vertexEntryPointName, SLANG_STAGE_VERTEX);
+ int fragmentIndex = spAddEntryPoint(slangRequest, translationUnitIndex, fragmentEntryPointName, SLANG_STAGE_FRAGMENT);
+
+ const SlangResult compileRes = spCompile(slangRequest);
+ if(auto diagnostics = spGetDiagnosticOutput(slangRequest))
+ {
+ reportError("%s", diagnostics);
+ }
+ if(SLANG_FAILED(compileRes))
+ {
+ spDestroyCompileRequest(slangRequest);
+ spDestroySession(slangSession);
+ assert(!"unexpected");
+ return;
+ }
+
+ ISlangBlob* vertexShaderBlob = nullptr;
+ spGetEntryPointCodeBlob(slangRequest, vertexIndex, 0, &vertexShaderBlob);
+
+ ISlangBlob* fragmentShaderBlob = nullptr;
+ spGetEntryPointCodeBlob(slangRequest, fragmentIndex, 0, &fragmentShaderBlob);
+
+ char const* vertexCode = (char const*) vertexShaderBlob->getBufferPointer();
+ char const* vertexCodeEnd = vertexCode + vertexShaderBlob->getBufferSize();
+
+ char const* fragmentCode = (char const*) fragmentShaderBlob->getBufferPointer();
+ char const* fragmentCodeEnd = fragmentCode + fragmentShaderBlob->getBufferSize();
+
+ spDestroyCompileRequest(slangRequest);
+ spDestroySession(slangSession);
+
+ gfx::ShaderProgram::KernelDesc kernelDescs[] =
+ {
+ { gfx::StageType::Vertex, vertexCode, vertexCodeEnd },
+ { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd },
+ };
+
+ gfx::ShaderProgram::Desc programDesc;
+ programDesc.pipelineType = gfx::PipelineType::Graphics;
+ programDesc.kernels = &kernelDescs[0];
+ programDesc.kernelCount = 2;
+
+ auto program = renderer->createProgram(programDesc);
+
+ vertexShaderBlob->release();
+ fragmentShaderBlob->release();
+
+ InputElementDesc inputElements[] = {
+ {"U", 0, Format::RG_Float32, offsetof(ImDrawVert, pos) },
+ {"U", 1, Format::RG_Float32, offsetof(ImDrawVert, uv) },
+ {"U", 2, Format::RGBA_Unorm_UInt8, offsetof(ImDrawVert, col) },
+ };
+ auto inputLayout = renderer->createInputLayout(
+ &inputElements[0],
+ SLANG_COUNT_OF(inputElements));
+
+ //
+
+ List<DescriptorSetLayout::SlotRangeDesc> descriptorSetRanges;
+ descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer));
+ descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::SampledImage));
+ descriptorSetRanges.add(DescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::Sampler));
+
+ DescriptorSetLayout::Desc descriptorSetLayoutDesc;
+ descriptorSetLayoutDesc.slotRangeCount = descriptorSetRanges.getCount();
+ descriptorSetLayoutDesc.slotRanges = descriptorSetRanges.getBuffer();
+
+ descriptorSetLayout = renderer->createDescriptorSetLayout(descriptorSetLayoutDesc);
+
+ List<PipelineLayout::DescriptorSetDesc> pipelineDescriptorSets;
+ pipelineDescriptorSets.add(PipelineLayout::DescriptorSetDesc(descriptorSetLayout));
+
+ PipelineLayout::Desc pipelineLayoutDesc;
+ pipelineLayoutDesc.descriptorSetCount = pipelineDescriptorSets.getCount();
+ pipelineLayoutDesc.descriptorSets = pipelineDescriptorSets.getBuffer();
+ pipelineLayoutDesc.renderTargetCount = 1;
+
+ pipelineLayout = renderer->createPipelineLayout(pipelineLayoutDesc);
+
+ TargetBlendDesc targetBlendDesc;
+ targetBlendDesc.color.srcFactor = BlendFactor::SrcAlpha;
+ targetBlendDesc.color.dstFactor = BlendFactor::InvSrcAlpha;
+ targetBlendDesc.alpha.srcFactor = BlendFactor::InvSrcAlpha;
+ targetBlendDesc.alpha.dstFactor = BlendFactor::Zero;
+
+ GraphicsPipelineStateDesc pipelineDesc;
+ pipelineDesc.renderTargetCount = 1;
+ pipelineDesc.program = program;
+ pipelineDesc.pipelineLayout = pipelineLayout;
+ pipelineDesc.inputLayout = inputLayout;
+ pipelineDesc.blend.targets = &targetBlendDesc;
+ pipelineDesc.blend.targetCount = 1;
+ pipelineDesc.rasterizer.cullMode = CullMode::None;
+
+ // Set up the pieces of fixed-function state that we care about
+ pipelineDesc.depthStencil.depthTestEnable = false;
+
+ // TODO: need to set up blending state...
+
+ pipelineState = renderer->createGraphicsPipelineState(pipelineDesc);
+
+ // Initialize the texture atlas
+ unsigned char* pixels;
+ int width, height;
+ io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
+
+ {
+ gfx::TextureResource::Desc desc;
+ desc.init2D(Resource::Type::Texture2D, Format::RGBA_Unorm_UInt8, width, height, 1);
+ desc.setDefaults(Resource::Usage::PixelShaderResource);
+
+
+ ptrdiff_t mipRowStrides[] = { ptrdiff_t(width * 4 * sizeof(unsigned char)) };
+ void* subResourceData[] = { pixels };
+ TextureResource::Data initData;
+ initData.mipRowStrides = mipRowStrides;
+ initData.numMips = 1;
+ initData.numSubResources = 1;
+ initData.subResources = subResourceData;
+
+ auto texture = renderer->createTextureResource(Resource::Usage::PixelShaderResource, desc, &initData);
+
+ gfx::ResourceView::Desc viewDesc;
+ viewDesc.format = desc.format;
+ viewDesc.type = ResourceView::Type::ShaderResource;
+ auto textureView = renderer->createTextureView(texture, viewDesc);
+
+ io.Fonts->TexID = (void*) textureView.detach();
+ }
+
+ {
+ SamplerState::Desc desc;
+ samplerState = renderer->createSamplerState(desc);
+ }
+}
+
+
+
+void GUI::beginFrame()
+{
+#ifdef _WIN32
+ ImGui_ImplWin32_NewFrame();
+#endif
+ ImGui::NewFrame();
+}
+
+void GUI::endFrame()
+{
+ ImGui::Render();
+
+ ImDrawData* draw_data = ImGui::GetDrawData();
+ auto vertexCount = draw_data->TotalVtxCount;
+ auto indexCount = draw_data->TotalIdxCount;
+ int commandListCount = draw_data->CmdListsCount;
+
+ if(!vertexCount) return;
+ if(!indexCount) return;
+ if(!commandListCount) return;
+
+ // Allocate transient vertex/index buffers to hold the data for this frame.
+
+ gfx::BufferResource::Desc vertexBufferDesc;
+ vertexBufferDesc.init(vertexCount * sizeof(ImDrawVert));
+ vertexBufferDesc.setDefaults(Resource::Usage::VertexBuffer);
+ vertexBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write;
+ auto vertexBuffer = renderer->createBufferResource(
+ Resource::Usage::VertexBuffer,
+ vertexBufferDesc);
+
+ gfx::BufferResource::Desc indexBufferDesc;
+ indexBufferDesc.init(indexCount * sizeof(ImDrawIdx));
+ indexBufferDesc.setDefaults(Resource::Usage::IndexBuffer);
+ indexBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write;
+ auto indexBuffer = renderer->createBufferResource(
+ Resource::Usage::IndexBuffer,
+ indexBufferDesc);
+
+ {
+ ImDrawVert* dstVertex = (ImDrawVert*) renderer->map(vertexBuffer, MapFlavor::WriteDiscard);
+ ImDrawIdx* dstIndex = (ImDrawIdx*) renderer->map(indexBuffer, MapFlavor::WriteDiscard);
+
+ for(int ii = 0; ii < commandListCount; ++ii)
+ {
+ const ImDrawList* commandList = draw_data->CmdLists[ii];
+ memcpy(dstVertex, commandList->VtxBuffer.Data, commandList->VtxBuffer.Size * sizeof(ImDrawVert));
+ memcpy(dstIndex, commandList->IdxBuffer.Data, commandList->IdxBuffer.Size * sizeof(ImDrawIdx));
+ dstVertex += commandList->VtxBuffer.Size;
+ dstIndex += commandList->IdxBuffer.Size;
+ }
+
+ renderer->unmap(vertexBuffer);
+ renderer->unmap(indexBuffer);
+ }
+
+ // Allocate a transient constant buffer for projection matrix
+ gfx::BufferResource::Desc constantBufferDesc;
+ constantBufferDesc.init(sizeof(glm::mat4x4));
+ constantBufferDesc.setDefaults(Resource::Usage::ConstantBuffer);
+ constantBufferDesc.cpuAccessFlags = Resource::AccessFlag::Write;
+ auto constantBuffer = renderer->createBufferResource(
+ Resource::Usage::ConstantBuffer,
+ constantBufferDesc);
+
+ {
+ glm::mat4x4* dstMVP = (glm::mat4x4*) renderer->map(constantBuffer, MapFlavor::WriteDiscard);
+
+ float L = draw_data->DisplayPos.x;
+ float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
+ float T = draw_data->DisplayPos.y;
+ float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
+ float mvp[4][4] =
+ {
+ { 2.0f/(R-L), 0.0f, 0.0f, 0.0f },
+ { 0.0f, 2.0f/(T-B), 0.0f, 0.0f },
+ { 0.0f, 0.0f, 0.5f, 0.0f },
+ { (R+L)/(L-R), (T+B)/(B-T), 0.5f, 1.0f },
+ };
+ memcpy(dstMVP, mvp, sizeof(mvp));
+
+ renderer->unmap(constantBuffer);
+ }
+
+ gfx::Viewport viewport;
+ viewport.originX = 0;
+ viewport.originY = 0;
+ viewport.extentY = draw_data->DisplaySize.y;
+ viewport.extentX = draw_data->DisplaySize.x;
+ viewport.extentY = draw_data->DisplaySize.y;
+ viewport.minZ = 0;
+ viewport.maxZ = 1;
+
+ renderer->setViewport(viewport);
+
+ auto pipelineType = PipelineType::Graphics;
+ renderer->setPipelineState(pipelineType, pipelineState);
+
+ renderer->setVertexBuffer(0, vertexBuffer, sizeof(ImDrawVert));
+ renderer->setIndexBuffer(indexBuffer, sizeof(ImDrawIdx) == 2 ? Format::R_UInt16 : Format::R_UInt32);
+ renderer->setPrimitiveTopology(PrimitiveTopology::TriangleList);
+
+ UInt vertexOffset = 0;
+ UInt indexOffset = 0;
+ ImVec2 pos = draw_data->DisplayPos;
+ for(int ii = 0; ii < commandListCount; ++ii)
+ {
+ auto commandList = draw_data->CmdLists[ii];
+ auto commandCount = commandList->CmdBuffer.Size;
+ for(int jj = 0; jj < commandCount; jj++)
+ {
+ auto command = &commandList->CmdBuffer[jj];
+ if(auto userCallback = command->UserCallback)
+ {
+ userCallback(commandList, command);
+ }
+ else
+ {
+ ScissorRect rect =
+ {
+ (Int)(command->ClipRect.x - pos.x),
+ (Int)(command->ClipRect.y - pos.y),
+ (Int)(command->ClipRect.z - pos.x),
+ (Int)(command->ClipRect.w - pos.y)
+ };
+ renderer->setScissorRect(rect);
+
+ // TODO: This should be a dynamic/transient descriptor set...
+ auto descriptorSet = renderer->createDescriptorSet(descriptorSetLayout);
+ descriptorSet->setConstantBuffer(0, 0, constantBuffer);
+ descriptorSet->setResource(1, 0,
+ (gfx::ResourceView*) command->TextureId);
+ descriptorSet->setSampler(2, 0,
+ samplerState);
+
+ renderer->setDescriptorSet(
+ pipelineType,
+ pipelineLayout,
+ 0,
+ descriptorSet);
+
+ renderer->drawIndexed(command->ElemCount, indexOffset, vertexOffset);
+ }
+ indexOffset += command->ElemCount;
+ }
+ vertexOffset += commandList->VtxBuffer.Size;
+ }
+}
+
+GUI::~GUI()
+{
+ auto& io = ImGui::GetIO();
+
+ {
+ RefPtr<ResourceView> textureView;
+ textureView.attach((ResourceView*) io.Fonts->TexID);
+ textureView = nullptr;
+ }
+
+#ifdef _WIN32
+ ImGui_ImplWin32_Shutdown();
+#endif
+
+ ImGui::DestroyContext();
+}
+
+} // gfx
+
+#include "external/imgui/imgui.cpp"
+#include "external/imgui/imgui_draw.cpp"
+#ifdef _WIN32
+#include "external/imgui/examples/imgui_impl_win32.cpp"
+#endif
diff --git a/tools/graphics-app-framework/gui.h b/tools/graphics-app-framework/gui.h
new file mode 100644
index 000000000..d2c52de3c
--- /dev/null
+++ b/tools/graphics-app-framework/gui.h
@@ -0,0 +1,28 @@
+// gui.h
+#pragma once
+
+#include "tools/gfx/render.h"
+#include "vector-math.h"
+#include "window.h"
+
+#include "external/imgui/imgui.h"
+
+namespace gfx {
+
+struct GUI : RefObject
+{
+ GUI(Window* window, Renderer* renderer);
+ ~GUI();
+
+ void beginFrame();
+ void endFrame();
+
+private:
+ RefPtr<Renderer> renderer;
+ RefPtr<PipelineState> pipelineState;
+ RefPtr<DescriptorSetLayout> descriptorSetLayout;
+ RefPtr<PipelineLayout> pipelineLayout;
+ RefPtr<SamplerState> samplerState;
+};
+
+} // gfx
diff --git a/tools/graphics-app-framework/model.cpp b/tools/graphics-app-framework/model.cpp
new file mode 100644
index 000000000..5d3a850e4
--- /dev/null
+++ b/tools/graphics-app-framework/model.cpp
@@ -0,0 +1,566 @@
+// model.cpp
+#include "model.h"
+
+#include "window.h"
+
+#define TINYOBJLOADER_IMPLEMENTATION
+#include "../../external/tinyobjloader/tiny_obj_loader.h"
+
+#define STB_IMAGE_IMPLEMENTATION
+#include "../../external/stb/stb_image.h"
+
+#define STB_IMAGE_RESIZE_IMPLEMENTATION
+#include "../../external/stb/stb_image_resize.h"
+
+#include "../../external/glm/glm/glm.hpp"
+#include "../../external/glm/glm/gtc/matrix_transform.hpp"
+#include "../../external/glm/glm/gtc/constants.hpp"
+
+#include <memory>
+#include <unordered_map>
+#include <unordered_set>
+
+namespace gfx {
+
+// TinyObj provides a tuple type that bundles up indices, but doesn't
+// provide equality comparison or hashing for that type. We'd like
+// to have a hash function so that we can unique indices.
+//
+// In the simplest case, we could define hashing and operator== operations
+// directly on `tinobj::index_t`, but that would create problems if they
+// revise their API.
+//
+// We will instead define our own wrapper type that supports equality
+// comparisons.
+//
+struct ObjIndexKey
+{
+ tinyobj::index_t index;
+};
+
+bool operator==(ObjIndexKey const& left, ObjIndexKey const& right)
+{
+ return left.index.vertex_index == right.index.vertex_index
+ && left.index.normal_index == right.index.normal_index
+ && left.index.texcoord_index == right.index.texcoord_index;
+}
+
+struct Hasher
+{
+ template<typename T>
+ void add(T const& v)
+ {
+ state ^= std::hash<T>()(v) + 0x9e3779b9 + (state << 6) + (state >> 2);
+ }
+ size_t state = 0;
+};
+
+struct SmoothingGroupVertexID
+{
+ size_t smoothingGroup;
+ size_t positionID;
+};
+bool operator==(SmoothingGroupVertexID const& left, SmoothingGroupVertexID const& right)
+{
+ return left.smoothingGroup == right.smoothingGroup
+ && left.positionID == right.positionID;
+}
+
+}
+
+namespace std
+{
+ template<> struct hash<gfx::ObjIndexKey>
+ {
+ size_t operator()(gfx::ObjIndexKey const& key) const
+ {
+ gfx::Hasher hasher;
+ hasher.add(key.index.vertex_index);
+ hasher.add(key.index.normal_index);
+ hasher.add(key.index.texcoord_index);
+ return hasher.state;
+ }
+ };
+
+ template<> struct hash<gfx::SmoothingGroupVertexID>
+ {
+ size_t operator()(gfx::SmoothingGroupVertexID const& id) const
+ {
+ gfx::Hasher hasher;
+ hasher.add(id.smoothingGroup);
+ hasher.add(id.positionID);
+ return hasher.state;
+ }
+ };
+}
+
+namespace gfx
+{
+
+RefPtr<TextureResource> loadTextureImage(
+ Renderer* renderer,
+ char const* path)
+{
+ int extentX = 0;
+ int extentY = 0;
+ int originalChannelCount = 0;
+ int requestedChannelCount = 4; // force to 4-component result
+ stbi_uc* data = stbi_load(
+ path,
+ &extentX,
+ &extentY,
+ &originalChannelCount,
+ requestedChannelCount);
+ if(!data)
+ return nullptr;
+
+ int channelCount = requestedChannelCount ? requestedChannelCount : originalChannelCount;
+
+ Format format;
+ switch(channelCount)
+ {
+ default:
+ return nullptr;
+
+ case 4: format = Format::RGBA_Unorm_UInt8;
+
+ // TODO: handle other cases here if/when we stop forcing 4-component
+ // results when loading the image with stb_image.
+ }
+
+ std::vector<void*> subresourceInitData;
+ std::vector<ptrdiff_t> mipRowStrides;
+
+ ptrdiff_t stride = extentX * channelCount * sizeof(stbi_uc);
+
+ subresourceInitData.push_back(data);
+ mipRowStrides.push_back(stride);
+
+ // create down-sampled images for the different mip levels
+ bool generateMips = true;
+ if(generateMips)
+ {
+ int prevExtentX = extentX;
+ int prevExtentY = extentY;
+ stbi_uc* prevData = data;
+ int prevStride = int(stride);
+
+ for(;;)
+ {
+ if(prevExtentX == 1 && prevExtentY == 1)
+ break;
+
+ int newExtentX = prevExtentX / 2;
+ int newExtentY = prevExtentY / 2;
+
+ if(!newExtentX) newExtentX = 1;
+ if(!newExtentY) newExtentY = 1;
+
+ stbi_uc* newData = (stbi_uc*) malloc(newExtentX * newExtentY * channelCount * sizeof(stbi_uc));
+ int newStride = int(newExtentX * channelCount * sizeof(stbi_uc));
+
+ stbir_resize_uint8_srgb(
+ prevData, prevExtentX, prevExtentY, prevStride,
+ newData, newExtentX, newExtentY, newStride,
+ channelCount,
+ STBIR_ALPHA_CHANNEL_NONE,
+ STBIR_FLAG_ALPHA_PREMULTIPLIED);
+
+ subresourceInitData.push_back(newData);
+ mipRowStrides.push_back(newStride);
+
+ prevExtentX = newExtentX;
+ prevExtentY = newExtentY;
+ prevData = newData;
+ prevStride = newStride;
+ }
+ }
+
+ int mipCount = (int) mipRowStrides.size();
+
+ TextureResource::Desc desc;
+ desc.init2D(Resource::Type::Texture2D, format, extentX, extentY, mipCount);
+
+ TextureResource::Data initData;
+ initData.numSubResources = mipCount;
+ initData.numMips = mipCount;
+ initData.subResources = &subresourceInitData[0];
+ initData.mipRowStrides = &mipRowStrides[0];
+
+ auto texture = renderer->createTextureResource(
+ Resource::Usage::PixelShaderResource,
+ desc,
+ &initData);
+
+ free(data);
+
+ return texture;
+}
+
+static std::string makeString(const char* start, const char* end)
+{
+ return std::string(start, size_t(end - start));
+}
+
+Result ModelLoader::load(
+ char const* inputPath,
+ void** outModel)
+{
+ // TODO: need to actually allocate/load the data
+
+ tinyobj::attrib_t objVertexAttributes;
+ std::vector<tinyobj::shape_t> objShapes;
+ std::vector<tinyobj::material_t> objMaterials;
+
+ std::string baseDir;
+ if( auto lastSlash = strrchr(inputPath, '/') )
+ {
+ baseDir = makeString(inputPath, lastSlash);
+ }
+
+ std::string diagnostics;
+ bool shouldTriangulate = true;
+ bool success = tinyobj::LoadObj(
+ &objVertexAttributes,
+ &objShapes,
+ &objMaterials,
+ &diagnostics,
+ inputPath,
+ baseDir.size() ? baseDir.c_str() : nullptr,
+ shouldTriangulate);
+
+ if(!diagnostics.empty())
+ {
+ log("%s", diagnostics.c_str());
+ }
+ if(!success)
+ {
+ return SLANG_FAIL;
+ }
+
+ // Translate each material imported by TinyObj into a format that
+ // we can actually use for rendering.
+ //
+ std::vector<void*> materials;
+ for(auto& objMaterial : objMaterials)
+ {
+ MaterialData materialData;
+
+ materialData.diffuseColor = glm::vec3(
+ objMaterial.diffuse[0],
+ objMaterial.diffuse[1],
+ objMaterial.diffuse[2]);
+
+ materialData.specularColor = glm::vec3(
+ objMaterial.specular[0],
+ objMaterial.specular[1],
+ objMaterial.specular[2]);
+
+ materialData.specularity = objMaterial.shininess;
+
+ // load any referenced textures here
+ if(objMaterial.diffuse_texname.length())
+ {
+ materialData.diffuseMap = loadTextureImage(
+ renderer,
+ objMaterial.diffuse_texname.c_str());
+ }
+
+ auto material = callbacks->createMaterial(materialData);
+ materials.push_back(material);
+ }
+
+ // Flip the winding order on all faces if we are asked to...
+ //
+ if(loadFlags & LoadFlag::FlipWinding)
+ {
+ for(auto& objShape : objShapes)
+ {
+ size_t objIndexCounter = 0;
+ size_t objFaceCounter = 0;
+ for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
+ {
+ size_t beginIndex = objIndexCounter;
+ size_t endIndex = beginIndex + objFaceVertexCount;
+ objIndexCounter = endIndex;
+
+ size_t halfCount = objFaceVertexCount / 2;
+ for(size_t ii = 0; ii < halfCount; ++ii)
+ {
+ std::swap(
+ objShape.mesh.indices[beginIndex + ii],
+ objShape.mesh.indices[endIndex - (ii + 1)]);
+ }
+ }
+ }
+
+ }
+
+ // Identify cases where a face has a vertex without a normal, and in that
+ // case remember that the given vertex needs to be "smoothed" as part of
+ // the smoothing group for that face. Note that it is possible for the
+ // same vertex (position) to be part of faces in distinct smoothing groups.
+ //
+ std::unordered_map<SmoothingGroupVertexID, size_t> smoothedVertexNormals;
+ size_t firstSmoothedNormalID = objVertexAttributes.normals.size() / 3;
+ size_t flatFaceCounter = 0;
+ for(auto& objShape : objShapes)
+ {
+ size_t objIndexCounter = 0;
+ size_t objFaceCounter = 0;
+ for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
+ {
+ const size_t flatFaceIndex = flatFaceCounter++;
+ const size_t objFaceIndex = objFaceCounter++;
+ size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
+ if(!smoothingGroup)
+ {
+ smoothingGroup = ~flatFaceIndex;
+ }
+
+ for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
+ {
+ tinyobj::index_t& objIndex = objShape.mesh.indices[objIndexCounter++];
+
+ if(objIndex.normal_index < 0)
+ {
+ SmoothingGroupVertexID smoothVertexID;
+ smoothVertexID.positionID = objIndex.vertex_index;
+ smoothVertexID.smoothingGroup = smoothingGroup;
+
+ if(smoothedVertexNormals.find(smoothVertexID) == smoothedVertexNormals.end())
+ {
+ size_t normalID = objVertexAttributes.normals.size() / 3;
+ objVertexAttributes.normals.push_back(0);
+ objVertexAttributes.normals.push_back(0);
+ objVertexAttributes.normals.push_back(0);
+
+ smoothedVertexNormals.insert(std::make_pair(smoothVertexID, normalID));
+
+ objIndex.normal_index = int(normalID);
+ }
+ }
+ }
+ }
+ }
+ //
+ // Having identified which vertices we need to smooth, we will make another
+ // pass to compute face normals and apply them to the vertices that belong
+ // to the same smoothing group.
+ //
+ flatFaceCounter = 0;
+ for(auto& objShape : objShapes)
+ {
+ size_t objIndexCounter = 0;
+ size_t objFaceCounter = 0;
+ for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
+ {
+ const size_t flatFaceIndex = flatFaceCounter++;
+ const size_t objFaceIndex = objFaceCounter++;
+ size_t smoothingGroup = objShape.mesh.smoothing_group_ids[objFaceIndex];
+ if(!smoothingGroup)
+ {
+ smoothingGroup = ~flatFaceIndex;
+ }
+
+ glm::vec3 faceNormal;
+ if(objFaceVertexCount >= 3)
+ {
+ glm::vec3 v[3];
+ for(size_t objFaceVertex = 0; objFaceVertex < 3; ++objFaceVertex)
+ {
+ tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter + objFaceVertex];
+ if(objIndex.vertex_index >= 0)
+ {
+ v[objFaceVertex] = glm::vec3(
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
+ }
+ }
+ faceNormal = cross(v[1] - v[0], v[2] - v[0]);
+ }
+
+ // Add this face normal to any to-be-smoothed vertex on the face.
+ for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
+ {
+ tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];
+
+ SmoothingGroupVertexID smoothVertexID;
+ smoothVertexID.positionID = objIndex.vertex_index;
+ smoothVertexID.smoothingGroup = smoothingGroup;
+
+ auto ii = smoothedVertexNormals.find(smoothVertexID);
+ if(ii != smoothedVertexNormals.end())
+ {
+ size_t normalID = ii->second;
+ objVertexAttributes.normals[normalID * 3 + 0] += faceNormal.x;
+ objVertexAttributes.normals[normalID * 3 + 1] += faceNormal.y;
+ objVertexAttributes.normals[normalID * 3 + 2] += faceNormal.z;
+ }
+ }
+ }
+ }
+ //
+ // Once we've added all contributions from each smoothing group,
+ // we can normalize the normals to compute the area-weighted average.
+ //
+ size_t normalCount = objVertexAttributes.normals.size() / 3;
+ for(size_t ii = firstSmoothedNormalID; ii < normalCount; ++ii)
+ {
+ glm::vec3 normal = glm::vec3(
+ objVertexAttributes.normals[3 * ii + 0],
+ objVertexAttributes.normals[3 * ii + 1],
+ objVertexAttributes.normals[3 * ii + 2]);
+
+ normal = normalize(normal);
+
+ objVertexAttributes.normals[3 * ii + 0] = normal.x;
+ objVertexAttributes.normals[3 * ii + 1] = normal.y;
+ objVertexAttributes.normals[3 * ii + 2] = normal.z;
+ }
+
+ // TODO: we should sort the faces to group faces with
+ // the same material ID together, in case they weren't
+ // grouped in the original file.
+
+ // We need to undo the .obj indexing stuff so that we have
+ // standard position/normal/etc. data in a single flat array
+
+ std::unordered_map<ObjIndexKey, Index> mapObjIndexToFlatIndex;
+ std::vector<Vertex> flatVertices;
+ std::vector<Index> flatIndices;
+
+ MeshData* currentMesh = nullptr;
+ MeshData currentMeshStorage;
+
+ std::vector<void*> meshes;
+
+ void* defaultMaterial = nullptr;
+
+ for(auto& objShape : objShapes)
+ {
+ size_t objIndexCounter = 0;
+ size_t objFaceCounter = 0;
+ for(auto objFaceVertexCount : objShape.mesh.num_face_vertices)
+ {
+ size_t objFaceIndex = objFaceCounter++;
+ int faceMaterialID = objShape.mesh.material_ids[objFaceIndex];
+ void* faceMaterial = nullptr;
+ if( faceMaterialID < 0 )
+ {
+ if( !defaultMaterial )
+ {
+ MaterialData defaultMaterialData;
+ defaultMaterialData.diffuseColor = glm::vec3(0.5, 0.5, 0.5);
+ defaultMaterial = callbacks->createMaterial(defaultMaterialData);
+ }
+ faceMaterial = defaultMaterial;
+ }
+ else
+ {
+ faceMaterial = materials[faceMaterialID];
+ }
+
+ if(!currentMesh || (faceMaterial != currentMesh->material))
+ {
+ // finish old mesh.
+ if(currentMesh)
+ {
+ meshes.push_back(callbacks->createMesh(*currentMesh));
+ }
+
+ // Need to start a new mesh.
+ currentMesh = &currentMeshStorage;
+ currentMesh->material = faceMaterial;
+ currentMesh->firstIndex = (int)flatIndices.size();
+ currentMesh->indexCount = 0;
+ }
+
+ for(size_t objFaceVertex = 0; objFaceVertex < objFaceVertexCount; ++objFaceVertex)
+ {
+ tinyobj::index_t objIndex = objShape.mesh.indices[objIndexCounter++];
+ ObjIndexKey objIndexKey; objIndexKey.index = objIndex;
+
+
+ Index flatIndex = Index(-1);
+ auto iter = mapObjIndexToFlatIndex.find(objIndexKey);
+ if(iter != mapObjIndexToFlatIndex.end())
+ {
+ flatIndex = iter->second;
+ }
+ else
+ {
+ Vertex flatVertex;
+ if(objIndex.vertex_index >= 0)
+ {
+ flatVertex.position = scale * glm::vec3(
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 0],
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 1],
+ objVertexAttributes.vertices[3 * objIndex.vertex_index + 2]);
+ }
+ if(objIndex.normal_index >= 0)
+ {
+ flatVertex.normal = glm::vec3(
+ objVertexAttributes.normals[3 * objIndex.normal_index + 0],
+ objVertexAttributes.normals[3 * objIndex.normal_index + 1],
+ objVertexAttributes.normals[3 * objIndex.normal_index + 2]);
+ }
+ if(objIndex.texcoord_index >= 0)
+ {
+ flatVertex.uv = glm::vec2(
+ objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 0],
+ objVertexAttributes.texcoords[2 * objIndex.texcoord_index + 1]);
+ }
+
+ flatIndex = uint32_t(flatVertices.size());
+ mapObjIndexToFlatIndex.insert(std::make_pair(objIndexKey, flatIndex));
+ flatVertices.push_back(flatVertex);
+ }
+
+ flatIndices.push_back(flatIndex);
+ currentMesh->indexCount++;
+ }
+ }
+ }
+
+ // finish last mesh.
+ if(currentMesh)
+ {
+ meshes.push_back(callbacks->createMesh(*currentMesh));
+ }
+
+ ModelData modelData;
+
+ modelData.vertexCount = (int)flatVertices.size();
+ modelData.indexCount = (int)flatIndices.size();
+
+ modelData.meshCount = int(meshes.size());
+ modelData.meshes = meshes.data();
+
+ BufferResource::Desc vertexBufferDesc;
+ vertexBufferDesc.init(modelData.vertexCount * sizeof(Vertex));
+ vertexBufferDesc.setDefaults(Resource::Usage::VertexBuffer);
+
+ modelData.vertexBuffer = renderer->createBufferResource(
+ Resource::Usage::VertexBuffer,
+ vertexBufferDesc,
+ flatVertices.data());
+ if(!modelData.vertexBuffer) return SLANG_FAIL;
+
+ BufferResource::Desc indexBufferDesc;
+ indexBufferDesc.init(modelData.indexCount * sizeof(Index));
+ vertexBufferDesc.setDefaults(Resource::Usage::IndexBuffer);
+
+ modelData.indexBuffer = renderer->createBufferResource(
+ Resource::Usage::IndexBuffer,
+ indexBufferDesc,
+ flatIndices.data());
+ if(!modelData.indexBuffer) return SLANG_FAIL;
+
+ *outModel = callbacks->createModel(modelData);
+
+ return SLANG_OK;
+}
+
+} // gfx
diff --git a/tools/graphics-app-framework/model.h b/tools/graphics-app-framework/model.h
new file mode 100644
index 000000000..e86940244
--- /dev/null
+++ b/tools/graphics-app-framework/model.h
@@ -0,0 +1,77 @@
+// model.h
+#pragma once
+
+#include "tools/gfx/render.h"
+#include "vector-math.h"
+
+#include <vector>
+#include <string>
+
+namespace gfx {
+
+struct ModelLoader
+{
+ struct MaterialData
+ {
+ glm::vec3 diffuseColor;
+ glm::vec3 specularColor;
+ float specularity;
+
+ RefPtr<TextureResource> diffuseMap;
+ };
+
+ struct Vertex
+ {
+ glm::vec3 position;
+ glm::vec3 normal;
+ glm::vec2 uv;
+ };
+
+ typedef uint32_t Index;
+
+ struct MeshData
+ {
+ int firstIndex;
+ int indexCount;
+
+ void* material;
+ };
+
+ struct ModelData
+ {
+ RefPtr<BufferResource> vertexBuffer;
+ RefPtr<BufferResource> indexBuffer;
+ PrimitiveTopology primitiveTopology;
+ int vertexCount;
+ int indexCount;
+ int meshCount;
+ void* const* meshes;
+ };
+
+ struct ICallbacks
+ {
+ typedef ModelLoader::MaterialData MaterialData;
+ typedef ModelLoader::MeshData MeshData;
+ typedef ModelLoader::ModelData ModelData;
+
+ virtual void* createMaterial(MaterialData const& data) = 0;
+ virtual void* createMesh(MeshData const& data) = 0;
+ virtual void* createModel(ModelData const& data) = 0;
+ };
+
+ typedef uint32_t LoadFlags;
+ enum LoadFlag : LoadFlags
+ {
+ FlipWinding = 1 << 0,
+ };
+
+ ICallbacks* callbacks = nullptr;
+ RefPtr<Renderer> renderer;
+ LoadFlags loadFlags = 0;
+ float scale = 1.0f;
+
+ Result load(char const* inputPath, void** outModel);
+};
+
+
+} // gfx
diff --git a/tools/graphics-app-framework/vector-math.h b/tools/graphics-app-framework/vector-math.h
new file mode 100644
index 000000000..e35cb46ac
--- /dev/null
+++ b/tools/graphics-app-framework/vector-math.h
@@ -0,0 +1,15 @@
+// vector-math.h
+#pragma once
+
+// We will use the GLM library for our vector math types, just for simplicity.
+
+#include "../../external/glm/glm/glm.hpp"
+#include "../../external/glm/glm/gtc/matrix_transform.hpp"
+#include "../../external/glm/glm/gtc/constants.hpp"
+#include "../../external/glm/glm/gtc/quaternion.hpp"
+
+namespace gfx {
+
+using namespace glm;
+
+} // gfx
diff --git a/tools/graphics-app-framework/window.h b/tools/graphics-app-framework/window.h
new file mode 100644
index 000000000..e6f886f42
--- /dev/null
+++ b/tools/graphics-app-framework/window.h
@@ -0,0 +1,122 @@
+// window.h
+#pragma once
+
+#include <stdint.h>
+
+namespace gfx {
+
+struct Window;
+
+enum class KeyCode
+{
+ Unknown,
+
+ // TODO: extend this to cover at least a standard US-English keyboard
+
+ A, B, C, D, E, F, G, H, I, J,
+ K, L, M, N, O, P, Q, R, S, T,
+ U, V, W, X, Y, Z,
+
+ Space,
+};
+
+enum class EventCode : uint32_t
+{
+ MouseDown,
+ MouseUp,
+ MouseMoved,
+ KeyDown,
+ KeyUp,
+};
+
+struct Event
+{
+ EventCode code;
+ Window* window;
+ union
+ {
+ struct
+ {
+ float x;
+ float y;
+ } mouse;
+
+ KeyCode key;
+ } u;
+};
+
+typedef void (*EventHandler)(Event const&);
+
+struct WindowDesc
+{
+ char const* title = nullptr;
+ void* userData = nullptr;
+ int width = 0;
+ int height = 0;
+ EventHandler eventHandler = nullptr;
+};
+
+Window* createWindow(WindowDesc const& desc);
+void showWindow(Window* window);
+
+void* getPlatformWindowHandle(Window* window);
+void* getUserData(Window* window);
+
+/// Opaque state provided by platform for a running application.
+typedef struct ApplicationContext ApplicationContext;
+
+/// User-defined application entry-point function.
+typedef void(*ApplicationFunc)(ApplicationContext* context);
+
+/// Dispatch any pending events for application.
+///
+/// @returns `true` if application should keep running.
+bool dispatchEvents(ApplicationContext* context);
+
+/// Exit the application with a given result code
+void exitApplication(ApplicationContext* context, int resultCode);
+
+/// Log a message to an appropriate logging destination.
+void log(char const* message, ...);
+
+/// Report an error to an appropriate logging destination.
+int reportError(char const* message, ...);
+
+uint64_t getCurrentTime();
+
+uint64_t getTimerFrequency();
+
+/// Run an application given the specified callback and command-line arguments.
+int runApplication(
+ ApplicationFunc func,
+ int argc,
+ char const* const* argv);
+
+#define GFX_CONSOLE_MAIN(APPLICATION_ENTRY) \
+ int main(int argc, char** argv) { \
+ return gfx::runApplication(&(APPLICATION_ENTRY), argc, argv); \
+ }
+
+#ifdef _WIN32
+
+int runWindowsApplication(
+ ApplicationFunc func,
+ void* instance,
+ int showCommand);
+
+#define GFX_UI_MAIN(APPLICATION_ENTRY) \
+ int __stdcall WinMain( \
+ void* instance, \
+ void* /* prevInstance */, \
+ void* /* commandLine */, \
+ int showCommand) { \
+ return gfx::runWindowsApplication(&(APPLICATION_ENTRY), instance, showCommand); \
+ }
+
+#else
+
+#define GFX_UI_MAIN(APPLICATION_ENTRY) GFX_CONSOLE_MAIN(APPLICATION_ENTRY)
+
+#endif
+
+} // gfx
diff --git a/tools/graphics-app-framework/windows/win-window.cpp b/tools/graphics-app-framework/windows/win-window.cpp
new file mode 100644
index 000000000..45315a27a
--- /dev/null
+++ b/tools/graphics-app-framework/windows/win-window.cpp
@@ -0,0 +1,398 @@
+// win-window.cpp
+#include "../window.h"
+
+#include <stdio.h>
+
+#ifdef _MSC_VER
+#include <stddef.h>
+#if (_MSC_VER < 1900)
+#define snprintf sprintf_s
+#endif
+#endif
+
+#include <stdint.h>
+
+#if _WIN32
+#include <Windows.h>
+#include <Windowsx.h>
+#else
+#error "The slang-graphics library currently only supports Windows platforms"
+#endif
+
+namespace gfx {
+
+#if _WIN32
+
+struct OSString
+{
+ OSString(char const* begin, char const* end)
+ {
+ _initialize(begin, end - begin);
+ }
+
+ OSString(char const* begin)
+ {
+ _initialize(begin, strlen(begin));
+ }
+
+ ~OSString()
+ {
+ free(mBegin);
+ }
+
+ operator WCHAR const*()
+ {
+ return mBegin;
+ }
+
+private:
+ WCHAR* mBegin;
+ WCHAR* mEnd;
+
+ void _initialize(char const* input, size_t inputSize)
+ {
+ const DWORD dwFlags = 0;
+ int outputCodeUnitCount = ::MultiByteToWideChar(CP_UTF8, dwFlags, input, int(inputSize), nullptr, 0);
+
+ WCHAR* buffer = (WCHAR*)malloc(sizeof(WCHAR) * (outputCodeUnitCount + 1));
+
+ ::MultiByteToWideChar(CP_UTF8, dwFlags, input, int(inputSize), buffer, outputCodeUnitCount);
+ buffer[outputCodeUnitCount] = 0;
+
+ mBegin = buffer;
+ mEnd = buffer + outputCodeUnitCount;
+ }
+};
+
+struct ApplicationContext
+{
+ HINSTANCE instance;
+ int showCommand = SW_SHOWDEFAULT;
+ int resultCode = 0;
+};
+
+static uint64_t gTimerFrequency;
+
+
+static void initApplication(ApplicationContext* context)
+{
+ LARGE_INTEGER timerFrequency;
+ QueryPerformanceFrequency(&timerFrequency);
+ gTimerFrequency = timerFrequency.QuadPart;
+}
+
+/// Run an application given the specified callback and command-line arguments.
+int runApplication(
+ ApplicationFunc func,
+ int argc,
+ char const* const* argv)
+{
+ ApplicationContext context;
+ context.instance = (HINSTANCE) GetModuleHandle(0);
+ initApplication(&context);
+ func(&context);
+ return context.resultCode;
+}
+
+int runWindowsApplication(
+ ApplicationFunc func,
+ void* instance,
+ int showCommand)
+{
+ ApplicationContext context;
+ context.instance = (HINSTANCE) instance;
+ context.showCommand = showCommand;
+ initApplication(&context);
+ func(&context);
+ return context.resultCode;
+}
+
+struct Window
+{
+ HWND handle;
+ WNDPROC nativeHook;
+ EventHandler eventHandler;
+ void* userData;
+};
+
+void setNativeWindowHook(Window* window, WNDPROC proc)
+{
+ window->nativeHook = proc;
+}
+
+static KeyCode translateKeyCode(int vkey)
+{
+ switch( vkey )
+ {
+ default:
+ return KeyCode::Unknown;
+
+#define CASE(FROM, TO) case FROM: return KeyCode::TO;
+ CASE('A', A); CASE('B', B); CASE('C', C); CASE('D', D); CASE('E', E);
+ CASE('F', F); CASE('G', G); CASE('H', H); CASE('I', I); CASE('J', J);
+ CASE('K', K); CASE('L', M); CASE('M', M); CASE('N', N); CASE('O', O);
+ CASE('P', P); CASE('Q', Q); CASE('R', R); CASE('S', S); CASE('T', T);
+ CASE('U', U); CASE('V', V); CASE('W', W); CASE('X', X); CASE('Y', Y);
+ CASE('Z', Z);
+#undef CASE
+
+#define CASE(FROM, TO) case VK_##FROM: return KeyCode::TO;
+ CASE(SPACE, Space);
+#undef CASE
+ }
+}
+
+static LRESULT CALLBACK windowProc(
+ HWND windowHandle,
+ UINT message,
+ WPARAM wParam,
+ LPARAM lParam)
+{
+ Window* window = (Window*) GetWindowLongPtrW(windowHandle, GWLP_USERDATA);
+
+ // Give the installed filter a chance to intercept messages.
+ // (This is used for ImGui)
+ if( window )
+ {
+ if(auto nativeHook = window->nativeHook)
+ {
+ auto result = nativeHook(windowHandle, message, wParam, lParam);
+ if(result)
+ return result;
+ }
+ }
+
+ auto eventHandler = window ? window->eventHandler : nullptr;
+
+ switch (message)
+ {
+ case WM_CREATE:
+ {
+ auto createInfo = (CREATESTRUCTW*) lParam;
+ window = (Window*) createInfo->lpCreateParams;
+ window->handle = windowHandle;
+
+ SetWindowLongPtrW(windowHandle, GWLP_USERDATA, (LONG_PTR) window);
+ }
+ break;
+
+ case WM_KEYDOWN:
+ case WM_KEYUP:
+ {
+ int virtualKey = (int) wParam;
+ auto keyCode = translateKeyCode(virtualKey);
+ if(eventHandler)
+ {
+ Event event;
+ event.window = window;
+ event.code = message == WM_KEYDOWN ? EventCode::KeyDown : EventCode::KeyUp;
+ event.u.key = keyCode;
+ eventHandler(event);
+ }
+ }
+ break;
+
+ case WM_LBUTTONDOWN:
+ case WM_LBUTTONUP:
+ {
+ if(eventHandler)
+ {
+ Event event;
+ event.window = window;
+ event.code = message == WM_LBUTTONDOWN ? EventCode::MouseDown : EventCode::MouseUp;
+ event.u.mouse.x = (float) GET_X_LPARAM(lParam);
+ event.u.mouse.y = (float) GET_Y_LPARAM(lParam);
+ eventHandler(event);
+ }
+ }
+ break;
+
+ case WM_MOUSEMOVE:
+ {
+ if(eventHandler)
+ {
+ Event event;
+ event.window = window;
+ event.code = EventCode::MouseMoved;
+ event.u.mouse.x = (float) GET_X_LPARAM(lParam);
+ event.u.mouse.y = (float) GET_Y_LPARAM(lParam);
+ eventHandler(event);
+ }
+ }
+ break;
+
+ case WM_CLOSE:
+ PostQuitMessage(0);
+ return 0;
+ }
+
+
+ return DefWindowProcW(windowHandle, message, wParam, lParam);
+}
+
+
+static ATOM createWindowClassAtom()
+{
+ 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 = (HINSTANCE) GetModuleHandle(0);
+ windowClassDesc.hIcon = 0;
+ windowClassDesc.hCursor = 0;
+ windowClassDesc.hbrBackground = 0;
+ windowClassDesc.lpszMenuName = 0;
+ windowClassDesc.lpszClassName = L"SlangGraphicsWindow";
+ windowClassDesc.hIconSm = 0;
+ ATOM windowClassAtom = RegisterClassExW(&windowClassDesc);
+ return windowClassAtom;
+}
+
+static ATOM getWindowClassAtom()
+{
+ static ATOM windowClassAtom = createWindowClassAtom();
+ return windowClassAtom;
+}
+
+Window* createWindow(WindowDesc const& desc)
+{
+ Window* window = new Window();
+ window->handle = nullptr;
+ window->nativeHook = nullptr;
+ window->eventHandler = desc.eventHandler;
+ window->userData = desc.userData;
+
+ OSString windowTitle(desc.title);
+
+ DWORD windowExtendedStyle = 0;
+ DWORD windowStyle = 0;
+
+ HINSTANCE instance = (HINSTANCE) GetModuleHandle(0);
+
+ HWND windowHandle = CreateWindowExW(
+ windowExtendedStyle,
+ (LPWSTR) getWindowClassAtom(),
+ windowTitle,
+ windowStyle,
+ 0, 0, // x, y
+ desc.width, desc.height,
+ NULL, // parent
+ NULL, // menu
+ instance,
+ window);
+
+ if(!windowHandle)
+ {
+ delete window;
+ return nullptr;
+ }
+
+ window->handle = windowHandle;
+ return window;
+}
+
+void showWindow(Window* window)
+{
+ ShowWindow(window->handle, SW_SHOW);
+}
+
+void* getPlatformWindowHandle(Window* window)
+{
+ return window->handle;
+}
+
+void* getUserData(Window* window)
+{
+ return window->userData;
+}
+
+bool dispatchEvents(ApplicationContext* context)
+{
+ for(;;)
+ {
+ MSG message;
+
+ int result = PeekMessageW(&message, NULL, 0, 0, PM_REMOVE);
+ if (result != 0)
+ {
+ if (message.message == WM_QUIT)
+ {
+ context->resultCode = (int)message.wParam;
+ return false;
+ }
+
+ TranslateMessage(&message);
+ DispatchMessageW(&message);
+ }
+ else
+ {
+ return true;
+ }
+ }
+
+}
+
+void exitApplication(ApplicationContext* context, int resultCode)
+{
+ ExitProcess(resultCode);
+}
+
+void log(char const* message, ...)
+{
+ va_list args;
+ va_start(args, message);
+
+ static const int kBufferSize = 1024;
+ char messageBuffer[kBufferSize];
+ vsnprintf(messageBuffer, kBufferSize - 1, message, args);
+ messageBuffer[kBufferSize - 1] = 0;
+
+ va_end(args);
+
+ fputs(messageBuffer, stderr);
+
+ OSString wideMessageBuffer(messageBuffer);
+ OutputDebugStringW(wideMessageBuffer);
+}
+
+int reportError(char const* message, ...)
+{
+ va_list args;
+ va_start(args, message);
+
+ static const int kBufferSize = 1024;
+ char messageBuffer[kBufferSize];
+ vsnprintf(messageBuffer, kBufferSize - 1, message, args);
+ messageBuffer[kBufferSize - 1] = 0;
+
+ va_end(args);
+
+ fputs(messageBuffer, stderr);
+
+ OSString wideMessageBuffer(messageBuffer);
+ OutputDebugStringW(wideMessageBuffer);
+
+ return 1;
+}
+
+uint64_t getCurrentTime()
+{
+ LARGE_INTEGER counter;
+ QueryPerformanceCounter(&counter);
+ return counter.QuadPart;
+}
+
+uint64_t getTimerFrequency()
+{
+ return gTimerFrequency;
+}
+
+#else
+
+// TODO: put an SDL version here
+
+#endif
+
+} // gfx