From fc9968dc4fd58fab37476f48e4405c2743c5349c Mon Sep 17 00:00:00 2001 From: Yong He Date: Mon, 8 Mar 2021 10:01:20 -0800 Subject: Refactor window library. (#1739) * Refactor window library. * Fix project file * Fix warnings. --- build/visual-studio/core/core.vcxproj | 1 + build/visual-studio/core/core.vcxproj.filters | 3 + .../cpu-hello-world/cpu-hello-world.vcxproj | 2 +- .../gpu-printing/gpu-printing.vcxproj | 2 +- .../visual-studio/hello-world/hello-world.vcxproj | 2 +- build/visual-studio/platform/platform.vcxproj | 176 +++++++ .../platform/platform.vcxproj.filters | 39 ++ .../render-test-tool/render-test-tool.vcxproj | 10 +- .../shader-object/shader-object.vcxproj | 2 +- build/visual-studio/shader-toy/shader-toy.vcxproj | 2 +- examples/gpu-printing/main.cpp | 22 +- examples/hello-world/main.cpp | 37 +- examples/shader-toy/main.cpp | 82 +-- premake5.lua | 16 +- slang.sln | 2 +- source/core/slang-func-ptr.h | 202 ++++++++ tools/graphics-app-framework/gui.cpp | 436 ---------------- tools/graphics-app-framework/gui.h | 31 -- tools/graphics-app-framework/model.cpp | 566 --------------------- tools/graphics-app-framework/model.h | 77 --- tools/graphics-app-framework/vector-math.h | 15 - tools/graphics-app-framework/window.h | 133 ----- .../graphics-app-framework/windows/win-window.cpp | 416 --------------- tools/platform/gui.cpp | 438 ++++++++++++++++ tools/platform/gui.h | 34 ++ tools/platform/model.cpp | 566 +++++++++++++++++++++ tools/platform/model.h | 77 +++ tools/platform/performance-counter.h | 30 ++ tools/platform/vector-math.h | 15 + tools/platform/window.h | 235 +++++++++ tools/platform/windows/win-window.cpp | 442 ++++++++++++++++ 31 files changed, 2325 insertions(+), 1786 deletions(-) create mode 100644 build/visual-studio/platform/platform.vcxproj create mode 100644 build/visual-studio/platform/platform.vcxproj.filters create mode 100644 source/core/slang-func-ptr.h delete mode 100644 tools/graphics-app-framework/gui.cpp delete mode 100644 tools/graphics-app-framework/gui.h delete mode 100644 tools/graphics-app-framework/model.cpp delete mode 100644 tools/graphics-app-framework/model.h delete mode 100644 tools/graphics-app-framework/vector-math.h delete mode 100644 tools/graphics-app-framework/window.h delete mode 100644 tools/graphics-app-framework/windows/win-window.cpp create mode 100644 tools/platform/gui.cpp create mode 100644 tools/platform/gui.h create mode 100644 tools/platform/model.cpp create mode 100644 tools/platform/model.h create mode 100644 tools/platform/performance-counter.h create mode 100644 tools/platform/vector-math.h create mode 100644 tools/platform/window.h create mode 100644 tools/platform/windows/win-window.cpp diff --git a/build/visual-studio/core/core.vcxproj b/build/visual-studio/core/core.vcxproj index 01a7e3eb2..38bcf2ade 100644 --- a/build/visual-studio/core/core.vcxproj +++ b/build/visual-studio/core/core.vcxproj @@ -185,6 +185,7 @@ + diff --git a/build/visual-studio/core/core.vcxproj.filters b/build/visual-studio/core/core.vcxproj.filters index 3c2a7371d..9c718a6fb 100644 --- a/build/visual-studio/core/core.vcxproj.filters +++ b/build/visual-studio/core/core.vcxproj.filters @@ -54,6 +54,9 @@ Header Files + + Header Files + Header Files diff --git a/build/visual-studio/cpu-hello-world/cpu-hello-world.vcxproj b/build/visual-studio/cpu-hello-world/cpu-hello-world.vcxproj index 2fb93b42c..0ba5973d0 100644 --- a/build/visual-studio/cpu-hello-world/cpu-hello-world.vcxproj +++ b/build/visual-studio/cpu-hello-world/cpu-hello-world.vcxproj @@ -180,7 +180,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/build/visual-studio/gpu-printing/gpu-printing.vcxproj b/build/visual-studio/gpu-printing/gpu-printing.vcxproj index f7b783f39..b55e289d1 100644 --- a/build/visual-studio/gpu-printing/gpu-printing.vcxproj +++ b/build/visual-studio/gpu-printing/gpu-printing.vcxproj @@ -186,7 +186,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/build/visual-studio/hello-world/hello-world.vcxproj b/build/visual-studio/hello-world/hello-world.vcxproj index a7a51362b..28f8d1425 100644 --- a/build/visual-studio/hello-world/hello-world.vcxproj +++ b/build/visual-studio/hello-world/hello-world.vcxproj @@ -180,7 +180,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/build/visual-studio/platform/platform.vcxproj b/build/visual-studio/platform/platform.vcxproj new file mode 100644 index 000000000..518ac20f7 --- /dev/null +++ b/build/visual-studio/platform/platform.vcxproj @@ -0,0 +1,176 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3565FE5E-4FA3-11EB-AE93-0242AC130002} + true + Win32Proj + platform + 10.0.14393.0 + + + + StaticLibrary + true + Unicode + v140 + + + StaticLibrary + true + Unicode + v140 + + + StaticLibrary + false + Unicode + v140 + + + StaticLibrary + false + Unicode + v140 + + + + + + + + + + + + + + + + + + + ..\..\..\bin\windows-x86\debug\ + ..\..\..\intermediate\windows-x86\debug\platform\ + platform + .lib + + + ..\..\..\bin\windows-x64\debug\ + ..\..\..\intermediate\windows-x64\debug\platform\ + platform + .lib + + + ..\..\..\bin\windows-x86\release\ + ..\..\..\intermediate\windows-x86\release\platform\ + platform + .lib + + + ..\..\..\bin\windows-x64\release\ + ..\..\..\intermediate\windows-x64\release\platform\ + platform + .lib + + + + NotUsing + Level3 + _DEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\external\imgui;..\..\..\tools\gfx;%(AdditionalIncludeDirectories) + EditAndContinue + Disabled + MultiThreadedDebug + + + Windows + true + + + + + NotUsing + Level3 + _DEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\external\imgui;..\..\..\tools\gfx;%(AdditionalIncludeDirectories) + EditAndContinue + Disabled + MultiThreadedDebug + + + Windows + true + + + + + NotUsing + Level3 + NDEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\external\imgui;..\..\..\tools\gfx;%(AdditionalIncludeDirectories) + Full + true + true + false + true + MultiThreaded + + + Windows + true + true + + + + + NotUsing + Level3 + NDEBUG;%(PreprocessorDefinitions) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\external\imgui;..\..\..\tools\gfx;%(AdditionalIncludeDirectories) + Full + true + true + false + true + MultiThreaded + + + Windows + true + true + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/build/visual-studio/platform/platform.vcxproj.filters b/build/visual-studio/platform/platform.vcxproj.filters new file mode 100644 index 000000000..17d756451 --- /dev/null +++ b/build/visual-studio/platform/platform.vcxproj.filters @@ -0,0 +1,39 @@ + + + + + {21EB8090-0D4E-1035-B6D3-48EBA215DCB7} + + + {E9C7FDCE-D52A-8D73-7EB0-C5296AF258F6} + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + \ No newline at end of file diff --git a/build/visual-studio/render-test-tool/render-test-tool.vcxproj b/build/visual-studio/render-test-tool/render-test-tool.vcxproj index 0ce5b5361..8cca49a83 100644 --- a/build/visual-studio/render-test-tool/render-test-tool.vcxproj +++ b/build/visual-studio/render-test-tool/render-test-tool.vcxproj @@ -99,7 +99,7 @@ NotUsing Level3 _DEBUG;SLANG_SHARED_LIBRARY_TOOL;%(PreprocessorDefinitions) - ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\graphics-app-framework;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\platform;%(AdditionalIncludeDirectories) EditAndContinue Disabled MultiThreadedDebug @@ -118,7 +118,7 @@ NotUsing Level3 _DEBUG;SLANG_SHARED_LIBRARY_TOOL;%(PreprocessorDefinitions) - ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\graphics-app-framework;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\platform;%(AdditionalIncludeDirectories) EditAndContinue Disabled MultiThreadedDebug @@ -137,7 +137,7 @@ NotUsing Level3 NDEBUG;SLANG_SHARED_LIBRARY_TOOL;%(PreprocessorDefinitions) - ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\graphics-app-framework;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\platform;%(AdditionalIncludeDirectories) Full true true @@ -160,7 +160,7 @@ NotUsing Level3 NDEBUG;SLANG_SHARED_LIBRARY_TOOL;%(PreprocessorDefinitions) - ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\graphics-app-framework;%(AdditionalIncludeDirectories) + ..\..\..;..\..\..\external;..\..\..\source;..\..\..\tools\gfx;..\..\..\tools\platform;%(AdditionalIncludeDirectories) Full true true @@ -210,7 +210,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/build/visual-studio/shader-object/shader-object.vcxproj b/build/visual-studio/shader-object/shader-object.vcxproj index 1af67af5b..04315f910 100644 --- a/build/visual-studio/shader-object/shader-object.vcxproj +++ b/build/visual-studio/shader-object/shader-object.vcxproj @@ -180,7 +180,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/build/visual-studio/shader-toy/shader-toy.vcxproj b/build/visual-studio/shader-toy/shader-toy.vcxproj index 9fd7014a3..823841a46 100644 --- a/build/visual-studio/shader-toy/shader-toy.vcxproj +++ b/build/visual-studio/shader-toy/shader-toy.vcxproj @@ -181,7 +181,7 @@ {F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B} - + {3565FE5E-4FA3-11EB-AE93-0242AC130002} diff --git a/examples/gpu-printing/main.cpp b/examples/gpu-printing/main.cpp index 63eb31a82..e95233571 100644 --- a/examples/gpu-printing/main.cpp +++ b/examples/gpu-printing/main.cpp @@ -6,7 +6,7 @@ using Slang::ComPtr; #include "slang-gfx.h" -#include "tools/graphics-app-framework/window.h" +#include "tools/platform/window.h" #include "source/core/slang-basic.h" using namespace gfx; @@ -44,7 +44,7 @@ ComPtr compileShaderModuleFromFile(slang::ISession* slangSession const SlangResult compileRes = spCompile(slangRequest); if(auto diagnostics = spGetDiagnosticOutput(slangRequest)) { - reportError("%s", diagnostics); + printf("%s", diagnostics); } if(SLANG_FAILED(compileRes)) @@ -63,7 +63,6 @@ struct ExampleProgram int gWindowWidth = 640; int gWindowHeight = 480; -gfx::ApplicationContext* gAppContext; ComPtr gRenderer; ComPtr gSlangSession; @@ -151,8 +150,6 @@ Result execute() auto descriptorSet = gRenderer->createDescriptorSet(descriptorSetLayout, IDescriptorSet::Flag::Transient); if(!descriptorSet) return SLANG_FAIL; -// descriptorSet->setConstantBuffer(0, 0, gConstantBuffer); - gDescriptorSet = descriptorSet; ComputePipelineStateDesc desc; @@ -201,21 +198,12 @@ Result execute() }; -// This "inner" main function is used by the platform abstraction -// layer to deal with differences in how an entry point needs -// to be defined for different platforms. -// -void innerMain(ApplicationContext* context) +int main() { ExampleProgram app; - if (SLANG_FAILED(app.execute())) { - return exitApplication(context, 1); + return -1; } + return 0; } - -// This macro instantiates an appropriate main function to -// invoke the `innerMain` above. -// -GFX_CONSOLE_MAIN(innerMain) diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index 47016b48d..330b52a59 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -34,7 +34,7 @@ // #include "slang-gfx.h" #include "gfx-util/shader-cursor.h" -#include "tools/graphics-app-framework/window.h" +#include "tools/platform/window.h" #include "slang-com-ptr.h" #include "source/core/slang-basic.h" @@ -76,7 +76,7 @@ void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) { if( diagnosticsBlob != nullptr ) { - reportError("%s", (const char*) diagnosticsBlob->getBufferPointer()); + printf("%s", (const char*) diagnosticsBlob->getBufferPointer()); } } @@ -210,8 +210,7 @@ const uint32_t kSwapchainImageCount = 2; // of them come from the utility library we are using to simplify // building an example program. // -gfx::ApplicationContext* gAppContext; -gfx::Window* gWindow; +RefPtr gWindow; Slang::ComPtr gRenderer; ComPtr gPipelineState; @@ -230,12 +229,12 @@ Slang::Result initialize() { // Create a window for our application to render into. // - WindowDesc windowDesc; + platform::WindowDesc windowDesc; windowDesc.title = "Hello, World!"; windowDesc.width = gWindowWidth; windowDesc.height = gWindowHeight; - gWindow = createWindow(windowDesc); - + gWindow = platform::Application::createWindow(windowDesc); + gWindow->events.mainLoop = [this]() { renderFrame(); }; // Initialize the rendering layer. // // Note: for now we are hard-coding logic to use the @@ -323,8 +322,9 @@ Slang::Result initialize() swapchainDesc.height = gWindowHeight; swapchainDesc.imageCount = kSwapchainImageCount; swapchainDesc.queue = gQueue; - gSwapchain = gRenderer->createSwapchain( - swapchainDesc, gfx::WindowHandle::FromHwnd(getPlatformWindowHandle(gWindow))); + gfx::WindowHandle windowHandle; + memcpy(&windowHandle, &gWindow->getNativeHandle(), sizeof(windowHandle)); + gSwapchain = gRenderer->createSwapchain(swapchainDesc, windowHandle); IFramebufferLayout::AttachmentLayout renderTargetLayout = {gSwapchain->getDesc().format, 1}; IFramebufferLayout::AttachmentLayout depthLayout = {gfx::Format::D_Float32, 1}; @@ -405,11 +405,6 @@ Slang::Result initialize() renderPassDesc.depthStencilAccess = &depthStencilAccess; gRenderPass = gRenderer->createRenderPassLayout(renderPassDesc); - // Once we've initialized all the graphics API objects, - // it is time to show our application window and start rendering. - // - showWindow(gWindow); - return SLANG_OK; } @@ -526,7 +521,6 @@ void finalize() { gQueue->wait(); gSwapchain = nullptr; - destroyWindow(gWindow); } }; @@ -535,7 +529,7 @@ void finalize() // layer to deal with differences in how an entry point needs // to be defined for different platforms. // -void innerMain(ApplicationContext* context) +int innerMain() { // We construct an instance of our example application // `struct` type, and then walk through the lifecyle @@ -545,18 +539,17 @@ void innerMain(ApplicationContext* context) if (SLANG_FAILED(app.initialize())) { - return exitApplication(context, 1); + return -1; } - while(dispatchEvents(context)) - { - app.renderFrame(); - } + platform::Application::run(app.gWindow); app.finalize(); + + return 0; } // This macro instantiates an appropriate main function to // invoke the `innerMain` above. // -GFX_UI_MAIN(innerMain) +PLATFORM_UI_MAIN(innerMain) diff --git a/examples/shader-toy/main.cpp b/examples/shader-toy/main.cpp index 0d058fa2c..7c22954c0 100644 --- a/examples/shader-toy/main.cpp +++ b/examples/shader-toy/main.cpp @@ -20,9 +20,12 @@ using Slang::ComPtr; // compiler, and API. // #include "slang-gfx.h" -#include "tools/graphics-app-framework/window.h" +#include "tools/platform/window.h" +#include "tools/platform/performance-counter.h" #include "source/core/slang-basic.h" +#include + using namespace gfx; // In order to display a shader toy effect using rasterization-based shader @@ -82,7 +85,7 @@ void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) { if( diagnosticsBlob != nullptr ) { - reportError("%s", (const char*) diagnosticsBlob->getBufferPointer()); + printf("%s", (const char*) diagnosticsBlob->getBufferPointer()); } } @@ -329,8 +332,7 @@ int gWindowWidth = 1024; int gWindowHeight = 768; const uint32_t kSwapchainImageCount = 2; -gfx::ApplicationContext* gAppContext; -gfx::Window* gWindow; +Slang::RefPtr gWindow; Slang::ComPtr gRenderer; ComPtr gConstantBuffer; ComPtr gPipelineLayout; @@ -344,13 +346,15 @@ ComPtr gQueue; Result initialize() { - WindowDesc windowDesc; + platform::WindowDesc windowDesc; windowDesc.title = "Slang Shader Toy"; windowDesc.width = gWindowWidth; windowDesc.height = gWindowHeight; - windowDesc.eventHandler = &_handleEvent; - windowDesc.userData = this; - gWindow = createWindow(windowDesc); + gWindow = platform::Application::createWindow(windowDesc); + gWindow->events.mainLoop = [this]() { renderFrame(); }; + gWindow->events.mouseMove = [this](const platform::MouseEventArgs& e) { handleEvent(e); }; + gWindow->events.mouseUp = [this](const platform::MouseEventArgs& e) { handleEvent(e); }; + gWindow->events.mouseDown = [this](const platform::MouseEventArgs& e) { handleEvent(e); }; IRenderer::Desc rendererDesc; rendererDesc.rendererType = RendererType::DirectX11; @@ -430,8 +434,9 @@ Result initialize() swapchainDesc.height = gWindowHeight; swapchainDesc.imageCount = kSwapchainImageCount; swapchainDesc.queue = gQueue; - gSwapchain = gRenderer->createSwapchain( - swapchainDesc, gfx::WindowHandle::FromHwnd(getPlatformWindowHandle(gWindow))); + gfx::WindowHandle windowHandle; + memcpy(&windowHandle, &gWindow->getNativeHandle(), sizeof(windowHandle)); + gSwapchain = gRenderer->createSwapchain(swapchainDesc, windowHandle); IFramebufferLayout::AttachmentLayout renderTargetLayout = {gSwapchain->getDesc().format, 1}; IFramebufferLayout::AttachmentLayout depthLayout = {gfx::Format::D_Float32, 1}; @@ -513,9 +518,6 @@ Result initialize() renderPassDesc.renderTargetAccess = &renderTargetAccess; renderPassDesc.depthStencilAccess = &depthStencilAccess; gRenderPass = gRenderer->createRenderPassLayout(renderPassDesc); - - showWindow(gWindow); - return SLANG_OK; } @@ -527,7 +529,7 @@ float clickMouseX = 0.0f; float clickMouseY = 0.0f; bool firstTime = true; -uint64_t startTime = 0; +platform::TimePoint startTime; void renderFrame() { @@ -535,7 +537,7 @@ void renderFrame() auto commandBuffer = gQueue->createCommandBuffer(); if( firstTime ) { - startTime = getCurrentTime(); + startTime = platform::PerformanceCounter::now(); firstTime = false; } @@ -557,7 +559,7 @@ void renderFrame() uniforms.iMouse[1] = lastMouseY; uniforms.iMouse[2] = isMouseDown ? clickMouseX : -clickMouseX; uniforms.iMouse[3] = isMouseClick ? clickMouseY : -clickMouseY; - uniforms.iTime = float( double(getCurrentTime() - startTime) / double(getTimerFrequency()) ); + uniforms.iTime = platform::PerformanceCounter::getElapsedTimeInSeconds(startTime); uniforms.iResolution[0] = float(gWindowWidth); uniforms.iResolution[1] = float(gWindowHeight); @@ -588,59 +590,31 @@ void renderFrame() void finalize() { gQueue->wait(); - destroyWindow(gWindow); -} - -void handleEvent(Event const& event) -{ - switch( event.code ) - { - case EventCode::MouseDown: - isMouseDown = true; - lastMouseX = event.u.mouse.x; - lastMouseY = event.u.mouse.y; - break; - - case EventCode::MouseMoved: - lastMouseX = event.u.mouse.x; - lastMouseY = event.u.mouse.y; - break; - - case EventCode::MouseUp: - isMouseDown = false; - lastMouseX = event.u.mouse.x; - lastMouseY = event.u.mouse.y; - break; - - default: - break; - } } -static void _handleEvent(Event const& event) +void handleEvent(const platform::MouseEventArgs& event) { - ShaderToyApp* app = (ShaderToyApp*) getUserData(event.window); - app->handleEvent(event); + isMouseDown = ((int)event.buttons & (int)platform::ButtonState::Enum::LeftButton) != 0; + lastMouseX = (float)event.x; + lastMouseY = (float)event.y; } - }; -void innerMain(ApplicationContext* context) +int innerMain() { ShaderToyApp app; if (SLANG_FAILED(app.initialize())) { - return exitApplication(context, 1); + return -1; } - while(dispatchEvents(context)) - { - app.renderFrame(); - } + platform::Application::run(app.gWindow); app.finalize(); + + return 0; } -GFX_UI_MAIN(innerMain) +PLATFORM_UI_MAIN(innerMain) diff --git a/premake5.lua b/premake5.lua index e4f9b91cc..bd5c72c63 100644 --- a/premake5.lua +++ b/premake5.lua @@ -525,7 +525,7 @@ function example(name) -- and the `gfx` abstraction layer (which in turn -- depends on the `core` library). We specify all of that here, -- rather than in each example. - links { "slang", "core", "gfx", "gfx-util", "graphics-app-framework" } + links { "slang", "core", "gfx", "gfx-util", "platform" } end -- @@ -695,8 +695,8 @@ toolSharedLibrary "slang-reflection-test" toolSharedLibrary "render-test" uuid "61F7EB00-7281-4BF3-9470-7C2EA92620C3" - includedirs { ".", "external", "source", "tools/gfx", "tools/graphics-app-framework" } - links { "core", "slang", "gfx", "gfx-util", "graphics-app-framework" } + includedirs { ".", "external", "source", "tools/gfx", "tools/platform" } + links { "core", "slang", "gfx", "gfx-util", "platform" } if isTargetWindows then addSourceDir "tools/render-test/windows" @@ -824,21 +824,21 @@ tool "gfx-util" addSourceDir "tools/gfx-util" -- --- `graphics-app-framework` contains all the utils for a simple graphics application. +-- `platform` contains all the platform abstractions for a GUI application. -- -tool "graphics-app-framework" +tool "platform" uuid "3565fe5e-4fa3-11eb-ae93-0242ac130002" kind "StaticLib" pic "On" includedirs { ".", "external", "source", "external/imgui", "tools/gfx" } - addSourceDir "tools/graphics-app-framework" - + addSourceDir "tools/platform" + addSourceDir "tools/platform/linux" + addSourceDir "tools/platform/windows" -- Include windowing support on Windows. if isTargetWindows then systemversion "10.0.14393.0" - addSourceDir "tools/graphics-app-framework/windows" end -- diff --git a/slang.sln b/slang.sln index 2021ad656..0662c00c3 100644 --- a/slang.sln +++ b/slang.sln @@ -47,7 +47,7 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx", "build\visual-studio\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gfx-util", "build\visual-studio\gfx-util\gfx-util.vcxproj", "{F5ADB74E-02A7-44FB-AA3B-FC02F8AC7A4B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "graphics-app-framework", "build\visual-studio\graphics-app-framework\graphics-app-framework.vcxproj", "{3565FE5E-4FA3-11EB-AE93-0242AC130002}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "platform", "build\visual-studio\platform\platform.vcxproj", "{3565FE5E-4FA3-11EB-AE93-0242AC130002}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-cpp-extractor", "build\visual-studio\slang-cpp-extractor\slang-cpp-extractor.vcxproj", "{CA8A30D1-8FA9-4330-B7F7-84709246D8DC}" EndProject diff --git a/source/core/slang-func-ptr.h b/source/core/slang-func-ptr.h new file mode 100644 index 000000000..c861bc6b8 --- /dev/null +++ b/source/core/slang-func-ptr.h @@ -0,0 +1,202 @@ +#ifndef SLANG_CORE_FUNC_PTR_H +#define SLANG_CORE_FUNC_PTR_H + +#include "slang-smart-pointer.h" + +namespace Slang +{ + +template class FuncPtr : public RefObject +{ +public: + virtual TResult operator()(Arguments...) const = 0; + virtual bool operator==(const FuncPtr*) { return false; } + virtual FuncPtr* clone() = 0; + virtual ~FuncPtr() {} +}; + +template +class CdeclFuncPtr : public FuncPtr +{ +public: + typedef TResult (*FuncType)(Arguments...); + +private: + FuncType funcPtr; + +public: + CdeclFuncPtr(FuncType func) + : funcPtr(func) + {} + virtual FuncPtr* clone() override + { + auto rs = new CdeclFuncPtr(funcPtr); + return rs; + } + + virtual TResult operator()(Arguments... params) const override { return funcPtr(params...); } + + virtual bool operator==(const FuncPtr* ptr) override + { + auto cptr = dynamic_cast*>(ptr); + if (cptr) + return funcPtr == cptr->funcPtr; + else + return false; + } +}; + +template +class MemberFuncPtr : public FuncPtr +{ +public: + typedef TResult (Class::*FuncType)(Arguments...); + +private: + FuncType funcPtr; + Class* object; + +public: + MemberFuncPtr(Class* obj, FuncType func) + : funcPtr(func) + , object(obj) + {} + virtual FuncPtr* clone() override + { + auto rs = new MemberFuncPtr(object, funcPtr); + return rs; + } + virtual TResult operator()(Arguments... params) const override + { + return (object->*funcPtr)(params...); + } + + virtual bool operator==(const FuncPtr* ptr) override + { + auto cptr = dynamic_cast*>(ptr); + if (cptr) + return funcPtr == cptr->funcPtr && object == cptr->object; + else + return false; + } +}; + +template +class LambdaFuncPtr : public FuncPtr +{ +private: + F func; + +public: + LambdaFuncPtr(const F& _func) + : func(_func) + {} + virtual TResult operator()(Arguments... params) const override { return func(params...); } + virtual FuncPtr* clone() override + { + auto rs = new LambdaFuncPtr(func); + return rs; + } + virtual bool operator==(const FuncPtr* /*ptr*/) override + { + return false; + } +}; + +template class Func +{ +private: + RefPtr> funcPtr; + +public: + Func() {} + Func(typename CdeclFuncPtr::FuncType func) + { + funcPtr = new CdeclFuncPtr(func); + } + template + Func(Class* object, typename MemberFuncPtr::FuncType func) + { + funcPtr = new MemberFuncPtr(object, func); + } + template Func(const TFuncObj& func) + { + funcPtr = new LambdaFuncPtr(func); + } + Func& operator=(typename CdeclFuncPtr::FuncType func) + { + funcPtr = new CdeclFuncPtr(func); + return *this; + } + template + Func& operator=(const MemberFuncPtr& func) + { + funcPtr = new MemberFuncPtr(func); + return *this; + } + template Func& operator=(const TFuncObj& func) + { + funcPtr = new LambdaFuncPtr(func); + return *this; + } + bool operator==(const Func& f) { return *funcPtr == f.funcPtr.Ptr(); } + bool operator!=(const Func& f) { return !(*this == f); } + TResult operator()(Arguments... params) const { return (*funcPtr)(params...); } +}; + +// template +// using Action = Func; + +template class Action : public Func +{ +private: + RefPtr> funcPtr; + +public: + Action() {} + Action(const Action& proc) { funcPtr = proc.funcPtr; } + Action(typename CdeclFuncPtr::FuncType func) + { + funcPtr = new CdeclFuncPtr(func); + } + template + Action(Class* object, typename MemberFuncPtr::FuncType func) + { + funcPtr = new MemberFuncPtr(object, func); + } + template Action(const TFuncObj& func) + { + funcPtr = new LambdaFuncPtr(func); + } + Action& operator=(typename CdeclFuncPtr::FuncType func) + { + funcPtr = new CdeclFuncPtr(func); + return *this; + } + template + Action& operator=(const MemberFuncPtr& func) + { + funcPtr = new MemberFuncPtr(func); + return *this; + } + template Action& operator=(const TFuncObj& func) + { + funcPtr = new LambdaFuncPtr(func); + return *this; + } + Action& operator=(const Action& proc) + { + funcPtr = proc.funcPtr; + return *this; + } + void clear() { funcPtr = nullptr; } + void operator()(Arguments... params) const + { + if (funcPtr) + (*funcPtr)(params...); + } +}; + +} // namespace Slang + +#endif diff --git a/tools/graphics-app-framework/gui.cpp b/tools/graphics-app-framework/gui.cpp deleted file mode 100644 index 3d4283131..000000000 --- a/tools/graphics-app-framework/gui.cpp +++ /dev/null @@ -1,436 +0,0 @@ -// gui.cpp -#include "gui.h" - -#ifdef _WIN32 -#include -#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, - IRenderer* inRenderer, - ICommandQueue* inQueue, - IFramebufferLayout* framebufferLayout) - : renderer(inRenderer) - , queue(inQueue) -{ - 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::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 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 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 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/graphics-app-framework/gui.h b/tools/graphics-app-framework/gui.h deleted file mode 100644 index 680cea14b..000000000 --- a/tools/graphics-app-framework/gui.h +++ /dev/null @@ -1,31 +0,0 @@ -// 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 gfx { - -struct GUI : Slang::RefObject -{ - GUI(Window* window, IRenderer* renderer, ICommandQueue* queue, IFramebufferLayout* framebufferLayout); - ~GUI(); - - void beginFrame(); - void endFrame(IFramebuffer* framebuffer); - -private: - Slang::ComPtr renderer; - Slang::ComPtr queue; - Slang::ComPtr renderPass; - Slang::ComPtr pipelineState; - Slang::ComPtr descriptorSetLayout; - Slang::ComPtr pipelineLayout; - Slang::ComPtr samplerState; -}; - -} // gfx diff --git a/tools/graphics-app-framework/model.cpp b/tools/graphics-app-framework/model.cpp deleted file mode 100644 index 6984a6818..000000000 --- a/tools/graphics-app-framework/model.cpp +++ /dev/null @@ -1,566 +0,0 @@ -// 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 -#include -#include - -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 - void add(T const& v) - { - state ^= std::hash()(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 - { - 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 - { - 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 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 subresourceInitData; - std::vector 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 objShapes; - std::vector 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 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 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 mapObjIndexToFlatIndex; - std::vector flatVertices; - std::vector flatIndices; - - MeshData* currentMesh = nullptr; - MeshData currentMeshStorage; - - std::vector 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 = ¤tMeshStorage; - 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/graphics-app-framework/model.h b/tools/graphics-app-framework/model.h deleted file mode 100644 index 412f10a1d..000000000 --- a/tools/graphics-app-framework/model.h +++ /dev/null @@ -1,77 +0,0 @@ -// model.h -#pragma once - -#include "slang-gfx.h" -#include "vector-math.h" -#include "slang-com-ptr.h" -#include -#include - -namespace gfx { - -struct ModelLoader -{ - struct MaterialData - { - glm::vec3 diffuseColor; - glm::vec3 specularColor; - float specularity; - - ComPtr 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 vertexBuffer; - ComPtr 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 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 deleted file mode 100644 index e35cb46ac..000000000 --- a/tools/graphics-app-framework/vector-math.h +++ /dev/null @@ -1,15 +0,0 @@ -// 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 deleted file mode 100644 index 2e216aacf..000000000 --- a/tools/graphics-app-framework/window.h +++ /dev/null @@ -1,133 +0,0 @@ -// window.h -#pragma once - -#include - -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 destroyWindow(Window* window); -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); - -#ifdef _MSC_VER -#ifdef _DEBUG -# define GFX_DUMP_LEAK _CrtDumpMemoryLeaks(); -#endif -#endif -#ifndef GFX_DUMP_LEAK -#define GFX_DUMP_LEAK -#endif -#define GFX_UI_MAIN(APPLICATION_ENTRY) \ - int __stdcall WinMain( \ - void* instance, \ - void* /* prevInstance */, \ - void* /* commandLine */, \ - int showCommand) { \ - auto result = gfx::runWindowsApplication(&(APPLICATION_ENTRY), instance, showCommand); \ - GFX_DUMP_LEAK \ - return result; \ - } - -#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 deleted file mode 100644 index 2beb65d70..000000000 --- a/tools/graphics-app-framework/windows/win-window.cpp +++ /dev/null @@ -1,416 +0,0 @@ -// win-window.cpp -#include "../window.h" - -#include "core/slang-smart-pointer.h" - -#include - -#ifdef _MSC_VER -#include -#if (_MSC_VER < 1900) -#define snprintf sprintf_s -#endif -#endif - -#include - -#if _WIN32 -#include -#include -#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 : public Slang::RefObject -{ - 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 = LoadCursorW(NULL, IDC_ARROW); - 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 = 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, windowStyle, FALSE); - - HWND windowHandle = CreateWindowExW( - windowExtendedStyle, - (LPWSTR) getWindowClassAtom(), - windowTitle, - windowStyle, - CW_USEDEFAULT, - 0, // x, y - windowRect.right - windowRect.left, - windowRect.bottom - windowRect.top, - NULL, // parent - NULL, // menu - instance, - window); - - - if(!windowHandle) - { - delete window; - return nullptr; - } - - window->handle = windowHandle; - return window; -} - -void destroyWindow(Window* window) -{ - DestroyWindow(window->handle); - delete 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 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 +#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 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 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 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 renderer; + Slang::ComPtr queue; + Slang::ComPtr renderPass; + Slang::ComPtr pipelineState; + Slang::ComPtr descriptorSetLayout; + Slang::ComPtr pipelineLayout; + Slang::ComPtr 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 +#include +#include + +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 + void add(T const& v) + { + state ^= std::hash()(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 + { + 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 + { + 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 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 subresourceInitData; + std::vector 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 objShapes; + std::vector 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 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 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 mapObjIndexToFlatIndex; + std::vector flatVertices; + std::vector flatIndices; + + MeshData* currentMesh = nullptr; + MeshData currentMeshStorage; + + std::vector 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 = ¤tMeshStorage; + 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 +#include + +namespace gfx { + +struct ModelLoader +{ + struct MaterialData + { + glm::vec3 diffuseColor; + glm::vec3 specularColor; + float specularity; + + ComPtr 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 vertexBuffer; + ComPtr 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 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 + +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(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 keyDown; + Slang::Action keyUp; + Slang::Action keyPress; + Slang::Action mouseMove; + Slang::Action mouseUp; + Slang::Action 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 +#include +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 mainWindow; + static OrderedDictionary windows; + static HWND mainWindowHandle; + static bool isTerminated; + static bool isWindows81OrGreater; +}; + +EnableNonClientDpiScalingProc Win32AppContext::enableNonClientDpiScaling = nullptr; +HWND Win32AppContext::mainWindowHandle = nullptr; +RefPtr Win32AppContext::mainWindow; +OrderedDictionary 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, ¤tRect); + + 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, ¤tRect); + 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, ¤tRect); + + 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 -- cgit v1.2.3