From daafcea9ac832e2886f7eae760e113d37b2e18ad Mon Sep 17 00:00:00 2001 From: hazeycode <22148308+hazeycode@users.noreply.github.com> Date: Mon, 5 May 2025 21:27:09 +0100 Subject: [PATCH] Fix glfw_wgpu backend dpi scaling Closes #3 --- build.zig | 8 +++- libs/imgui/backends/imgui_impl_glfw.cpp | 59 +++---------------------- libs/imgui/backends/imgui_impl_glfw.h | 16 ++++--- libs/imgui/backends/imgui_impl_wgpu.cpp | 50 ++------------------- libs/imgui/backends/imgui_impl_wgpu.h | 9 ++-- src/backend_glfw_wgpu.zig | 29 +++++++----- 6 files changed, 49 insertions(+), 122 deletions(-) diff --git a/build.zig b/build.zig index aaa3ca7..1ca61cb 100644 --- a/build.zig +++ b/build.zig @@ -241,7 +241,11 @@ pub fn build(b: *std.Build) void { "libs/imgui/backends/imgui_impl_glfw.cpp", "libs/imgui/backends/imgui_impl_wgpu.cpp", }, - .flags = cflags, + .flags = &(cflags.* ++ .{ + "-DGLFW_INCLUDE_NONE", + // TODO: This should be IMGUI_IMPL_WEBGPU_BACKEND_DAWN but we're using an old version of Dawn that looks more like wgpu_native + "-DIMGUI_IMPL_WEBGPU_BACKEND_WGPU", + }), }); }, .glfw_opengl3 => { @@ -265,7 +269,7 @@ pub fn build(b: *std.Build) void { "libs/imgui/backends/imgui_impl_glfw.cpp", "libs/imgui/backends/imgui_impl_dx12.cpp", }, - .flags = cflags, + .flags = &(cflags.* ++ .{"-DGLFW_INCLUDE_NONE"}), }); imgui.linkSystemLibrary("d3dcompiler_47"); }, diff --git a/libs/imgui/backends/imgui_impl_glfw.cpp b/libs/imgui/backends/imgui_impl_glfw.cpp index 796bad7..7a0b1f5 100644 --- a/libs/imgui/backends/imgui_impl_glfw.cpp +++ b/libs/imgui/backends/imgui_impl_glfw.cpp @@ -90,8 +90,7 @@ #include "imgui.h" #ifndef IMGUI_DISABLE -// FIX(zig-gamedev): -// #include "imgui_impl_glfw.h" +#include "imgui_impl_glfw.h" // Clang warnings with -Weverything #if defined(__clang__) @@ -101,8 +100,6 @@ #endif // GLFW -// FIX(zig-gamedev): -#define GLFW_INCLUDE_NONE #include #ifdef _WIN32 @@ -160,41 +157,6 @@ #define GLFW_HAS_GAMEPAD_API (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetGamepadState() new api #define GLFW_HAS_GETKEYNAME (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName() #define GLFW_HAS_GETERROR (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError() -#include - -// FIX(zig-gamedev): -extern "C" { - -bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); -bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); -bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks); -void ImGui_ImplGlfw_Shutdown(); -void ImGui_ImplGlfw_NewFrame(); - -// GLFW callbacks install -// - When calling Init with 'install_callbacks=true': ImGui_ImplGlfw_InstallCallbacks() is called. GLFW callbacks will be installed for you. They will chain-call user's previously installed callbacks, if any. -// - When calling Init with 'install_callbacks=false': GLFW callbacks won't be installed. You will need to call individual function yourself from your own GLFW callbacks. -void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window); -void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window); - -// GFLW callbacks options: -// - Set 'chain_for_all_windows=true' to enable chaining callbacks for all windows (including secondary viewports created by backends or by user) -void ImGui_ImplGlfw_SetCallbacksChainForAllWindows(bool chain_for_all_windows); - -// GLFW callbacks (individual callbacks to call yourself if you didn't install callbacks) -void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused); // Since 1.84 -void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered); // Since 1.84 -void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y); // Since 1.87 -void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods); -void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset); -void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); -void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c); -void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event); - -// GLFW helpers -void ImGui_ImplGlfw_Sleep(int milliseconds); - -} // extern "C" // GLFW data enum GlfwClientApi @@ -218,8 +180,6 @@ struct ImGui_ImplGlfw_Data bool InstalledCallbacks; bool CallbacksChainForAllWindows; bool WantUpdateMonitors; - - ImVec2 DpiScale; // fix(zig-gamedev) #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 const char* CanvasSelector; #endif @@ -510,7 +470,7 @@ void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y) { ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); if (bd->PrevUserCallbackCursorPos != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) - bd->PrevUserCallbackCursorPos(window, x * bd->DpiScale.x, y * bd->DpiScale.y); // fix(zig-gamedev) + bd->PrevUserCallbackCursorPos(window, x, y); ImGuiIO& io = ImGui::GetIO(); if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) @@ -520,8 +480,8 @@ void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y) x += window_x; y += window_y; } - io.AddMousePosEvent((float)x * bd->DpiScale.x, (float)y * bd->DpiScale.y); // fix(zig-gamedev) - bd->LastValidMousePos = ImVec2((float)x * bd->DpiScale.x, (float)y * bd->DpiScale.y); // fix(zig-gamedev) + io.AddMousePosEvent((float)x, (float)y); + bd->LastValidMousePos = ImVec2((float)x, (float)y); } // Workaround: X11 seems to send spurious Leave/Enter events which would make us lose our position, @@ -668,8 +628,6 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw bd->Time = 0.0; bd->WantUpdateMonitors = true; - bd->DpiScale = ImVec2{ 1.0f, 1.0f }; // fix(zig-gamedev) - ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); platform_io.Platform_SetClipboardTextFn = [](ImGuiContext*, const char* text) { glfwSetClipboardString(nullptr, text); }; platform_io.Platform_GetClipboardTextFn = [](ImGuiContext*) { return glfwGetClipboardString(nullptr); }; @@ -837,8 +795,8 @@ static void ImGui_ImplGlfw_UpdateMouseData() mouse_x += window_x; mouse_y += window_y; } - bd->LastValidMousePos = ImVec2((float)mouse_x * bd->DpiScale.x, (float)mouse_y * bd->DpiScale.y); // fix(zig-gamedev) - io.AddMousePosEvent((float)mouse_x * bd->DpiScale.x, (float)mouse_y * bd->DpiScale.y); // fix(zig-gamedev) + bd->LastValidMousePos = ImVec2((float)mouse_x, (float)mouse_y); + io.AddMousePosEvent((float)mouse_x, (float)mouse_y); } } @@ -1007,11 +965,6 @@ void ImGui_ImplGlfw_NewFrame() io.DisplaySize = ImVec2((float)w, (float)h); if (w > 0 && h > 0) io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h); - - // fix(zig-gamedev) - bd->DpiScale.x = ceil(io.DisplayFramebufferScale.x); - bd->DpiScale.y = ceil(io.DisplayFramebufferScale.y); - if (bd->WantUpdateMonitors) ImGui_ImplGlfw_UpdateMonitors(); diff --git a/libs/imgui/backends/imgui_impl_glfw.h b/libs/imgui/backends/imgui_impl_glfw.h index 16930d1..f833b31 100644 --- a/libs/imgui/backends/imgui_impl_glfw.h +++ b/libs/imgui/backends/imgui_impl_glfw.h @@ -11,7 +11,9 @@ // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Resizing cursors requires GLFW 3.4+! Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // Missing features or Issues: -// [ ] Platform: Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). +// [ ] Touch events are only correctly identified as Touch on Windows. This create issues with some interactions. GLFW doesn't provide a way to identify touch inputs from mouse inputs, we cannot call io.AddMouseSourceEvent() to identify the source. We provide a Windows-specific workaround. +// [ ] Missing ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress cursors. +// [ ] Multi-viewport: ParentViewportID not honored, and so io.ConfigViewportsNoDefaultParent has no effect (minor). // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. @@ -29,11 +31,13 @@ struct GLFWwindow; struct GLFWmonitor; // Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); -IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); -IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks); -IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); +extern "C" { // fix(zig-gamedev) + IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOpenGL(GLFWwindow* window, bool install_callbacks); + IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForVulkan(GLFWwindow* window, bool install_callbacks); + IMGUI_IMPL_API bool ImGui_ImplGlfw_InitForOther(GLFWwindow* window, bool install_callbacks); + IMGUI_IMPL_API void ImGui_ImplGlfw_Shutdown(); + IMGUI_IMPL_API void ImGui_ImplGlfw_NewFrame(); +}; // Emscripten related initialization phase methods (call after ImGui_ImplGlfw_InitForOpenGL) #ifdef __EMSCRIPTEN__ diff --git a/libs/imgui/backends/imgui_impl_wgpu.cpp b/libs/imgui/backends/imgui_impl_wgpu.cpp index b565c4d..12a9444 100644 --- a/libs/imgui/backends/imgui_impl_wgpu.cpp +++ b/libs/imgui/backends/imgui_impl_wgpu.cpp @@ -45,9 +45,6 @@ // When targeting native platforms (i.e. NOT emscripten), one of IMGUI_IMPL_WEBGPU_BACKEND_DAWN // or IMGUI_IMPL_WEBGPU_BACKEND_WGPU must be provided. See imgui_impl_wgpu.h for more details. -// FIX(zig-gamedev) -#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU - #ifndef __EMSCRIPTEN__ #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) == defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #error exactly one of IMGUI_IMPL_WEBGPU_BACKEND_DAWN or IMGUI_IMPL_WEBGPU_BACKEND_WGPU must be defined! @@ -61,8 +58,7 @@ #include "imgui.h" #ifndef IMGUI_DISABLE -// FIX(zig-gamedev): -// #include "imgui_impl_wgpu.h" +#include "imgui_impl_wgpu.h" #include #include @@ -71,47 +67,6 @@ extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed = 0); #define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro). -// FIX(zig-gamedev): We removed header file and declare all our external functions here. -extern "C" { - -// Initialization data, for ImGui_ImplWGPU_Init() -struct ImGui_ImplWGPU_InitInfo -{ - WGPUDevice Device; - int NumFramesInFlight = 3; - WGPUTextureFormat RenderTargetFormat = WGPUTextureFormat_Undefined; - WGPUTextureFormat DepthStencilFormat = WGPUTextureFormat_Undefined; - WGPUMultisampleState PipelineMultisampleState = {}; - - ImGui_ImplWGPU_InitInfo() - { - PipelineMultisampleState.count = 1; - PipelineMultisampleState.mask = UINT32_MAX; - PipelineMultisampleState.alphaToCoverageEnabled = false; - } -}; - -// Follow "Getting Started" link and check examples/ folder to learn about using backends! -IMGUI_IMPL_API bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info); -IMGUI_IMPL_API void ImGui_ImplWGPU_Shutdown(); -IMGUI_IMPL_API void ImGui_ImplWGPU_NewFrame(); -IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder pass_encoder); - -// Use if you want to reset your rendering device without losing Dear ImGui state. -IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateDeviceObjects(); -IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects(); - -// [BETA] Selected render state data shared with callbacks. -// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplWGPU_RenderDrawData() call. -// (Please open an issue if you feel you need access to more data) -struct ImGui_ImplWGPU_RenderState -{ - WGPUDevice Device; - WGPURenderPassEncoder RenderPassEncoder; -}; - -} // extern "C" - // WebGPU data struct RenderResources { @@ -399,7 +354,8 @@ static void ImGui_ImplWGPU_SetupRenderState(ImDrawData* draw_data, WGPURenderPas } // Setup viewport - wgpuRenderPassEncoderSetViewport(ctx, 0, 0, draw_data->FramebufferScale.x * draw_data->DisplaySize.x, draw_data->FramebufferScale.y * draw_data->DisplaySize.y, 0, 1); + // FIX(zig-gamedev): Clamp bounds to workaround WGPU on Vulkan validation error + wgpuRenderPassEncoderSetViewport(ctx, 0, 0, (float)(int)(draw_data->FramebufferScale.x * draw_data->DisplaySize.x), (float)(int)(draw_data->FramebufferScale.y * draw_data->DisplaySize.y), 0, 1); // Bind shader and vertex buffers wgpuRenderPassEncoderSetVertexBuffer(ctx, 0, fr->VertexBuffer, 0, fr->VertexBufferSize * sizeof(ImDrawVert)); diff --git a/libs/imgui/backends/imgui_impl_wgpu.h b/libs/imgui/backends/imgui_impl_wgpu.h index 0457a63..f5f84e0 100644 --- a/libs/imgui/backends/imgui_impl_wgpu.h +++ b/libs/imgui/backends/imgui_impl_wgpu.h @@ -8,9 +8,6 @@ // This requirement will be removed once WebGPU stabilizes and backends converge on a unified interface. //#define IMGUI_IMPL_WEBGPU_BACKEND_DAWN -// FIX(zig-gamedev) -#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU - // Implemented features: // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). @@ -32,6 +29,9 @@ #include +// FIX(zig-gamedev) +extern "C" { + // Initialization data, for ImGui_ImplWGPU_Init() struct ImGui_ImplWGPU_InitInfo { @@ -68,4 +68,7 @@ struct ImGui_ImplWGPU_RenderState WGPURenderPassEncoder RenderPassEncoder; }; +// FIX(zig-gamedev) +} // extern "C" + #endif // #ifndef IMGUI_DISABLE diff --git a/src/backend_glfw_wgpu.zig b/src/backend_glfw_wgpu.zig index e6fa517..714b8fa 100644 --- a/src/backend_glfw_wgpu.zig +++ b/src/backend_glfw_wgpu.zig @@ -10,8 +10,6 @@ pub fn init( wgpu_swap_chain_format: u32, // wgpu.TextureFormat wgpu_depth_format: u32, // wgpu.TextureFormat ) void { - backend_glfw.init(window); - var info = ImGui_ImplWGPU_InitInfo{ .device = wgpu_device, .num_frames_in_flight = 1, @@ -23,6 +21,8 @@ pub fn init( if (!ImGui_ImplWGPU_Init(&info)) { unreachable; } + + backend_glfw.init(window); } pub fn deinit() void { @@ -30,13 +30,18 @@ pub fn deinit() void { backend_glfw.deinit(); } -pub fn newFrame(fb_width: u32, fb_height: u32) void { +var _width: u32 = 0; +var _height: u32 = 0; +pub fn newFrame(width: u32, height: u32) void { + if (width != _width or height != _height) { + ImGui_ImplWGPU_InvalidateDeviceObjects(); + if (ImGui_ImplWGPU_CreateDeviceObjects()) { + _width = width; + _height = height; + } + } ImGui_ImplWGPU_NewFrame(); backend_glfw.newFrame(); - - gui.io.setDisplaySize(@floatFromInt(fb_width), @floatFromInt(fb_height)); - gui.io.setDisplayFramebufferScale(1.0, 1.0); - gui.newFrame(); } @@ -61,7 +66,9 @@ pub const ImGui_ImplWGPU_InitInfo = extern struct { // Those functions are defined in 'imgui_impl_wgpu.cpp` // (they include few custom changes). -extern fn ImGui_ImplWGPU_Init(init_info: *ImGui_ImplWGPU_InitInfo) bool; -extern fn ImGui_ImplWGPU_NewFrame() void; -extern fn ImGui_ImplWGPU_RenderDrawData(draw_data: *const anyopaque, pass_encoder: *const anyopaque) void; -extern fn ImGui_ImplWGPU_Shutdown() void; +extern fn ImGui_ImplWGPU_Init(init_info: *ImGui_ImplWGPU_InitInfo) callconv(.c) bool; +extern fn ImGui_ImplWGPU_InvalidateDeviceObjects() callconv(.c) void; +extern fn ImGui_ImplWGPU_CreateDeviceObjects() callconv(.c) bool; +extern fn ImGui_ImplWGPU_NewFrame() callconv(.c) void; +extern fn ImGui_ImplWGPU_RenderDrawData(draw_data: *const anyopaque, pass_encoder: *const anyopaque) callconv(.c) void; +extern fn ImGui_ImplWGPU_Shutdown() callconv(.c) void;