From 2edc32391cc8fdfa4536ac44d95e57efca4c3d72 Mon Sep 17 00:00:00 2001 From: Patrick Dawson Date: Fri, 19 Jan 2024 19:14:17 +0100 Subject: [PATCH] import (slightly outdated) gdext --- .gitignore | 20 + .gitmodules | 9 + .../imgui-godot/ImGuiGodot/ImGuiExtensions.cs | 2 + addons/imgui-godot/ImGuiGodot/ImGuiGD.cs | 2 + addons/imgui-godot/ImGuiGodot/ImGuiSync.cs | 32 ++ .../ImGuiGodot/Internal/BackendNative.cs | 3 + .../ImGuiGodot/Internal/BackendNet.cs | 9 + .../ImGuiGodot/Internal/IBackend.cs | 3 + .../ImGuiGodot/Internal/IRenderer.cs | 2 + addons/imgui-godot/plugin.cfg | 2 +- addons/imgui-godot/scripts/ImGuiRoot.gd | 11 +- gdext/.clang-format | 14 + gdext/.gdignore | 0 gdext/CMakeLists.txt | 92 +++ gdext/SConstruct | 77 +++ gdext/dear_bindings | 1 + gdext/gds_bindings.py | 543 ++++++++++++++++++ gdext/godot-cpp | 1 + gdext/imgui | 1 + gdext/imgui-godot-native.gdextension | 13 + gdext/include/imconfig-godot.h | 53 ++ gdext/include/imgui-godot.h | 452 +++++++++++++++ gdext/src/CMakeLists.txt | 30 + gdext/src/Context.cpp | 268 +++++++++ gdext/src/Context.h | 40 ++ gdext/src/DummyRenderer.h | 26 + gdext/src/Fonts.cpp | 188 ++++++ gdext/src/Fonts.h | 24 + gdext/src/ImGuiAPI.cpp | 51 ++ gdext/src/ImGuiAPI.h | 171 ++++++ gdext/src/ImGuiGD.cpp | 152 +++++ gdext/src/ImGuiGD.h | 47 ++ gdext/src/ImGuiLayer.cpp | 216 +++++++ gdext/src/ImGuiLayer.h | 43 ++ gdext/src/ImGuiLayerHelper.cpp | 52 ++ gdext/src/ImGuiLayerHelper.h | 36 ++ gdext/src/ImGuiRoot.cpp | 56 ++ gdext/src/ImGuiRoot.h | 36 ++ gdext/src/Input.cpp | 309 ++++++++++ gdext/src/Input.h | 27 + gdext/src/RdRenderer.cpp | 365 ++++++++++++ gdext/src/RdRenderer.h | 37 ++ gdext/src/RdRendererThreadSafe.cpp | 101 ++++ gdext/src/RdRendererThreadSafe.h | 33 ++ gdext/src/Renderer.h | 31 + gdext/src/ShortTermCache.cpp | 98 ++++ gdext/src/ShortTermCache.h | 31 + gdext/src/Viewports.cpp | 194 +++++++ gdext/src/Viewports.h | 61 ++ gdext/src/common.h | 27 + gdext/src/main.cpp | 99 ++++ gdext/vcpkg-macos.sh | 20 + gdext/vcpkg.json | 6 + src/MySecondNode.cs | 4 +- 54 files changed, 4215 insertions(+), 6 deletions(-) create mode 100644 .gitmodules create mode 100644 addons/imgui-godot/ImGuiGodot/ImGuiSync.cs create mode 100644 gdext/.clang-format create mode 100644 gdext/.gdignore create mode 100644 gdext/CMakeLists.txt create mode 100644 gdext/SConstruct create mode 160000 gdext/dear_bindings create mode 100644 gdext/gds_bindings.py create mode 160000 gdext/godot-cpp create mode 160000 gdext/imgui create mode 100644 gdext/imgui-godot-native.gdextension create mode 100644 gdext/include/imconfig-godot.h create mode 100644 gdext/include/imgui-godot.h create mode 100644 gdext/src/CMakeLists.txt create mode 100644 gdext/src/Context.cpp create mode 100644 gdext/src/Context.h create mode 100644 gdext/src/DummyRenderer.h create mode 100644 gdext/src/Fonts.cpp create mode 100644 gdext/src/Fonts.h create mode 100644 gdext/src/ImGuiAPI.cpp create mode 100644 gdext/src/ImGuiAPI.h create mode 100644 gdext/src/ImGuiGD.cpp create mode 100644 gdext/src/ImGuiGD.h create mode 100644 gdext/src/ImGuiLayer.cpp create mode 100644 gdext/src/ImGuiLayer.h create mode 100644 gdext/src/ImGuiLayerHelper.cpp create mode 100644 gdext/src/ImGuiLayerHelper.h create mode 100644 gdext/src/ImGuiRoot.cpp create mode 100644 gdext/src/ImGuiRoot.h create mode 100644 gdext/src/Input.cpp create mode 100644 gdext/src/Input.h create mode 100644 gdext/src/RdRenderer.cpp create mode 100644 gdext/src/RdRenderer.h create mode 100644 gdext/src/RdRendererThreadSafe.cpp create mode 100644 gdext/src/RdRendererThreadSafe.h create mode 100644 gdext/src/Renderer.h create mode 100644 gdext/src/ShortTermCache.cpp create mode 100644 gdext/src/ShortTermCache.h create mode 100644 gdext/src/Viewports.cpp create mode 100644 gdext/src/Viewports.h create mode 100644 gdext/src/common.h create mode 100644 gdext/src/main.cpp create mode 100644 gdext/vcpkg-macos.sh create mode 100644 gdext/vcpkg.json diff --git a/.gitignore b/.gitignore index ee4f82d..3633094 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,23 @@ msvc.*/ *.pdb doc/cpp-demo/MyGame/addons *.old.* +gdext/vcpkg_installed/ +.sconsign.dblite +gdext/x64/ +*.exp +*.obj +*.lib +*.os +gdext/proj/addons/imgui-godot-native/include/*.h +gdext/gen/ +*.o +*.pyc +gdext/proj/doc/ +gdext/proj/modules/ +gdext/proj/platform/ + +addons/imgui-godot-native/ +gdext/proj/addons/imgui-godot/ +gdext/samples/project/addons/ +addons/imgui-godot/imgui-godot-native.gdextension +addons/imgui-godot/include/*.h diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd7b205 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,9 @@ +[submodule "gdext/dear_bindings"] + path = gdext/dear_bindings + url = https://github.com/dearimgui/dear_bindings +[submodule "gdext/imgui"] + path = gdext/imgui + url = https://github.com/ocornut/imgui +[submodule "gdext/godot-cpp"] + path = gdext/godot-cpp + url = https://github.com/godotengine/godot-cpp diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiExtensions.cs b/addons/imgui-godot/ImGuiGodot/ImGuiExtensions.cs index aed6829..5b00ae4 100644 --- a/addons/imgui-godot/ImGuiGodot/ImGuiExtensions.cs +++ b/addons/imgui-godot/ImGuiGodot/ImGuiExtensions.cs @@ -1,3 +1,4 @@ +#if GODOT_PC using Godot; using ImGuiNET; using Vector3 = System.Numerics.Vector3; @@ -63,3 +64,4 @@ public static void SetIniFilename(this ImGuiIOPtr io, string fileName) Internal.State.Instance.SetIniFilename(io, fileName); } } +#endif diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs index 96a0b28..27ae133 100644 --- a/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs +++ b/addons/imgui-godot/ImGuiGodot/ImGuiGD.cs @@ -36,6 +36,8 @@ public static float Scale } } + public static bool Visible { get; set; } + static ImGuiGD() { _backend = ClassDB.ClassExists("ImGuiGD") ? new Internal.BackendNative() : new Internal.BackendNet(); diff --git a/addons/imgui-godot/ImGuiGodot/ImGuiSync.cs b/addons/imgui-godot/ImGuiGodot/ImGuiSync.cs new file mode 100644 index 0000000..a9214ca --- /dev/null +++ b/addons/imgui-godot/ImGuiGodot/ImGuiSync.cs @@ -0,0 +1,32 @@ +using Godot; +#if GODOT_PC +using ImGuiNET; +using System.Runtime.InteropServices; +using System; + +namespace ImGuiGodot; + +public partial class ImGuiSync : GodotObject +{ + public static void SyncPtrs() + { + GodotObject gd = Engine.GetSingleton("ImGuiGD"); + long[] ptrs = (long[])gd.Call("GetImGuiPtrs", + ImGui.GetVersion(), + Marshal.SizeOf(), + Marshal.SizeOf(), + sizeof(ushort), + sizeof(ushort) + ); + + if (ptrs.Length != 3) + return; + + checked + { + ImGui.SetCurrentContext((IntPtr)ptrs[0]); + ImGui.SetAllocatorFunctions((IntPtr)ptrs[1], (IntPtr)ptrs[2]); + } + } +} +#endif diff --git a/addons/imgui-godot/ImGuiGodot/Internal/BackendNative.cs b/addons/imgui-godot/ImGuiGodot/Internal/BackendNative.cs index d2bf4b6..191598a 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/BackendNative.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/BackendNative.cs @@ -1,3 +1,4 @@ +#if GODOT_PC using Godot; namespace ImGuiGodot.Internal; @@ -6,6 +7,7 @@ internal sealed class BackendNative : IBackend { public float JoyAxisDeadZone { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } public float Scale { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } + public bool Visible { get => throw new System.NotImplementedException(); set => throw new System.NotImplementedException(); } public void AddFont(FontFile fontData, int fontSize, bool merge) { @@ -37,3 +39,4 @@ public bool SubViewportWidget(SubViewport svp) throw new System.NotImplementedException(); } } +#endif diff --git a/addons/imgui-godot/ImGuiGodot/Internal/BackendNet.cs b/addons/imgui-godot/ImGuiGodot/Internal/BackendNet.cs index 94502cf..d7177e8 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/BackendNet.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/BackendNet.cs @@ -1,3 +1,4 @@ +#if GODOT_PC using Godot; using ImGuiNET; using System; @@ -8,12 +9,19 @@ namespace ImGuiGodot.Internal; internal sealed class BackendNet : IBackend { public float JoyAxisDeadZone { get; set; } = 0.15f; + public float Scale { get => State.Instance.Scale; set => State.Instance.Scale = value; } + public bool Visible + { + get => ImGuiLayer.Instance.Visible; + set => ImGuiLayer.Instance.Visible = value; + } + public void AddFont(FontFile fontData, int fontSize, bool merge) { State.Instance.Fonts.AddFont(fontData, fontSize, merge); @@ -59,3 +67,4 @@ public bool SubViewportWidget(SubViewport vp) return false; } } +#endif diff --git a/addons/imgui-godot/ImGuiGodot/Internal/IBackend.cs b/addons/imgui-godot/ImGuiGodot/Internal/IBackend.cs index 110131f..609e5b3 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/IBackend.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/IBackend.cs @@ -1,9 +1,11 @@ +#if GODOT_PC using Godot; namespace ImGuiGodot.Internal; internal interface IBackend { + public bool Visible { get; set; } public float JoyAxisDeadZone { get; set; } public float Scale { get; set; } public void ResetFonts(); @@ -13,3 +15,4 @@ internal interface IBackend public void Connect(Callable callable); public bool SubViewportWidget(SubViewport svp); } +#endif diff --git a/addons/imgui-godot/ImGuiGodot/Internal/IRenderer.cs b/addons/imgui-godot/ImGuiGodot/Internal/IRenderer.cs index e8d0a17..1413de4 100644 --- a/addons/imgui-godot/ImGuiGodot/Internal/IRenderer.cs +++ b/addons/imgui-godot/ImGuiGodot/Internal/IRenderer.cs @@ -1,3 +1,4 @@ +#if GODOT_PC using Godot; namespace ImGuiGodot.Internal; @@ -11,3 +12,4 @@ internal interface IRenderer public void OnHide(); public void Shutdown(); } +#endif diff --git a/addons/imgui-godot/plugin.cfg b/addons/imgui-godot/plugin.cfg index a69a7a8..34b4f11 100644 --- a/addons/imgui-godot/plugin.cfg +++ b/addons/imgui-godot/plugin.cfg @@ -3,5 +3,5 @@ name="imgui-godot" description="Dear ImGui for Godot" author="Patrick Dawson" -version="4.1.0" +version="5.0.0" script="scripts/ImGuiPlugin.gd" diff --git a/addons/imgui-godot/scripts/ImGuiRoot.gd b/addons/imgui-godot/scripts/ImGuiRoot.gd index 7ba00a0..7b03e03 100644 --- a/addons/imgui-godot/scripts/ImGuiRoot.gd +++ b/addons/imgui-godot/scripts/ImGuiRoot.gd @@ -6,11 +6,16 @@ const csharp_layer := "res://addons/imgui-godot/ImGuiGodot/ImGuiLayer.cs" const csharp_sync := "res://addons/imgui-godot/ImGuiGodot/ImGuiSync.cs" func _enter_tree(): + Engine.register_singleton("ImGuiRoot", self) var features := ProjectSettings.get_setting("application/config/features") - if ClassDB.class_exists("ImGuiLayerNative"): - add_child(ClassDB.instantiate("ImGuiLayerNative")) + if ClassDB.class_exists("ImGuiLayer"): + # native + add_child(ClassDB.instantiate("ImGuiLayer")) if "C#" in features: - add_child(load(csharp_sync).new()) + var obj: Object = load(csharp_sync).new() + obj.SyncPtrs() + obj.free() else: + # C# only if "C#" in features: add_child(load(csharp_layer).new()) diff --git a/gdext/.clang-format b/gdext/.clang-format new file mode 100644 index 0000000..c6eaef4 --- /dev/null +++ b/gdext/.clang-format @@ -0,0 +1,14 @@ +Language: Cpp +BasedOnStyle: Microsoft +AccessModifierOffset: -4 +AllowAllArgumentsOnNextLine: false +AlignEscapedNewlines: Left +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: true +PointerAlignment: Left + +BreakBeforeBraces: Custom +BraceWrapping: + AfterNamespace: false + AfterExternBlock: false diff --git a/gdext/.gdignore b/gdext/.gdignore new file mode 100644 index 0000000..e69de29 diff --git a/gdext/CMakeLists.txt b/gdext/CMakeLists.txt new file mode 100644 index 0000000..f5995f5 --- /dev/null +++ b/gdext/CMakeLists.txt @@ -0,0 +1,92 @@ +cmake_minimum_required(VERSION 3.26) + +# using cmake for dev on Windows, scons for production builds + +# set up vcpkg +if (WIN32) + set(VCPKG_TARGET_TRIPLET x64-windows-static-md) +endif() +file(TO_CMAKE_PATH "$ENV{VCPKG_ROOT}" VCPKG_ROOT) +set(CMAKE_TOOLCHAIN_FILE "${VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake") + +project(imgui-godot-native CXX) + +if (NOT TARGET godot-cpp) + include(FetchContent) + FetchContent_Declare( + godot-cpp + GIT_REPOSITORY https://github.com/godotengine/godot-cpp + GIT_TAG 4.2 + ) + FetchContent_MakeAvailable(godot-cpp) +endif() + +find_package(freetype CONFIG REQUIRED) +#find_package(unofficial-lunasvg CONFIG REQUIRED) + +add_library(imgui-godot-native SHARED) +target_compile_features(imgui-godot-native PRIVATE cxx_std_20) +target_compile_definitions(imgui-godot-native PUBLIC + IMGUI_USER_CONFIG="imconfig-godot.h" + IMGUI_ENABLE_FREETYPE +# IMGUI_ENABLE_FREETYPE_LUNASVG + IGN_EXPORT + ) + +if (MSVC) + target_compile_options(godot-cpp PRIVATE "/MP") + target_compile_options(imgui-godot-native PRIVATE "/MP") +endif() + +add_subdirectory(src) + +target_sources(imgui-godot-native PRIVATE + imgui/imgui.cpp + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_tables.cpp + imgui/imgui_widgets.cpp + imgui/imgui.h + imgui/misc/freetype/imgui_freetype.cpp + imgui/misc/freetype/imgui_freetype.h + include/imconfig-godot.h + include/imgui-godot.h + gen/imgui_bindings.gen.h + gen/cimgui.cpp + gen/cimgui.h + ) + +target_link_libraries(imgui-godot-native PUBLIC + godot-cpp + freetype +# unofficial::lunasvg::lunasvg + ) + +target_include_directories(imgui-godot-native PRIVATE src imgui gen PUBLIC include) +set_target_properties(imgui-godot-native PROPERTIES PUBLIC_HEADER "include/imconfig-godot.h;include/imgui-godot.h") + +if(WIN32) + set_property(TARGET imgui-godot-native + PROPERTY OUTPUT_NAME "libimgui-godot-native.windows.$,debug,release>.x86_64") +elseif(APPLE) + set_property(TARGET imgui-godot-native + PROPERTY OUTPUT_NAME "imgui-godot-native.macos.$,debug,release>") + set_target_properties(imgui-godot-native PROPERTIES SUFFIX "") +endif() + +set(CMAKE_INSTALL_PREFIX "${CMAKE_SOURCE_DIR}/../addons/imgui-godot") + +if(WIN32) + install(TARGETS imgui-godot-native + RUNTIME DESTINATION bin + PUBLIC_HEADER DESTINATION include + ) +elseif(APPLE) + install(TARGETS imgui-godot-native + DESTINATION bin/libimgui-godot-native.macos.$,debug,release>.framework + PUBLIC_HEADER DESTINATION include + ) +endif() +install(FILES "${CMAKE_SOURCE_DIR}/imgui-godot-native.gdextension" + DESTINATION . + ) diff --git a/gdext/SConstruct b/gdext/SConstruct new file mode 100644 index 0000000..8b828d7 --- /dev/null +++ b/gdext/SConstruct @@ -0,0 +1,77 @@ +#!/usr/bin/env python +from glob import glob +from pathlib import Path + +env = SConscript("godot-cpp/SConstruct") +env.Append(CPPDEFINES=['IMGUI_USER_CONFIG="\\"imconfig-godot.h\\""', "IGN_EXPORT"]) +env.Append(CPPPATH=["src/", "imgui/", "include/", "gen/"]) +env.Replace(CXXFLAGS=str(env["CXXFLAGS"]).replace("c++17", "c++20")) + +sources = Glob("src/*.cpp") + Glob("imgui/*.cpp") + Glob("gen/*.cpp") + +(extension_path,) = glob("proj/addons/*/*.gdextension") +addon_path = Path(extension_path).parent +project_name = Path(extension_path).stem + +config = "release" if env["target"] == "template_release" else "debug" + +libpath = env.get("LIBPATH", []) +libs = [env["LIBS"]] + +windows = env["platform"] == "windows" +linux = env["platform"] == "linux" + +if config == "release": + if windows: + env.Append(CPPDEFINES=["NDEBUG"]) + +if windows: + triplet = "x64-windows-static" +elif linux: + triplet = "x64-linux" +else: + triplet = "arm64-osx" + +env.Append(CPPPATH=["imgui/misc/freetype/"]) +env.Append(CPPPATH=[f"vcpkg_installed/{triplet}/include"]) +env.Append(CPPDEFINES=["IMGUI_ENABLE_FREETYPE"]) #, "IMGUI_ENABLE_FREETYPE_LUNASVG"]) +sources += Glob("imgui/misc/freetype/*.cpp") +libpath += [f"vcpkg_installed/{triplet}/lib"] +libs += [ + "freetype", +# "lunasvg", + "bz2", + "libpng16" if windows else "png16", + "zlib" if windows else "z", + "brotlidec", + "brotlicommon", +] + +if env["platform"] == "macos": + library = env.SharedLibrary( + "{0}/bin/lib{1}.{2}.{3}.framework/{1}.{2}.{3}".format( + addon_path, + project_name, + env["platform"], + config, + ), + source=sources, + LIBPATH=libpath, + LIBS=libs, + ) +else: + library = env.SharedLibrary( + "{}/bin/lib{}.{}.{}.{}{}".format( + addon_path, + project_name, + env["platform"], + config, + env["arch"], + env["SHLIBSUFFIX"], + ), + source=sources, + LIBPATH=libpath, + LIBS=libs, + ) + +Default(library) diff --git a/gdext/dear_bindings b/gdext/dear_bindings new file mode 160000 index 0000000..0c33579 --- /dev/null +++ b/gdext/dear_bindings @@ -0,0 +1 @@ +Subproject commit 0c33579c155a8d87dcbfab68b508157396eb10ca diff --git a/gdext/gds_bindings.py b/gdext/gds_bindings.py new file mode 100644 index 0000000..a37f557 --- /dev/null +++ b/gdext/gds_bindings.py @@ -0,0 +1,543 @@ +import json +import os +import sys +import subprocess + +sys.path += ["dear_bindings"] +import dear_bindings + +# TODO: callbacks as Callable +# TODO: array wrappers + +type_map = { + "bool": "bool", + "bool*": "Array", + "char": "char", + "char*": "Array", + "const char*": "String", + "const float*": "Array", + "const ImGuiTableColumnSortSpecs*": "Array", + "double": "double", + "double*": "Array", + "float": "float", + "float*": "Array", + "ImDrawFlags": "BitField", + "ImDrawList*": "Ref", + "ImFont*": "int64_t", + "ImGuiBackendFlags": "BitField", + "ImGuiButtonFlags": "BitField", + "ImGuiCol": "Col", + "ImGuiColorEditFlags": "BitField", + "ImGuiComboFlags": "BitField", + "ImGuiCond": "Cond", + "ImGuiConfigFlags": "BitField", + "ImGuiDataType": "DataType", + "ImGuiDir": "ImGui::Dir", + "ImGuiDockNodeFlags": "BitField", + "ImGuiFocusedFlags": "BitField", + "ImGuiHoveredFlags": "BitField", + "ImGuiID": "uint32_t", + "ImGuiInputTextFlags": "BitField", + "ImGuiIO*": "Ref", + "ImGuiKey": "ImGui::Key", + "ImGuiMouseButton": "MouseButton", + "ImGuiPopupFlags": "BitField", + "ImGuiSliderFlags": "BitField", + "ImGuiSortDirection": "SortDirection", + "ImGuiStyle*": "Ref", + "ImGuiStyleVar": "StyleVar", + "ImGuiTabBarFlags": "BitField", + "ImGuiTabItemFlags": "BitField", + "ImGuiTableColumnFlags": "BitField", + "ImGuiTableFlags": "BitField", + "ImGuiTableRowFlags": "BitField", + "ImGuiTreeNodeFlags": "BitField", + "ImGuiViewportFlags": "BitField", + "ImGuiWindowFlags": "BitField", + "ImS16": "int16_t", + "ImTextureID": "Ref", + "ImU16": "uint16_t", + "ImU32": "Color", + "ImU8": "uint8_t", + "ImVec2": "Vector2", + "ImVec4": "Color", + "int": "int", + "int*": "Array", + "short": "short", + "size_t": "int64_t", + "void": "void", +} + +# use StringName for these const char* params +sn_names = ( + "desc_id", + "id", + "label", + "name", + "str_id_begin", + "str_id_end", + "str_id", + "tab_or_docked_window_label", +) + +exclude_funcs = ( + "ImGui_GetKeyIndex", + "ImGui_ColorConvertFloat4ToU32", + "ImGui_ColorConvertHSVtoRGB", + "ImGui_ColorConvertU32ToFloat4", + "ImGui_CreateContext", + "ImGui_DestroyContext", + "ImGui_DestroyPlatformWindows", + "ImGui_EndFrame", + "ImGui_GetColorU32", + "ImGui_GetColorU32Ex", + "ImGui_GetColorU32ImU32", + "ImGui_GetColorU32ImVec4", + "ImGui_GetCurrentContext", + "ImGui_NewFrame", + "ImGui_Render", + "ImGui_RenderPlatformWindowsDefault", + "ImGui_SetCurrentContext", + "ImGui_TextUnformatted", # this is called by Text() + "ImGui_TextUnformattedEx", + "ImGui_UpdatePlatformWindows", +) + +include_structs = ( + "ImGuiIO", + "ImGuiStyle", + "ImDrawList", + # "ImGuiTableColumnSortSpecs", + # "ImGuiTableSortSpecs", + # "ImGuiTextFilter", + # "ImGuiWindowClass", +) + +array_types = { + "bool*": "bool", + "char*": "String", + "int*": "int", + "float*": "float", + "double*": "double", + "const float*": "float", +} + + +def is_obsolete(j): + for cond in j.get("conditionals", ()): + if cond["condition"] == "ifndef" and cond["expression"].find("OBSOLETE") != -1: + return True + return False + + +class Enum: + def __init__(self, j): + self.orig_name = j["name"] + self.obsolete = is_obsolete(j) + self.bitfield = self.orig_name.endswith("Flags_") + self.vals = [] + + self.name = self.orig_name.strip("_") + if self.name.startswith("ImGui"): + self.name = self.name.replace("ImGui", "", 1) + elif self.name.startswith("Im"): + self.name = self.name.replace("Im", "", 1) + + ignore_endings = tuple(["COUNT", "_", "BEGIN", "END", "OFFSET", "SIZE"]) + for e in j["elements"]: + name = e["name"] + if not is_obsolete(e) and not name.endswith(ignore_endings): + if name.startswith("ImGui"): + gdname = name.replace("ImGui", "", 1) + elif name.startswith("Im"): + gdname = name.replace("Im", "", 1) + self.vals.append((gdname, name)) + + def gen_def(self): + rv = f"enum {self.name} {{ \\\n" + for kv in self.vals: + rv += f"{kv[0]} = {kv[1]}, " + rv += "}; \\\n" + return rv + + def gen_cast(self): + macro = "VARIANT_BITFIELD_CAST" if self.bitfield else "VARIANT_ENUM_CAST" + return f"{macro}(ImGui::Godot::ImGui::{self.name}); \\\n" + + def gen_bindings(self): + macro = "BIND_BITFIELD_FLAG" if self.bitfield else "BIND_ENUM_CONSTANT" + rv = "" + for kv in self.vals: + rv += f"{macro}({kv[0]}); \\\n" + return rv + + +class ReturnType: + def __init__(self, j): + self.orig_type = j["declaration"] + self.gdtype = type_map.get(self.orig_type) + self.is_struct = self.gdtype and self.gdtype.endswith("Ptr>") + + +class Param: + def __init__(self, j): + self.name = j["name"] + self.is_array = j["is_array"] + self.is_varargs = j["is_varargs"] + if not self.is_varargs: + self.orig_type = j["type"]["declaration"] + else: + self.orig_type = None + self.gdtype = type_map.get(self.orig_type) + if self.is_array: + self.gdtype = "Array" + self.orig_type = self.orig_type[:self.orig_type.find('[')] + + if self.gdtype == "String" and self.name in sn_names: + self.gdtype = "StringName" + + if self.name == "items_separated_by_zeros": + self.gdtype = "Array" + + dv = j.get("default_value") + if dv: + dv = dv.replace("ImVec2", "Vector2") + dv = dv.replace("ImVec4", "Color") + dv = dv.replace("FLT_MIN", "std::numeric_limits::min()") + dv = dv.replace("FLT_MAX", "std::numeric_limits::max()") + dv = dv.replace("sizeof", "(uint64_t)sizeof") + if self.gdtype is not None: + dv = dv.replace("NULL", f"{self.gdtype}()") + self.dv = dv + self.is_struct = self.gdtype and self.gdtype.endswith("Ptr>") + + def gen_decl(self): + rv = f"{self.gdtype} {self.name}" + # if self.dv: + # rv += f" = {self.dv}" + return rv + + def gen_def(self): + return f"{self.gdtype} {self.name}" + + def gen_arg(self, safe_fmt=True): + if self.gdtype == "String": + if self.dv is not None: + return f"{self.name}.ptr() ? {self.name}.utf8().get_data() : nullptr" + else: + rv = f"{self.name}.utf8().get_data()" + if safe_fmt and self.name == "fmt": + return f'"%s", {rv}' + else: + return rv + elif self.gdtype == "Vector2": + return f"{{{self.name}.x, {self.name}.y}}" + elif self.gdtype == "Ref": + return f"(ImTextureID){self.name}->get_rid().get_id()" + elif self.gdtype == "Color": + if self.orig_type == "ImU32": + return f"{self.name}.to_abgr32()" + else: + return f"{{{self.name}.r, {self.name}.g, {self.name}.b, {self.name}.a}}" + elif self.gdtype == "StringName": + return f"sn_to_cstr({self.name})" + elif self.gdtype == "Array": + if self.is_array: + rv = f"({self.orig_type}*)GdsArray<{self.orig_type}>({self.name})" + elif self.name == "items_separated_by_zeros": + rv = f"({self.orig_type})GdsZeroArray({self.name})" + else: + atype = array_types[self.orig_type] + rv = f"{self.name}.size() == 0 ? nullptr : " if self.dv else "" + rv += f"({self.orig_type})GdsPtr<{atype}>({self.name}" + if atype == "String": + rv += ", buf_size, label" + rv += ")" + return rv + elif self.orig_type in ["ImFont*"]: # opaque pointers + return f"({self.orig_type}){self.name}" + elif self.is_struct: + return f"{self.name}->_GetPtr()" + else: + return self.name + + +class Function: + def __init__(self, j): + self.obsolete = is_obsolete(j) + self.orig_name = j["name"] + self.name = self.orig_name + if self.name.startswith("ImGui_"): + self.name = self.name.replace("ImGui_", "", 1) + + self.rt = ReturnType(j["return_type"]) + self.params = [] + for ja in j["arguments"]: + self.params.append(Param(ja)) + for p in self.params: + if p.is_varargs or p.orig_type == "va_list": + self.params.remove(p) + + self.valid = ( + self.rt.gdtype is not None + and not self.obsolete + and self.orig_name not in exclude_funcs + and not self.orig_name.endswith("V") + and not self.orig_name.startswith("ImGuiIO_") + and not self.orig_name.startswith("ImFont_") + ) + if self.valid: + for p in self.params: + if p.gdtype is None: + self.valid = False + + def gen_decl(self): + return f'static {self.rt.gdtype} {self.name}({", ".join(p.gen_decl() for p in self.params)}); \\\n' + + def gen_def(self): + fname = self.orig_name + safe_fmt = True + if fname == "ImGui_Text": + safe_fmt = False + fname = "ImGui_TextUnformatted" # do your own formatting + fcall = f'::{fname}({", ".join(p.gen_arg(safe_fmt) for p in self.params)})' + if self.rt.gdtype in ("Vector2", "Color"): + fcall = f"To{self.rt.gdtype}({fcall})" + elif self.rt.orig_type in ["ImFont*"]: + fcall = f"({self.rt.gdtype}){fcall}" + + rv = f'{self.rt.gdtype} ImGui::{self.name}({", ".join(p.gen_def() for p in self.params)}) {{ \\\n' + + if self.rt.is_struct: + rv += f"{self.rt.gdtype} rv; \\\n" + rv += "rv.instantiate(); \\\n" + rv += f"rv->_SetPtr({fcall}); \\\n" + rv += "return rv" + else: + if self.rt.gdtype != "void": + rv += "return " + rv += fcall + + rv += "; } \\\n" + return rv + + def gen_bindings(self): + rv = f'ClassDB::bind_static_method("ImGui", D_METHOD("{self.name}"' + for p in self.params: + rv += f', "{p.name}"' + rv += f"), &ImGui::{self.name}" + for p in self.params: + if p.dv is not None: + rv += f", DEFVAL({p.dv})" + rv += "); \\\n" + return rv + + +class Property: + variant_types = { + "float": "FLOAT", + "Vector2": "VECTOR2", + "bool": "BOOL", + } + + def __init__(self, j, name, struct_name): + self.struct_name = struct_name + self.name = name + self.is_array = j.get("is_array", False) + self.is_internal = j.get("is_internal", False) + self.orig_type = j["type"]["declaration"] + self.valid = False + if self.is_internal: + return + self.gdtype = type_map.get(self.orig_type, None) + if self.is_array: + self.gdtype = None + if self.gdtype == "String": + self.gdtype = None + self.valid = self.gdtype is not None + + def gen_decl(self): + rv = f"{self.gdtype} _Get{self.name}(); \\\n" + rv += f"void _Set{self.name}({self.gdtype} x); \\\n" + return rv + + def gen_def(self): + rv = f"{self.gdtype} {self.struct_name}::_Get{self.name}() {{ \\\n" + fcall = f"ptr->{self.name}" + # TODO: refactor + if self.orig_type == "ImVec2": + fcall = f"ToVector2({fcall})" + elif self.orig_type in ["ImFont*"]: + fcall = f"(int64_t){fcall}" + + dv = "{}" + if self.gdtype.startswith("BitField"): + dv = "0" + cast = "" + if self.gdtype == "ImGui::Dir": + cast = f"({self.gdtype})" + rv += f"if (ptr) return {cast}{fcall}; else return {dv};\\\n" + rv += "} \\\n" + + rv += f"void {self.struct_name}::_Set{self.name}({self.gdtype} x) {{ \\\n" + x = "x" + if self.orig_type == "ImVec2": + x = "{x.x, x.y}" + elif self.orig_type in ["ImFont*"]: + x = "(ImFont*)x" + rv += f"ptr->{self.name} = {x}; \\\n" + rv += "} \\\n" + return rv + + def gen_bindings(self): + getter = f"_Get{self.name}" + setter = f"_Set{self.name}" + rv = f'ClassDB::bind_method(D_METHOD("{getter}"), &{self.struct_name}::{getter}); \\\n' + rv += f'ClassDB::bind_method(D_METHOD("{setter}", "x"), &{self.struct_name}::{setter}); \\\n' + + vtype = Property.variant_types.get(self.gdtype, "INT") + rv += f'ADD_PROPERTY(PropertyInfo(Variant::{vtype}, "{self.name}"), "{setter}", "{getter}"); \\\n' + return rv + + +class Struct: + def __init__(self, j): + self.orig_name = j["name"] + self.name = self.orig_name + "Ptr" + self.valid = self.orig_name in include_structs + self.properties = [] + + if not self.valid: + return + + for jfield in j["fields"]: + prop = Property(jfield, jfield["name"], self.name) + if prop.valid: + self.properties.append(prop) + + def gen_decl(self): + rv = f"class {self.name} : public RefCounted {{ \\\n" + rv += f"GDCLASS({self.name}, RefCounted); \\\n" + rv += "protected: static void _bind_methods(); \\\n" + rv += "public: \\\n" + rv += f"void _SetPtr({self.orig_name}* p) {{ ptr = p; }} \\\n" + rv += f"{self.orig_name}* _GetPtr() {{ return ptr; }} \\\n" + for prop in self.properties: + rv += prop.gen_decl() + rv += "private: \\\n" + rv += f"{self.orig_name}* ptr = nullptr; \\\n" + rv += "}; \\\n" + return rv + + def gen_def(self): + rv = f"void {self.name}::_bind_methods() {{ \\\n" + for prop in self.properties: + rv += prop.gen_bindings() + rv += "} \\\n" + for prop in self.properties: + rv += prop.gen_def() + return rv + + def gen_bindings(self): + return f"ClassDB::register_internal_class<{self.name}>(); \\\n" + + +class JsonParser: + array_types = { + "bool*": "bool", + "char*": "String", + "int*": "int", + "float*": "float", + "double*": "double", + } + + def __init__(self): + for s in include_structs: + type_map[f"{s}*"] = f"Ref<{s}Ptr>" + + self.enums = [] + self.structs = [] + self.funcs = [] + self.enum_defs = "#define DEFINE_IMGUI_ENUMS() \\\n" + self.enum_casts = "#define CAST_IMGUI_ENUMS() \\\n" + self.enum_binds = "#define REGISTER_IMGUI_ENUMS() \\\n" + self.func_decls = "#define DECLARE_IMGUI_FUNCS() \\\n" + self.func_binds = "#define BIND_IMGUI_FUNCS() \\\n" + self.func_defs = "#define DEFINE_IMGUI_FUNCS() \\\n" + self.struct_decls = "#define DECLARE_IMGUI_STRUCTS() \\\n" + self.struct_defs = "#define DEFINE_IMGUI_STRUCTS() \\\n" + self.struct_binds = "#define BIND_IMGUI_STRUCTS() \\\n" + + def write(self): + try: + os.mkdir("gen") + except: + pass + + with open("gen/imgui_bindings.gen.h", "w") as fi: + fi.write("#include \n\n") + fi.write(self.enum_defs) + fi.write(self.enum_binds) + fi.write(self.enum_casts) + fi.write(self.struct_decls) + fi.write(self.struct_defs) + fi.write(self.struct_binds) + fi.write(self.func_decls) + fi.write(self.func_binds) + fi.write(self.func_defs) + + def load(self, jdat): + enums = [] + for je in jdat["enums"]: + e = Enum(je) + self.enum_defs += e.gen_def() + self.enum_casts += e.gen_cast() + self.enum_binds += e.gen_bindings() + enums.append(e) + self.enum_defs += "\n\n" + self.enum_binds += "\n\n" + self.enum_casts += "\n\n" + + for js in jdat["structs"]: + s = Struct(js) + if s.valid: + self.structs.append(s) + for s in self.structs: + self.struct_decls += s.gen_decl() + self.struct_defs += s.gen_def() + self.struct_binds += s.gen_bindings() + self.struct_decls += "\n\n" + self.struct_defs += "\n\n" + self.struct_binds += "\n\n" + + for jf in jdat["functions"]: + f = Function(jf) + if f.valid: + self.funcs.append(f) + for f in self.funcs: + self.func_decls += f.gen_decl() + self.func_defs += f.gen_def() + self.func_binds += f.gen_bindings() + + self.func_decls += "\n\n" + self.func_defs += "\n\n" + self.func_binds += "\n\n" + + +def main(): + os.makedirs("gen", exist_ok=True) + # dear_bindings.convert_header( + # "imgui/imgui.h", "gen/cimgui", "dear_bindings/src/templates" + # ) + + parser = JsonParser() + with open("gen/cimgui.json") as jfi: + jdat = json.loads(jfi.read()) + parser.load(jdat) + parser.write() + + subprocess.call("clang-format -i gen/imgui_bindings.gen.h", shell=True) + + +if __name__ == "__main__": + main() diff --git a/gdext/godot-cpp b/gdext/godot-cpp new file mode 160000 index 0000000..78ffea5 --- /dev/null +++ b/gdext/godot-cpp @@ -0,0 +1 @@ +Subproject commit 78ffea5b136f3178c31cddb28f6b963ceaa89420 diff --git a/gdext/imgui b/gdext/imgui new file mode 160000 index 0000000..2dc85e6 --- /dev/null +++ b/gdext/imgui @@ -0,0 +1 @@ +Subproject commit 2dc85e6e438e6d7f485317c9b76dc153535fed16 diff --git a/gdext/imgui-godot-native.gdextension b/gdext/imgui-godot-native.gdextension new file mode 100644 index 0000000..08c14db --- /dev/null +++ b/gdext/imgui-godot-native.gdextension @@ -0,0 +1,13 @@ +[configuration] + +entry_symbol = "ign_init" +compatibility_minimum = 4.2 + +[libraries] + +macos.debug = "bin/libimgui-godot-native.macos.debug.framework" +macos.release = "bin/libimgui-godot-native.macos.release.framework" +windows.debug.x86_64 = "bin/libimgui-godot-native.windows.debug.x86_64.dll" +windows.release.x86_64 = "bin/libimgui-godot-native.windows.release.x86_64.dll" +linux.debug.x86_64 = "bin/libimgui-godot-native.linux.debug.x86_64.so" +linux.release.x86_64 = "bin/libimgui-godot-native.linux.release.x86_64.so" diff --git a/gdext/include/imconfig-godot.h b/gdext/include/imconfig-godot.h new file mode 100644 index 0000000..df2c124 --- /dev/null +++ b/gdext/include/imconfig-godot.h @@ -0,0 +1,53 @@ +#pragma once + +#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS // match ImGui.NET + +#if __has_include("godot_cpp/godot.hpp") +#pragma warning(push, 0) +#include +#include +#include +#include +using godot::Color; +using godot::Vector2; +using godot::Vector2i; +using godot::Vector4; +#pragma warning(pop) +#else +#include "core/math/color.h" +#include "core/math/vector2.h" +#include "core/math/vector2i.h" +#include "core/math/vector4.h" +#endif + +#define IM_VEC2_CLASS_EXTRA \ + constexpr ImVec2(const Vector2& f) : x(f.x), y(f.y) \ + { \ + } \ + operator Vector2() const \ + { \ + return Vector2(x, y); \ + } \ + constexpr ImVec2(const Vector2i& f) : x(static_cast(f.x)), y(static_cast(f.y)) \ + { \ + } \ + operator Vector2i() const \ + { \ + return Vector2i(static_cast(x), static_cast(y)); \ + } + +#define IM_VEC4_CLASS_EXTRA \ + constexpr ImVec4(const Vector4& f) : x(f.x), y(f.y), z(f.z), w(f.w) \ + { \ + } \ + operator Vector4() const \ + { \ + return Vector4(x, y, z, w); \ + } \ + constexpr ImVec4(const Color& c) : x(c.r), y(c.g), z(c.b), w(c.a) \ + { \ + } \ + operator Color() const \ + { \ + return Color(x, y, z, w); \ + } diff --git a/gdext/include/imgui-godot.h b/gdext/include/imgui-godot.h new file mode 100644 index 0000000..ec23fb6 --- /dev/null +++ b/gdext/include/imgui-godot.h @@ -0,0 +1,452 @@ +#pragma once + +#ifdef _WIN32 +#ifdef IGN_EXPORT +#define IGN_API __declspec(dllexport) +#else +#define IGN_API __declspec(dllimport) +#endif +#else +#define IGN_API +#endif + +#include + +#ifndef IMGUI_HAS_VIEWPORT +#error use ImGui docking branch +#endif + +#if __has_include("godot_cpp/godot.hpp") +#define IGN_GDEXT +#endif + +#ifdef IGN_GDEXT +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +using godot::Callable; +using godot::CharString; +using godot::Color; +using godot::Engine; +using godot::FontFile; +using godot::InputEvent; +using godot::JoyButton; +using godot::Key; +using godot::Object; +using godot::Ref; +using godot::Resource; +using godot::RID; +using godot::String; +using godot::SubViewport; +using godot::Texture2D; +using godot::TypedArray; +using godot::Vector2; +using godot::Window; +#else +#include "core/config/engine.h" +#include "core/variant/callable.h" +#include "scene/main/viewport.h" +#include "scene/main/window.h" +#include "scene/resources/texture.h" +#endif + +static_assert(sizeof(RID) == 8); +static_assert(sizeof(void*) == 8); +static_assert(sizeof(ImDrawIdx) == 2); +static_assert(sizeof(ImWchar) == 2); + +namespace ImGui::Godot { +#ifndef IGN_EXPORT +// C++ user interface +namespace detail { +inline static Object* ImGuiGD = nullptr; + +inline bool GET_IMGUIGD() +{ + if (ImGuiGD) + return true; +#ifdef IGN_GDEXT + ImGuiGD = Engine::get_singleton()->get_singleton("ImGuiGD"); +#else + ImGuiGD = Engine::get_singleton()->get_singleton_object("ImGuiGD"); +#endif + return ImGuiGD != nullptr; +} +} // namespace detail + +inline void AddFont(const Ref& fontFile, int fontSize, bool merge = false) +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("AddFont"); + detail::ImGuiGD->call(sn, fontSize, merge); +} + +inline void Connect(const Callable& callable) +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("Connect"); + detail::ImGuiGD->call(sn, callable); +} + +inline void RebuildFontAtlas(float scale) +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("RebuildFontAtlas"); + detail::ImGuiGD->call(sn, scale); +} + +inline void ResetFonts() +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("ResetFonts"); + detail::ImGuiGD->call(sn); +} + +inline void SetJoyAxisDeadZone(float deadZone) +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("SetJoyAxisDeadZone"); + detail::ImGuiGD->call(sn, deadZone); +} + +inline void SetVisible(bool vis) +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("SetVisible"); + detail::ImGuiGD->call(sn, vis); +} + +inline void ToolInit() +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("ToolInit"); + detail::ImGuiGD->call(sn); +} + +inline bool SubViewport(SubViewport* svp) +{ + ERR_FAIL_COND_V(!detail::GET_IMGUIGD(), false); + static const StringName sn("SubViewport"); + return detail::ImGuiGD->call(sn, svp); +} + +inline void SyncImGuiPtrs() +{ + ERR_FAIL_COND(!detail::GET_IMGUIGD()); + static const StringName sn("GetImGuiPtrs"); + TypedArray ptrs = detail::ImGuiGD->call(sn, + String(ImGui::GetVersion()), + (int32_t)sizeof(ImGuiIO), + (int32_t)sizeof(ImDrawVert), + (int32_t)sizeof(ImDrawIdx), + (int32_t)sizeof(ImWchar)); + + ERR_FAIL_COND(ptrs.size() != 3); + + ImGui::SetCurrentContext(reinterpret_cast((int64_t)ptrs[0])); + ImGuiMemAllocFunc alloc_func = reinterpret_cast((int64_t)ptrs[1]); + ImGuiMemFreeFunc free_func = reinterpret_cast((int64_t)ptrs[2]); + ImGui::SetAllocatorFunctions(alloc_func, free_func, nullptr); +} + +inline ImTextureID BindTexture(Texture2D* tex) +{ + return reinterpret_cast(tex->get_rid().get_id()); +} + +inline void Image(Texture2D* tex, const Vector2& size, const Vector2& uv0 = {0, 0}, const Vector2& uv1 = {1, 1}, + const Color& tint_col = {1, 1, 1, 1}, const Color& border_col = {0, 0, 0, 0}) +{ + ImGui::Image(BindTexture(tex), size, uv0, uv1, tint_col, border_col); +} + +inline void Image(const Ref& tex, const Vector2& size, const Vector2& uv0 = {0, 0}, + const Vector2& uv1 = {1, 1}, const Color& tint_col = {1, 1, 1, 1}, + const Color& border_col = {0, 0, 0, 0}) +{ + ImGui::Image(BindTexture(tex.ptr()), size, uv0, uv1, tint_col, border_col); +} + +inline bool ImageButton(const char* str_id, Texture2D* tex, const Vector2& size, const Vector2& uv0 = {0, 0}, + const Vector2& uv1 = {1, 1}, const Color& bg_col = {0, 0, 0, 0}, + const Color& tint_col = {1, 1, 1, 1}) +{ + return ImGui::ImageButton(str_id, BindTexture(tex), size, uv0, uv1, bg_col, tint_col); +} + +inline bool ImageButton(const char* str_id, const Ref& tex, const Vector2& size, const Vector2& uv0 = {0, 0}, + const Vector2& uv1 = {1, 1}, const Color& bg_col = {0, 0, 0, 0}, + const Color& tint_col = {1, 1, 1, 1}) +{ + return ImGui::ImageButton(str_id, BindTexture(tex.ptr()), size, uv0, uv1, bg_col, tint_col); +} +#endif + +#ifdef IGN_GDEXT +inline ImGuiKey ToImGuiKey(Key key) +{ + switch (key) + { + case Key::KEY_ESCAPE: + return ImGuiKey_Escape; + case Key::KEY_TAB: + return ImGuiKey_Tab; + case Key::KEY_BACKSPACE: + return ImGuiKey_Backspace; + case Key::KEY_ENTER: + return ImGuiKey_Enter; + case Key::KEY_KP_ENTER: + return ImGuiKey_KeypadEnter; + case Key::KEY_INSERT: + return ImGuiKey_Insert; + case Key::KEY_DELETE: + return ImGuiKey_Delete; + case Key::KEY_PAUSE: + return ImGuiKey_Pause; + case Key::KEY_PRINT: + return ImGuiKey_PrintScreen; + case Key::KEY_HOME: + return ImGuiKey_Home; + case Key::KEY_END: + return ImGuiKey_End; + case Key::KEY_LEFT: + return ImGuiKey_LeftArrow; + case Key::KEY_UP: + return ImGuiKey_UpArrow; + case Key::KEY_RIGHT: + return ImGuiKey_RightArrow; + case Key::KEY_DOWN: + return ImGuiKey_DownArrow; + case Key::KEY_PAGEUP: + return ImGuiKey_PageUp; + case Key::KEY_PAGEDOWN: + return ImGuiKey_PageDown; + case Key::KEY_SHIFT: + return ImGuiKey_LeftShift; + case Key::KEY_CTRL: + return ImGuiKey_LeftCtrl; + case Key::KEY_META: + return ImGuiKey_LeftSuper; + case Key::KEY_ALT: + return ImGuiKey_LeftAlt; + case Key::KEY_CAPSLOCK: + return ImGuiKey_CapsLock; + case Key::KEY_NUMLOCK: + return ImGuiKey_NumLock; + case Key::KEY_SCROLLLOCK: + return ImGuiKey_ScrollLock; + case Key::KEY_F1: + return ImGuiKey_F1; + case Key::KEY_F2: + return ImGuiKey_F2; + case Key::KEY_F3: + return ImGuiKey_F3; + case Key::KEY_F4: + return ImGuiKey_F4; + case Key::KEY_F5: + return ImGuiKey_F5; + case Key::KEY_F6: + return ImGuiKey_F6; + case Key::KEY_F7: + return ImGuiKey_F7; + case Key::KEY_F8: + return ImGuiKey_F8; + case Key::KEY_F9: + return ImGuiKey_F9; + case Key::KEY_F10: + return ImGuiKey_F10; + case Key::KEY_F11: + return ImGuiKey_F11; + case Key::KEY_F12: + return ImGuiKey_F12; + case Key::KEY_KP_MULTIPLY: + return ImGuiKey_KeypadMultiply; + case Key::KEY_KP_DIVIDE: + return ImGuiKey_KeypadDivide; + case Key::KEY_KP_SUBTRACT: + return ImGuiKey_KeypadSubtract; + case Key::KEY_KP_PERIOD: + return ImGuiKey_KeypadDecimal; + case Key::KEY_KP_ADD: + return ImGuiKey_KeypadAdd; + case Key::KEY_KP_0: + return ImGuiKey_Keypad0; + case Key::KEY_KP_1: + return ImGuiKey_Keypad1; + case Key::KEY_KP_2: + return ImGuiKey_Keypad2; + case Key::KEY_KP_3: + return ImGuiKey_Keypad3; + case Key::KEY_KP_4: + return ImGuiKey_Keypad4; + case Key::KEY_KP_5: + return ImGuiKey_Keypad5; + case Key::KEY_KP_6: + return ImGuiKey_Keypad6; + case Key::KEY_KP_7: + return ImGuiKey_Keypad7; + case Key::KEY_KP_8: + return ImGuiKey_Keypad8; + case Key::KEY_KP_9: + return ImGuiKey_Keypad9; + case Key::KEY_MENU: + return ImGuiKey_Menu; + case Key::KEY_SPACE: + return ImGuiKey_Space; + case Key::KEY_APOSTROPHE: + return ImGuiKey_Apostrophe; + case Key::KEY_COMMA: + return ImGuiKey_Comma; + case Key::KEY_MINUS: + return ImGuiKey_Minus; + case Key::KEY_PERIOD: + return ImGuiKey_Period; + case Key::KEY_SLASH: + return ImGuiKey_Slash; + case Key::KEY_0: + return ImGuiKey_0; + case Key::KEY_1: + return ImGuiKey_1; + case Key::KEY_2: + return ImGuiKey_2; + case Key::KEY_3: + return ImGuiKey_3; + case Key::KEY_4: + return ImGuiKey_4; + case Key::KEY_5: + return ImGuiKey_5; + case Key::KEY_6: + return ImGuiKey_6; + case Key::KEY_7: + return ImGuiKey_7; + case Key::KEY_8: + return ImGuiKey_8; + case Key::KEY_9: + return ImGuiKey_9; + case Key::KEY_SEMICOLON: + return ImGuiKey_Semicolon; + case Key::KEY_EQUAL: + return ImGuiKey_Equal; + case Key::KEY_A: + return ImGuiKey_A; + case Key::KEY_B: + return ImGuiKey_B; + case Key::KEY_C: + return ImGuiKey_C; + case Key::KEY_D: + return ImGuiKey_D; + case Key::KEY_E: + return ImGuiKey_E; + case Key::KEY_F: + return ImGuiKey_F; + case Key::KEY_G: + return ImGuiKey_G; + case Key::KEY_H: + return ImGuiKey_H; + case Key::KEY_I: + return ImGuiKey_I; + case Key::KEY_J: + return ImGuiKey_J; + case Key::KEY_K: + return ImGuiKey_K; + case Key::KEY_L: + return ImGuiKey_L; + case Key::KEY_M: + return ImGuiKey_M; + case Key::KEY_N: + return ImGuiKey_N; + case Key::KEY_O: + return ImGuiKey_O; + case Key::KEY_P: + return ImGuiKey_P; + case Key::KEY_Q: + return ImGuiKey_Q; + case Key::KEY_R: + return ImGuiKey_R; + case Key::KEY_S: + return ImGuiKey_S; + case Key::KEY_T: + return ImGuiKey_T; + case Key::KEY_U: + return ImGuiKey_U; + case Key::KEY_V: + return ImGuiKey_V; + case Key::KEY_W: + return ImGuiKey_W; + case Key::KEY_X: + return ImGuiKey_X; + case Key::KEY_Y: + return ImGuiKey_Y; + case Key::KEY_Z: + return ImGuiKey_Z; + case Key::KEY_BRACKETLEFT: + return ImGuiKey_LeftBracket; + case Key::KEY_BACKSLASH: + return ImGuiKey_Backslash; + case Key::KEY_BRACKETRIGHT: + return ImGuiKey_RightBracket; + case Key::KEY_QUOTELEFT: + return ImGuiKey_GraveAccent; + default: + return ImGuiKey_None; + }; +} + +inline ImGuiKey ToImGuiKey(JoyButton btn) +{ + switch (btn) + { + case JoyButton::JOY_BUTTON_A: + return ImGuiKey_GamepadFaceDown; + case JoyButton::JOY_BUTTON_B: + return ImGuiKey_GamepadFaceRight; + case JoyButton::JOY_BUTTON_X: + return ImGuiKey_GamepadFaceLeft; + case JoyButton::JOY_BUTTON_Y: + return ImGuiKey_GamepadFaceUp; + case JoyButton::JOY_BUTTON_BACK: + return ImGuiKey_GamepadBack; + case JoyButton::JOY_BUTTON_START: + return ImGuiKey_GamepadStart; + case JoyButton::JOY_BUTTON_LEFT_STICK: + return ImGuiKey_GamepadL3; + case JoyButton::JOY_BUTTON_RIGHT_STICK: + return ImGuiKey_GamepadR3; + case JoyButton::JOY_BUTTON_LEFT_SHOULDER: + return ImGuiKey_GamepadL1; + case JoyButton::JOY_BUTTON_RIGHT_SHOULDER: + return ImGuiKey_GamepadR1; + case JoyButton::JOY_BUTTON_DPAD_UP: + return ImGuiKey_GamepadDpadUp; + case JoyButton::JOY_BUTTON_DPAD_DOWN: + return ImGuiKey_GamepadDpadDown; + case JoyButton::JOY_BUTTON_DPAD_LEFT: + return ImGuiKey_GamepadDpadLeft; + case JoyButton::JOY_BUTTON_DPAD_RIGHT: + return ImGuiKey_GamepadDpadRight; + default: + return ImGuiKey_None; + }; +} +#endif + +#define IMGUI_GODOT_MODULE_INIT() \ + extern "C" { \ + void imgui_godot_module_init(uint32_t ver, ImGuiContext* ctx, ImGuiMemAllocFunc afunc, ImGuiMemFreeFunc ffunc) \ + { \ + IM_ASSERT(ver == IMGUI_VERSION_NUM); \ + ImGui::SetCurrentContext(ctx); \ + ImGui::SetAllocatorFunctions(afunc, ffunc, nullptr); \ + } \ + } + +} // namespace ImGui::Godot diff --git a/gdext/src/CMakeLists.txt b/gdext/src/CMakeLists.txt new file mode 100644 index 0000000..eae2de7 --- /dev/null +++ b/gdext/src/CMakeLists.txt @@ -0,0 +1,30 @@ +target_sources(imgui-godot-native PRIVATE + common.h + Context.cpp + Context.h + DummyRenderer.h + Fonts.cpp + Fonts.h + ImGuiAPI.cpp + ImGuiAPI.h + ImGuiGD.cpp + ImGuiGD.h + ImGuiLayer.cpp + ImGuiLayer.h + ImGuiLayerHelper.cpp + ImGuiLayerHelper.h + ImGuiRoot.cpp + ImGuiRoot.h + Input.cpp + Input.h + main.cpp + RdRenderer.cpp + RdRenderer.h + RdRendererThreadSafe.cpp + RdRendererThreadSafe.h + Renderer.h + ShortTermCache.cpp + ShortTermCache.h + Viewports.cpp + Viewports.h +) diff --git a/gdext/src/Context.cpp b/gdext/src/Context.cpp new file mode 100644 index 0000000..d586255 --- /dev/null +++ b/gdext/src/Context.cpp @@ -0,0 +1,268 @@ +#include "Context.h" +#include "DummyRenderer.h" +#include "Fonts.h" +#include "ImGuiGD.h" +#include "Input.h" +#include "RdRenderer.h" +#include "RdRendererThreadSafe.h" +#include "Renderer.h" +#include "ShortTermCache.h" +#include "Viewports.h" +#include "common.h" +#include + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +using namespace godot; + +namespace ImGui::Godot { + +namespace { +struct Context +{ + Window* mainWindow = nullptr; + std::unique_ptr renderer; + std::unique_ptr input; + std::unique_ptr fonts; + std::unique_ptr viewports; + RID svp; + RID ci; + Ref fontTexture; + bool headless = false; + int dpiFactor = 1; + bool scaleToDPI = false; + std::vector iniFilename; + + ~Context() + { + RenderingServer::get_singleton()->free_rid(ci); + RenderingServer::get_singleton()->free_rid(svp); + } +}; + +std::unique_ptr ctx; + +const char* PlatformName = "godot4"; +} // namespace + +void Init(godot::Window* mainWindow, RID canvasItem, const Ref& cfg) +{ + // re-init not allowed + if (ctx) + return; + + ctx = std::make_unique(); + ctx->mainWindow = mainWindow; + ctx->ci = canvasItem; + ctx->input = std::make_unique(ctx->mainWindow); + + int32_t screenDPI = DisplayServer::get_singleton()->screen_get_dpi(); + ctx->dpiFactor = std::max(1, screenDPI / 96); + ctx->scaleToDPI = ProjectSettings::get_singleton()->get_setting("display/window/dpi/allow_hidpi"); + + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ctx->mainWindow->get_size(); + + io.BackendPlatformName = PlatformName; + + io.BackendFlags |= ImGuiBackendFlags_HasGamepad; + io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; + io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; + io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; + io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; + io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; + + Array fonts = cfg->get("Fonts"); + bool addDefaultFont = cfg->get("AddDefaultFont"); + float scale = cfg->get("Scale"); + String iniFilename = cfg->get("IniFilename"); + String rendererName = cfg->get("Renderer"); + + SetIniFilename(iniFilename); + + RenderingServer* RS = RenderingServer::get_singleton(); + + ctx->headless = DisplayServer::get_singleton()->get_name() == "headless"; + + if (!ctx->headless && !RS->get_rendering_device()) + { + ctx->headless = true; + UtilityFunctions::printerr("imgui-godot requires RenderingDevice"); + } + + if (ctx->headless || rendererName == "Dummy") + { + ctx->renderer = std::make_unique(); + } + else + { + int threadModel = ProjectSettings::get_singleton()->get_setting("rendering/driver/threads/thread_model"); +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint()) + threadModel = 0; +#endif + if (threadModel == 2) + ctx->renderer = std::make_unique(); + else + ctx->renderer = std::make_unique(); + } + io.BackendRendererName = ctx->renderer->Name(); + + Object* igl = Engine::get_singleton()->get_singleton("ImGuiLayer"); + RS->connect("frame_pre_draw", Callable(igl, "on_frame_pre_draw")); + + ctx->svp = RS->viewport_create(); + RS->viewport_set_transparent_background(ctx->svp, true); + RS->viewport_set_update_mode(ctx->svp, RenderingServer::VIEWPORT_UPDATE_ALWAYS); + RS->viewport_set_clear_mode(ctx->svp, RenderingServer::VIEWPORT_CLEAR_NEVER); + RS->viewport_set_active(ctx->svp, true); + RS->viewport_set_parent_viewport(ctx->svp, ctx->mainWindow->get_viewport_rid()); + + ctx->fonts = std::make_unique(); + + for (int i = 0; i < fonts.size(); ++i) + { + Ref fontres = fonts[i]; + Ref font = fontres->get("FontData"); + int fontSize = fontres->get("FontSize"); + bool merge = fontres->get("Merge"); + AddFont(font, fontSize, i > 0 && merge); + } + if (addDefaultFont) + AddFontDefault(); + RebuildFontAtlas(scale); + + ctx->viewports = std::make_unique(ctx->mainWindow, ctx->svp); +} + +void Update(double delta) +{ + ImGuiIO& io = ImGui::GetIO(); + io.DisplaySize = ctx->mainWindow->get_size(); + io.DeltaTime = static_cast(delta); + + if (!ctx->headless) + ctx->input->Update(); + + gdscache->OnNewFrame(); + ImGui::NewFrame(); +} + +bool ProcessInput(const Ref& evt, Window* window) +{ + return ctx->input->ProcessInput(evt, window); +} + +void ProcessNotification(int what) +{ + if (ctx) + ctx->input->ProcessNotification(what); +} + +void Render() +{ + RenderingServer* RS = RenderingServer::get_singleton(); + godot::Vector2i winSize = ctx->mainWindow->get_size(); + RS->viewport_set_size(ctx->svp, winSize.x, winSize.y); + RID vptex = RS->viewport_get_texture(ctx->svp); + RS->canvas_item_clear(ctx->ci); + RS->canvas_item_set_transform(ctx->ci, ctx->mainWindow->get_final_transform().affine_inverse()); + RS->canvas_item_add_texture_rect(ctx->ci, godot::Rect2(0, 0, winSize.x, winSize.y), vptex); + + ImGui::Render(); + ImGui::UpdatePlatformWindows(); + ctx->renderer->Render(); +} + +void Shutdown() +{ + if (ImGui::GetCurrentContext()) + ImGui::DestroyContext(); + ctx.reset(); +} + +void Connect(const godot::Callable& callable) +{ + Object* igl = Engine::get_singleton()->get_singleton("ImGuiLayer"); + ERR_FAIL_COND(!igl); + igl->connect("imgui_layout", callable); +} + +void ResetFonts() +{ + ctx->fonts->Reset(); +} + +void AddFont(const Ref& fontFile, int fontSize, bool merge) +{ + ctx->fonts->Add(fontFile, fontSize, merge); +} + +void AddFontDefault() +{ + ctx->fonts->Add(nullptr, 13, false); +} + +void RebuildFontAtlas(float scale) +{ + ctx->fonts->RebuildFontAtlas(ctx->scaleToDPI ? ctx->dpiFactor * scale : scale); +} + +void SetIniFilename(const String& fn) +{ + ImGuiIO& io = ImGui::GetIO(); + if (fn.length() > 0) + { + std::string globalfn = ProjectSettings::get_singleton()->globalize_path(fn).utf8().get_data(); + ctx->iniFilename.resize(globalfn.length() + 1); + std::copy(globalfn.begin(), globalfn.end(), ctx->iniFilename.begin()); + ctx->iniFilename.back() = '\0'; + io.IniFilename = ctx->iniFilename.data(); + } + else + io.IniFilename = nullptr; +} + +void SetVisible(bool visible) +{ + CanvasLayer* igl = Object::cast_to(Engine::get_singleton()->get_singleton("ImGuiLayer")); + ERR_FAIL_COND(!igl); + igl->set_visible(visible); +} + +void OnFramePreDraw() +{ + ctx->renderer->OnFramePreDraw(); +} + +bool SubViewport(godot::SubViewport* svp) +{ + ImVec2 vpSize = svp->get_size(); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec2 pos_max = {pos.x + vpSize.x, pos.y + vpSize.y}; + ImGui::GetWindowDrawList()->AddImage((ImTextureID)svp->get_texture()->get_rid().get_id(), pos, pos_max); + + ImGui::PushID(svp->get_instance_id()); + ImGui::InvisibleButton("godot_subviewport", vpSize); + ImGui::PopID(); + + if (ImGui::IsItemHovered()) + { + ctx->input->SetActiveSubViewport(svp, pos); + return true; + } + return false; +} + +} // namespace ImGui::Godot diff --git a/gdext/src/Context.h b/gdext/src/Context.h new file mode 100644 index 0000000..c43e0e6 --- /dev/null +++ b/gdext/src/Context.h @@ -0,0 +1,40 @@ +#pragma once + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +using namespace godot; + +namespace ImGui::Godot +{ +//class Context +//{ +//}; + + void Init(Window* mainWindow, RID canvasItem, const Ref& config); + void Update(double delta); + bool ProcessInput(const Ref& evt, Window* window); + void ProcessNotification(int what); + void Render(); + void Shutdown(); + void Connect(const Callable& callable); + void ResetFonts(); + void AddFont(const Ref& fontFile, int fontSize, bool merge = false); + void AddFontDefault(); + void RebuildFontAtlas(float scale); + void SetIniFilename(const String& fn); + void SetVisible(bool visible); + + bool SubViewport(SubViewport* svp); + + void OnFramePreDraw(); +} diff --git a/gdext/src/DummyRenderer.h b/gdext/src/DummyRenderer.h new file mode 100644 index 0000000..6a4eafb --- /dev/null +++ b/gdext/src/DummyRenderer.h @@ -0,0 +1,26 @@ +#pragma once +#include "Renderer.h" + +namespace ImGui::Godot { + +class DummyRenderer : public Renderer +{ +public: + DummyRenderer() + { + } + ~DummyRenderer() + { + } + + const char* Name() override + { + return "godot4_dummy"; + } + + void Render() override + { + } +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/Fonts.cpp b/gdext/src/Fonts.cpp new file mode 100644 index 0000000..959d6f6 --- /dev/null +++ b/gdext/src/Fonts.cpp @@ -0,0 +1,188 @@ +#include "Fonts.h" + +#include +#include +#include +#include + +#include +#include + +using namespace godot; +using namespace std::literals; +namespace fs = std::filesystem; + +namespace ImGui::Godot { + +struct Fonts::Impl +{ + Ref fontTexture; + + struct FontParams + { + Ref font; + int fontSize; + bool merge; + ImVector ranges; + }; + std::vector fontConfig; + + void AddFontToAtlas(const Ref& font, int fontSize, bool merge, const ImVector& ranges); + + static ImVector GetRanges(const Ref& font) + { + ImVector rv; + if (!font.is_null()) + { + ImFontGlyphRangesBuilder builder; + builder.AddText(font->get_supported_chars().utf8().get_data()); + builder.BuildRanges(&rv); + } + return rv; + } + + static void ResetStyle() + { + ImGuiStyle defaultStyle; + ImGuiStyle& style = ImGui::GetStyle(); + + style.WindowPadding = defaultStyle.WindowPadding; + style.WindowRounding = defaultStyle.WindowRounding; + style.WindowMinSize = defaultStyle.WindowMinSize; + style.ChildRounding = defaultStyle.ChildRounding; + style.PopupRounding = defaultStyle.PopupRounding; + style.FramePadding = defaultStyle.FramePadding; + style.FrameRounding = defaultStyle.FrameRounding; + style.ItemSpacing = defaultStyle.ItemSpacing; + style.ItemInnerSpacing = defaultStyle.ItemInnerSpacing; + style.CellPadding = defaultStyle.CellPadding; + style.TouchExtraPadding = defaultStyle.TouchExtraPadding; + style.IndentSpacing = defaultStyle.IndentSpacing; + style.ColumnsMinSpacing = defaultStyle.ColumnsMinSpacing; + style.ScrollbarSize = defaultStyle.ScrollbarSize; + style.ScrollbarRounding = defaultStyle.ScrollbarRounding; + style.GrabMinSize = defaultStyle.GrabMinSize; + style.GrabRounding = defaultStyle.GrabRounding; + style.LogSliderDeadzone = defaultStyle.LogSliderDeadzone; + style.TabRounding = defaultStyle.TabRounding; + style.TabMinWidthForCloseButton = defaultStyle.TabMinWidthForCloseButton; + style.SeparatorTextPadding = defaultStyle.SeparatorTextPadding; +#if IMGUI_VERSION_NUM >= 18980 + style.DockingSeparatorSize = defaultStyle.DockingSeparatorSize; +#endif + style.DisplayWindowPadding = defaultStyle.DisplayWindowPadding; + style.DisplaySafeAreaPadding = defaultStyle.DisplaySafeAreaPadding; + style.MouseCursorScale = defaultStyle.MouseCursorScale; + } +}; + +void Fonts::Impl::AddFontToAtlas(const Ref& font, int fontSize, bool merge, const ImVector& ranges) +{ + ImFontConfig fc; + if (merge) + fc.MergeMode = 1; + + if (font.is_null()) + { + // default font + fc = {}; + fc.SizePixels = fontSize; + fc.OversampleH = 1; + fc.OversampleV = 1; + fc.PixelSnapH = true; + ImGui::GetIO().Fonts->AddFontDefault(&fc); + } + else + { + fs::path fontpath = (font->get_path().utf8().get_data()); + + // no std::format in Clang 14 + std::string fontdesc = fontpath.filename().string() + ", "s + std::to_string(fontSize) + "px"; + if (fontdesc.length() > 39) + fontdesc.resize(39); + std::copy(fontdesc.begin(), fontdesc.end(), fc.Name); + + int64_t len = font->get_data().size(); + // let ImGui manage this memory + void* p = ImGui::MemAlloc(len); + memcpy(p, font->get_data().ptr(), len); + ImGui::GetIO().Fonts->AddFontFromMemoryTTF(p, len, fontSize, &fc, ranges.Data); + } + + if (merge) + { + ImGui::GetIO().Fonts->Build(); + } +} + +Fonts::Fonts() : impl(std::make_unique()) +{ +} + +Fonts::~Fonts() +{ +} + +void Fonts::Reset() +{ + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->Clear(); + io.FontDefault = nullptr; + impl->fontConfig.clear(); +} + +void Fonts::Add(Ref fontData, int fontSize, bool merge) +{ + impl->fontConfig.push_back({fontData, fontSize, merge, Impl::GetRanges(fontData)}); +} + +void Fonts::RebuildFontAtlas(float scale) +{ + ImGuiIO& io = ImGui::GetIO(); + int fontIndex = -1; + if (io.FontDefault != nullptr) + { + for (int i = 0; i < io.Fonts->Fonts.Size; ++i) + { + if (io.Fonts->Fonts[i] == io.FontDefault) + { + fontIndex = i; + break; + } + } + io.FontDefault = nullptr; + } + io.Fonts->Clear(); + + for (const auto& fp : impl->fontConfig) + { + impl->AddFontToAtlas(fp.font, fp.fontSize * scale, fp.merge, fp.ranges); + } + + uint8_t* pixelData; + int width; + int height; + int bytesPerPixel; + io.Fonts->GetTexDataAsRGBA32(&pixelData, &width, &height, &bytesPerPixel); + + PackedByteArray pixels; + pixels.resize(width * height * bytesPerPixel); + memcpy(pixels.ptrw(), pixelData, pixels.size()); + + Ref img = Image::create_from_data(width, height, false, Image::FORMAT_RGBA8, pixels); + Ref imgtex = ImageTexture::create_from_image(img); + impl->fontTexture = imgtex; + + io.Fonts->SetTexID((ImTextureID)impl->fontTexture->get_rid().get_id()); + io.Fonts->ClearTexData(); + + if (fontIndex != -1 && fontIndex < io.Fonts->Fonts.Size) + { + io.FontDefault = io.Fonts->Fonts[fontIndex]; + } + + Impl::ResetStyle(); + ImGui::GetStyle().ScaleAllSizes(scale); +} + +} // namespace ImGui::Godot diff --git a/gdext/src/Fonts.h b/gdext/src/Fonts.h new file mode 100644 index 0000000..02d3a33 --- /dev/null +++ b/gdext/src/Fonts.h @@ -0,0 +1,24 @@ +#pragma once +#include + +#include +using namespace godot; + +namespace ImGui::Godot { + +class Fonts +{ +public: + Fonts(); + ~Fonts(); + + void Reset(); + void Add(Ref fontData, int fontSize, bool merge); + void RebuildFontAtlas(float scale); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiAPI.cpp b/gdext/src/ImGuiAPI.cpp new file mode 100644 index 0000000..1670879 --- /dev/null +++ b/gdext/src/ImGuiAPI.cpp @@ -0,0 +1,51 @@ +#include "ImGuiAPI.h" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +using namespace godot; + +namespace { +inline Vector2 ToVector2(ImVec2 v) +{ + return Vector2(v.x, v.y); +} + +inline Color ToColor(ImVec4 v) +{ + return Color(v.x, v.y, v.z, v.w); +} + +std::unordered_map stringname_cache; +const char* sn_to_cstr(const StringName& sn) +{ + if (!stringname_cache.contains(sn)) + { + stringname_cache[sn] = std::string(String(sn).utf8().get_data()); + } + return stringname_cache[sn].c_str(); +} +} // namespace + +namespace ImGui::Godot { + +void register_imgui_api() +{ + ClassDB::register_class<::ImGui::Godot::ImGui>(); +} + +void ImGui::_bind_methods() +{ + REGISTER_IMGUI_ENUMS(); + BIND_IMGUI_STRUCTS(); + BIND_IMGUI_FUNCS(); +} + +DEFINE_IMGUI_STRUCTS() + +DEFINE_IMGUI_FUNCS() + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiAPI.h b/gdext/src/ImGuiAPI.h new file mode 100644 index 0000000..b3a58be --- /dev/null +++ b/gdext/src/ImGuiAPI.h @@ -0,0 +1,171 @@ +#pragma once + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#pragma warning(pop) + +#include +#include +#include + +#include "ShortTermCache.h" +#include "imgui_bindings.gen.h" + +using namespace godot; + +namespace ImGui::Godot { + +template +struct GdsPtr +{ + // TypedArray& arr; + Array& arr; + T val; + + GdsPtr(Array& x) : arr(x) + { + // safety for tool script reload, and programming errors + if (arr.size() == 0) + arr.append(T()); + val = arr[0]; + } + + ~GdsPtr() + { + arr[0] = val; + } + + operator T*() + { + return &val; + } +}; + +template <> +struct GdsPtr +{ + Array& arr; + std::vector& buf; + int64_t bufhash; + + GdsPtr(Array& x, size_t s, const StringName& label) : arr(x), buf(gdscache->GetTextBuf(label, s, x)) + { + bufhash = std::hash{}({buf.begin(), buf.end()}); + } + + ~GdsPtr() + { + if (bufhash != std::hash{}({buf.begin(), buf.end()})) + { + arr[0] = String(buf.data()); + } + } + + operator char*() + { + return buf.data(); + } +}; + +template +struct GdsArray +{ + Array& arr; + std::vector buf; + + GdsArray(Array& a) : arr(a), buf(a.size()) + { + for (int i = 0; i < arr.size(); ++i) + { + buf[i] = arr[i]; + } + } + + ~GdsArray() + { + for (int i = 0; i < arr.size(); ++i) + { + arr[i] = buf[i]; + } + } + + operator T*() + { + return buf.data(); + } +}; + +template <> +struct GdsArray +{ + Array& arr; + std::vector buf; + std::vector ptrs; + + GdsArray(Array& a) : arr(a), buf(a.size()), ptrs(a.size()) + { + for (int i = 0; i < arr.size(); ++i) + { + buf[i] = String(arr[i]).utf8(); + ptrs[i] = buf[i].get_data(); + } + } + + ~GdsArray() + { + for (int i = 0; i < arr.size(); ++i) + { + arr[i] = buf[i].get_data(); + } + } + + operator const char* const*() + { + return ptrs.data(); + } +}; + +struct GdsZeroArray +{ + const std::vector& buf; + + GdsZeroArray(const Array& a) : buf(gdscache->GetZeroArray(a)) + { + } + + operator const char*() + { + return buf.data(); + } +}; + +#define VARIANT_CSTR(v) v.get_type() == Variant::STRING ? static_cast(v).utf8().get_data() : nullptr + +#define GDS_PTR(T, a) a.size() == 0 ? nullptr : (T*)GdsPtr(a) +// #define GDS_PTR_STR(a, len, label) a.size() == 0 ? nullptr : (char*)GdsPtr(a, len, label) + +class ImGuiIOPtr; +class ImGuiStylePtr; +class ImDrawListPtr; + +class ImGui : public Object +{ + GDCLASS(ImGui, Object); + +protected: + static void _bind_methods(); + +public: + DEFINE_IMGUI_ENUMS() + DECLARE_IMGUI_FUNCS() +}; + +DECLARE_IMGUI_STRUCTS() + +} // namespace ImGui::Godot + +CAST_IMGUI_ENUMS() diff --git a/gdext/src/ImGuiGD.cpp b/gdext/src/ImGuiGD.cpp new file mode 100644 index 0000000..47e01bf --- /dev/null +++ b/gdext/src/ImGuiGD.cpp @@ -0,0 +1,152 @@ +#include "ImGuiGD.h" +#include "Context.h" +#include "ImGuiLayer.h" +#include "ImGuiRoot.h" +#include "common.h" +#include +#include +#include +#include +#include + +namespace ImGui::Godot { + +void ImGuiGD::_bind_methods() +{ + ClassDB::bind_method(D_METHOD("_SetJoyAxisDeadZone", "swap"), &ImGuiGD::_SetJoyAxisDeadZone); + ClassDB::bind_method(D_METHOD("_GetJoyAxisDeadZone"), &ImGuiGD::_GetJoyAxisDeadZone); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "JoyAxisDeadZone"), "_SetJoyAxisDeadZone", "_GetJoyAxisDeadZone"); + + ClassDB::bind_method(D_METHOD("_SetVisible", "visible"), &ImGuiGD::_SetVisible); + ClassDB::bind_method(D_METHOD("_GetVisible"), &ImGuiGD::_GetVisible); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "Visible"), "_SetVisible", "_GetVisible"); + + ClassDB::bind_method(D_METHOD("AddFont", "font_file", "font_size", "merge"), &ImGuiGD::AddFont, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("AddFontDefault"), &ImGuiGD::AddFontDefault); + ClassDB::bind_method(D_METHOD("Connect", "callable"), &ImGuiGD::Connect); + ClassDB::bind_method(D_METHOD("RebuildFontAtlas", "scale"), &ImGuiGD::RebuildFontAtlas, DEFVAL(1.0f)); + ClassDB::bind_method(D_METHOD("ResetFonts"), &ImGuiGD::ResetFonts); + ClassDB::bind_method(D_METHOD("SubViewport", "svp"), &ImGuiGD::SubViewport); + ClassDB::bind_method(D_METHOD("GetImGuiPtrs", "version", "ioSize", "vertSize", "idxSize", "charSize"), + &ImGuiGD::GetImGuiPtrs); + ClassDB::bind_method(D_METHOD("ToolInit"), &ImGuiGD::ToolInit); + + ClassDB::bind_method(D_METHOD("GetFontPtrs"), &ImGuiGD::GetFontPtrs); + ClassDB::bind_method(D_METHOD("InitEditor", "parent"), &ImGuiGD::InitEditor); +} + +void ImGuiGD::InitEditor(Node* parent) +{ +#ifdef DEBUG_ENABLED + if (!Engine::get_singleton()->is_editor_hint()) + return; + + if (!Engine::get_singleton()->has_singleton("ImGuiRoot")) + { + String resPath = "res://addons/imgui-godot-native/ImGuiGodot.tscn"; + if (ResourceLoader::get_singleton()->exists(resPath)) + { + Ref scene = ResourceLoader::get_singleton()->load(resPath); + if (scene.is_valid()) + parent->add_child(scene->instantiate()); + } + } +#endif +} + +void ImGuiGD::ToolInit() +{ +#ifdef DEBUG_ENABLED + if (!Engine::get_singleton()->is_editor_hint()) + return; + ImGuiLayer* igl = Object::cast_to(Engine::get_singleton()->get_singleton("ImGuiLayer")); + if (igl) + igl->ToolInit(); +#endif +} + +void ImGuiGD::Connect(const Callable& callable) +{ + ImGui::Godot::Connect(callable); +} + +void ImGuiGD::ResetFonts() +{ + ImGui::Godot::ResetFonts(); +} + +void ImGuiGD::AddFont(const Ref& fontFile, int fontSize, bool merge) +{ + ImGui::Godot::AddFont(fontFile, fontSize, merge); +} + +void ImGuiGD::AddFontDefault() +{ + ImGui::Godot::AddFontDefault(); +} + +void ImGuiGD::RebuildFontAtlas(float scale) +{ + ImGui::Godot::RebuildFontAtlas(scale); +} + +void ImGuiGD::_SetVisible(bool visible) +{ + ImGui::Godot::SetVisible(visible); +} + +bool ImGuiGD::_GetVisible() +{ + return true; +} + +void ImGuiGD::_SetJoyAxisDeadZone(float zone) +{ +} + +float ImGuiGD::_GetJoyAxisDeadZone() +{ + return 0.15f; +} + +PackedInt64Array ImGuiGD::GetFontPtrs() +{ + ImGuiIO& io = ImGui::GetIO(); + PackedInt64Array rv; + rv.resize(io.Fonts->Fonts.Size); + for (int i = 0; i < io.Fonts->Fonts.Size; ++i) + { + rv[i] = (int64_t)io.Fonts->Fonts[i]; + } + return rv; +} + +PackedInt64Array ImGuiGD::GetImGuiPtrs(String version, int ioSize, int vertSize, int idxSize, int charSize) +{ + if (version != String(ImGui::GetVersion()) || ioSize != sizeof(ImGuiIO) || vertSize != sizeof(ImDrawVert) || + idxSize != sizeof(ImDrawIdx) || charSize != sizeof(ImWchar)) + { + UtilityFunctions::push_error("ImGui version mismatch, use ", ImGui::GetVersion(), "-docking"); + return {}; + } + + ImGuiMemAllocFunc alloc_func = nullptr; + ImGuiMemFreeFunc free_func = nullptr; + void* user_data = nullptr; + + ImGui::GetAllocatorFunctions(&alloc_func, &free_func, &user_data); + + PackedInt64Array rv; + rv.resize(3); + rv[0] = reinterpret_cast(ImGui::GetCurrentContext()); + rv[1] = reinterpret_cast(alloc_func); + rv[2] = reinterpret_cast(free_func); + return rv; +} + +bool ImGuiGD::SubViewport(godot::SubViewport* svp) +{ + return ImGui::Godot::SubViewport(svp); +} + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiGD.h b/gdext/src/ImGuiGD.h new file mode 100644 index 0000000..781aaf5 --- /dev/null +++ b/gdext/src/ImGuiGD.h @@ -0,0 +1,47 @@ +#pragma once + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#pragma warning(pop) + +#include + +using namespace godot; + +namespace ImGui::Godot { + +class ImGuiGD : public Object +{ + GDCLASS(ImGuiGD, Object); + +protected: + static void _bind_methods(); + +public: + void InitEditor(Node* parent); + void ToolInit(); + + void Connect(const Callable& cb); + + void ResetFonts(); + void AddFont(const Ref& fontFile, int fontSize, bool merge = false); + void AddFontDefault(); + void RebuildFontAtlas(float scale); + + void _SetJoyAxisDeadZone(float zone); + float _GetJoyAxisDeadZone(); + + void _SetVisible(bool visible); + bool _GetVisible(); + + PackedInt64Array GetFontPtrs(); + PackedInt64Array GetImGuiPtrs(String version, int ioSize, int vertSize, int idxSize, int charSize); + + bool SubViewport(godot::SubViewport* svp); +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiLayer.cpp b/gdext/src/ImGuiLayer.cpp new file mode 100644 index 0000000..8c807d6 --- /dev/null +++ b/gdext/src/ImGuiLayer.cpp @@ -0,0 +1,216 @@ +#include "ImGuiLayer.h" +#include "Context.h" +#include "ImGuiLayerHelper.h" + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +#include +#include + +using namespace godot; + +namespace ImGui::Godot { + +struct ImGuiLayer::Impl +{ + ImGuiLayerHelper* helper = nullptr; + Window* window = nullptr; + RID canvasItem; + bool wantHide = false; + Ref cfg; +}; + +ImGuiLayer::ImGuiLayer() : impl(std::make_unique()) +{ +} + +ImGuiLayer::~ImGuiLayer() +{ +} + +void ImGuiLayer::_bind_methods() +{ + ADD_SIGNAL(MethodInfo("imgui_layout")); + ClassDB::bind_method(D_METHOD("on_visibility_changed"), &ImGuiLayer::on_visibility_changed); + ClassDB::bind_method(D_METHOD("on_frame_pre_draw"), &ImGuiLayer::on_frame_pre_draw); +} + +void ImGuiLayer::_enter_tree() +{ + Node* parent = get_parent(); + if (!parent) + return; + //if (parent->get_class() != "ImGuiRoot") + // return; + if (Engine::get_singleton()->has_singleton("ImGuiLayer")) + return; + + set_name("ImGuiLayer"); + Engine::get_singleton()->register_singleton("ImGuiLayer", this); + impl->window = get_window(); + + Ref cfg = parent->get("Config"); + if (cfg.is_null()) + { + Ref script = ResourceLoader::get_singleton()->load("res://addons/imgui-godot/scripts/ImGuiConfig.gd"); + cfg = script->new_(); + } + impl->cfg = cfg; + + set_layer(cfg->get("Layer")); + + RenderingServer* RS = RenderingServer::get_singleton(); + impl->canvasItem = RS->canvas_item_create(); + RS->canvas_item_set_parent(impl->canvasItem, get_canvas()); + + impl->helper = memnew(ImGuiLayerHelper); + add_child(impl->helper); + +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint()) + return; +#endif + ImGui::Godot::Init(get_window(), impl->canvasItem, cfg); +} + +void ImGuiLayer::_ready() +{ + set_process_priority(std::numeric_limits::max()); + set_physics_process(false); + +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint()) + { + set_visible(false); + set_process(false); + impl->helper->set_process(false); + set_process_input(false); + } +#endif + + connect("visibility_changed", Callable(this, "on_visibility_changed")); +} + +void ImGuiLayer::_exit_tree() +{ + Engine::get_singleton()->unregister_singleton("ImGuiLayer"); + ImGui::Godot::Shutdown(); + RenderingServer::get_singleton()->free_rid(impl->canvasItem); +} + +void ImGuiLayer::_process(double delta) +{ +#ifdef DEBUG_ENABLED + if (Engine::get_singleton()->is_editor_hint()) + { + // verify signal connections + auto conns = get_signal_connection_list("imgui_layout"); + for (int i = 0; i < conns.size(); ++i) + { + const Dictionary& conn = conns[i]; + const Callable& cb = conn["callable"]; + if (!cb.is_valid()) + disconnect("imgui_layout", cb); + } + } +#endif + emit_signal("imgui_layout"); + ImGui::Godot::Render(); + + if (impl->wantHide) + { + impl->wantHide = false; + set_process(false); + set_physics_process(true); + impl->helper->set_process(false); + set_process_input(false); + RenderingServer::get_singleton()->canvas_item_clear(impl->canvasItem); + + // hide viewport windows immediately + if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + ImGui::NewFrame(); + ImGui::EndFrame(); + ImGui::UpdatePlatformWindows(); + } + ImGui::NewFrame(); + } +} + +void ImGuiLayer::_physics_process(double delta) +{ + static int count = 0; + if (++count > 60) + { + count = 0; + ImGui::EndFrame(); + ImGui::UpdatePlatformWindows(); + ImGui::NewFrame(); + } +} + +void ImGuiLayer::_input(const Ref& event) +{ + if (ImGui::Godot::ProcessInput(event, impl->window)) + { + impl->window->set_input_as_handled(); + } +} + +void ImGuiLayer::_notification(int p_what) +{ + // quick filter + if (p_what > 2000) + { + ImGui::Godot::ProcessNotification(p_what); + } +} + +void ImGuiLayer::on_visibility_changed() +{ + if (is_visible()) + { + set_process(true); + set_physics_process(false); + impl->helper->set_process(true); + set_process_input(true); + } + else + { + impl->wantHide = true; + } +} + +void ImGuiLayer::on_frame_pre_draw() +{ + ImGui::Godot::OnFramePreDraw(); +} + +void ImGuiLayer::NewFrame(double delta) +{ + if (ImGui::GetCurrentContext()->WithinFrameScope) + { + ImGui::EndFrame(); + ImGui::UpdatePlatformWindows(); + } + ImGui::Godot::Update(delta); +} + +void ImGuiLayer::ToolInit() +{ + if (!is_visible()) + { + ImGui::Godot::Init(get_window(), impl->canvasItem, impl->cfg); + set_visible(true); + } +} + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiLayer.h b/gdext/src/ImGuiLayer.h new file mode 100644 index 0000000..19dffad --- /dev/null +++ b/gdext/src/ImGuiLayer.h @@ -0,0 +1,43 @@ +#pragma once + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +#include + +using namespace godot; + +namespace ImGui::Godot { + +class ImGuiLayer : public CanvasLayer +{ + GDCLASS(ImGuiLayer, CanvasLayer); + +protected: + static void _bind_methods(); + +public: + ImGuiLayer(); + ~ImGuiLayer(); + + void _ready() override; + void _enter_tree() override; + void _exit_tree() override; + void _process(double delta) override; + void _physics_process(double delta) override; + void _input(const Ref& event) override; + void _notification(int p_what); + void on_visibility_changed(); + void on_frame_pre_draw(); + + void NewFrame(double delta); + void ToolInit(); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiLayerHelper.cpp b/gdext/src/ImGuiLayerHelper.cpp new file mode 100644 index 0000000..ff4c816 --- /dev/null +++ b/gdext/src/ImGuiLayerHelper.cpp @@ -0,0 +1,52 @@ +#include "ImGuiLayerHelper.h" +#include "ImGuiLayer.h" +#include "imgui-godot.h" + +#pragma warning(push, 0) +#include +#include +#include +#pragma warning(pop) + +#include +using namespace godot; + +namespace ImGui::Godot { + +struct ImGuiLayerHelper::Impl +{ + ImGuiLayer* igl = nullptr; +}; + +ImGuiLayerHelper::ImGuiLayerHelper() : impl(std::make_unique()) +{ +} + +ImGuiLayerHelper::~ImGuiLayerHelper() +{ +} + +void ImGuiLayerHelper::_bind_methods() +{ +} + +void ImGuiLayerHelper::_enter_tree() +{ + impl->igl = Object::cast_to(get_parent()); +} + +void ImGuiLayerHelper::_ready() +{ + set_process_priority(std::numeric_limits::min()); +} + +void ImGuiLayerHelper::_exit_tree() +{ +} + +void ImGuiLayerHelper::_process(double delta) +{ + impl->igl->NewFrame(delta); +} + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiLayerHelper.h b/gdext/src/ImGuiLayerHelper.h new file mode 100644 index 0000000..ccfb441 --- /dev/null +++ b/gdext/src/ImGuiLayerHelper.h @@ -0,0 +1,36 @@ +#pragma once + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +#include + +using godot::InputEvent; +using godot::Node; +using godot::Ref; + +namespace ImGui::Godot { + +class ImGuiLayerHelper : public Node +{ + GDCLASS(ImGuiLayerHelper, Node); + +protected: + static void _bind_methods(); + +public: + void _ready() override; + void _enter_tree() override; + void _exit_tree() override; + void _process(double delta) override; + + ImGuiLayerHelper(); + ~ImGuiLayerHelper(); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiRoot.cpp b/gdext/src/ImGuiRoot.cpp new file mode 100644 index 0000000..b017f10 --- /dev/null +++ b/gdext/src/ImGuiRoot.cpp @@ -0,0 +1,56 @@ +#include "ImGuiRoot.h" +#include "ImGuiLayer.h" +#include +#include +#include + +namespace ImGui::Godot { + +struct ImGuiRoot::Impl +{ + Ref cfg; +}; + +ImGuiRoot::ImGuiRoot() : impl(std::make_unique()) +{ +} + +ImGuiRoot::~ImGuiRoot() +{ +} + +void ImGuiRoot::_bind_methods() +{ + ClassDB::bind_method(D_METHOD("SetConfig", "cfg"), &ImGuiRoot::SetConfig); + ClassDB::bind_method(D_METHOD("GetConfig"), &ImGuiRoot::GetConfig); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "Config", PROPERTY_HINT_RESOURCE_TYPE, "ImGuiConfig"), + "SetConfig", + "GetConfig"); +} + +void ImGuiRoot::_get_property_list(List* p_list) const +{ +} + +void ImGuiRoot::_enter_tree() +{ + Node* parent = get_parent(); + if (parent == get_window() || parent->get_name() == StringName("ImGuiGodotNativeEditorPlugin")) + { + Engine::get_singleton()->register_singleton("ImGuiRoot", this); + ImGuiLayer* igl = memnew(ImGuiLayer); + add_child(igl); + } +} + +void ImGuiRoot::SetConfig(Ref cfg) +{ + impl->cfg = cfg; +} + +Ref ImGuiRoot::GetConfig() +{ + return impl->cfg; +} + +} // namespace ImGui::Godot diff --git a/gdext/src/ImGuiRoot.h b/gdext/src/ImGuiRoot.h new file mode 100644 index 0000000..0b5de2a --- /dev/null +++ b/gdext/src/ImGuiRoot.h @@ -0,0 +1,36 @@ +#pragma once + +#pragma warning(push, 0) +#include +#include +#pragma warning(pop) + +#include + +using namespace godot; + +namespace ImGui::Godot { + +class ImGuiRoot : public Node +{ + GDCLASS(ImGuiRoot, Node); + +protected: + static void _bind_methods(); + void _get_property_list(List* p_list) const; + +public: + ImGuiRoot(); + ~ImGuiRoot(); + + void _enter_tree() override; + + void SetConfig(Ref cfg); + Ref GetConfig(); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/Input.cpp b/gdext/src/Input.cpp new file mode 100644 index 0000000..84e79b7 --- /dev/null +++ b/gdext/src/Input.cpp @@ -0,0 +1,309 @@ +#include "Input.h" +#include "imgui-godot.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#endif + +using namespace godot; + +namespace ImGui::Godot { + +struct Input::Impl +{ + Window* mainWindow = nullptr; + godot::SubViewport* currentSubViewport = nullptr; + Vector2 currentSubViewportPos; + Vector2 mouseWheel; + ImGuiMouseCursor currentCursor = ImGuiMouseCursor_None; + bool hasMouse; + + void UpdateMouse(); +}; + +namespace { +DisplayServer::CursorShape ToCursorShape(ImGuiMouseCursor cur) +{ + switch (cur) + { + case ImGuiMouseCursor_Arrow: + return DisplayServer::CURSOR_ARROW; + case ImGuiMouseCursor_TextInput: + return DisplayServer::CURSOR_IBEAM; + case ImGuiMouseCursor_ResizeAll: + return DisplayServer::CURSOR_MOVE; + case ImGuiMouseCursor_ResizeNS: + return DisplayServer::CURSOR_VSIZE; + case ImGuiMouseCursor_ResizeEW: + return DisplayServer::CURSOR_HSIZE; + case ImGuiMouseCursor_ResizeNESW: + return DisplayServer::CURSOR_BDIAGSIZE; + case ImGuiMouseCursor_ResizeNWSE: + return DisplayServer::CURSOR_FDIAGSIZE; + case ImGuiMouseCursor_Hand: + return DisplayServer::CURSOR_POINTING_HAND; + case ImGuiMouseCursor_NotAllowed: + return DisplayServer::CURSOR_FORBIDDEN; + default: + return DisplayServer::CURSOR_ARROW; + }; +} + +void UpdateKeyMods(ImGuiIO& io) +{ + static godot::Input* INP = godot::Input::get_singleton(); + + io.AddKeyEvent(ImGuiMod_Ctrl, INP->is_key_pressed(Key::KEY_CTRL)); + io.AddKeyEvent(ImGuiMod_Shift, INP->is_key_pressed(Key::KEY_SHIFT)); + io.AddKeyEvent(ImGuiMod_Alt, INP->is_key_pressed(Key::KEY_ALT)); + io.AddKeyEvent(ImGuiMod_Super, INP->is_key_pressed(Key::KEY_META)); +} + +} // namespace + +Input::Input(Window* mainWindow) : impl(std::make_unique()) +{ + impl->mainWindow = mainWindow; + impl->hasMouse = DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_MOUSE); +} + +Input::~Input() +{ +} + +void Input::Impl::UpdateMouse() +{ + ImGuiIO& io = ImGui::GetIO(); + + DisplayServer* DS = DisplayServer::get_singleton(); + Vector2i mousePos = DS->mouse_get_position(); + + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + { + if (io.WantSetMousePos) + { + // WarpMouse is relative to the current focused window + PackedInt32Array windows = DS->get_window_list(); + for (int w : windows) + { + if (DS->window_is_focused(w)) + { + Vector2i winPos = DS->window_get_position(w); + DS->warp_mouse({(int)io.MousePos.x - winPos.x, (int)io.MousePos.y - winPos.y}); + break; + } + } + } + else + { + io.AddMousePosEvent(mousePos.x, mousePos.y); + } + } + else + { + if (io.WantSetMousePos) + { + godot::Input::get_singleton()->warp_mouse({io.MousePos.x, io.MousePos.y}); + } + else + { + Vector2i winPos = mainWindow->get_position(); + io.AddMousePosEvent(mousePos.x - winPos.x, mousePos.y - winPos.y); + } + } + + if (mouseWheel != Vector2()) + { + io.AddMouseWheelEvent(mouseWheel.x, mouseWheel.y); + mouseWheel = Vector2(); + } + + if (io.WantCaptureMouse && !(io.ConfigFlags & ImGuiConfigFlags_NoMouseCursorChange)) + { + ImGuiMouseCursor newCursor = ImGui::GetMouseCursor(); + if (newCursor != currentCursor) + { + DS->cursor_set_shape(ToCursorShape(newCursor)); + } + } + else + { + currentCursor = ImGuiMouseCursor_None; + } +} + +void Input::Update() +{ + if (impl->hasMouse) + impl->UpdateMouse(); + + impl->currentSubViewport = nullptr; +} + +bool Input::ProcessInput(const Ref& evt, Window* window) +{ + ImGuiIO& io = ImGui::GetIO(); + + Vector2i windowPos; + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) + windowPos = window->get_position(); + + if (impl->currentSubViewport != nullptr) + { + Ref vpevt = evt->duplicate(); + + if (Ref me = vpevt; me.is_valid()) + { + Vector2 gpos = me->get_global_position(); + me->set_position(Vector2(windowPos.x + gpos.x - impl->currentSubViewportPos.x, + windowPos.y + gpos.y - impl->currentSubViewportPos.y) + .clamp(Vector2(0, 0), impl->currentSubViewport->get_size())); + } + impl->currentSubViewport->push_input(vpevt, true); + } + + bool consumed = false; + + if (Ref mm = evt; mm.is_valid()) + { + consumed = io.WantCaptureMouse; + } + else if (Ref mb = evt; mb.is_valid()) + { + bool pressed = mb->is_pressed(); + switch (mb->get_button_index()) + { + case MOUSE_BUTTON_LEFT: + io.AddMouseButtonEvent(ImGuiMouseButton_Left, pressed); + break; + case MOUSE_BUTTON_RIGHT: + io.AddMouseButtonEvent(ImGuiMouseButton_Right, pressed); + break; + case MOUSE_BUTTON_MIDDLE: + io.AddMouseButtonEvent(ImGuiMouseButton_Middle, pressed); + break; + case MOUSE_BUTTON_XBUTTON1: + io.AddMouseButtonEvent(ImGuiMouseButton_Middle + 1, pressed); + break; + case MOUSE_BUTTON_XBUTTON2: + io.AddMouseButtonEvent(ImGuiMouseButton_Middle + 2, pressed); + case MOUSE_BUTTON_WHEEL_UP: + impl->mouseWheel.y = mb->get_factor(); + break; + case MOUSE_BUTTON_WHEEL_DOWN: + impl->mouseWheel.y = -mb->get_factor(); + break; + case MOUSE_BUTTON_WHEEL_LEFT: + impl->mouseWheel.x = -mb->get_factor(); + break; + case MOUSE_BUTTON_WHEEL_RIGHT: + impl->mouseWheel.x = mb->get_factor(); + break; + } + consumed = io.WantCaptureMouse; + } + else if (Ref k = evt; k.is_valid()) + { + UpdateKeyMods(io); + ImGuiKey igk = ToImGuiKey(k->get_keycode()); + if (igk != ImGuiKey_None) + { + bool pressed = k->is_pressed(); + io.AddKeyEvent(igk, k->is_pressed()); + if (pressed && k->get_unicode() != 0 && io.WantTextInput) + { + io.AddInputCharacter(k->get_unicode()); + } + } + consumed = io.WantCaptureKeyboard || io.WantTextInput; + } + else if (Ref pg = evt; pg.is_valid()) + { + impl->mouseWheel = Vector2(-pg->get_delta().x, -pg->get_delta().y); + consumed = io.WantCaptureMouse; + } + else if (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) + { + if (Ref jb = evt; jb.is_valid()) + { + ImGuiKey igk = ToImGuiKey(jb->get_button_index()); + if (igk != ImGuiKey_None) + { + io.AddKeyEvent(igk, jb->is_pressed()); + consumed = true; + } + } + else if (Ref jm = evt; jm.is_valid()) + { + bool pressed = true; + float v = jm->get_axis_value(); + if (std::abs(v) < 0.15f) // TODO: set dead zone + { + v = 0; + pressed = false; + } + + switch (jm->get_axis()) + { + case JOY_AXIS_LEFT_X: + io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, pressed, v); + break; + case JOY_AXIS_LEFT_Y: + io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, pressed, v); + break; + case JOY_AXIS_RIGHT_X: + io.AddKeyAnalogEvent(ImGuiKey_GamepadRStickRight, pressed, v); + break; + case JOY_AXIS_RIGHT_Y: + io.AddKeyAnalogEvent(ImGuiKey_GamepadRStickDown, pressed, v); + break; + case JOY_AXIS_TRIGGER_LEFT: + io.AddKeyAnalogEvent(ImGuiKey_GamepadL2, pressed, v); + break; + case JOY_AXIS_TRIGGER_RIGHT: + io.AddKeyAnalogEvent(ImGuiKey_GamepadR2, pressed, v); + break; + }; + + consumed = true; + } + } + + return consumed; +} + +void Input::ProcessNotification(int what) +{ + switch (what) + { + case Node::NOTIFICATION_APPLICATION_FOCUS_IN: + ImGui::GetIO().AddFocusEvent(true); + break; + case Node::NOTIFICATION_APPLICATION_FOCUS_OUT: + ImGui::GetIO().AddFocusEvent(false); + break; + }; +} + +void Input::SetActiveSubViewport(godot::SubViewport* svp, Vector2 pos) +{ + impl->currentSubViewport = svp; + impl->currentSubViewportPos = pos; +} + +} // namespace ImGui::Godot diff --git a/gdext/src/Input.h b/gdext/src/Input.h new file mode 100644 index 0000000..880b113 --- /dev/null +++ b/gdext/src/Input.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include +#include + +using namespace godot; + +namespace ImGui::Godot { +class Input +{ +public: + Input(Window* mainWindow); + ~Input(); + + void Update(); + bool ProcessInput(const Ref& evt, Window* window); + void ProcessNotification(int what); + void SetActiveSubViewport(godot::SubViewport* svp, Vector2 pos); + +private: + struct Impl; + std::unique_ptr impl; +}; +} // namespace ImGui::Godot diff --git a/gdext/src/RdRenderer.cpp b/gdext/src/RdRenderer.cpp new file mode 100644 index 0000000..87c6d5a --- /dev/null +++ b/gdext/src/RdRenderer.cpp @@ -0,0 +1,365 @@ +#include "RdRenderer.h" +#include "common.h" +#include +#include +#include +#include + +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +using namespace godot; + +template <> +struct std::hash +{ + std::size_t operator()(const RID& rid) const noexcept + { + return std::hash{}(rid.get_id()); + } +}; + +namespace ImGui::Godot { + +struct RdRenderer::Impl +{ + RID shader; + int64_t vtxFormat = 0; + RID pipeline; + RID sampler; + TypedArray srcBuffers; + TypedArray uniforms; + // TypedArray storageTextures; + godot::PackedColorArray clearColors; + + std::unordered_map framebuffers; + std::unordered_map uniformSets; + std::unordered_set usedTextures; + + godot::PackedInt64Array vtxOffsets; + + RID idxBuffer; + int idxBufferSize = 0; // size in indices + RID vtxBuffer; + int vtxBufferSize = 0; // size in vertices + + void SetupBuffers(ImDrawData* drawData); + void RenderOne(RID vprid, ImDrawData* drawData); + + Impl() + { + clearColors.push_back(godot::Color(0, 0, 0, 0)); + srcBuffers.resize(3); + vtxOffsets.resize(3); + } +}; + +RID RdRenderer::GetFramebuffer(RID vprid) +{ + if (!vprid.is_valid()) + return RID(); + + RenderingServer* RS = RenderingServer::get_singleton(); + RenderingDevice* RD = RS->get_rendering_device(); + auto it = impl->framebuffers.find(vprid); + if (it != impl->framebuffers.end()) + { + RID fb = it->second; + if (RD->framebuffer_is_valid(fb)) + return fb; + } + + RID vptex = RS->texture_get_rd_texture(RS->viewport_get_texture(vprid)); + godot::TypedArray arr; + arr.push_back(vptex); + RID fb = RD->framebuffer_create(arr); + impl->framebuffers[vprid] = fb; + return fb; +} + +void RdRenderer::Impl::SetupBuffers(ImDrawData* drawData) +{ + RenderingDevice* RD = RenderingServer::get_singleton()->get_rendering_device(); + + // clean out invalidated uniform sets so they can be refreshed + for (auto it = uniformSets.begin(); it != uniformSets.end();) + { + if (!RD->uniform_set_is_valid(it->second)) + it = uniformSets.erase(it); + else + ++it; + } + + // allocate merged index and vertex buffers + if (idxBufferSize < drawData->TotalIdxCount) + { + if (idxBuffer.get_id() != 0) + RD->free_rid(idxBuffer); + idxBuffer = RD->index_buffer_create(drawData->TotalIdxCount, RenderingDevice::INDEX_BUFFER_FORMAT_UINT16); + idxBufferSize = drawData->TotalIdxCount; + } + + if (vtxBufferSize < drawData->TotalVtxCount) + { + if (vtxBuffer.get_id() != 0) + RD->free_rid(vtxBuffer); + vtxBuffer = RD->vertex_buffer_create(drawData->TotalVtxCount * sizeof(ImDrawVert)); + vtxBufferSize = drawData->TotalVtxCount; + } + + if (drawData->TotalIdxCount == 0) + return; + + int globalIdxOffset = 0; + int globalVtxOffset = 0; + + int idxBufSize = drawData->TotalIdxCount * sizeof(ImDrawIdx); + godot::PackedByteArray idxBuf; + idxBuf.resize(idxBufSize); + + int vertBufSize = drawData->TotalVtxCount * sizeof(ImDrawVert); + godot::PackedByteArray vertBuf; + vertBuf.resize(vertBufSize); + + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + ImDrawList* cmdList = drawData->CmdLists[i]; + + std::copy(cmdList->IdxBuffer.begin(), + cmdList->IdxBuffer.end(), + reinterpret_cast(idxBuf.ptrw() + globalIdxOffset)); + std::copy(cmdList->VtxBuffer.begin(), + cmdList->VtxBuffer.end(), + reinterpret_cast(vertBuf.ptrw() + globalVtxOffset)); + + globalIdxOffset += cmdList->IdxBuffer.size_in_bytes(); + globalVtxOffset += cmdList->VtxBuffer.size_in_bytes(); + + for (int cmdi = 0; cmdi < cmdList->CmdBuffer.Size; ++cmdi) + { + const ImDrawCmd& drawCmd = cmdList->CmdBuffer[cmdi]; + ImTextureID texid = drawCmd.GetTexID(); + if (!texid) + continue; + + usedTextures.insert(texid); + if (!uniformSets.contains(texid)) + { + RID texrid = make_rid(texid); + Ref uniform; + uniform.instantiate(); + uniform->set_binding(0); + uniform->set_uniform_type(RenderingDevice::UNIFORM_TYPE_SAMPLER_WITH_TEXTURE); + uniform->add_id(sampler); + uniform->add_id(texrid); + + uniforms[0] = uniform; + + RID uniformSet = RD->uniform_set_create(uniforms, shader, 0); + uniformSets[texid] = uniformSet; + } + } + } + + RD->buffer_update(idxBuffer, 0, idxBuf.size(), idxBuf); + RD->buffer_update(vtxBuffer, 0, vertBuf.size(), vertBuf); +} + +RdRenderer::RdRenderer() : impl(std::make_unique()) +{ + RenderingDevice* RD = RenderingServer::get_singleton()->get_rendering_device(); + Ref shaderFile = + ResourceLoader::get_singleton()->load("res://addons/imgui-godot/data/ImGuiShader.glsl"); + + impl->shader = RD->shader_create_from_spirv(shaderFile->get_spirv()); + + TypedArray vattrs; + + RDVertexAttribute attr_points; + attr_points.set_location(0); + attr_points.set_format(RenderingDevice::DATA_FORMAT_R32G32_SFLOAT); + attr_points.set_stride(sizeof(ImDrawVert)); + attr_points.set_offset(0); + + RDVertexAttribute attr_uvs; + attr_uvs.set_location(1); + attr_uvs.set_format(RenderingDevice::DATA_FORMAT_R32G32_SFLOAT); + attr_uvs.set_stride(sizeof(ImDrawVert)); + attr_uvs.set_offset(sizeof(float) * 2); + + RDVertexAttribute attr_colors; + attr_colors.set_location(2); + attr_colors.set_format(RenderingDevice::DATA_FORMAT_R8G8B8A8_UNORM); + attr_colors.set_stride(sizeof(ImDrawVert)); + attr_colors.set_offset(sizeof(float) * 4); + + vattrs.append(&attr_points); + vattrs.append(&attr_uvs); + vattrs.append(&attr_colors); + + impl->vtxFormat = RD->vertex_format_create(vattrs); + + RDPipelineColorBlendStateAttachment bsa; + bsa.set_enable_blend(true); + bsa.set_src_color_blend_factor(RenderingDevice::BLEND_FACTOR_SRC_ALPHA); + bsa.set_dst_color_blend_factor(RenderingDevice::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA); + bsa.set_color_blend_op(RenderingDevice::BLEND_OP_ADD); + bsa.set_src_alpha_blend_factor(RenderingDevice::BLEND_FACTOR_ONE); + bsa.set_dst_alpha_blend_factor(RenderingDevice::BLEND_FACTOR_ONE_MINUS_SRC_ALPHA); + bsa.set_alpha_blend_op(RenderingDevice::BLEND_OP_ADD); + + Ref blend; + blend.instantiate(); + blend->set_blend_constant(Color(0.0f, 0.0f, 0.0f, 0.0f)); + blend->get_attachments().append(&bsa); + + Ref raster_state; + raster_state.instantiate(); + raster_state->set_front_face(RenderingDevice::POLYGON_FRONT_FACE_COUNTER_CLOCKWISE); + + impl->pipeline = RD->render_pipeline_create(impl->shader, + RD->screen_get_framebuffer_format(), + impl->vtxFormat, + RenderingDevice::RENDER_PRIMITIVE_TRIANGLES, + raster_state, + {}, + {}, + blend); + + RDSamplerState sampler_state; + sampler_state.set_min_filter(RenderingDevice::SAMPLER_FILTER_LINEAR); + sampler_state.set_mag_filter(RenderingDevice::SAMPLER_FILTER_LINEAR); + sampler_state.set_mip_filter(RenderingDevice::SAMPLER_FILTER_LINEAR); + sampler_state.set_repeat_u(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); + sampler_state.set_repeat_v(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); + sampler_state.set_repeat_w(RenderingDevice::SAMPLER_REPEAT_MODE_REPEAT); + + impl->sampler = RD->sampler_create(&sampler_state); + + impl->srcBuffers.resize(3); + impl->uniforms.resize(1); +} + +void RdRenderer::Render(RID fb, ImDrawData* drawData) +{ + RenderingDevice* RD = RenderingServer::get_singleton()->get_rendering_device(); + + impl->SetupBuffers(drawData); + + godot::PackedFloat32Array pcfloats; + pcfloats.resize(4); + pcfloats[0] = 2.0f / drawData->DisplaySize.x; + pcfloats[1] = 2.0f / drawData->DisplaySize.y; + pcfloats[2] = -1.0f - (drawData->DisplayPos.x * pcfloats[0]); + pcfloats[3] = -1.0f - (drawData->DisplayPos.y * pcfloats[1]); + godot::PackedByteArray pcbuf = pcfloats.to_byte_array(); + + int64_t dl = RD->draw_list_begin(fb, + RenderingDevice::INITIAL_ACTION_CLEAR, + RenderingDevice::FINAL_ACTION_READ, + RenderingDevice::INITIAL_ACTION_CLEAR, + RenderingDevice::FINAL_ACTION_READ, + impl->clearColors); + + RD->draw_list_bind_render_pipeline(dl, impl->pipeline); + RD->draw_list_set_push_constant(dl, pcbuf, pcbuf.size()); + + int globalIdxOffset = 0; + int globalVtxOffset = 0; + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + ImDrawList* cmdList = drawData->CmdLists[i]; + + for (int cmdi = 0; cmdi < cmdList->CmdBuffer.Size; ++cmdi) + { + ImDrawCmd& drawCmd = cmdList->CmdBuffer[cmdi]; + if (drawCmd.ElemCount == 0) + continue; + if (!impl->uniformSets.contains(drawCmd.GetTexID())) + continue; + + RID idxArray = + RD->index_array_create(impl->idxBuffer, drawCmd.IdxOffset + globalIdxOffset, drawCmd.ElemCount); + + int64_t voff = (drawCmd.VtxOffset + globalVtxOffset) * sizeof(ImDrawVert); + impl->srcBuffers[0] = impl->srcBuffers[1] = impl->srcBuffers[2] = impl->vtxBuffer; + impl->vtxOffsets[0] = impl->vtxOffsets[1] = impl->vtxOffsets[2] = voff; + RID vtxArray = + RD->vertex_array_create(cmdList->VtxBuffer.Size, impl->vtxFormat, impl->srcBuffers, impl->vtxOffsets); + + RD->draw_list_bind_uniform_set(dl, impl->uniformSets[drawCmd.GetTexID()], 0); + RD->draw_list_bind_index_array(dl, idxArray); + RD->draw_list_bind_vertex_array(dl, vtxArray); + + godot::Rect2 clipRect = {drawCmd.ClipRect.x, + drawCmd.ClipRect.y, + drawCmd.ClipRect.z - drawCmd.ClipRect.x, + drawCmd.ClipRect.w - drawCmd.ClipRect.y}; + clipRect.position -= godot::Vector2i(drawData->DisplayPos.x, drawData->DisplayPos.y); + RD->draw_list_enable_scissor(dl, clipRect); + + RD->draw_list_draw(dl, true, 1); + + RD->free_rid(idxArray); + RD->free_rid(vtxArray); + } + globalIdxOffset += cmdList->IdxBuffer.Size; + globalVtxOffset += cmdList->VtxBuffer.Size; + } + RD->draw_list_end(); +} + +RdRenderer::~RdRenderer() +{ + RenderingDevice* RD = RenderingServer::get_singleton()->get_rendering_device(); + RD->free_rid(impl->shader); + RD->free_rid(impl->sampler); + if (impl->idxBuffer.is_valid()) + RD->free_rid(impl->idxBuffer); + if (impl->vtxBuffer.is_valid()) + RD->free_rid(impl->vtxBuffer); +} + +void RdRenderer::Render() +{ + auto& pio = ImGui::GetPlatformIO(); + for (ImGuiViewport* vp : pio.Viewports) + { + if (!(vp->Flags & ImGuiViewportFlags_IsMinimized)) + { + ReplaceTextureRIDs(vp->DrawData); + RID vprid = make_rid(vp->RendererUserData); + Render(GetFramebuffer(vprid), vp->DrawData); + } + } +} + +void RdRenderer::ReplaceTextureRIDs(ImDrawData* drawData) +{ + RenderingServer* RS = RenderingServer::get_singleton(); + for (int i = 0; i < drawData->CmdListsCount; ++i) + { + ImDrawList* cmdList = drawData->CmdLists[i]; + for (ImDrawCmd& drawCmd : cmdList->CmdBuffer) + { + drawCmd.TextureId = (ImTextureID)RS->texture_get_rd_texture(make_rid(drawCmd.TextureId)).get_id(); + } + } +} + +} // namespace ImGui::Godot diff --git a/gdext/src/RdRenderer.h b/gdext/src/RdRenderer.h new file mode 100644 index 0000000..a4e78fa --- /dev/null +++ b/gdext/src/RdRenderer.h @@ -0,0 +1,37 @@ +#pragma once +#include "Renderer.h" +#include +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +using godot::RID; + +namespace ImGui::Godot { + +class RdRenderer : public Renderer +{ +public: + RdRenderer(); + virtual ~RdRenderer(); + + virtual const char* Name() override + { + return "godot4_rd"; + } + + virtual void Render() override; + +protected: + void Render(RID fb, ImDrawData* drawData); + static void ReplaceTextureRIDs(ImDrawData* drawData); + RID GetFramebuffer(RID vprid); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/RdRendererThreadSafe.cpp b/gdext/src/RdRendererThreadSafe.cpp new file mode 100644 index 0000000..1d7321e --- /dev/null +++ b/gdext/src/RdRendererThreadSafe.cpp @@ -0,0 +1,101 @@ +#include "RdRendererThreadSafe.h" +#include "common.h" +#include +#include + +using namespace godot; + +namespace ImGui::Godot { + +struct RdRendererThreadSafe::Impl +{ + struct ClonedDrawData + { + ClonedDrawData(ImDrawData* drawData) + { + data = IM_NEW(ImDrawData); + data->Valid = drawData->Valid; + data->CmdListsCount = drawData->CmdListsCount; + data->TotalIdxCount = drawData->TotalIdxCount; + data->TotalVtxCount = drawData->TotalVtxCount; + data->CmdLists = {}; + data->DisplayPos = drawData->DisplayPos; + data->DisplaySize = drawData->DisplaySize; + data->FramebufferScale = drawData->FramebufferScale; + data->OwnerViewport = drawData->OwnerViewport; + + for (int i = 0; i < drawData->CmdLists.Size; ++i) + { + data->CmdLists.push_back(drawData->CmdLists[i]->CloneOutput()); + } + } + + ~ClonedDrawData() + { + for (int i = 0; i < data->CmdLists.Size; ++i) + { + IM_DELETE(data->CmdLists[i]); + } + IM_DELETE(data); + } + + ImDrawData* data; + }; + + using SharedData = std::pair>; + + std::mutex sharedDataMutex; + std::vector dataToDraw; +}; + +RdRendererThreadSafe::RdRendererThreadSafe() : impl(std::make_unique()) +{ +} + +RdRendererThreadSafe::~RdRendererThreadSafe() +{ +} + +void RdRendererThreadSafe::Render() +{ + auto& pio = ImGui::GetPlatformIO(); + std::vector newData(pio.Viewports.size()); + + for (int i = 0; i < pio.Viewports.size(); ++i) + { + // TODO: skip minimized windows + ImGuiViewport* vp = pio.Viewports[i]; + ReplaceTextureRIDs(vp->DrawData); + RID vprid = make_rid(vp->RendererUserData); + newData[i].first = GetFramebuffer(vprid); + newData[i].second = std::make_unique(vp->DrawData); + } + + { + std::unique_lock lock(impl->sharedDataMutex); + impl->dataToDraw = std::move(newData); + } +} + +void RdRendererThreadSafe::OnFramePreDraw() +{ + std::vector dataArray; + + { + std::unique_lock lock(impl->sharedDataMutex); + // take ownership of shared data + dataArray = std::move(impl->dataToDraw); + } + + if (dataArray.size() == 0) + return; + + RenderingDevice* RD = RenderingServer::get_singleton()->get_rendering_device(); + for (auto& kv : dataArray) + { + if (RD->framebuffer_is_valid(kv.first)) + RdRenderer::Render(kv.first, kv.second->data); + } +} + +} // namespace ImGui::Godot diff --git a/gdext/src/RdRendererThreadSafe.h b/gdext/src/RdRendererThreadSafe.h new file mode 100644 index 0000000..0905cb4 --- /dev/null +++ b/gdext/src/RdRendererThreadSafe.h @@ -0,0 +1,33 @@ +#pragma once +#include "RdRenderer.h" +#include +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +using godot::RID; + +namespace ImGui::Godot { + +class RdRendererThreadSafe : public RdRenderer +{ +public: + RdRendererThreadSafe(); + virtual ~RdRendererThreadSafe(); + + const char* Name() override + { + return "godot4_rd_mt"; + } + + void Render() override; + void OnFramePreDraw() override; + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/Renderer.h b/gdext/src/Renderer.h new file mode 100644 index 0000000..910dc17 --- /dev/null +++ b/gdext/src/Renderer.h @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +using godot::RID; + +namespace ImGui::Godot { + +class Renderer +{ +public: + Renderer() + { + } + + virtual ~Renderer() + { + } + + virtual const char* Name() = 0; + virtual void Render() = 0; + virtual void OnFramePreDraw() + { + } +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/ShortTermCache.cpp b/gdext/src/ShortTermCache.cpp new file mode 100644 index 0000000..a09896a --- /dev/null +++ b/gdext/src/ShortTermCache.cpp @@ -0,0 +1,98 @@ +#include "ShortTermCache.h" +#include +#include +#include +#include +#include + +using namespace godot; + +namespace ImGui::Godot { + +std::unique_ptr gdscache = std::make_unique(); + +struct ShortTermCache::Impl +{ + std::unordered_map> bufs; + std::unordered_map used; + + static void CopyInput(std::vector& buf, const Array& a) + { + CharString cs = String(a[0]).utf8(); + std::string_view sv = cs.get_data(); + if (sv.size() >= buf.size()) + return; + std::copy(sv.begin(), sv.end(), buf.begin()); + buf[sv.size()] = '\0'; + } +}; + +ShortTermCache::ShortTermCache() : impl(std::make_unique()) +{ +} + +ShortTermCache::~ShortTermCache() +{ +} + +void ShortTermCache::OnNewFrame() +{ + for (auto it = impl->used.begin(); it != impl->used.end();) + { + if (!it->second) + { + impl->bufs.erase(it->first); + it = impl->used.erase(it); + } + else + { + it->second = false; + ++it; + } + } +} + +std::vector& ShortTermCache::GetTextBuf(const StringName& label, size_t size, const Array& a) +{ + int64_t hash = ImGui::GetID((void*)label.hash()); + impl->used[hash] = true; + auto it = impl->bufs.find(hash); + if (it == impl->bufs.end()) + { + impl->bufs[hash] = std::vector(size); + std::vector& buf = impl->bufs[hash]; + Impl::CopyInput(buf, a); + return buf; + } + else + { + std::vector& buf = it->second; + buf.resize(size); + Impl::CopyInput(buf, a); + return buf; + } +} + +const std::vector& ShortTermCache::GetZeroArray(const Array& a) +{ + int64_t hash = a.hash(); + impl->used[hash] = true; + if (auto it = impl->bufs.find(hash); it != impl->bufs.end()) + { + return it->second; + } + + impl->bufs[hash] = {}; + std::vector& buf = impl->bufs[hash]; + for (int i = 0; i < a.size(); ++i) + { + CharString cs = String(a[i]).utf8(); + std::string_view sv = cs.get_data(); + std::copy(sv.begin(), sv.end(), std::back_inserter(buf)); + buf.push_back('\0'); + } + buf.push_back('\0'); + return buf; +} + +} // namespace ImGui::Godot diff --git a/gdext/src/ShortTermCache.h b/gdext/src/ShortTermCache.h new file mode 100644 index 0000000..1b5c681 --- /dev/null +++ b/gdext/src/ShortTermCache.h @@ -0,0 +1,31 @@ +#pragma once + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +#include +#include + +using namespace godot; + +namespace ImGui::Godot { + +class ShortTermCache +{ +public: + ShortTermCache(); + ~ShortTermCache(); + + void OnNewFrame(); + std::vector& GetTextBuf(const StringName& label, size_t size, const Array& a); + const std::vector& GetZeroArray(const Array& a); + +private: + struct Impl; + std::unique_ptr impl; +}; + +extern std::unique_ptr gdscache; + +} // namespace ImGui::Godot diff --git a/gdext/src/Viewports.cpp b/gdext/src/Viewports.cpp new file mode 100644 index 0000000..276f97e --- /dev/null +++ b/gdext/src/Viewports.cpp @@ -0,0 +1,194 @@ +#include "Viewports.h" +#include + +#include +#include +#include +#include + +namespace ImGui::Godot { + +struct Godot_ViewportData +{ + Window* window = nullptr; +}; + +struct Viewports::Impl +{ + void InitPlatformInterface(); + void UpdateMonitors(); +}; + +static void Godot_CreateWindow(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = IM_NEW(Godot_ViewportData); + vp->PlatformUserData = vd; + + { + Godot_ViewportData* mainvd = (Godot_ViewportData*)ImGui::GetMainViewport()->PlatformUserData; + Window* mainWindow = mainvd->window; + if (mainWindow->is_embedding_subwindows()) + { + UtilityFunctions::push_warning( + "ImGui Viewports: 'display/window/subwindows/embed_subwindows' needs to be disabled"); + mainWindow->set_embedding_subwindows(false); + } + } + + Rect2i winRect = Rect2i(vp->Pos, vp->Size); + + ImGuiWindow* igwin = memnew(ImGuiWindow); + igwin->init(vp); + vd->window = Object::cast_to(igwin); + vd->window->set_flag(Window::FLAG_BORDERLESS, true); + vd->window->set_position(winRect.position); + vd->window->set_size(winRect.size); + vd->window->set_transparent_background(true); + vd->window->set_flag(Window::FLAG_TRANSPARENT, true); + + // Callable::bind is not yet implemented... + // vd->window->connect("window_input", Callable(vd->signalProxy, "window_input")); + // vd->window->connect("close_requested", Callable(vd->signalProxy, "close_requested")); + // vd->window->connect("size_changed", Callable(vd->signalProxy, "size_changed")); + + Node* root = Object::cast_to(Engine::get_singleton()->get_singleton("ImGuiRoot")); + root->add_child(vd->window); + + // need to do this after add_child + vd->window->set_flag(Window::FLAG_TRANSPARENT, true); + + // it's our window, so just draw directly to the root viewport + RID vprid = vd->window->get_viewport_rid(); + vp->RendererUserData = (void*)vprid.get_id(); + + RenderingServer* RS = RenderingServer::get_singleton(); + RS->viewport_set_clear_mode(vprid, RenderingServer::VIEWPORT_CLEAR_NEVER); + RS->viewport_set_transparent_background(vprid, true); +} + +static void Godot_DestroyWindow(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + if (vd) + { + if (vd->window && vp != ImGui::GetMainViewport()) + { + vd->window->queue_free(); + } + IM_DELETE(vd); + vp->PlatformUserData = nullptr; + vp->RendererUserData = nullptr; + } +} + +static void Godot_ShowWindow(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + vd->window->show(); +} + +static void Godot_SetWindowPos(ImGuiViewport* vp, ImVec2 pos) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + vd->window->set_position(pos); +} + +ImVec2 Godot_GetWindowPos(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + return vd->window->get_position(); +} + +void Godot_SetWindowSize(ImGuiViewport* vp, ImVec2 size) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + vd->window->set_size(size); +} + +ImVec2 Godot_GetWindowSize(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + return vd->window->get_size(); +} + +void Godot_SetWindowFocus(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + vd->window->grab_focus(); +} + +bool Godot_GetWindowFocus(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + return vd->window->has_focus(); +} + +bool Godot_GetWindowMinimized(ImGuiViewport* vp) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + return vd->window->get_mode() & Window::MODE_MINIMIZED; +} + +void Godot_SetWindowTitle(ImGuiViewport* vp, const char* str) +{ + Godot_ViewportData* vd = (Godot_ViewportData*)vp->PlatformUserData; + vd->window->set_title(String(str)); +} + +void Viewports::Impl::InitPlatformInterface() +{ + auto& pio = ImGui::GetPlatformIO(); + pio.Platform_CreateWindow = Godot_CreateWindow; + pio.Platform_DestroyWindow = Godot_DestroyWindow; + pio.Platform_ShowWindow = Godot_ShowWindow; + pio.Platform_SetWindowPos = Godot_SetWindowPos; + pio.Platform_GetWindowPos = Godot_GetWindowPos; + pio.Platform_SetWindowSize = Godot_SetWindowSize; + pio.Platform_GetWindowSize = Godot_GetWindowSize; + pio.Platform_SetWindowFocus = Godot_SetWindowFocus; + pio.Platform_GetWindowFocus = Godot_GetWindowFocus; + pio.Platform_GetWindowMinimized = Godot_GetWindowMinimized; + pio.Platform_SetWindowTitle = Godot_SetWindowTitle; +} + +void Viewports::Impl::UpdateMonitors() +{ + auto& pio = ImGui::GetPlatformIO(); + DisplayServer* DS = DisplayServer::get_singleton(); + int screenCount = DS->get_screen_count(); + + pio.Monitors.resize(0); + for (int i = 0; i < screenCount; ++i) + { + ImGuiPlatformMonitor monitor; + monitor.MainPos = DS->screen_get_position(i); + monitor.MainSize = DS->screen_get_size(i); + monitor.DpiScale = DS->screen_get_scale(i); + + Rect2i rect = DS->screen_get_usable_rect(i); + monitor.WorkPos = rect.position; + monitor.WorkSize = rect.size; + + pio.Monitors.push_back(monitor); + } +} + +Viewports::Viewports(Window* mainWindow, RID mainSubViewport) : impl(std::make_unique()) +{ + auto& io = ImGui::GetIO(); + io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; + impl->InitPlatformInterface(); + impl->UpdateMonitors(); + + Godot_ViewportData* mainvd = IM_NEW(Godot_ViewportData); + mainvd->window = mainWindow; + ImGuiViewport* vp = ImGui::GetMainViewport(); + vp->PlatformUserData = mainvd; + vp->RendererUserData = (void*)mainSubViewport.get_id(); +} + +Viewports::~Viewports() +{ +} + +} // namespace ImGui::Godot diff --git a/gdext/src/Viewports.h b/gdext/src/Viewports.h new file mode 100644 index 0000000..8d6e1dc --- /dev/null +++ b/gdext/src/Viewports.h @@ -0,0 +1,61 @@ +#pragma once +#include "Context.h" +#include +#include +#include +#include + +using namespace godot; + +namespace ImGui::Godot { + +class ImGuiWindow : public Window +{ + GDCLASS(ImGuiWindow, Window); + +protected: + static void _bind_methods() + { + ClassDB::bind_method(D_METHOD("_close_requested"), &ImGuiWindow::_close_requested); + ClassDB::bind_method(D_METHOD("_size_changed"), &ImGuiWindow::_size_changed); + } + +public: + void init(ImGuiViewport* vp) + { + _vp = vp; + connect("close_requested", Callable(this, "_close_requested")); + connect("size_changed", Callable(this, "_size_changed")); + } + + void _input(const Ref& evt) override + { + ImGui::Godot::ProcessInput(evt, this); + } + + void _close_requested() + { + _vp->PlatformRequestClose = true; + } + + void _size_changed() + { + _vp->PlatformRequestResize = true; + } + +private: + ImGuiViewport* _vp = nullptr; +}; + +class Viewports +{ +public: + Viewports(Window* mainWindow, RID mainSubViewport); + ~Viewports(); + +private: + struct Impl; + std::unique_ptr impl; +}; + +} // namespace ImGui::Godot diff --git a/gdext/src/common.h b/gdext/src/common.h new file mode 100644 index 0000000..e466178 --- /dev/null +++ b/gdext/src/common.h @@ -0,0 +1,27 @@ +#pragma once + +#pragma warning(push, 0) +#include +#pragma warning(pop) + +#include + +using godot::RID; + +namespace ImGui::Godot { + +inline RID make_rid(int64_t id) +{ + // ugly, may break in the future + RID rv; + *(int64_t*)(rv._native_ptr()) = id; + assert(rv.get_id() == id); + return rv; +} + +inline RID make_rid(ImTextureID id) +{ + return make_rid(reinterpret_cast(id)); +} + +} // namespace ImGui::Godot diff --git a/gdext/src/main.cpp b/gdext/src/main.cpp new file mode 100644 index 0000000..9603b7b --- /dev/null +++ b/gdext/src/main.cpp @@ -0,0 +1,99 @@ +#pragma warning(push, 0) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#pragma warning(pop) + +static_assert(GODOT_VERSION_MAJOR == 4 && GODOT_VERSION_MINOR >= 2); + +#include + +#include "ImGuiGD.h" +#include "ImGuiLayer.h" +#include "ImGuiLayerHelper.h" +#include "ImGuiRoot.h" +#include "Viewports.h" + +// avoid including cimgui.h elsewhere + +namespace ImGui::Godot { +void register_imgui_api(); +} + +using namespace godot; +using namespace ImGui::Godot; + +ImGuiGD* gd = nullptr; + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#else +#include +#endif + +void sync_modules() +{ + typedef void (*pmodinit)(uint32_t, ImGuiContext*, ImGuiMemAllocFunc, ImGuiMemFreeFunc); +#ifdef _WIN32 + pmodinit mod_init = (pmodinit)GetProcAddress(GetModuleHandle(nullptr), "imgui_godot_module_init"); +#else + pmodinit mod_init = (pmodinit)dlsym(dlopen(nullptr, RTLD_LAZY), "imgui_godot_module_init"); +#endif + if (mod_init) + { + ImGuiMemAllocFunc afunc; + ImGuiMemFreeFunc ffunc; + void* ud; + ImGui::GetAllocatorFunctions(&afunc, &ffunc, &ud); + mod_init(IMGUI_VERSION_NUM, ImGui::GetCurrentContext(), afunc, ffunc); + } +} + +void initialize_ign_module(ModuleInitializationLevel p_level) +{ + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) + return; + + ImGui::CreateContext(); + + // ClassDB::register_internal_class(); + ClassDB::register_internal_class(); + ClassDB::register_internal_class(); + ClassDB::register_class(); + ClassDB::register_internal_class(); + register_imgui_api(); + + gd = memnew(ImGuiGD); + Engine::get_singleton()->register_singleton("ImGuiGD", gd); + sync_modules(); +} + +void uninitialize_ign_module(ModuleInitializationLevel p_level) +{ + if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) + return; + + Engine::get_singleton()->unregister_singleton("ImGuiGD"); + memdelete(gd); +} + +extern "C" { +GDExtensionBool GDE_EXPORT ign_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, + GDExtensionClassLibraryPtr p_library, GDExtensionInitialization* r_initialization) +{ + GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); + + init_obj.register_initializer(initialize_ign_module); + init_obj.register_terminator(uninitialize_ign_module); + init_obj.set_minimum_library_initialization_level(MODULE_INITIALIZATION_LEVEL_SCENE); + + return init_obj.init(); +} +} diff --git a/gdext/vcpkg-macos.sh b/gdext/vcpkg-macos.sh new file mode 100644 index 0000000..756fd35 --- /dev/null +++ b/gdext/vcpkg-macos.sh @@ -0,0 +1,20 @@ +#!/bin/zsh + +vcpkg_x64=$(pwd)/vcpkg_installed.x64 + +$VCPKG_ROOT/vcpkg install --triplet x64-osx +mv vcpkg_installed $vcpkg_x64 +$VCPKG_ROOT/vcpkg install --triplet arm64-osx + +for libpath in "lib" "debug/lib" +do + pushd vcpkg_installed/arm64-osx/$libpath + for x in *.a + do + echo "$libpath/$x" + lipo -create -output $x $x $vcpkg_x64/x64-osx/$libpath/$x + done + popd +done + +rm -r $vcpkg_x64 diff --git a/gdext/vcpkg.json b/gdext/vcpkg.json new file mode 100644 index 0000000..af967b4 --- /dev/null +++ b/gdext/vcpkg.json @@ -0,0 +1,6 @@ +{ + "dependencies": [ + "freetype", + "lunasvg" + ] +} \ No newline at end of file diff --git a/src/MySecondNode.cs b/src/MySecondNode.cs index 5322738..1015799 100644 --- a/src/MySecondNode.cs +++ b/src/MySecondNode.cs @@ -155,8 +155,8 @@ private void OnImGuiLayout() private void OnShowHidePressed() { - ImGuiLayer.Instance.Visible = !ImGuiLayer.Instance.Visible; - GetNode