summaryrefslogtreecommitdiffstats
path: root/tools/platform
diff options
context:
space:
mode:
authorYong He <yonghe@outlook.com>2021-03-08 10:01:20 -0800
committerGitHub <noreply@github.com>2021-03-08 10:01:20 -0800
commitfc9968dc4fd58fab37476f48e4405c2743c5349c (patch)
tree6119b293a5a5cc24401dde5ff54287beb28fe63b /tools/platform
parent95ca93938f5d45f4eaf340867965bd77a1724d6c (diff)
Refactor window library. (#1739)
* Refactor window library. * Fix project file * Fix warnings.
Diffstat (limited to 'tools/platform')
-rw-r--r--tools/platform/gui.cpp438
-rw-r--r--tools/platform/gui.h34
-rw-r--r--tools/platform/model.cpp566
-rw-r--r--tools/platform/model.h77
-rw-r--r--tools/platform/performance-counter.h30
-rw-r--r--tools/platform/vector-math.h15
-rw-r--r--tools/platform/window.h235
-rw-r--r--tools/platform/windows/win-window.cpp442
8 files changed, 1837 insertions, 0 deletions
diff --git a/tools/platform/gui.cpp b/tools/platform/gui.cpp
new file mode 100644
index 000000000..cf7e74acc
--- /dev/null
+++ b/tools/platform/gui.cpp
@@ -0,0 +1,438 @@
+// 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
+
+using namespace gfx;
+
+namespace platform
+{
+
+#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,
+ IRenderer* inRenderer,
+ ICommandQueue* inQueue,
+ IFramebufferLayout* framebufferLayout)
+ : renderer(inRenderer)
+ , queue(inQueue)
+{
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO();
+
+#ifdef _WIN32
+ ImGui_ImplWin32_Init((HWND)window->getNativeHandle().handleValues[0]);
+
+ 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))
+ {
+ printf("%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::IShaderProgram::KernelDesc kernelDescs[] =
+ {
+ { gfx::StageType::Vertex, vertexCode, vertexCodeEnd },
+ { gfx::StageType::Fragment, fragmentCode, fragmentCodeEnd },
+ };
+
+ gfx::IShaderProgram::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));
+
+ //
+
+ Slang::List<IDescriptorSetLayout::SlotRangeDesc> descriptorSetRanges;
+ descriptorSetRanges.add(IDescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::UniformBuffer));
+ descriptorSetRanges.add(IDescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::SampledImage));
+ descriptorSetRanges.add(IDescriptorSetLayout::SlotRangeDesc(DescriptorSlotType::Sampler));
+
+ IDescriptorSetLayout::Desc descriptorSetLayoutDesc;
+ descriptorSetLayoutDesc.slotRangeCount = descriptorSetRanges.getCount();
+ descriptorSetLayoutDesc.slotRanges = descriptorSetRanges.getBuffer();
+
+ descriptorSetLayout = renderer->createDescriptorSetLayout(descriptorSetLayoutDesc);
+
+ Slang::List<IPipelineLayout::DescriptorSetDesc> pipelineDescriptorSets;
+ pipelineDescriptorSets.add(IPipelineLayout::DescriptorSetDesc(descriptorSetLayout));
+
+ IPipelineLayout::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.framebufferLayout = framebufferLayout;
+ 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::ITextureResource::Desc desc;
+ desc.init2D(IResource::Type::Texture2D, Format::RGBA_Unorm_UInt8, width, height, 1);
+ desc.setDefaults(IResource::Usage::PixelShaderResource);
+
+
+ ptrdiff_t mipRowStrides[] = { ptrdiff_t(width * 4 * sizeof(unsigned char)) };
+ void* subResourceData[] = { pixels };
+ ITextureResource::Data initData;
+ initData.mipRowStrides = mipRowStrides;
+ initData.numMips = 1;
+ initData.numSubResources = 1;
+ initData.subResources = subResourceData;
+
+ auto texture = renderer->createTextureResource(IResource::Usage::PixelShaderResource, desc, &initData);
+
+ gfx::IResourceView::Desc viewDesc;
+ viewDesc.format = desc.format;
+ viewDesc.type = IResourceView::Type::ShaderResource;
+ auto textureView = renderer->createTextureView(texture, viewDesc);
+
+ io.Fonts->TexID = (void*) textureView.detach();
+ }
+
+ {
+ ISamplerState::Desc desc;
+ samplerState = renderer->createSamplerState(desc);
+ }
+
+ {
+ IRenderPassLayout::Desc desc;
+ desc.framebufferLayout = framebufferLayout;
+ IRenderPassLayout::AttachmentAccessDesc colorAccess;
+ desc.depthStencilAccess = nullptr;
+ colorAccess.initialState = ResourceState::Present;
+ colorAccess.finalState = ResourceState::Present;
+ colorAccess.loadOp = IRenderPassLayout::AttachmentLoadOp::Load;
+ colorAccess.storeOp = IRenderPassLayout::AttachmentStoreOp::Store;
+ desc.renderTargetAccess = &colorAccess;
+ desc.renderTargetCount = 1;
+ renderPass = renderer->createRenderPassLayout(desc);
+ }
+}
+
+
+
+void GUI::beginFrame()
+{
+#ifdef _WIN32
+ ImGui_ImplWin32_NewFrame();
+#endif
+ ImGui::NewFrame();
+}
+
+void GUI::endFrame(IFramebuffer* framebuffer)
+{
+ 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::IBufferResource::Desc vertexBufferDesc;
+ vertexBufferDesc.init(vertexCount * sizeof(ImDrawVert));
+ vertexBufferDesc.setDefaults(IResource::Usage::VertexBuffer);
+ vertexBufferDesc.cpuAccessFlags = IResource::AccessFlag::Write;
+ auto vertexBuffer = renderer->createBufferResource(
+ IResource::Usage::VertexBuffer,
+ vertexBufferDesc);
+
+ gfx::IBufferResource::Desc indexBufferDesc;
+ indexBufferDesc.init(indexCount * sizeof(ImDrawIdx));
+ indexBufferDesc.setDefaults(IResource::Usage::IndexBuffer);
+ indexBufferDesc.cpuAccessFlags = IResource::AccessFlag::Write;
+ auto indexBuffer = renderer->createBufferResource(
+ IResource::Usage::IndexBuffer,
+ indexBufferDesc);
+ auto cmdBuf = queue->createCommandBuffer();
+ auto encoder = cmdBuf->encodeResourceCommands();
+ {
+ for(int ii = 0; ii < commandListCount; ++ii)
+ {
+ const ImDrawList* commandList = draw_data->CmdLists[ii];
+ encoder->uploadBufferData(
+ vertexBuffer,
+ commandList->VtxBuffer.Size * ii * sizeof(ImDrawVert),
+ commandList->VtxBuffer.Size * sizeof(ImDrawVert),
+ commandList->VtxBuffer.Data);
+ encoder->uploadBufferData(
+ indexBuffer,
+ commandList->IdxBuffer.Size * ii * sizeof(ImDrawIdx),
+ commandList->IdxBuffer.Size * sizeof(ImDrawIdx),
+ commandList->IdxBuffer.Data);
+ }
+ }
+
+ // Allocate a transient constant buffer for projection matrix
+ gfx::IBufferResource::Desc constantBufferDesc;
+ constantBufferDesc.init(sizeof(glm::mat4x4));
+ constantBufferDesc.setDefaults(IResource::Usage::ConstantBuffer);
+ constantBufferDesc.cpuAccessFlags = IResource::AccessFlag::Write;
+ auto constantBuffer = renderer->createBufferResource(
+ IResource::Usage::ConstantBuffer,
+ constantBufferDesc);
+
+ {
+ 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 },
+ };
+ encoder->uploadBufferData(constantBuffer, 0, sizeof(mvp), mvp);
+ }
+
+ encoder->endEncoding();
+
+ 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;
+
+ auto renderEncoder = cmdBuf->encodeRenderCommands(renderPass, framebuffer);
+ renderEncoder->setViewportAndScissor(viewport);
+
+ renderEncoder->setPipelineState(pipelineState);
+
+ renderEncoder->setVertexBuffer(0, vertexBuffer, sizeof(ImDrawVert));
+ renderEncoder->setIndexBuffer(
+ indexBuffer, sizeof(ImDrawIdx) == 2 ? Format::R_UInt16 : Format::R_UInt32);
+ renderEncoder->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)
+ };
+ renderEncoder->setScissorRects(1, &rect);
+
+ // TODO: This should be a dynamic/transient descriptor set...
+ auto descriptorSet = renderer->createDescriptorSet(descriptorSetLayout, gfx::IDescriptorSet::Flag::Transient);
+ descriptorSet->setConstantBuffer(0, 0, constantBuffer);
+ descriptorSet->setResource(1, 0,
+ (gfx::IResourceView*) command->TextureId);
+ descriptorSet->setSampler(2, 0,
+ samplerState);
+
+ renderEncoder->setDescriptorSet(
+ pipelineLayout,
+ 0,
+ descriptorSet);
+
+ renderEncoder->drawIndexed(command->ElemCount, indexOffset, vertexOffset);
+ }
+ indexOffset += command->ElemCount;
+ }
+ vertexOffset += commandList->VtxBuffer.Size;
+ }
+ renderEncoder->endEncoding();
+ cmdBuf->close();
+ queue->executeCommandBuffer(cmdBuf);
+}
+
+GUI::~GUI()
+{
+ auto& io = ImGui::GetIO();
+
+ {
+ ComPtr<IResourceView> textureView;
+ textureView.attach((IResourceView*) 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/platform/gui.h b/tools/platform/gui.h
new file mode 100644
index 000000000..d22da3299
--- /dev/null
+++ b/tools/platform/gui.h
@@ -0,0 +1,34 @@
+// gui.h
+#pragma once
+
+#include "slang-gfx.h"
+#include "vector-math.h"
+#include "window.h"
+#include "slang-com-ptr.h"
+#include "external/imgui/imgui.h"
+#include "source/core/slang-basic.h"
+
+namespace platform {
+
+struct GUI : Slang::RefObject
+{
+ GUI(Window* window,
+ gfx::IRenderer* renderer,
+ gfx::ICommandQueue* queue,
+ gfx::IFramebufferLayout* framebufferLayout);
+ ~GUI();
+
+ void beginFrame();
+ void endFrame(gfx::IFramebuffer* framebuffer);
+
+private:
+ Slang::ComPtr<gfx::IRenderer> renderer;
+ Slang::ComPtr<gfx::ICommandQueue> queue;
+ Slang::ComPtr<gfx::IRenderPassLayout> renderPass;
+ Slang::ComPtr<gfx::IPipelineState> pipelineState;
+ Slang::ComPtr<gfx::IDescriptorSetLayout> descriptorSetLayout;
+ Slang::ComPtr<gfx::IPipelineLayout> pipelineLayout;
+ Slang::ComPtr<gfx::ISamplerState> samplerState;
+};
+
+} // gfx
diff --git a/tools/platform/model.cpp b/tools/platform/model.cpp
new file mode 100644
index 000000000..f28577631
--- /dev/null
+++ b/tools/platform/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
+{
+
+ComPtr<ITextureResource> loadTextureImage(
+ IRenderer* 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();
+
+ ITextureResource::Desc desc;
+ desc.init2D(IResource::Type::Texture2D, format, extentX, extentY, mipCount);
+
+ ITextureResource::Data initData;
+ initData.numSubResources = mipCount;
+ initData.numMips = mipCount;
+ initData.subResources = &subresourceInitData[0];
+ initData.mipRowStrides = &mipRowStrides[0];
+
+ auto texture = renderer->createTextureResource(
+ IResource::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())
+ {
+ printf("%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();
+
+ IBufferResource::Desc vertexBufferDesc;
+ vertexBufferDesc.init(modelData.vertexCount * sizeof(Vertex));
+ vertexBufferDesc.setDefaults(IResource::Usage::VertexBuffer);
+
+ modelData.vertexBuffer = renderer->createBufferResource(
+ IResource::Usage::VertexBuffer,
+ vertexBufferDesc,
+ flatVertices.data());
+ if(!modelData.vertexBuffer) return SLANG_FAIL;
+
+ IBufferResource::Desc indexBufferDesc;
+ indexBufferDesc.init(modelData.indexCount * sizeof(Index));
+ vertexBufferDesc.setDefaults(IResource::Usage::IndexBuffer);
+
+ modelData.indexBuffer = renderer->createBufferResource(
+ IResource::Usage::IndexBuffer,
+ indexBufferDesc,
+ flatIndices.data());
+ if(!modelData.indexBuffer) return SLANG_FAIL;
+
+ *outModel = callbacks->createModel(modelData);
+
+ return SLANG_OK;
+}
+
+} // gfx
diff --git a/tools/platform/model.h b/tools/platform/model.h
new file mode 100644
index 000000000..412f10a1d
--- /dev/null
+++ b/tools/platform/model.h
@@ -0,0 +1,77 @@
+// model.h
+#pragma once
+
+#include "slang-gfx.h"
+#include "vector-math.h"
+#include "slang-com-ptr.h"
+#include <vector>
+#include <string>
+
+namespace gfx {
+
+struct ModelLoader
+{
+ struct MaterialData
+ {
+ glm::vec3 diffuseColor;
+ glm::vec3 specularColor;
+ float specularity;
+
+ ComPtr<ITextureResource> 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
+ {
+ ComPtr<IBufferResource> vertexBuffer;
+ ComPtr<IBufferResource> 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;
+ Slang::ComPtr<IRenderer> renderer;
+ LoadFlags loadFlags = 0;
+ float scale = 1.0f;
+
+ Result load(char const* inputPath, void** outModel);
+};
+
+
+} // gfx
diff --git a/tools/platform/performance-counter.h b/tools/platform/performance-counter.h
new file mode 100644
index 000000000..e9e990f45
--- /dev/null
+++ b/tools/platform/performance-counter.h
@@ -0,0 +1,30 @@
+#ifndef PLATFORM_PERFORMANCE_COUNTER_H
+#define PLATFORM_PERFORMANCE_COUNTER_H
+
+#include <chrono>
+
+namespace platform
+{
+typedef std::chrono::high_resolution_clock::time_point TimePoint;
+typedef std::chrono::high_resolution_clock::duration Duration;
+
+class PerformanceCounter
+{
+public:
+ static inline TimePoint now()
+ {
+ return std::chrono::high_resolution_clock::now();
+ }
+ static inline Duration getElapsedTime(TimePoint counter) { return now() - counter; }
+ static inline float getElapsedTimeInSeconds(TimePoint counter)
+ {
+ return (float)toSeconds(now() - counter);
+ }
+ static inline double toSeconds(Duration duration)
+ {
+ return std::chrono::duration<float>(duration).count();
+ }
+};
+} // namespace platform
+
+#endif
diff --git a/tools/platform/vector-math.h b/tools/platform/vector-math.h
new file mode 100644
index 000000000..e35cb46ac
--- /dev/null
+++ b/tools/platform/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/platform/window.h b/tools/platform/window.h
new file mode 100644
index 000000000..c776c3ffa
--- /dev/null
+++ b/tools/platform/window.h
@@ -0,0 +1,235 @@
+// window.h
+#pragma once
+
+#include "slang-com-ptr.h"
+#include "source/core/slang-basic.h"
+#include "source/core/slang-func-ptr.h"
+
+namespace platform {
+
+enum class KeyCode : uint32_t
+{
+ None = 0,
+ Left = 0x25,
+ Up = 0x26,
+ Down = 0x28,
+ Right = 0x27,
+ Escape = 0x1B,
+ Return = 0x0D,
+ Space = 0x20,
+ Shift = 0x10,
+ Ctrl = 0x11,
+ Alt = 0x12,
+ Backspace = 0x08,
+ Delete = 0x2E,
+ Home = 0x24,
+ End = 0x23,
+ PageUp = 0x21,
+ PageDown = 0x22,
+ Insert = 0x2D,
+ Tab = 0x09,
+ A = 0x41,
+ B = 0x42,
+ C = 0x43,
+ D = 0x44,
+ E = 0x45,
+ F = 0x46,
+ G = 0x47,
+ H = 0x48,
+ I = 0x49,
+ J = 0x4A,
+ K = 0x4B,
+ L = 0x4C,
+ M = 0x4D,
+ N = 0x4E,
+ O = 0x4F,
+ P = 0x50,
+ Q = 0x51,
+ R = 0x52,
+ S = 0x53,
+ T = 0x54,
+ U = 0x55,
+ V = 0x56,
+ W = 0x57,
+ X = 0x58,
+ Y = 0x59,
+ Z = 0x5A,
+ Semicolon = 0xBA,
+ Comma = 0xBC,
+ Dot = 0xBE,
+ Slash = 0xBF,
+ Quote = 0xDE,
+ LBracket = 0xDB,
+ RBracket = 0xDD,
+ Backslash = 0xDC,
+ Minus = 0xBD,
+ Plus = 0xBB,
+ Tilde = 0xC0,
+ Key0 = 0x30,
+ Key1 = 0x31,
+ Key2 = 0x32,
+ Key3 = 0x33,
+ Key4 = 0x34,
+ Key5 = 0x35,
+ Key6 = 0x36,
+ Key7 = 0x37,
+ Key8 = 0x38,
+ Key9 = 0x39,
+ F1 = 0x70,
+ F2 = 0x71,
+ F3 = 0x72,
+ F4 = 0x73,
+ F5 = 0x74,
+ F6 = 0x75,
+ F7 = 0x76,
+ F8 = 0x77,
+ F9 = 0x78,
+ F10 = 0x79,
+ F11 = 0x7A,
+ F12 = 0x7B,
+};
+
+struct WindowHandle
+{
+ enum class Type
+ {
+ Unknown,
+ Win32Handle,
+ XLibHandle,
+ };
+ Type type;
+ intptr_t handleValues[2];
+ static WindowHandle FromHwnd(void* hwnd)
+ {
+ WindowHandle handle = {};
+ handle.type = WindowHandle::Type::Win32Handle;
+ handle.handleValues[0] = (intptr_t)(hwnd);
+ return handle;
+ }
+ static WindowHandle FromXWindow(void* xdisplay, uint32_t xwindow)
+ {
+ WindowHandle handle = {};
+ handle.type = WindowHandle::Type::XLibHandle;
+ handle.handleValues[0] = (intptr_t)(xdisplay);
+ handle.handleValues[1] = xwindow;
+ return handle;
+ }
+};
+
+struct ButtonState
+{
+ enum Enum
+ {
+ None = 0, LeftButton = 1, RightButton = 2, MiddleButton = 4,
+ Shift = 8, Control = 16, Alt = 32
+ };
+};
+
+struct KeyEventArgs
+{
+ KeyCode key;
+ wchar_t keyChar; // For KeyPress event
+ ButtonState::Enum buttons;
+ bool cancelEvent;
+};
+
+struct MouseEventArgs
+{
+ int x, y;
+ int delta;
+ ButtonState::Enum buttons;
+};
+
+struct Rect
+{
+ int x, y;
+ int width, height;
+};
+
+struct WindowDesc
+{
+ char const* title = nullptr;
+ int width = 0;
+ int height = 0;
+};
+
+class Window : public Slang::RefObject
+{
+public:
+ struct Events
+ {
+ Slang::Action<> mainLoop;
+ Slang::Action<> sizeChanged;
+ Slang::Action<> focus;
+ Slang::Action<> lostFocus;
+ Slang::Action<KeyEventArgs&> keyDown;
+ Slang::Action<KeyEventArgs&> keyUp;
+ Slang::Action<KeyEventArgs&> keyPress;
+ Slang::Action<MouseEventArgs> mouseMove;
+ Slang::Action<MouseEventArgs> mouseUp;
+ Slang::Action<MouseEventArgs> mouseDown;
+ };
+
+ Events events;
+
+ virtual void setClientSize(uint32_t width, uint32_t height) = 0;
+ virtual Rect getClientRect() = 0;
+ virtual void centerScreen() = 0;
+ virtual void close() = 0;
+ virtual bool getFocused() = 0;
+ virtual bool getVisible() = 0;
+ virtual WindowHandle getNativeHandle() = 0;
+ virtual void setText(Slang::String text) = 0;
+ virtual void show() = 0;
+ virtual void hide() = 0;
+ virtual int getCurrentDpi() = 0;
+};
+
+class Application
+{
+public:
+ static Window* createWindow(const WindowDesc& desc);
+ static void init();
+ static void run(Window* mainWindow, bool waitForEvents = false);
+ static void quit();
+ static void doEvents();
+ static void dispose();
+};
+
+} // namespace platform
+
+#ifdef _WIN32
+
+# ifdef _MSC_VER
+# ifdef _DEBUG
+# define GFX_DUMP_LEAK _CrtDumpMemoryLeaks();
+# endif
+# endif
+# ifndef GFX_DUMP_LEAK
+# define GFX_DUMP_LEAK
+# endif
+# define PLATFORM_UI_MAIN(APPLICATION_ENTRY) \
+ int __stdcall WinMain( \
+ void* /*instance*/, \
+ void* /* prevInstance */, \
+ void* /* commandLine */, \
+ int /*showCommand*/) \
+ { \
+ platform::Application::init(); \
+ auto result = APPLICATION_ENTRY(); \
+ platform::Application::dispose(); \
+ GFX_DUMP_LEAK \
+ return result; \
+ }
+
+#else
+
+# define PLATFORM_UI_MAIN(APPLICATION_ENTRY) \
+ int main() \
+ { \
+ platform::Application::init(); \
+ auto rs - APPLICATION_ENTRY(); \
+ platform::Application::dispose(); \
+ }
+
+#endif
diff --git a/tools/platform/windows/win-window.cpp b/tools/platform/windows/win-window.cpp
new file mode 100644
index 000000000..0362a6839
--- /dev/null
+++ b/tools/platform/windows/win-window.cpp
@@ -0,0 +1,442 @@
+#ifdef _WIN32
+
+#include "../window.h"
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <windowsx.h>
+using namespace Slang;
+
+namespace platform
+{
+
+static const wchar_t* kWindowClassName = L"slang-platform-window";
+
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(_In_ HWND hwnd);
+
+class Win32AppContext
+{
+public:
+ static EnableNonClientDpiScalingProc enableNonClientDpiScaling;
+ static RefPtr<Window> mainWindow;
+ static OrderedDictionary<HWND, Window*> windows;
+ static HWND mainWindowHandle;
+ static bool isTerminated;
+ static bool isWindows81OrGreater;
+};
+
+EnableNonClientDpiScalingProc Win32AppContext::enableNonClientDpiScaling = nullptr;
+HWND Win32AppContext::mainWindowHandle = nullptr;
+RefPtr<Window> Win32AppContext::mainWindow;
+OrderedDictionary<HWND, Window*> Win32AppContext::windows;
+bool Win32AppContext::isTerminated = false;
+bool Win32AppContext::isWindows81OrGreater = false;
+
+
+ButtonState::Enum _addButtonState(ButtonState::Enum val, ButtonState::Enum newState)
+{
+ return (ButtonState::Enum)((int)val | (int)newState);
+}
+
+ButtonState::Enum getModifierState()
+{
+ ButtonState::Enum result = ButtonState::Enum::None;
+ if (GetAsyncKeyState(VK_CONTROL))
+ result = _addButtonState(result, ButtonState::Enum::Control);
+ if (GetAsyncKeyState(VK_SHIFT))
+ result = _addButtonState(result, ButtonState::Enum::Shift);
+ if (GetAsyncKeyState(VK_MENU))
+ result = _addButtonState(result, ButtonState::Enum::Alt);
+ return result;
+}
+
+ButtonState::Enum getModifierState(WPARAM wParam)
+{
+ ButtonState::Enum result = ButtonState::Enum::None;
+ if (wParam & MK_CONTROL)
+ result = _addButtonState(result, ButtonState::Enum::Control);
+ if (wParam & MK_MBUTTON)
+ result = _addButtonState(result, ButtonState::Enum::MiddleButton);
+ if (wParam & MK_RBUTTON)
+ result = _addButtonState(result, ButtonState::Enum::RightButton);
+ if (wParam & MK_SHIFT)
+ result = _addButtonState(result, ButtonState::Enum::Shift);
+ if (GetAsyncKeyState(VK_MENU))
+ result = _addButtonState(result, ButtonState::Enum::Alt);
+ return result;
+}
+
+LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
+{
+ bool useDefProc = true;
+ Window* window = nullptr;
+ Win32AppContext::windows.TryGetValue(hWnd, window);
+ switch (message)
+ {
+ case WM_LBUTTONUP:
+ case WM_MBUTTONUP:
+ case WM_RBUTTONUP:
+ {
+ int mx = GET_X_LPARAM(lParam);
+ int my = GET_Y_LPARAM(lParam);
+ bool processed = false;
+ if (window)
+ {
+ window->events.mouseUp(MouseEventArgs{
+ mx,
+ my,
+ 0, getModifierState(wParam)});
+ }
+ }
+ break;
+ case WM_LBUTTONDOWN:
+ case WM_MBUTTONDOWN:
+ case WM_RBUTTONDOWN:
+ {
+ int mx = GET_X_LPARAM(lParam);
+ int my = GET_Y_LPARAM(lParam);
+ bool processed = false;
+ if (window)
+ {
+ window->events.mouseDown(MouseEventArgs{mx, my, 0, getModifierState(wParam)});
+ }
+ }
+ break;
+ case WM_MOUSEMOVE:
+ {
+ int mx = GET_X_LPARAM(lParam);
+ int my = GET_Y_LPARAM(lParam);
+ if (window)
+ {
+ window->events.mouseMove(MouseEventArgs{mx, my, 0, getModifierState(wParam)});
+ }
+ }
+ break;
+ case WM_MOUSEWHEEL:
+ {
+ int delta = GET_WHEEL_DELTA_WPARAM(wParam);
+ if (window)
+ {
+ window->events.mouseMove(MouseEventArgs{0, 0, delta, getModifierState(wParam)});
+ }
+ }
+ break;
+ case WM_CHAR:
+ {
+ if (window)
+ {
+ KeyEventArgs keyEventArgs = {
+ KeyCode::None, (wchar_t)(wParam), ButtonState::Enum::None, false};
+ window->events.keyPress(keyEventArgs);
+ if (keyEventArgs.cancelEvent)
+ useDefProc = false;
+ }
+ }
+ break;
+ case WM_KEYDOWN:
+ {
+ if (window)
+ {
+ KeyEventArgs keyEventArgs = {(KeyCode)(wParam), 0, getModifierState(), false};
+ window->events.keyDown(keyEventArgs);
+ if (keyEventArgs.cancelEvent)
+ useDefProc = false;
+ }
+ }
+ break;
+ case WM_KEYUP:
+ {
+ if (window)
+ {
+ KeyEventArgs keyEventArgs = {(KeyCode)(wParam), 0, getModifierState(), false};
+ window->events.keyUp(keyEventArgs);
+ if (keyEventArgs.cancelEvent)
+ useDefProc = false;
+ }
+ }
+ break;
+ case WM_SETFOCUS:
+ {
+ if (window)
+ {
+ window->events.focus();
+ }
+ }
+ break;
+ case WM_KILLFOCUS:
+ {
+ if (window)
+ {
+ window->events.lostFocus();
+ }
+ }
+ break;
+ case WM_SIZE:
+ {
+ if (window)
+ {
+ window->events.sizeChanged();
+ }
+ }
+ break;
+ case WM_NCCREATE:
+ {
+ if (Win32AppContext::enableNonClientDpiScaling)
+ Win32AppContext::enableNonClientDpiScaling(hWnd);
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ }
+ break;
+ default:
+ break;
+ }
+ if (message == WM_DESTROY && hWnd == Win32AppContext::mainWindowHandle)
+ {
+ PostQuitMessage(0);
+ return 0;
+ }
+ if (useDefProc)
+ return DefWindowProc(hWnd, message, wParam, lParam);
+ return 0;
+}
+
+void registerWindowClass()
+{
+ WNDCLASSEX wcex;
+
+ wcex.cbSize = sizeof(WNDCLASSEX);
+
+ wcex.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC | CS_DBLCLKS;
+ wcex.lpfnWndProc = WndProc;
+ wcex.cbClsExtra = 0;
+ wcex.cbWndExtra = 0;
+ wcex.hInstance = GetModuleHandle(NULL);
+ wcex.hIcon = 0;
+ wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
+ wcex.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
+ wcex.lpszMenuName = 0;
+ wcex.lpszClassName = kWindowClassName;
+ wcex.hIconSm = 0;
+
+ RegisterClassExW(&wcex);
+}
+
+void unregisterWindowClass() { UnregisterClassW(kWindowClassName, GetModuleHandle(NULL)); }
+
+HRESULT(WINAPI* getDpiForMonitor) (void* hmonitor, int dpiType, unsigned int* dpiX, unsigned int* dpiY);
+
+void Application::init()
+{
+ *(FARPROC*)&Win32AppContext::enableNonClientDpiScaling =
+ GetProcAddress(GetModuleHandleA("User32"), "EnableNonClientDpiScaling");
+ void*(WINAPI * RtlGetVersion)(LPOSVERSIONINFOEXW);
+ OSVERSIONINFOEXW osInfo;
+ *(FARPROC*)&RtlGetVersion = GetProcAddress(GetModuleHandleA("ntdll"), "RtlGetVersion");
+
+ if (RtlGetVersion)
+ {
+ osInfo.dwOSVersionInfoSize = sizeof(osInfo);
+ RtlGetVersion(&osInfo);
+ if (osInfo.dwMajorVersion > 8 || (osInfo.dwMajorVersion == 8 && osInfo.dwMinorVersion >= 1))
+ Win32AppContext::isWindows81OrGreater = true;
+ }
+ HRESULT(WINAPI * setProcessDpiAwareness)(int value);
+ *(FARPROC*)&setProcessDpiAwareness =
+ GetProcAddress(GetModuleHandleA("Shcore"), "SetProcessDpiAwareness");
+ *(FARPROC*)&getDpiForMonitor = GetProcAddress(GetModuleHandleA("Shcore"), "GetDpiForMonitor");
+ if (setProcessDpiAwareness)
+ {
+ if (Win32AppContext::isWindows81OrGreater)
+ setProcessDpiAwareness(2); // PROCESS_PER_MONITOR_DPI_AWARE
+ else
+ setProcessDpiAwareness(1); // PROCESS_SYSTEM_DPI_AWARE
+ }
+ registerWindowClass();
+}
+
+void doEventsImpl(bool waitForEvents)
+{
+ int hasMsg = 0;
+ do
+ {
+ MSG msg = {};
+ hasMsg =
+ (waitForEvents ? GetMessage(&msg, NULL, 0, 0) : PeekMessage(&msg, NULL, 0, 0, TRUE));
+ if (hasMsg)
+ {
+ TranslateMessage(&msg);
+ DispatchMessage(&msg);
+ }
+ if (msg.message == WM_QUIT)
+ Win32AppContext::isTerminated = true;
+ } while (!Win32AppContext::isTerminated && hasMsg);
+}
+
+void Application::doEvents() { doEventsImpl(false); }
+
+void Application::quit() { Win32AppContext::isTerminated = true; }
+
+void Application::dispose()
+{
+ Win32AppContext::mainWindow = nullptr;
+ Win32AppContext::windows = decltype(Win32AppContext::windows)();
+ unregisterWindowClass();
+}
+
+void Application::run(Window* mainWindow, bool waitForEvents)
+{
+ if (mainWindow)
+ {
+ Win32AppContext::mainWindow = mainWindow;
+ Win32AppContext::mainWindowHandle = (HWND)mainWindow->getNativeHandle().handleValues[0];
+ ShowWindow(Win32AppContext::mainWindowHandle, SW_SHOW);
+ UpdateWindow(Win32AppContext::mainWindowHandle);
+ }
+ while (!Win32AppContext::isTerminated)
+ {
+ doEventsImpl(waitForEvents);
+ if (mainWindow)
+ {
+ mainWindow->events.mainLoop();
+ }
+ }
+}
+
+class Win32PlatformWindow : public Window
+{
+public:
+ HWND handle;
+ DWORD style;
+ bool visible = false;
+ Win32PlatformWindow(const WindowDesc& desc)
+ {
+ DWORD windowExtendedStyle = 0;
+ DWORD style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU;
+ HINSTANCE instance = (HINSTANCE)GetModuleHandle(0);
+
+ RECT windowRect;
+ windowRect.left = 0;
+ windowRect.top = 0;
+ windowRect.bottom = desc.height;
+ windowRect.right = desc.width;
+ AdjustWindowRect(&windowRect, style, FALSE);
+
+ handle = CreateWindowExW(
+ windowExtendedStyle,
+ (LPWSTR)kWindowClassName,
+ String(desc.title).toWString().begin(),
+ style,
+ CW_USEDEFAULT,
+ 0, // x, y
+ windowRect.right,
+ windowRect.bottom,
+ NULL, // parent
+ NULL, // menu
+ instance,
+ NULL);
+
+ if (handle)
+ Win32AppContext::windows[handle] = this;
+ }
+
+ ~Win32PlatformWindow()
+ {
+ if (handle)
+ {
+ Win32AppContext::windows.Remove(handle);
+ }
+ DestroyWindow(handle);
+ }
+
+ virtual void setClientSize(uint32_t width, uint32_t height) override
+ {
+ RECT currentRect;
+ GetWindowRect(handle, &currentRect);
+
+ RECT windowRect;
+ windowRect.left = currentRect.left;
+ windowRect.top = currentRect.top;
+ windowRect.bottom = height;
+ windowRect.right = width;
+ AdjustWindowRect(&windowRect, style, FALSE);
+
+ MoveWindow(
+ handle,
+ windowRect.left,
+ windowRect.top,
+ windowRect.right - windowRect.left,
+ windowRect.bottom - windowRect.top,
+ FALSE);
+ }
+
+ virtual Rect getClientRect() override
+ {
+ RECT currentRect;
+ GetClientRect(handle, &currentRect);
+ Rect rect;
+ rect.x = currentRect.left;
+ rect.y = currentRect.top;
+ rect.width = currentRect.right - currentRect.left;
+ rect.height = currentRect.bottom - currentRect.top;
+ return rect;
+ }
+
+ virtual void centerScreen() override
+ {
+ RECT screenRect;
+ GetClientRect(GetDesktopWindow(), &screenRect);
+ RECT currentRect;
+ GetWindowRect(handle, &currentRect);
+
+ auto width = currentRect.right - currentRect.left;
+ auto height = currentRect.bottom - currentRect.top;
+
+ auto left = (screenRect.right - width) / 2;
+ auto top = (screenRect.bottom - height) / 2;
+
+ MoveWindow(handle, left, top, width, height, FALSE);
+ }
+
+ virtual void close() override {}
+ virtual bool getFocused() override { return GetFocus() == handle; }
+ virtual bool getVisible() override { return visible; }
+ virtual WindowHandle getNativeHandle() override
+ {
+ return WindowHandle::FromHwnd(handle);
+ }
+ virtual void setText(Slang::String text) override
+ {
+ SetWindowText(handle, text.toWString().begin());
+ }
+ virtual void show() override
+ {
+ ShowWindow(handle, SW_SHOW);
+ visible = true;
+ }
+ virtual void hide() override
+ {
+ ShowWindow(handle, SW_HIDE);
+ visible = false;
+ }
+ virtual int getCurrentDpi() override
+ {
+ int dpi = 96;
+ if (Win32AppContext::isWindows81OrGreater && getDpiForMonitor)
+ {
+ getDpiForMonitor(
+ MonitorFromWindow(handle, MONITOR_DEFAULTTOPRIMARY),
+ 0,
+ (UINT*)&dpi,
+ (UINT*)&dpi);
+ return dpi;
+ }
+ dpi = GetDeviceCaps(NULL, LOGPIXELSY);
+ return dpi;
+ }
+};
+
+Window* Application::createWindow(const WindowDesc& desc) { return new Win32PlatformWindow(desc); }
+
+
+} // namespace gfx
+
+#endif