From 34094038d51d9bdca6093d72081b1991581923ff Mon Sep 17 00:00:00 2001 From: RH Date: Mon, 10 Oct 2022 06:53:24 +1100 Subject: [PATCH] Enable ImGui for Android (#909) * Add support for ImGui usage on Android --- extensions/CMakeLists.txt | 2 +- extensions/ImGui/CMakeLists.txt | 14 +- extensions/ImGui/ImGuiPresenter.cpp | 28 +- extensions/ImGui/ImGuiPresenter.h | 2 + extensions/ImGui/imgui_impl_ax.cpp | 9 +- extensions/ImGui/imgui_impl_ax.h | 3 + extensions/ImGui/imgui_impl_ax_android.cpp | 715 ++++++++++++++++++ extensions/ImGui/imgui_impl_ax_android.h | 30 + .../cpp-tests/Classes/ImGuiTest/ImGuiTest.cpp | 8 +- tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.h | 2 +- tests/cpp-tests/Classes/controller.cpp | 2 +- tests/cpp-tests/Classes/tests.h | 2 +- 12 files changed, 800 insertions(+), 17 deletions(-) create mode 100644 extensions/ImGui/imgui_impl_ax_android.cpp create mode 100644 extensions/ImGui/imgui_impl_ax_android.h diff --git a/extensions/CMakeLists.txt b/extensions/CMakeLists.txt index b405d1b7f5..d0d001407d 100644 --- a/extensions/CMakeLists.txt +++ b/extensions/CMakeLists.txt @@ -18,7 +18,7 @@ option(AX_ENABLE_EXT_FAIRYGUI "Build extension FairyGUI" ON) option(AX_ENABLE_EXT_LIVE2D "Build extension Live2D" OFF) -if(WINDOWS OR MACOSX OR LINUX) +if(WINDOWS OR MACOSX OR LINUX OR ANDROID) option(AX_ENABLE_EXT_IMGUI "Build extension ImGui" ON) else() set(AX_ENABLE_EXT_IMGUI OFF) diff --git a/extensions/ImGui/CMakeLists.txt b/extensions/ImGui/CMakeLists.txt index 7ff6fe8115..3b67c0ad3d 100644 --- a/extensions/ImGui/CMakeLists.txt +++ b/extensions/ImGui/CMakeLists.txt @@ -5,7 +5,6 @@ include_directories(imgui) set(HEADER ImGuiPresenter.h # CCImGuiColorTextEdit.h - imgui_impl_ax.h imgui/imconfig.h imgui/imgui.h imgui/imgui_internal.h @@ -21,7 +20,6 @@ set(HEADER set(SOURCE ImGuiPresenter.cpp # CCImGuiColorTextEdit.cpp - imgui_impl_ax.cpp imgui/imgui.cpp imgui/imgui_demo.cpp imgui/imgui_draw.cpp @@ -33,6 +31,14 @@ set(SOURCE #~ implot/implot_demo.cpp ) +if(ANDROID) + list(APPEND HEADER imgui_impl_ax_android.cpp) + list(APPEND SOURCE imgui_impl_ax_android.h) +else() + list(APPEND HEADER imgui_impl_ax.cpp) + list(APPEND SOURCE imgui_impl_ax.h) +endif() + #~ if(AX_ENABLE_EXT_LUA) #~ include_directories( #~ lua-bindings @@ -64,8 +70,8 @@ set(SOURCE #~ ) #~ endif() -add_library(${target_name} STATIC - ${HEADER} +add_library(${target_name} STATIC + ${HEADER} ${SOURCE}) setup_ax_extension_config(${target_name}) diff --git a/extensions/ImGui/ImGuiPresenter.cpp b/extensions/ImGui/ImGuiPresenter.cpp index 1efa7d12b2..aef16b139c 100644 --- a/extensions/ImGui/ImGuiPresenter.cpp +++ b/extensions/ImGui/ImGuiPresenter.cpp @@ -1,6 +1,10 @@ #include "ImGuiPresenter.h" #include -#include "imgui_impl_ax.h" +#if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) + #include "imgui_impl_ax_android.h" +#else + #include "imgui_impl_ax.h" +#endif #include "imgui_internal.h" // TODO: mac metal @@ -197,8 +201,12 @@ void ImGuiPresenter::init() style.Colors[ImGuiCol_WindowBg].w = 1.0f; } +#if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) + ImGui_ImplAndroid_InitForAx(Director::getInstance()->getOpenGLView(), true); +#else auto window = static_cast(Director::getInstance()->getOpenGLView())->getWindow(); ImGui_ImplGlfw_InitForAx(window, true); +#endif ImGui_ImplAx_Init(); ImGui_ImplAx_SetCustomFontLoader(&ImGuiPresenter::loadCustomFonts, this); @@ -218,7 +226,11 @@ void ImGuiPresenter::cleanup() ImGui_ImplAx_SetCustomFontLoader(nullptr, nullptr); ImGui_ImplAx_Shutdown(); +#if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) + ImGui_ImplAndroid_Shutdown(); +#else ImGui_ImplGlfw_Shutdown(); +#endif AX_SAFE_RELEASE_NULL(_fontsTexture); @@ -265,9 +277,12 @@ void ImGuiPresenter::loadCustomFonts(void* ud) float ImGuiPresenter::scaleAllByDPI(float userScale) { - // Gets scale float xscale = 1.0f; +#if (AX_TARGET_PLATFORM != AX_PLATFORM_ANDROID) + // Gets scale glfwGetMonitorContentScale(glfwGetPrimaryMonitor(), &xscale, nullptr); +#endif + auto zoomFactor = userScale * xscale; auto imFonts = ImGui::GetIO().Fonts; @@ -293,6 +308,11 @@ float ImGuiPresenter::scaleAllByDPI(float userScale) return zoomFactor; } +void ImGuiPresenter::setViewResolution(float width, float height) +{ + ImGui_ImplAx_SetViewResolution(width, height); +} + void ImGuiPresenter::addFont(std::string_view fontFile, float fontSize, CHS_GLYPH_RANGE glyphRange) { if (FileUtils::getInstance()->isFileExistInternal(fontFile)) @@ -340,7 +360,11 @@ void ImGuiPresenter::beginFrame() { // create frame ImGui_ImplAx_NewFrame(); +#if (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) + ImGui_ImplAndroid_NewFrame(); +#else ImGui_ImplGlfw_NewFrame(); +#endif ImGui::NewFrame(); // move to endFrame? diff --git a/extensions/ImGui/ImGuiPresenter.h b/extensions/ImGui/ImGuiPresenter.h index e8603ffdd7..696f7cbaed 100644 --- a/extensions/ImGui/ImGuiPresenter.h +++ b/extensions/ImGui/ImGuiPresenter.h @@ -45,6 +45,8 @@ public: float scaleAllByDPI(float userScale); float getContentZoomFactor() const { return _contentZoomFactor; } + void setViewResolution(float width, float height); + /// /// Add ImGui font with contentZoomFactor /// diff --git a/extensions/ImGui/imgui_impl_ax.cpp b/extensions/ImGui/imgui_impl_ax.cpp index a00326d7d3..a3012398f7 100644 --- a/extensions/ImGui/imgui_impl_ax.cpp +++ b/extensions/ImGui/imgui_impl_ax.cpp @@ -1296,7 +1296,7 @@ void ImGui_ImplAx_Shutdown() ImGui_ImplAx_DestroyDeviceObjects(); } -IMGUI_IMPL_API void ImGui_ImplAx_NewFrame() { +IMGUI_IMPL_API void ImGui_ImplAx_NewFrame() { auto bd = ImGui_ImplGlfw_GetBackendData(); //bd->CallbackCommands.clear(); bd->CustomCommands.clear(); @@ -1330,6 +1330,13 @@ IMGUI_IMPL_API void ImGui_ImplAx_SetDeviceObjectsDirty() bd->FontDeviceObjectsDirty = true; } +IMGUI_IMPL_API void ImGui_ImplAx_SetViewResolution(float width, float height) +{ + // Resize (expand) window + auto* view = (GLViewImpl*)Director::getInstance()->getOpenGLView(); + view->setWindowed(width, height); +} + static void ImGui_ImplAx_CreateWindow(ImGuiViewport* viewport) { ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); diff --git a/extensions/ImGui/imgui_impl_ax.h b/extensions/ImGui/imgui_impl_ax.h index 64e50263e6..318b6bb2fc 100644 --- a/extensions/ImGui/imgui_impl_ax.h +++ b/extensions/ImGui/imgui_impl_ax.h @@ -23,3 +23,6 @@ IMGUI_IMPL_API void* ImGui_ImplAx_GetFontsTexture(); // Sets Device objects dirty IMGUI_IMPL_API void ImGui_ImplAx_SetDeviceObjectsDirty(); + +// Set the required view resolution for the UI +IMGUI_IMPL_API void ImGui_ImplAx_SetViewResolution(float width, float height); diff --git a/extensions/ImGui/imgui_impl_ax_android.cpp b/extensions/ImGui/imgui_impl_ax_android.cpp new file mode 100644 index 0000000000..eb71b10164 --- /dev/null +++ b/extensions/ImGui/imgui_impl_ax_android.cpp @@ -0,0 +1,715 @@ +#include "imgui_impl_ax_android.h" +#include "cocos2d.h" +#include "renderer/backend/Backend.h" +#include +#include +#include +#include +#include + +USING_NS_AX; +using namespace backend; + +// Clang warnings with -Weverything +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast +#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +// GLFW + +// Android data +static char g_LogTag[] = "ImGuiAndroid"; + +// axmol spec data +constexpr IndexFormat IMGUI_INDEX_FORMAT = sizeof(ImDrawIdx) == 2 ? IndexFormat::U_SHORT : IndexFormat::U_INT; + +struct ProgramInfoData +{ + Program* program = nullptr; + // Uniforms location + UniformLocation texture{}; + UniformLocation projection{}; + // Vertex attributes location + int position = 0; + int uv = 0; + int color = 0; + VertexLayout layout{}; +}; + +struct SavedRenderStateData +{ + backend::CullMode cull{}; + Viewport vp{}; + ScissorRect scissorRect{}; + bool scissorTest{}; + bool depthTest{}; +}; +// end of axmol spec + + +struct ImGui_ImplAndroid_Data +{ + GLView* Window; + double Time; + bool InstalledCallbacks; + + // ImGui_ImplAndroid_Data() { memset(this, 0, sizeof(*this)); } + + // axmol spec data + std::chrono::steady_clock::time_point LastFrameTime{}; + + ImGuiImplCocos2dxLoadFontFun LoadCustomFont = nullptr; + void* LoadCustomFontUserData = nullptr; + + ProgramInfoData ProgramInfo{}; + ProgramInfoData ProgramFontInfo{}; + bool FontDeviceObjectsDirty = false; + Texture2D* FontTexture = nullptr; + Mat4 Projection; + ImVec2 LastValidMousePos; + EventListener* TouchListener = nullptr; + + // std::vector> CallbackCommands{}; + std::vector> CustomCommands{}; + Vector ProgramStates{}; + + SavedRenderStateData SavedRenderState{}; + Vec2 ViewResolution = Vec2(1920, 1080); +}; + +// Backend data stored in io.BackendPlatformUserData to allow support for multiple Dear ImGui contexts +static ImGui_ImplAndroid_Data* ImGui_ImplAndroid_GetBackendData() +{ + return ImGui::GetCurrentContext() ? (ImGui_ImplAndroid_Data*)ImGui::GetIO().BackendPlatformUserData : NULL; +} + +// Forward Declarations +static void ImGui_ImplAndroid_ShutdownPlatformInterface(); + +// Functions +static bool ImGui_ImplAndroid_Init(GLView* window, bool install_callbacks) +{ + ImGuiIO& io = ImGui::GetIO(); + IM_ASSERT(io.BackendPlatformUserData == NULL && "Already initialized a platform backend!"); + + // Setup backend capabilities flags + ImGui_ImplAndroid_Data* bd = IM_NEW(ImGui_ImplAndroid_Data)(); + io.BackendPlatformUserData = (void*)bd; + io.BackendPlatformName = "imgui_impl_android"; + //io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) + //io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) + + bd->Window = window; + bd->Time = 0.0; + + io.ClipboardUserData = bd->Window; + + // Our mouse update function expect PlatformHandle to be filled for the main viewport + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->PlatformHandle = (void*)bd->Window; + + io.AddFocusEvent(true); + + auto* touchListener = EventListenerTouchOneByOne::create(); + touchListener->setSwallowTouches(true); + touchListener->retain(); + bd->TouchListener = touchListener; + + touchListener->onTouchBegan = [](Touch* touch, Event* /*event*/) -> bool { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + auto location = touch->getLocationInView(); + auto origin = bd->Window->getVisibleOrigin(); + auto realX = location.x * bd->Window->getScaleX(); + auto realY = (location.y - origin.y) * bd->Window->getScaleY(); + realX /= io.DisplayFramebufferScale.x; + realY /= io.DisplayFramebufferScale.y; + io.AddMousePosEvent(realX, realY); + bd->LastValidMousePos = ImVec2(realX, realY); + io.AddMouseButtonEvent(0, true); + + // We can't check if we're actually hovering over a ImGui element, since the + // AddMousePosEvent is not instant, it's queued. So, just return true here + // to indicate that we're handling this event. + return true; + }; + + touchListener->onTouchMoved = [](Touch* touch, Event* /*event*/) { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + auto location = touch->getLocationInView(); + auto origin = bd->Window->getVisibleOrigin(); + auto realX = location.x * bd->Window->getScaleX(); + auto realY = (location.y - origin.y) * bd->Window->getScaleY(); + realX /= io.DisplayFramebufferScale.x; + realY /= io.DisplayFramebufferScale.y; + io.AddMousePosEvent(realX, realY); + bd->LastValidMousePos = ImVec2(realX, realY);; + }; + + touchListener->onTouchEnded = [](Touch* touch, Event* /*event*/) { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + auto location = touch->getLocationInView(); + auto origin = bd->Window->getVisibleOrigin(); + auto realX = location.x * bd->Window->getScaleX(); + auto realY = (location.y - origin.y) * bd->Window->getScaleY(); + realX /= io.DisplayFramebufferScale.x; + realY /= io.DisplayFramebufferScale.y; + io.AddMousePosEvent(realX, realY); + bd->LastValidMousePos = ImVec2(realX, realY);; + io.AddMouseButtonEvent(0, false); + }; + + touchListener->onTouchCancelled = [](Touch* touch, Event* /*event*/) { + ImGuiIO& io = ImGui::GetIO(); + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + auto location = touch->getLocationInView(); + auto origin = bd->Window->getVisibleOrigin(); + auto realX = location.x * bd->Window->getScaleX(); + auto realY = (location.y - origin.y) * bd->Window->getScaleY(); + realX /= io.DisplayFramebufferScale.x; + realY /= io.DisplayFramebufferScale.y; + io.AddMousePosEvent(realX, realY); + bd->LastValidMousePos = ImVec2(realX, realY); + io.AddMouseButtonEvent(0, false); + }; + + Director::getInstance()->getEventDispatcher()->addEventListenerWithFixedPriority(touchListener, 1); + + return true; +} + +IMGUI_IMPL_API void ImGui_ImplAx_RenderPlatform() +{ + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::UpdatePlatformWindows(); + ImGui::RenderPlatformWindowsDefault(); + } +} + +void ImGui_ImplAndroid_Shutdown() +{ + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + IM_ASSERT(bd != NULL && "No platform backend to shutdown, or already shutdown?"); + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplAndroid_ShutdownPlatformInterface(); + + io.BackendPlatformName = NULL; + io.BackendPlatformUserData = NULL; + + IM_DELETE(bd); +} + +void ImGui_ImplAndroid_NewFrame() +{ + ImGuiIO& io = ImGui::GetIO(); + + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + IM_ASSERT(bd != NULL && "Did you call ImGui_ImplAndroid_InitForXXX()?"); + + // Setup display size (every frame to accommodate for window resizing) + int32_t window_width = bd->ViewResolution.width; + int32_t window_height = bd->ViewResolution.height; + int display_width = bd->Window->getFrameSize().width; + int display_height = bd->Window->getFrameSize().height; + + io.DisplaySize = ImVec2((float)window_width, (float)window_height); + if (window_width > 0 && window_height > 0) + io.DisplayFramebufferScale = ImVec2((float)display_width / window_width, (float)display_height / window_height); + + // Setup time step + struct timespec current_timespec; + clock_gettime(CLOCK_MONOTONIC, ¤t_timespec); + double current_time = (double)(current_timespec.tv_sec) + (current_timespec.tv_nsec / 1000000000.0); + io.DeltaTime = bd->Time > 0.0 ? (float)(current_time - bd->Time) : (float)(1.0f / 60.0f); + bd->Time = current_time; +} + +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously. +// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +// Helper structure we store in the void* RenderUserData field of each ImGuiViewport to easily retrieve our backend data. +struct ImGui_ImplAndroid_ViewportData +{ + GLView* Window; + bool WindowOwned; + int IgnoreWindowPosEventFrame; + int IgnoreWindowSizeEventFrame; + + ImGui_ImplAndroid_ViewportData() { Window = NULL; WindowOwned = false; IgnoreWindowSizeEventFrame = IgnoreWindowPosEventFrame = -1; } + ~ImGui_ImplAndroid_ViewportData() { IM_ASSERT(Window == NULL); } +}; + +static void ImGui_ImplAndroid_WindowCloseCallback(GLView* window) +{ + if (ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(window)) + viewport->PlatformRequestClose = true; +} + +static void ImGui_ImplAndroid_ShutdownPlatformInterface() +{ + ImGui::DestroyPlatformWindows(); +} + +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + +////////////////////////// axmol spec ///////////////////////// + +#define AX_PTR_CAST(v, pointer_type) reinterpret_cast(v) + + +// fps macro +#define AX_IMGUI_DEFAULT_DELTA (1 / 60.f) +#define AX_IMGUI_MIN_DELTA (1 / 1000.f) +#define AX_IMGUI_MAX_DELTA (1 / 30.f) + +enum +{ + GlfwClientApi_Axmol = 0xadee, +}; + +// axmol spec +bool ImGui_ImplAndroid_InitForAx(GLView* window, bool install_callbacks) +{ + return ImGui_ImplAndroid_Init(window, install_callbacks); +} + +struct ImGui_ImplAx_Data +{ + // axmol spec data + + ImGuiImplCocos2dxLoadFontFun LoadCustomFont = nullptr; + void* LoadCustomFontUserData = nullptr; + + ProgramInfoData ProgramInfo{}; + ProgramInfoData ProgramFontInfo{}; + bool FontDeviceObjectsDirty = false; + Texture2D* FontTexture = nullptr; + Mat4 Projection; + + std::vector> CallbackCommands{}; + std::vector> CustomCommands{}; + Vector ProgramStates{}; + + SavedRenderStateData SavedRenderState{}; +}; + +static bool ImGui_ImplAx_CreateFontsTexture(); +static void ImGui_ImplAx_DestroyFontsTexture(); +static void ImGui_ImplAx_DestroyDeviceObjects(); +static bool ImGui_ImplAx_CreateDeviceObjects(); +static void ImGui_ImplAx_RenderWindow(ImGuiViewport* viewport, void*); +static void AddRendererCommand(const std::function& f); + + +static bool ImGui_ImplAx_createShaderPrograms(); +static void ImGui_ImplAx_Renderer_RenderWindow(ImGuiViewport* viewport, void*); + +static void ImGui_ImplAx_InitPlatformInterface() +{ + // Register platform interface (will be coupled with a renderer interface) + ImGui_ImplAndroid_Data* bd = ImGui_ImplAndroid_GetBackendData(); + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + + platform_io.Renderer_RenderWindow = ImGui_ImplAx_Renderer_RenderWindow; +} + +void ImGui_ImplAx_Init() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + auto& io = ImGui::GetIO(); + io.BackendRendererUserData = (void*)bd; + io.BackendRendererName = "imgui_impl_axmol"; + io.BackendFlags |= + ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) + // axmol spec: disable auto load and save + io.IniFilename = nullptr; + + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + ImGui_ImplAx_InitPlatformInterface(); +} + +void ImGui_ImplAx_Shutdown() +{ + ImGui_ImplAx_DestroyDeviceObjects(); +} + +IMGUI_IMPL_API void ImGui_ImplAx_NewFrame() { + auto bd = ImGui_ImplAndroid_GetBackendData(); + //bd->CallbackCommands.clear(); + bd->CustomCommands.clear(); + bd->ProgramStates.clear(); + + if (!bd->FontTexture) + ImGui_ImplAx_CreateDeviceObjects(); + else if (bd->FontDeviceObjectsDirty) + { // recreate device objects, fonts also should be device objects + ImGui_ImplAx_DestroyDeviceObjects(); + ImGui_ImplAx_CreateDeviceObjects(); + } +} + +IMGUI_IMPL_API void ImGui_ImplAx_SetCustomFontLoader(ImGuiImplCocos2dxLoadFontFun fun, void* userdata) +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + bd->LoadCustomFont = fun; + bd->LoadCustomFontUserData = userdata; +} + +IMGUI_IMPL_API void* ImGui_ImplAx_GetFontsTexture() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + return bd->FontTexture; +} + +IMGUI_IMPL_API void ImGui_ImplAx_SetDeviceObjectsDirty() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + bd->FontDeviceObjectsDirty = true; +} + +IMGUI_IMPL_API void ImGui_ImplAx_SetViewResolution(float width, float height) +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + bd->ViewResolution = Vec2(width, height); +} + +static bool ImGui_ImplAx_CreateDeviceObjects() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + if (bd->LoadCustomFont) + bd->LoadCustomFont(bd->LoadCustomFontUserData); + + ImGui_ImplAx_createShaderPrograms(); + ImGui_ImplAx_CreateFontsTexture(); + + bd->FontDeviceObjectsDirty = false; + return true; +} + +static void ImGui_ImplAx_DestroyDeviceObjects() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + AX_SAFE_RELEASE_NULL(bd->ProgramInfo.program); + AX_SAFE_RELEASE_NULL(bd->ProgramFontInfo.program); + AX_SAFE_RELEASE_NULL(bd->TouchListener); + + ImGui_ImplAx_DestroyFontsTexture(); +} + +static bool ImGui_ImplAx_createShaderPrograms() +{ + auto vertex_shader = + "uniform mat4 u_MVPMatrix;\n" + "attribute vec2 a_position;\n" + "attribute vec2 a_texCoord;\n" + "attribute vec4 a_color;\n" + "varying vec2 v_texCoord;\n" + "varying vec4 v_fragmentColor;\n" + "void main()\n" + "{\n" + " v_texCoord = a_texCoord;\n" + " v_fragmentColor = a_color;\n" + " gl_Position = u_MVPMatrix * vec4(a_position.xy, 0.0, 1.0);\n" + "}\n"; + auto fragment_shader = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D u_tex0;\n" + "varying vec2 v_texCoord;\n" + "varying vec4 v_fragmentColor;\n" + "void main()\n" + "{\n" + " gl_FragColor = v_fragmentColor * texture2D(u_tex0, v_texCoord);\n" + "}\n"; + auto fragment_shader_font = + "#ifdef GL_ES\n" + " precision mediump float;\n" + "#endif\n" + "uniform sampler2D u_tex0;\n" + "varying vec2 v_texCoord;\n" + "varying vec4 v_fragmentColor;\n" + "void main()\n" + "{\n" + " float a = texture2D(u_tex0, v_texCoord).a;\n" + " gl_FragColor = vec4(v_fragmentColor.rgb, v_fragmentColor.a * a);\n" + "}\n"; + + auto bd = ImGui_ImplAndroid_GetBackendData(); + + AX_SAFE_RELEASE(bd->ProgramInfo.program); + AX_SAFE_RELEASE(bd->ProgramFontInfo.program); + bd->ProgramInfo.program = backend::Device::getInstance()->newProgram(vertex_shader, fragment_shader); + bd->ProgramFontInfo.program = backend::Device::getInstance()->newProgram(vertex_shader, fragment_shader_font); + IM_ASSERT(bd->ProgramInfo.program); + IM_ASSERT(bd->ProgramFontInfo.program); + if (!bd->ProgramInfo.program || !bd->ProgramFontInfo.program) + return false; + + for (auto& p : {&bd->ProgramInfo, &bd->ProgramFontInfo}) + { + p->texture = p->program->getUniformLocation(TEXTURE); + p->projection = p->program->getUniformLocation(MVP_MATRIX); + p->position = p->program->getAttributeLocation(POSITION); + p->uv = p->program->getAttributeLocation(TEXCOORD); + p->color = p->program->getAttributeLocation(COLOR); + IM_ASSERT(bool(p->texture)); + IM_ASSERT(bool(p->projection)); + IM_ASSERT(p->position >= 0); + IM_ASSERT(p->uv >= 0); + IM_ASSERT(p->color >= 0); + auto& layout = p->layout; + layout.setAttribute("a_position", p->position, VertexFormat::FLOAT2, 0, false); + layout.setAttribute("a_texCoord", p->uv, VertexFormat::FLOAT2, offsetof(ImDrawVert, uv), false); + layout.setAttribute("a_color", p->color, VertexFormat::UBYTE4, offsetof(ImDrawVert, col), true); + layout.setStride(sizeof(ImDrawVert)); + } + + return true; +} + +bool ImGui_ImplAx_CreateFontsTexture() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + // Load as RGBA 32-bits (75% of the memory is wasted, but default font is so small) + // because it is more likely to be compatible with user's existing shaders. + // If your ImTextureId represent a higher-level concept than just a GL texture id, + // consider calling GetTexDataAsAlpha8() instead to save on GPU memory. + io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height); + + AX_SAFE_RELEASE(bd->FontTexture); + bd->FontTexture = new Texture2D(); + + bd->FontTexture->setAntiAliasTexParameters(); + bd->FontTexture->initWithData(pixels, width * height, backend::PixelFormat::A8, width, height); + io.Fonts->TexID = (ImTextureID)bd->FontTexture; + return true; +} + +IMGUI_IMPL_API void ImGui_ImplAx_DestroyFontsTexture() +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + if (bd->FontTexture) + { + ImGui::GetIO().Fonts->TexID = nullptr; + AX_SAFE_RELEASE_NULL(bd->FontTexture); + } +} + +static void AddRendererCommand(const std::function& f) +{ + auto bd = ImGui_ImplAndroid_GetBackendData(); + const auto renderer = Director::getInstance()->getRenderer(); + auto cmd = renderer->nextCallbackCommand(); + cmd->init(0.f); + cmd->func = f; + renderer->addCommand(cmd); + //bd->CallbackCommands.push_back(cmd); +} + +static void ImGui_ImplAx_SaveRenderState(ax::Renderer* renderer) +{ + AddRendererCommand([renderer]() { + auto bd = ImGui_ImplAndroid_GetBackendData(); + bd->SavedRenderState.cull = renderer->getCullMode(); + bd->SavedRenderState.vp = renderer->getViewport(); + bd->SavedRenderState.scissorTest = renderer->getScissorTest(); + bd->SavedRenderState.scissorRect = renderer->getScissorRect(); + bd->SavedRenderState.depthTest = renderer->getDepthTest(); + }); +} + +static void ImGui_ImplAx_SetupRenderState(ax::Renderer* renderer, + ImDrawData* draw_data, + int fb_width, + int fb_height) +{ + AddRendererCommand([=]() { + renderer->setCullMode(backend::CullMode::NONE); + renderer->setDepthTest(false); + renderer->setScissorTest(true); + renderer->setViewPort(0, 0, fb_width, fb_height); + }); + + const auto L = draw_data->DisplayPos.x; + const auto R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; + const auto T = draw_data->DisplayPos.y; + const auto B = draw_data->DisplayPos.y + draw_data->DisplaySize.y; + + auto bd = ImGui_ImplAndroid_GetBackendData(); + Mat4::createOrthographicOffCenter(L, R, B, T, -1.f, 1.f, &bd->Projection); +} + +static void ImGui_ImplAx_RestoreRenderState(ax::Renderer* renderer) +{ + AddRendererCommand([renderer]() { + auto bd = ImGui_ImplAndroid_GetBackendData(); + renderer->setCullMode(bd->SavedRenderState.cull); + auto& vp = bd->SavedRenderState.vp; + renderer->setViewPort(vp.x, vp.y, vp.w, vp.h); + renderer->setScissorTest(bd->SavedRenderState.scissorTest); + auto& sc = bd->SavedRenderState.scissorRect; + renderer->setScissorRect(sc.x, sc.y, sc.width, sc.height); + renderer->setDepthTest(bd->SavedRenderState.depthTest); + + // apply raster state + renderer->beginRenderPass(); + renderer->endRenderPass(); + }); +} + +IMGUI_IMPL_API void ImGui_ImplAx_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays + // (screen coordinates != framebuffer coordinates) + int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); + int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); + if (fb_width <= 0 || fb_height <= 0) + return; + + const auto renderer = Director::getInstance()->getRenderer(); + + ImGui_ImplAx_SaveRenderState(renderer); + + ImGui_ImplAx_SetupRenderState(renderer, draw_data, fb_width, fb_height); + + // Will project scissor/clipping rectangles into framebuffer space + ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports + ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2) + + // Render command lists + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + size_t ibuffer_offset = 0; + + // Upload vertex/index buffers + const auto vsize = cmd_list->VtxBuffer.Size * sizeof(ImDrawVert); + IM_ASSERT(vsize > 0); + auto vbuffer = backend::Device::getInstance()->newBuffer(vsize, BufferType::VERTEX, BufferUsage::STATIC); + vbuffer->autorelease(); + vbuffer->updateData(cmd_list->VtxBuffer.Data, vsize); + const auto isize = cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx); + IM_ASSERT(isize > 0); + auto ibuffer = backend::Device::getInstance()->newBuffer(isize, BufferType::INDEX, BufferUsage::STATIC); + ibuffer->autorelease(); + ibuffer->updateData(cmd_list->IdxBuffer.Data, isize); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback != nullptr) + { + // User callback, registered via ImDrawList::AddCallback() + // (ImDrawCallback_ResetRenderState is a special callback value used by the user + // to request the renderer to reset render state.) + if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) + ImGui_ImplAx_SetupRenderState(renderer, draw_data, fb_width, fb_height); + else + { + AddRendererCommand([=]() { pcmd->UserCallback(cmd_list, pcmd); }); + } + } + else + { + // Project scissor/clipping rectangles into framebuffer space + ImVec4 clip_rect; + clip_rect.x = (pcmd->ClipRect.x - clip_off.x) * clip_scale.x; + clip_rect.y = (pcmd->ClipRect.y - clip_off.y) * clip_scale.y; + clip_rect.z = (pcmd->ClipRect.z - clip_off.x) * clip_scale.x; + clip_rect.w = (pcmd->ClipRect.w - clip_off.y) * clip_scale.y; + + if (clip_rect.x < fb_width && clip_rect.y < fb_height && clip_rect.z >= 0.0f && clip_rect.w >= 0.0f) + { + // Apply scissor/clipping rectangle + AddRendererCommand([=]() { + renderer->setScissorRect(clip_rect.x, fb_height - clip_rect.w, clip_rect.z - clip_rect.x, + clip_rect.w - clip_rect.y); + }); + + auto bd = ImGui_ImplAndroid_GetBackendData(); + + if (typeid(*((Ref*)pcmd->TextureId)) == typeid(Texture2D)) + { + auto tex = AX_PTR_CAST(pcmd->TextureId, Texture2D*); + auto cmd = std::make_shared(); + bd->CustomCommands.push_back(cmd); + cmd->init(0.f, BlendFunc::ALPHA_NON_PREMULTIPLIED); + const auto pinfo = tex == bd->FontTexture ? &bd->ProgramFontInfo : &bd->ProgramInfo; + // create new ProgramState + auto state = new ProgramState(pinfo->program); + state->autorelease(); + bd->ProgramStates.pushBack(state); + auto& desc = cmd->getPipelineDescriptor(); + desc.programState = state; + // setup attributes for ImDrawVert + desc.programState->setVertexLayout(pinfo->layout); + desc.programState->setUniform(pinfo->projection, &bd->Projection, sizeof(Mat4)); + desc.programState->setTexture(pinfo->texture, 0, tex->getBackendTexture()); + // In order to composite our output buffer we need to preserve alpha + desc.blendDescriptor.sourceAlphaBlendFactor = BlendFactor::ONE; + // set vertex/index buffer + cmd->setIndexBuffer(ibuffer, IMGUI_INDEX_FORMAT); + cmd->setVertexBuffer(vbuffer); + cmd->setDrawType(CustomCommand::DrawType::ELEMENT); + cmd->setPrimitiveType(PrimitiveType::TRIANGLE); + cmd->setIndexDrawInfo(ibuffer_offset, pcmd->ElemCount); + renderer->addCommand(cmd.get()); + } + else + { + auto node = AX_PTR_CAST(pcmd->TextureId, Node*); + const auto tr = node->getNodeToParentTransform(); + node->setVisible(true); + node->setNodeToParentTransform(tr); + const auto& proj = + Director::getInstance()->getMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_PROJECTION); + node->visit(Director::getInstance()->getRenderer(), proj.getInversed() * bd->Projection, 0); + node->setVisible(false); + } + } + } + ibuffer_offset += pcmd->ElemCount; + } + } + + ImGui_ImplAx_RestoreRenderState(renderer); +} + +//-------------------------------------------------------------------------------------------------------- +// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT +// This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports +// simultaneously. If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you +// completely ignore this section first.. +//-------------------------------------------------------------------------------------------------------- + +static void ImGui_ImplAx_Renderer_RenderWindow(ImGuiViewport* viewport, void*) +{ + if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) + { + const auto renderer = Director::getInstance()->getRenderer(); + renderer->clear(ClearFlag::COLOR, {0, 0, 0, 1}, 1, 0, 0); + } + ImGui_ImplAx_RenderDrawData(viewport->DrawData); +} + diff --git a/extensions/ImGui/imgui_impl_ax_android.h b/extensions/ImGui/imgui_impl_ax_android.h new file mode 100644 index 0000000000..18a495b6e9 --- /dev/null +++ b/extensions/ImGui/imgui_impl_ax_android.h @@ -0,0 +1,30 @@ +#pragma once +#include "imgui.h" +#include "platform/CCGLView.h" + +struct ANativeWindow; +struct AInputEvent; + +typedef void (*ImGuiImplCocos2dxLoadFontFun)(void* userdata); + +/// ImGui glfw APIs +IMGUI_IMPL_API bool ImGui_ImplAndroid_InitForAx(ax::GLView* window, bool install_callbacks); +IMGUI_IMPL_API void ImGui_ImplAndroid_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplAndroid_NewFrame(); + +/// ImGui axmol render APIs +IMGUI_IMPL_API void ImGui_ImplAx_Init(); +IMGUI_IMPL_API void ImGui_ImplAx_Shutdown(); +IMGUI_IMPL_API void ImGui_ImplAx_NewFrame(); +IMGUI_IMPL_API void ImGui_ImplAx_RenderDrawData(ImDrawData* draw_data); +IMGUI_IMPL_API void ImGui_ImplAx_RenderPlatform(); + +// Get FontTexture object ax::Texture2D* +IMGUI_IMPL_API void ImGui_ImplAx_SetCustomFontLoader(ImGuiImplCocos2dxLoadFontFun fun, void* userdata); +IMGUI_IMPL_API void* ImGui_ImplAx_GetFontsTexture(); + +// Sets Device objects dirty +IMGUI_IMPL_API void ImGui_ImplAx_SetDeviceObjectsDirty(); + +// Set the required view resolution for the UI +IMGUI_IMPL_API void ImGui_ImplAx_SetViewResolution(float width, float height); diff --git a/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.cpp b/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.cpp index dc047adafd..0fa0f787e3 100644 --- a/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.cpp +++ b/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.cpp @@ -6,7 +6,7 @@ USING_NS_AX; USING_NS_AX_EXT; -#if defined(AX_PLATFORM_PC) +#if defined(AX_PLATFORM_PC) || (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) static bool show_test_window = true; static bool show_another_window = true; @@ -14,11 +14,7 @@ static ImVec4 clear_color = ImColor(114, 144, 154); ImGuiTests::ImGuiTests() { - // Resize (expand) window - static Size resourceSize(1280, 720); - auto director = Director::getInstance(); - GLViewImpl* view = (GLViewImpl*)Director::getInstance()->getOpenGLView(); - view->setWindowed(resourceSize.width, resourceSize.height); + ImGuiPresenter::getInstance()->setViewResolution(1280, 720); ADD_TEST_CASE(ImGuiTest); } diff --git a/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.h b/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.h index 9bab430ec2..8bfb701ccf 100644 --- a/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.h +++ b/tests/cpp-tests/Classes/ImGuiTest/ImGuiTest.h @@ -30,7 +30,7 @@ #include "cocos2d.h" #include "../BaseTest.h" -#if defined(AX_PLATFORM_PC) +#if defined(AX_PLATFORM_PC) || (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) DEFINE_TEST_SUITE(ImGuiTests); diff --git a/tests/cpp-tests/Classes/controller.cpp b/tests/cpp-tests/Classes/controller.cpp index 0b3f5076f8..dcb77dbf21 100644 --- a/tests/cpp-tests/Classes/controller.cpp +++ b/tests/cpp-tests/Classes/controller.cpp @@ -44,7 +44,7 @@ public: RootTests() { // addTest("Node: Scene3D", [](){return new Scene3DTests(); }); -#if defined(AX_PLATFORM_PC) +#if defined(AX_PLATFORM_PC) || (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) addTest("ImGui", []() { return new ImGuiTests(); }); #endif addTest("Texture2D", []() { return new Texture2DTests(); }); diff --git a/tests/cpp-tests/Classes/tests.h b/tests/cpp-tests/Classes/tests.h index 1ba06e5c45..9879ef050e 100644 --- a/tests/cpp-tests/Classes/tests.h +++ b/tests/cpp-tests/Classes/tests.h @@ -119,7 +119,7 @@ #include "ZwoptexTest/ZwoptexTest.h" #include "SpriteFrameCacheTest/SpriteFrameCacheTest.h" #include "ZipTest/ZipTests.h" -#if defined(AX_PLATFORM_PC) +#if defined(AX_PLATFORM_PC) || (AX_TARGET_PLATFORM == AX_PLATFORM_ANDROID) # include "ImGuiTest/ImGuiTest.h" #endif #endif