From 1a5ec8b5690d8037a026a76645eee305f8175786 Mon Sep 17 00:00:00 2001 From: Luke Street Date: Mon, 24 May 2021 17:25:31 -0400 Subject: [PATCH] Add initial imgui implementation --- .gitmodules | 3 + CMakeLists.txt | 1 + Runtime/CMain.cpp | 93 ++++++++++--- Runtime/CMakeLists.txt | 2 +- Runtime/IMain.hpp | 2 +- Runtime/MP1/MP1.cpp | 14 +- Runtime/MP1/MP1.hpp | 6 +- extern/boo | 2 +- hecl/lib/CMakeLists.txt | 1 + hecl/lib/Pipeline.cpp | 2 +- hecl/shaderc/shaderc.cpp | 9 +- imgui/CMakeLists.txt | 16 +++ imgui/ImGuiEngine.cpp | 286 +++++++++++++++++++++++++++++++++++++++ imgui/ImGuiEngine.hpp | 48 +++++++ imgui/ImguiShader.shader | 134 ++++++++++++++++++ imgui/imconfig_user.h | 6 + 16 files changed, 585 insertions(+), 40 deletions(-) create mode 100644 imgui/CMakeLists.txt create mode 100644 imgui/ImGuiEngine.cpp create mode 100644 imgui/ImGuiEngine.hpp create mode 100644 imgui/ImguiShader.shader create mode 100644 imgui/imconfig_user.h diff --git a/.gitmodules b/.gitmodules index 48255ce88..53410b35b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -58,3 +58,6 @@ path = extern/zeus url = ../zeus.git branch = master +[submodule "extern/imgui"] + path = extern/imgui + url = https://github.com/ocornut/imgui.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 076807cbd..f9cf62c32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -396,6 +396,7 @@ add_subdirectory(extern/boo) add_subdirectory(hecl/shaderc) include(hecl/ApplicationTools.cmake) add_subdirectory(Shaders) +add_subdirectory(imgui) add_subdirectory(extern/libSquish) add_subdirectory(extern/libpng) add_subdirectory(extern/libjpeg-turbo) diff --git a/Runtime/CMain.cpp b/Runtime/CMain.cpp index c2e3923ca..d8d7439bc 100644 --- a/Runtime/CMain.cpp +++ b/Runtime/CMain.cpp @@ -1,17 +1,21 @@ #include #include -#include #include #include "boo/boo.hpp" #include "logvisor/logvisor.hpp" +#include "ImGuiEngine.hpp" #include "Runtime/Graphics/CGraphics.hpp" #include "Runtime/MP1/MP1.hpp" -#include "cmake-build-debug-llvm/hecl/DataSpecRegistry.hpp" #include "amuse/BooBackend.hpp" + #include "../version.h" +/* Static reference to dataspec additions + * (used by MSVC to definitively link DataSpecs) */ +#include "DataSpecRegistry.hpp" + using namespace std::literals; static logvisor::Module AthenaLog("Athena"); @@ -104,10 +108,14 @@ static hecl::SystemString CPUFeatureString(const zeus::CPUInfo& cpuInf) { } struct WindowCallback : boo::IWindowCallback { + friend struct Application; + +private: bool m_fullscreenToggleRequested = false; boo::SWindowRect m_lastRect; bool m_rectDirty = false; bool m_windowInvalid = false; + ImGuiWindowCallback m_imguiCallback; void resized(const boo::SWindowRect& rect, bool sync) override { m_lastRect = rect; @@ -115,11 +123,12 @@ struct WindowCallback : boo::IWindowCallback { } void mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override { - if (g_mainMP1) { + if (!ImGuiWindowCallback::m_mouseCaptured && g_mainMP1) { if (MP1::CGameArchitectureSupport* as = g_mainMP1->GetArchSupport()) { as->mouseDown(coord, button, mods); } } + m_imguiCallback.mouseDown(coord, button, mods); } void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override { @@ -128,30 +137,38 @@ struct WindowCallback : boo::IWindowCallback { as->mouseUp(coord, button, mods); } } + m_imguiCallback.mouseUp(coord, button, mods); } void mouseMove(const boo::SWindowCoord& coord) override { - if (g_mainMP1) { + if (!ImGuiWindowCallback::m_mouseCaptured && g_mainMP1) { if (MP1::CGameArchitectureSupport* as = g_mainMP1->GetArchSupport()) { as->mouseMove(coord); } } + m_imguiCallback.mouseMove(coord); } + void mouseEnter(const boo::SWindowCoord& coord) override { m_imguiCallback.mouseEnter(coord); } + + void mouseLeave(const boo::SWindowCoord& coord) override { m_imguiCallback.mouseLeave(coord); } + void scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) override { - if (g_mainMP1) { + if (!ImGuiWindowCallback::m_mouseCaptured && g_mainMP1) { if (MP1::CGameArchitectureSupport* as = g_mainMP1->GetArchSupport()) { as->scroll(coord, scroll); } } + m_imguiCallback.scroll(coord, scroll); } void charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) override { - if (g_mainMP1) { + if (!ImGuiWindowCallback::m_keyboardCaptured && g_mainMP1) { if (MP1::CGameArchitectureSupport* as = g_mainMP1->GetArchSupport()) { as->charKeyDown(charCode, mods, isRepeat); } } + m_imguiCallback.charKeyDown(charCode, mods, isRepeat); } void charKeyUp(unsigned long charCode, boo::EModifierKey mods) override { @@ -160,16 +177,19 @@ struct WindowCallback : boo::IWindowCallback { as->charKeyUp(charCode, mods); } } + m_imguiCallback.charKeyUp(charCode, mods); } void specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) override { - if (g_mainMP1) { + if (!ImGuiWindowCallback::m_keyboardCaptured && g_mainMP1) { if (MP1::CGameArchitectureSupport* as = g_mainMP1->GetArchSupport()) { as->specialKeyDown(key, mods, isRepeat); } } - if (key == boo::ESpecialKey::Enter && True(mods & boo::EModifierKey::Alt)) + if (key == boo::ESpecialKey::Enter && True(mods & boo::EModifierKey::Alt)) { m_fullscreenToggleRequested = true; + } + m_imguiCallback.specialKeyDown(key, mods, isRepeat); } void specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) override { @@ -178,12 +198,14 @@ struct WindowCallback : boo::IWindowCallback { as->specialKeyUp(key, mods); } } + m_imguiCallback.specialKeyUp(key, mods); } void destroyed() override { m_windowInvalid = true; } }; struct Application : boo::IApplicationCallback { +private: std::shared_ptr m_window; WindowCallback m_windowCallback; hecl::Runtime::FileStoreManager& m_fileMgr; @@ -201,6 +223,10 @@ struct Application : boo::IApplicationCallback { std::atomic_bool m_running = {true}; bool m_noShaderWarmup = false; + bool m_firstFrame = true; + using delta_clock = std::chrono::high_resolution_clock; + std::chrono::time_point m_prevFrameTime; + public: Application(hecl::Runtime::FileStoreManager& fileMgr, hecl::CVarManager& cvarMgr, hecl::CVarCommons& cvarCmns) : m_fileMgr(fileMgr), m_cvarManager(cvarMgr), m_cvarCommons(cvarCmns) {} @@ -216,12 +242,13 @@ public: m_window->showWindow(); boo::SWindowRect rect = m_window->getWindowFrame(); - m_window->getMainContextDataFactory()->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) { + boo::IGraphicsDataFactory* gfxF = m_window->getMainContextDataFactory(); + gfxF->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) { m_renderTex = ctx.newRenderTexture(rect.size[0], rect.size[1], boo::TextureClampMode::ClampToEdge, 3, 3); return true; } BooTrace); - m_pipelineConv = hecl::NewPipelineConverter(m_window->getMainContextDataFactory()); + m_pipelineConv = hecl::NewPipelineConverter(gfxF); hecl::conv = m_pipelineConv.get(); m_voiceEngine = boo::NewAudioVoiceEngine(); @@ -258,6 +285,7 @@ public: onAppIdle(); } + ImGuiEngine::Shutdown(); if (m_window) { m_window->getCommandQueue()->stopRenderer(); } @@ -329,29 +357,60 @@ public: m_windowCallback.m_fullscreenToggleRequested = false; } + boo::IGraphicsDataFactory* gfxF = m_window->getMainContextDataFactory(); if (!g_mainMP1) { - g_mainMP1.emplace(nullptr, nullptr, m_window->getMainContextDataFactory(), gfxQ, m_renderTex.get()); + g_mainMP1.emplace(nullptr, nullptr, gfxF, gfxQ, m_renderTex.get()); g_mainMP1->Init(m_fileMgr, &m_cvarManager, m_window.get(), m_voiceEngine.get(), *m_amuseAllocWrapper); if (!m_noShaderWarmup) { g_mainMP1->WarmupShaders(); } + ImGuiEngine::Initialize(gfxF, m_window->getWindowFrame()); } - if (g_mainMP1->Proc()) { + float dt = 1 / 60.f; + float realDt = dt; + auto now = delta_clock::now(); + if (m_firstFrame) { + m_firstFrame = false; + } else { + using delta_duration = std::chrono::duration>; + realDt = std::chrono::duration_cast(now - m_prevFrameTime).count(); + if (m_cvarCommons.m_variableDt->toBoolean()) { + dt = std::min(realDt, 1 / 30.f); + } + } + m_prevFrameTime = now; + + ImGuiEngine::Begin(realDt); + + if (g_mainMP1->Proc(dt)) { m_running.store(false); return; } - gfxQ->setRenderTarget(m_renderTex); { OPTICK_EVENT("Draw"); - if (g_Renderer) + gfxQ->setRenderTarget(m_renderTex); + if (g_Renderer != nullptr) { g_Renderer->BeginScene(); + } g_mainMP1->Draw(); - if (g_Renderer) + if (g_Renderer != nullptr) { g_Renderer->EndScene(); + } + } + + { + OPTICK_EVENT("ImGui Draw"); + ImGuiEngine::End(); + ImGuiEngine::Draw(gfxQ); + } + + { + OPTICK_EVENT("Execute"); gfxQ->execute(); } + gfxQ->resolveDisplay(m_renderTex); if (g_ResFactory != nullptr) { @@ -380,7 +439,9 @@ public: [[nodiscard]] bool getDeepColor() const { return m_cvarCommons.getDeepColor(); } - [[nodiscard]] int64_t getTargetFrameTime() const { return m_cvarCommons.getVariableFrameTime() ? 0 : 1000000000L / 60; } + [[nodiscard]] int64_t getTargetFrameTime() const { + return m_cvarCommons.getVariableFrameTime() ? 0 : 1000000000L / 60; + } }; } // namespace metaforce diff --git a/Runtime/CMakeLists.txt b/Runtime/CMakeLists.txt index b31f55147..8053af14b 100644 --- a/Runtime/CMakeLists.txt +++ b/Runtime/CMakeLists.txt @@ -201,7 +201,7 @@ elseif(UNIX) endif() add_executable(metaforce CMain.cpp ${PLAT_SRCS}) -target_link_libraries(metaforce PUBLIC RuntimeCommon ${PLAT_LIBS}) +target_link_libraries(metaforce PUBLIC RuntimeCommon ${PLAT_LIBS} imgui) if(COMMAND add_sanitizers) add_sanitizers(metaforce) diff --git a/Runtime/IMain.hpp b/Runtime/IMain.hpp index 416435c48..19193ea69 100644 --- a/Runtime/IMain.hpp +++ b/Runtime/IMain.hpp @@ -36,7 +36,7 @@ public: virtual void Init(const hecl::Runtime::FileStoreManager& storeMgr, hecl::CVarManager* cvarMgr, boo::IWindow* window, boo::IAudioVoiceEngine* voiceEngine, amuse::IBackendVoiceAllocator& backend) = 0; virtual void Draw() = 0; - virtual bool Proc() = 0; + virtual bool Proc(float dt) = 0; virtual void Shutdown() = 0; virtual boo::IWindow* GetMainWindow() const = 0; virtual EFlowState GetFlowState() const = 0; diff --git a/Runtime/MP1/MP1.cpp b/Runtime/MP1/MP1.cpp index a7f297a3f..080f7ab7a 100644 --- a/Runtime/MP1/MP1.cpp +++ b/Runtime/MP1/MP1.cpp @@ -897,7 +897,7 @@ void CMain::WarmupShaders() { WarmupLog.report(logvisor::Info, FMT_STRING("Began warmup of {} objects"), m_warmupTags.size()); } -bool CMain::Proc() { +bool CMain::Proc(float dt) { CRandom16::ResetNumNextCalls(); // Warmup cycle overrides update if (m_warmupTags.size()) @@ -907,18 +907,6 @@ bool CMain::Proc() { m_loadedPersistentResources = true; } - float dt = 1 / 60.f; - if (m_cvarCommons->m_variableDt->toBoolean()) { - auto now = delta_clock::now(); - if (m_firstFrame) { - m_firstFrame = false; - } else { - using delta_duration = std::chrono::duration>; - dt = std::min(std::chrono::duration_cast(now - m_prevFrameTime).count(), 1 / 30.f); - } - m_prevFrameTime = now; - } - m_console->proc(); if (!m_console->isOpen()) { CGBASupport::GlobalPoll(); diff --git a/Runtime/MP1/MP1.hpp b/Runtime/MP1/MP1.hpp index 17d7d0ece..b53978ec1 100644 --- a/Runtime/MP1/MP1.hpp +++ b/Runtime/MP1/MP1.hpp @@ -253,10 +253,6 @@ private: bool m_needsWarmupClear = false; bool m_loadedPersistentResources = false; bool m_doQuit = false; - - bool m_firstFrame = true; - using delta_clock = std::chrono::high_resolution_clock; - std::chrono::time_point m_prevFrameTime; DataSpec::MetaforceVersionInfo m_version; void InitializeSubsystems(); @@ -286,7 +282,7 @@ public: void Init(const hecl::Runtime::FileStoreManager& storeMgr, hecl::CVarManager* cvarManager, boo::IWindow* window, boo::IAudioVoiceEngine* voiceEngine, amuse::IBackendVoiceAllocator& backend) override; void WarmupShaders() override; - bool Proc() override; + bool Proc(float dt) override; void Draw() override; void Shutdown() override; boo::IWindow* GetMainWindow() const override; diff --git a/extern/boo b/extern/boo index 55deba091..d13fbda0c 160000 --- a/extern/boo +++ b/extern/boo @@ -1 +1 @@ -Subproject commit 55deba091336b787d405a8747bff9757713e7960 +Subproject commit d13fbda0c0ef0a832ed31ab4a7e91a23c6167f52 diff --git a/hecl/lib/CMakeLists.txt b/hecl/lib/CMakeLists.txt index 566702531..1f841455a 100644 --- a/hecl/lib/CMakeLists.txt +++ b/hecl/lib/CMakeLists.txt @@ -40,6 +40,7 @@ set(HECL_HEADERS ../include/hecl/MathExtras.hpp ../include/hecl/UniformBufferPool.hpp ../include/hecl/VertexBufferPool.hpp + ../include/hecl/IndexBufferPool.hpp ../include/hecl/PipelineBase.hpp ../include/hecl/Pipeline.hpp ../include/hecl/Compilers.hpp) diff --git a/hecl/lib/Pipeline.cpp b/hecl/lib/Pipeline.cpp index 71bee706a..05eef00cd 100644 --- a/hecl/lib/Pipeline.cpp +++ b/hecl/lib/Pipeline.cpp @@ -164,4 +164,4 @@ SPECIALIZE_STAGE_CONVERTER(PlatformType::NX) #endif -} // namespace hecl \ No newline at end of file +} // namespace hecl diff --git a/hecl/shaderc/shaderc.cpp b/hecl/shaderc/shaderc.cpp index 74b0b58ad..ca4cfb181 100644 --- a/hecl/shaderc/shaderc.cpp +++ b/hecl/shaderc/shaderc.cpp @@ -537,6 +537,9 @@ bool Compiler::compileFile(SystemStringView file, std::string_view baseName, fmt::print(out.second, FMT_STRING("static const boo::VertexElementDescriptor {}_vtxfmtelems[] = {{\n"), shaderName); for (const auto& attr : shaderAttributes) { switch (attr.first & boo::VertexSemantic::SemanticMask) { + case boo::VertexSemantic::Position2: + SemanticOut(out.second, attr, FMT_STRING("{{boo::VertexSemantic::Position2{}, {}}},\n")); + break; case boo::VertexSemantic::Position3: SemanticOut(out.second, attr, FMT_STRING("{{boo::VertexSemantic::Position3{}, {}}},\n")); break; @@ -611,7 +614,9 @@ bool Compiler::compileFile(SystemStringView file, std::string_view baseName, boo::VertexSemantic orsem = inst ? boo::VertexSemantic::Instanced : boo::VertexSemantic::None; int idxNum = int(strtoul(idx.c_str(), nullptr, 10)); std::transform(semantic.begin(), semantic.end(), semantic.begin(), ::tolower); - if (semantic == "position3") + if (semantic == "position2") + shaderAttributes.push_back(std::make_pair(boo::VertexSemantic::Position2 | orsem, idxNum)); + else if (semantic == "position3") shaderAttributes.push_back(std::make_pair(boo::VertexSemantic::Position3 | orsem, idxNum)); else if (semantic == "position4") shaderAttributes.push_back(std::make_pair(boo::VertexSemantic::Position4 | orsem, idxNum)); @@ -794,7 +799,7 @@ bool Compiler::compileFile(SystemStringView file, std::string_view baseName, out.first << ","; } out.first << "\n"; - + return true; } diff --git a/imgui/CMakeLists.txt b/imgui/CMakeLists.txt new file mode 100644 index 000000000..bd56e2514 --- /dev/null +++ b/imgui/CMakeLists.txt @@ -0,0 +1,16 @@ +add_library(imgui + ../extern/imgui/imgui.cpp + ../extern/imgui/imgui_demo.cpp + ../extern/imgui/imgui_draw.cpp + ../extern/imgui/imgui_tables.cpp + ../extern/imgui/imgui_widgets.cpp + ../extern/imgui/backends/imgui_impl_vulkan.cpp + ImGuiEngine.cpp + ImGuiEngine.hpp + ) +target_include_directories(imgui PUBLIC ../extern/imgui ${CMAKE_CURRENT_SOURCE_DIR}) +target_compile_definitions(imgui PUBLIC IMGUI_USER_CONFIG="imconfig_user.h") +target_link_libraries(imgui PRIVATE boo hecl-light RetroDataSpec) + +add_shader(ImguiShader) +target_link_libraries(shader_ImguiShader PRIVATE hecl-light) diff --git a/imgui/ImGuiEngine.cpp b/imgui/ImGuiEngine.cpp new file mode 100644 index 000000000..5883356d6 --- /dev/null +++ b/imgui/ImGuiEngine.cpp @@ -0,0 +1,286 @@ +#include "ImGuiEngine.hpp" + +#include "hecl/Pipeline.hpp" +#include "hecl/VertexBufferPool.hpp" + +#include + +namespace metaforce { +static logvisor::Module Log{"ImGuiEngine"}; + +struct ImGuiEngine::Input ImGuiEngine::Input; + +static boo::IGraphicsDataFactory* m_factory; +static boo::ObjToken ShaderPipeline; +static boo::ObjToken VertexBuffer; +static boo::ObjToken IndexBuffer; +static boo::ObjToken UniformBuffer; +static boo::ObjToken ShaderDataBinding; +static boo::ObjToken ImGuiAtlas; +static boo::SWindowRect WindowRect; + +struct Uniform { + zeus::CMatrix4f xf; +}; + +static size_t VertexBufferSize = 5000; +static size_t IndexBufferSize = 5000; + +void ImGuiEngine::Initialize(boo::IGraphicsDataFactory* factory, const boo::SWindowRect& rect) { + m_factory = factory; + WindowRect = rect; + + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + io.KeyMap[ImGuiKey_Tab] = 256 + static_cast(boo::ESpecialKey::Tab); + io.KeyMap[ImGuiKey_LeftArrow] = 256 + static_cast(boo::ESpecialKey::Left); + io.KeyMap[ImGuiKey_RightArrow] = 256 + static_cast(boo::ESpecialKey::Right); + io.KeyMap[ImGuiKey_UpArrow] = 256 + static_cast(boo::ESpecialKey::Up); + io.KeyMap[ImGuiKey_DownArrow] = 256 + static_cast(boo::ESpecialKey::Down); + io.KeyMap[ImGuiKey_PageUp] = 256 + static_cast(boo::ESpecialKey::PgUp); + io.KeyMap[ImGuiKey_PageDown] = 256 + static_cast(boo::ESpecialKey::PgDown); + io.KeyMap[ImGuiKey_Home] = 256 + static_cast(boo::ESpecialKey::Home); + io.KeyMap[ImGuiKey_End] = 256 + static_cast(boo::ESpecialKey::End); + io.KeyMap[ImGuiKey_Insert] = 256 + static_cast(boo::ESpecialKey::Insert); + io.KeyMap[ImGuiKey_Delete] = 256 + static_cast(boo::ESpecialKey::Delete); + io.KeyMap[ImGuiKey_Backspace] = 256 + static_cast(boo::ESpecialKey::Backspace); + io.KeyMap[ImGuiKey_Space] = ' '; + io.KeyMap[ImGuiKey_Enter] = 256 + static_cast(boo::ESpecialKey::Enter); + io.KeyMap[ImGuiKey_Escape] = 256 + static_cast(boo::ESpecialKey::Esc); + io.KeyMap[ImGuiKey_A] = 'A'; // for text edit CTRL+A: select all + io.KeyMap[ImGuiKey_C] = 'C'; // for text edit CTRL+C: copy + io.KeyMap[ImGuiKey_V] = 'V'; // for text edit CTRL+V: paste + io.KeyMap[ImGuiKey_X] = 'X'; // for text edit CTRL+X: cut + io.KeyMap[ImGuiKey_Y] = 'Y'; // for text edit CTRL+Y: redo + io.KeyMap[ImGuiKey_Z] = 'Z'; // for text edit CTRL+Z: undo + + int width = 0; + int height = 0; + unsigned char* pixels = nullptr; + io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); + factory->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) { + ShaderPipeline = hecl::conv->convert(Shader_ImguiShader{}); + ImGuiAtlas = ctx.newStaticTexture(width, height, 1, boo::TextureFormat::RGBA8, boo::TextureClampMode::ClampToEdge, + pixels, width * height * 4); + VertexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Vertex, sizeof(ImDrawVert), VertexBufferSize); + IndexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Index, sizeof(ImDrawIdx), IndexBufferSize); + UniformBuffer = ctx.newDynamicBuffer(boo::BufferUse::Uniform, sizeof(Uniform), 1); + BuildShaderDataBinding(ctx); + return true; + } BooTrace); + io.Fonts->SetTexID(ImGuiAtlas.get()); +} + +void ImGuiEngine::Shutdown() { + ImGui::DestroyContext(); + ShaderPipeline.reset(); +} + +void ImGuiEngine::Begin(float dt) { + ImGuiIO& io = ImGui::GetIO(); + io.DeltaTime = dt; + io.DisplaySize.x = WindowRect.size[0]; + io.DisplaySize.y = WindowRect.size[1]; + // TODO + // io.DisplayFramebufferScale = ImVec2{1.f, 1.f}; + if (Input.m_mouseIn) { + io.MousePos = ImVec2{static_cast(Input.m_mousePos.pixel[0]), + static_cast(WindowRect.size[1] - Input.m_mousePos.pixel[1])}; + } else { + io.MousePos = ImVec2{-FLT_MAX, -FLT_MAX}; + } + memcpy(io.MouseDown, Input.m_mouseButtons.data(), sizeof(io.MouseDown)); + io.MouseWheel = static_cast(Input.m_scrollDelta.delta[1]); + io.MouseWheelH = static_cast(Input.m_scrollDelta.delta[0]); + Input.m_scrollDelta.zeroOut(); + io.KeyCtrl = True(Input.m_modifiers & boo::EModifierKey::Ctrl); + io.KeyShift = True(Input.m_modifiers & boo::EModifierKey::Shift); + io.KeyAlt = True(Input.m_modifiers & boo::EModifierKey::Alt); + io.KeySuper = True(Input.m_modifiers & boo::EModifierKey::Command); + memcpy(io.KeysDown, Input.m_keys.data(), sizeof(io.KeysDown)); + + for (const auto c : ImGuiEngine::Input.m_charCodes) { + io.AddInputCharacter(c); + } + ImGuiEngine::Input.m_charCodes.clear(); + + ImGuiWindowCallback::m_mouseCaptured = io.WantCaptureMouse; + ImGuiWindowCallback::m_keyboardCaptured = io.WantCaptureKeyboard; + + ImGui::NewFrame(); + ImGui::ShowDemoWindow(); +} + +void ImGuiEngine::End() { + ImGui::EndFrame(); + ImGui::Render(); + ImDrawData* drawData = ImGui::GetDrawData(); + + float L = drawData->DisplayPos.x; + float R = drawData->DisplayPos.x + drawData->DisplaySize.x; + float T = drawData->DisplayPos.y; + float B = drawData->DisplayPos.y + drawData->DisplaySize.y; + float N = 0.f; + float F = 1.f; + zeus::CMatrix4f projXf = zeus::CMatrix4f{ + zeus::CVector4f{2.0f / (R - L), 0.0f, 0.0f, 0.0f}, + zeus::CVector4f{0.0f, 2.0f / (T - B), 0.0f, 0.0f}, + zeus::CVector4f{0.0f, 0.0f, 1 / (F - N), 0.0f}, + zeus::CVector4f{(R + L) / (L - R), (T + B) / (B - T), N / (F - N), 1.0f}, + }; + + m_factory->commitTransaction([&](boo::IGraphicsDataFactory::Context& ctx) { + bool rebind = false; + if (drawData->TotalIdxCount > IndexBufferSize) { + Log.report(logvisor::Info, FMT_STRING(_SYS_STR("Resizing index buffer from {} to {}")), IndexBufferSize, + drawData->TotalIdxCount); + IndexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Index, sizeof(ImDrawIdx), drawData->TotalIdxCount); + IndexBufferSize = drawData->TotalIdxCount; + rebind = true; + } + if (drawData->TotalVtxCount > VertexBufferSize) { + Log.report(logvisor::Info, FMT_STRING(_SYS_STR("Resizing vertex buffer from {} to {}")), VertexBufferSize, + drawData->TotalVtxCount); + VertexBuffer = ctx.newDynamicBuffer(boo::BufferUse::Vertex, sizeof(ImDrawVert), drawData->TotalVtxCount); + VertexBufferSize = drawData->TotalVtxCount; + rebind = true; + } + if (rebind) { + BuildShaderDataBinding(ctx); + } + + UniformBuffer->load(&projXf, sizeof(Uniform)); + auto* vtxBuf = static_cast(VertexBuffer->map(drawData->TotalVtxCount * sizeof(ImDrawVert))); + auto* idxBuf = static_cast(IndexBuffer->map(drawData->TotalIdxCount * sizeof(ImDrawIdx))); + + for (int i = 0; i < drawData->CmdListsCount; ++i) { + const auto* cmdList = drawData->CmdLists[i]; + int vtxBufferSz = cmdList->VtxBuffer.size(); + int idxBufferSz = cmdList->IdxBuffer.size(); + memcpy(vtxBuf, cmdList->VtxBuffer.begin(), vtxBufferSz * sizeof(ImDrawVert)); + memcpy(idxBuf, cmdList->IdxBuffer.begin(), idxBufferSz * sizeof(ImDrawIdx)); + vtxBuf += vtxBufferSz; + idxBuf += idxBufferSz; + } + + VertexBuffer->unmap(); + IndexBuffer->unmap(); + return true; + } BooTrace); +} + +void ImGuiEngine::Draw(boo::IGraphicsCommandQueue* gfxQ) { + ImDrawData* drawData = ImGui::GetDrawData(); + + gfxQ->setShaderDataBinding(ShaderDataBinding); + + boo::SWindowRect viewportRect = WindowRect; + int viewportHeight = viewportRect.size[1]; + viewportRect.location = {0, 0}; + if (m_factory->platform() == boo::IGraphicsDataFactory::Platform::Vulkan) { + viewportRect.location[1] = viewportHeight; + viewportRect.size[1] = -viewportHeight; + } + gfxQ->setViewport(viewportRect); + + size_t idxOffset = 0; + size_t vtxOffset = 0; + for (int i = 0; i < drawData->CmdListsCount; ++i) { + const auto* cmdList = drawData->CmdLists[i]; + for (const auto& drawCmd : cmdList->CmdBuffer) { + if (drawCmd.UserCallback != nullptr) { + drawCmd.UserCallback(cmdList, &drawCmd); + continue; + } + ImVec2 pos = drawData->DisplayPos; + int clipX = static_cast(drawCmd.ClipRect.x - pos.x); + int clipY = static_cast(drawCmd.ClipRect.y - pos.y); + int clipW = static_cast(drawCmd.ClipRect.z - pos.x) - clipX; + int clipH = static_cast(drawCmd.ClipRect.w - pos.y) - clipY; + boo::SWindowRect clipRect{clipX, clipY, clipW, clipH}; + if (m_factory->platform() == boo::IGraphicsDataFactory::Platform::Vulkan) { + clipRect.location[1] = viewportHeight - clipRect.location[1] - clipRect.size[1]; + } + gfxQ->setScissor(clipRect); + gfxQ->drawIndexed(idxOffset + drawCmd.IdxOffset, drawCmd.ElemCount, vtxOffset + drawCmd.VtxOffset); + } + idxOffset += cmdList->IdxBuffer.size(); + vtxOffset += cmdList->VtxBuffer.size(); + } +} + +void ImGuiEngine::BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx) { + boo::ObjToken uniforms[] = {UniformBuffer.get()}; + boo::PipelineStage unistages[] = {boo::PipelineStage::Vertex}; + size_t unioffs[] = {0}; + size_t unisizes[] = {sizeof(Uniform)}; + boo::ObjToken texs[] = {ImGuiAtlas.get()}; + ShaderDataBinding = ctx.newShaderDataBinding(ShaderPipeline, VertexBuffer.get(), nullptr, IndexBuffer.get(), 1, + uniforms, unistages, unioffs, unisizes, 1, texs, nullptr, nullptr); +} + +bool ImGuiWindowCallback::m_mouseCaptured = false; +bool ImGuiWindowCallback::m_keyboardCaptured = false; + +void ImGuiWindowCallback::mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) { + ImGuiEngine::Input.m_mousePos = coord; + if (button != boo::EMouseButton::None) { + ImGuiEngine::Input.m_mouseButtons[static_cast(button) - 1] = true; + } + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) { + ImGuiEngine::Input.m_mousePos = coord; + if (button != boo::EMouseButton::None) { + ImGuiEngine::Input.m_mouseButtons[static_cast(button) - 1] = false; + } + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::mouseMove(const boo::SWindowCoord& coord) { ImGuiEngine::Input.m_mousePos = coord; } + +void ImGuiWindowCallback::mouseEnter(const boo::SWindowCoord& coord) { + ImGuiEngine::Input.m_mouseIn = true; + ImGuiEngine::Input.m_mousePos = coord; +} + +void ImGuiWindowCallback::mouseLeave(const boo::SWindowCoord& coord) { + ImGuiEngine::Input.m_mouseIn = false; + ImGuiEngine::Input.m_mousePos = coord; +} + +void ImGuiWindowCallback::scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) { + ImGuiEngine::Input.m_mousePos = coord; + ImGuiEngine::Input.m_scrollDelta += scroll; +} + +void ImGuiWindowCallback::charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) { + if (charCode < 256) { + ImGuiEngine::Input.m_keys[charCode] = true; + } + ImGuiEngine::Input.m_charCodes.push_back(charCode); + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::charKeyUp(unsigned long charCode, boo::EModifierKey mods) { + if (charCode < 256) { + ImGuiEngine::Input.m_keys[charCode] = false; + } + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) { + ImGuiEngine::Input.m_keys[256 + static_cast(key)] = true; + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) { + ImGuiEngine::Input.m_keys[256 + static_cast(key)] = false; + ImGuiEngine::Input.m_modifiers = mods; +} + +void ImGuiWindowCallback::resized(const boo::SWindowRect& rect, bool sync) { WindowRect = rect; } + +} // namespace metaforce diff --git a/imgui/ImGuiEngine.hpp b/imgui/ImGuiEngine.hpp new file mode 100644 index 000000000..ff5e31642 --- /dev/null +++ b/imgui/ImGuiEngine.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include "imgui.h" + +#include +#include + +namespace metaforce { +class ImGuiEngine { +public: + static struct Input { + boo::SWindowCoord m_mousePos{}; + std::array m_mouseButtons{}; + boo::SScrollDelta m_scrollDelta{}; + boo::EModifierKey m_modifiers{}; + std::array m_keys{}; + std::vector m_charCodes{}; + bool m_mouseIn = true; + } Input; + + static void Initialize(boo::IGraphicsDataFactory* factory, const boo::SWindowRect &rect); + static void Shutdown(); + + static void Begin(float dt); + static void End(); + static void Draw(boo::IGraphicsCommandQueue* gfxQ); + +private: + static void BuildShaderDataBinding(boo::IGraphicsDataFactory::Context& ctx); +}; + +struct ImGuiWindowCallback : boo::IWindowCallback { + static bool m_mouseCaptured; + static bool m_keyboardCaptured; + + void mouseDown(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override; + void mouseUp(const boo::SWindowCoord& coord, boo::EMouseButton button, boo::EModifierKey mods) override; + void mouseMove(const boo::SWindowCoord& coord) override; + void mouseEnter(const boo::SWindowCoord& coord) override; + void mouseLeave(const boo::SWindowCoord& coord) override; + void scroll(const boo::SWindowCoord& coord, const boo::SScrollDelta& scroll) override; + void charKeyDown(unsigned long charCode, boo::EModifierKey mods, bool isRepeat) override; + void charKeyUp(unsigned long charCode, boo::EModifierKey mods) override; + void specialKeyDown(boo::ESpecialKey key, boo::EModifierKey mods, bool isRepeat) override; + void specialKeyUp(boo::ESpecialKey key, boo::EModifierKey mods) override; + void resized(const boo::SWindowRect &rect, bool sync) override; +}; +} // namespace metaforce diff --git a/imgui/ImguiShader.shader b/imgui/ImguiShader.shader new file mode 100644 index 000000000..256eb3b44 --- /dev/null +++ b/imgui/ImguiShader.shader @@ -0,0 +1,134 @@ +#shader ImguiShader +#attribute position2 +#attribute uv2 +#attribute colorunorm +#srcfac srcalpha +#dstfac invsrcalpha +#primitive triangles +#depthtest none +#depthwrite false +#culling none + + +#vertex glsl +layout (location = 0) in vec2 pos; +layout (location = 1) in vec2 uv; +layout (location = 2) in vec4 color; +UBINDING0 uniform ImguiShaderUniform +{ + mat4 xf; +}; +struct VertToFrag +{ + vec2 uv; + vec4 color; +}; + +SBINDING(0) out VertToFrag vtf; +void main() +{ + vtf.uv = uv; + vtf.color = color; + gl_Position = xf * vec4(pos.xy,0,1); +} + +#fragment glsl +struct VertToFrag +{ + vec2 uv; + vec4 color; +}; + +SBINDING(0) in VertToFrag vtf; +layout (location = 0) out vec4 colorOut; +TBINDING0 uniform sampler2D tex; +void main() +{ + colorOut = vtf.color * texture(tex, vtf.uv.st); +} + + +#vertex hlsl +cbuffer ImguiShaderUniform : register(b0) +{ + float4x4 xf; +}; + +struct VertData +{ + float2 pos : POSITION; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; + +struct VertToFrag +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; + +VertToFrag main(in VS_INPUT v) +{ + VertToFrag vtf; + vtf.pos = mul(xf, float4(v.pos.xy, 0.f, 1.f)); + vtf.col = v.col; + vtf.uv = v.uv; + return vtf; +} + +#fragment hlsl +struct VertToFrag +{ + float4 pos : SV_POSITION; + float2 uv : TEXCOORD0; + float4 col : COLOR0; +}; +SamplerState samp : register(s0); +Texture2D tex0 : register(t0); + +float4 main(in VertToFrag vtf) : SV_Target0 +{ + return vtf.col * tex0.Sample(samp, vtf.uv); +} + + +#vertex metal +struct ImguiShaderUniform { + float4x4 xf; +}; + +struct VertexIn { + float2 position [[attribute(0)]]; + float2 texCoords [[attribute(1)]]; + uchar4 color [[attribute(2)]]; +}; + +struct VertToFrag { + float4 position [[position]]; + float2 texCoords; + float4 color; +}; + +vertex VertToFrag vmain(VertexIn v [[stage_in]], + constant ImguiShaderUniform& u [[buffer(2)]]) { + VertToFrag vtf; + vtf.position = u.xf * float4(v.position, 0, 1); + vtf.texCoords = v.texCoords; + vtf.color = float4(v.color) / float4(255.0); + return vtf; +} + +#fragment metal +struct VertToFrag { + float4 position [[position]]; + float2 texCoords; + float4 color; +}; + +fragment half4 fmain(VertToFrag vtf [[stage_in]], + texture2d tex [[texture(0)]]) { + constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear); + half4 texColor = tex.sample(linearSampler, vtf.texCoords); + return half4(vtf.color) * texColor; +} diff --git a/imgui/imconfig_user.h b/imgui/imconfig_user.h new file mode 100644 index 000000000..8620e0895 --- /dev/null +++ b/imgui/imconfig_user.h @@ -0,0 +1,6 @@ +#include + +#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS + +// Use 32-bit index type for boo +#define ImDrawIdx uint32_t