From d29a5f9fcfe27d5c5666ebb702bc5d3a1bc8445b Mon Sep 17 00:00:00 2001 From: manuel Date: Wed, 23 Jul 2025 09:52:09 +0200 Subject: [PATCH 1/2] Update webgpu backend and example 1 --- backends/imgui_impl_wgpu.cpp | 57 +++-- examples/example_glfw_wgpu/main.cpp | 322 +++++++++++++++++++++++----- 2 files changed, 298 insertions(+), 81 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index d20028d55e26..a30112c3d8b6 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -44,13 +44,14 @@ // 2021-01-28: Initial version. #include "imgui.h" +#include // 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. #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! - #endif + //#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! + //#endif #else #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #error neither IMGUI_IMPL_WEBGPU_BACKEND_DAWN nor IMGUI_IMPL_WEBGPU_BACKEND_WGPU may be defined if targeting emscripten! @@ -254,11 +255,11 @@ static void SafeRelease(FrameResources& res) SafeRelease(res.VertexBufferHost); } -static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const char* wgsl_source) +static WGPUShaderModule ImGui_ImplWGPU_CreateShaderModule(const char* wgsl_source) { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPLE_WEBGPU_BACKEND_WGVK) WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; wgsl_desc.code = { wgsl_source, WGPU_STRLEN }; @@ -271,15 +272,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const c WGPUShaderModuleDescriptor desc = {}; desc.nextInChain = reinterpret_cast(&wgsl_desc); - WGPUProgrammableStageDescriptor stage_desc = {}; - stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); - -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) - stage_desc.entryPoint = { "main", WGPU_STRLEN }; -#else - stage_desc.entryPoint = "main"; -#endif - return stage_desc; + return wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); } static WGPUBindGroup ImGui_ImplWGPU_CreateImageBindGroup(WGPUBindGroupLayout layout, WGPUTextureView texture) @@ -367,7 +360,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder // Avoid rendering when minimized int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y); - if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdLists.Size == 0) + if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0) return; // Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do. @@ -442,8 +435,9 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder // Upload vertex/index data into a single contiguous GPU buffer ImDrawVert* vtx_dst = (ImDrawVert*)fr->VertexBufferHost; ImDrawIdx* idx_dst = (ImDrawIdx*)fr->IndexBufferHost; - for (const ImDrawList* draw_list : draw_data->CmdLists) + for (int n = 0; n < draw_data->CmdListsCount; n++) { + const ImDrawList* draw_list = draw_data->CmdLists[n]; memcpy(vtx_dst, draw_list->VtxBuffer.Data, draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy(idx_dst, draw_list->IdxBuffer.Data, draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); vtx_dst += draw_list->VtxBuffer.Size; @@ -470,8 +464,9 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder int global_idx_offset = 0; ImVec2 clip_scale = draw_data->FramebufferScale; ImVec2 clip_off = draw_data->DisplayPos; - for (const ImDrawList* draw_list : draw_data->CmdLists) + for (int n = 0; n < draw_data->CmdListsCount; n++) { + const ImDrawList* draw_list = draw_data->CmdLists[n]; for (int cmd_i = 0; cmd_i < draw_list->CmdBuffer.Size; cmd_i++) { const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; @@ -510,7 +505,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder continue; // Apply scissor/clipping rectangle, Draw - wgpuRenderPassEncoderSetScissorRect(pass_encoder, (uint32_t)clip_min.x, (uint32_t)clip_min.y, (uint32_t)(clip_max.x - clip_min.x), (uint32_t)(clip_max.y - clip_min.y)); + //wgpuRenderPassEncoderSetScissorRect(pass_encoder, (uint32_t)clip_min.x, (uint32_t)clip_min.y, (uint32_t)(clip_max.x - clip_min.x), (uint32_t)(clip_max.y - clip_min.y)); wgpuRenderPassEncoderDrawIndexed(pass_encoder, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); } } @@ -560,11 +555,7 @@ void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex) // Create texture WGPUTextureDescriptor tex_desc = {}; -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) tex_desc.label = { "Dear ImGui Texture", WGPU_STRLEN }; -#else - tex_desc.label = "Dear ImGui Texture"; -#endif tex_desc.dimension = WGPUTextureDimension_2D; tex_desc.size.width = tex->Width; tex_desc.size.height = tex->Height; @@ -696,9 +687,9 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.layout = wgpuDeviceCreatePipelineLayout(bd->wgpuDevice, &layout_desc); // Create the vertex shader - WGPUProgrammableStageDescriptor vertex_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_vert_wgsl); - graphics_pipeline_desc.vertex.module = vertex_shader_desc.module; - graphics_pipeline_desc.vertex.entryPoint = vertex_shader_desc.entryPoint; + WGPUShaderModule vertex_shader_module = ImGui_ImplWGPU_CreateShaderModule(__shader_vert_wgsl); + graphics_pipeline_desc.vertex.module = vertex_shader_module; + graphics_pipeline_desc.vertex.entryPoint = WGPUStringView{"main", sizeof("main") - 1}; // Vertex input configuration WGPUVertexAttribute attribute_desc[] = @@ -724,7 +715,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.vertex.buffers = buffer_layouts; // Create the pixel shader - WGPUProgrammableStageDescriptor pixel_shader_desc = ImGui_ImplWGPU_CreateShaderModule(__shader_frag_wgsl); + WGPUShaderModule pixel_shader_module = ImGui_ImplWGPU_CreateShaderModule(__shader_frag_wgsl); // Create the blending setup WGPUBlendState blend_state = {}; @@ -741,8 +732,8 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() color_state.writeMask = WGPUColorWriteMask_All; WGPUFragmentState fragment_state = {}; - fragment_state.module = pixel_shader_desc.module; - fragment_state.entryPoint = pixel_shader_desc.entryPoint; + fragment_state.module = pixel_shader_module; + fragment_state.entryPoint = WGPUStringView{"main", sizeof("main") - 1}; fragment_state.targetCount = 1; fragment_state.targets = &color_state; @@ -770,7 +761,9 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() graphics_pipeline_desc.depthStencil = (bd->depthStencilFormat == WGPUTextureFormat_Undefined) ? nullptr : &depth_stencil_state; bd->pipelineState = wgpuDeviceCreateRenderPipeline(bd->wgpuDevice, &graphics_pipeline_desc); - + if(bd->pipelineState == nullptr){ + abort(); + } ImGui_ImplWGPU_CreateUniformBuffer(); // Create sampler @@ -798,8 +791,8 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() bd->renderResources.CommonBindGroup = wgpuDeviceCreateBindGroup(bd->wgpuDevice, &common_bg_descriptor); bd->renderResources.ImageBindGroupLayout = bg_layouts[1]; - SafeRelease(vertex_shader_desc.module); - SafeRelease(pixel_shader_desc.module); + SafeRelease(vertex_shader_module); + SafeRelease(pixel_shader_module); SafeRelease(graphics_pipeline_desc.layout); SafeRelease(bg_layouts[0]); @@ -905,4 +898,4 @@ void ImGui_ImplWGPU_NewFrame() //----------------------------------------------------------------------------- -#endif // #ifndef IMGUI_DISABLE +#endif // #ifndef IMGUI_DISABLE \ No newline at end of file diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index c150b59610ad..5d2348859f1c 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -8,22 +8,52 @@ // - Documentation https://dearimgui.com/docs (same as your local docs/ folder). // - Introduction, links and more at the top of imgui.cpp + #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" +#include "wgvk.h" #include +#include #ifdef __EMSCRIPTEN__ #include #include #include #else -#include #endif - #include + +#ifdef __EMSCRIPTEN__ +# define GLFW_EXPOSE_NATIVE_EMSCRIPTEN +# ifndef GLFW_PLATFORM_EMSCRIPTEN // not defined in older versions of emscripten +# define GLFW_PLATFORM_EMSCRIPTEN 0 +# endif +#else // __EMSCRIPTEN__ +# ifdef _GLFW_X11 +# define GLFW_EXPOSE_NATIVE_X11 +# endif +# ifdef _GLFW_WAYLAND +# define GLFW_EXPOSE_NATIVE_WAYLAND +# endif +# ifdef _GLFW_COCOA +# define GLFW_EXPOSE_NATIVE_COCOA +# endif +# ifdef _GLFW_WIN32 +# define GLFW_EXPOSE_NATIVE_WIN32 +# endif +#endif // __EMSCRIPTEN__ + +#ifdef GLFW_EXPOSE_NATIVE_COCOA +# include +# include +#endif + +#ifndef __EMSCRIPTEN__ +# include +#endif #include -#include +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window); // This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. #ifdef __EMSCRIPTEN__ @@ -34,14 +64,12 @@ static WGPUInstance wgpu_instance = nullptr; static WGPUDevice wgpu_device = nullptr; static WGPUSurface wgpu_surface = nullptr; -static WGPUTextureFormat wgpu_preferred_fmt = WGPUTextureFormat_RGBA8Unorm; -static WGPUSwapChain wgpu_swap_chain = nullptr; +static WGPUTextureFormat wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; static int wgpu_swap_chain_width = 1280; static int wgpu_swap_chain_height = 720; // Forward declarations static bool InitWGPU(GLFWwindow* window); -static void CreateSwapChain(int width, int height); static void glfw_error_callback(int error, const char* description) { @@ -56,25 +84,28 @@ static void wgpu_error_callback(WGPUErrorType error_type, const char* message, v case WGPUErrorType_Validation: error_type_lbl = "Validation"; break; case WGPUErrorType_OutOfMemory: error_type_lbl = "Out of memory"; break; case WGPUErrorType_Unknown: error_type_lbl = "Unknown"; break; - case WGPUErrorType_DeviceLost: error_type_lbl = "Device lost"; break; + //case WGPUErrorType_DeviceLost: error_type_lbl = "Device lost"; break; default: error_type_lbl = "Unknown"; } printf("%s error: %s\n", error_type_lbl, message); } // Main code -int main(int, char**) -{ +int main(int, char**){ glfwSetErrorCallback(glfw_error_callback); - if (!glfwInit()) + if (!glfwInit()){ + std::cerr << "glfwInit failed\n"; return 1; + } // Make sure GLFW does not initialize any graphics context. // This needs to be done explicitly later. glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); GLFWwindow* window = glfwCreateWindow(wgpu_swap_chain_width, wgpu_swap_chain_height, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr); - if (window == nullptr) + if (window == nullptr){ + std::cerr << "glfwCreateWindow failed\n"; return 1; + } // Initialize the WebGPU environment if (!InitWGPU(window)) @@ -82,9 +113,9 @@ int main(int, char**) if (window) glfwDestroyWindow(window); glfwTerminate(); + std::cerr << "InitWPU failed\n"; return 1; } - CreateSwapChain(wgpu_swap_chain_width, wgpu_swap_chain_height); glfwShowWindow(window); // Setup Dear ImGui context @@ -133,7 +164,7 @@ int main(int, char**) // Our state bool show_demo_window = true; bool show_another_window = false; - ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + ImVec4 clear_color = ImVec4(0.0f, 0.05f, 1.f, 1.00f); // Main loop #ifdef __EMSCRIPTEN__ @@ -142,6 +173,7 @@ int main(int, char**) io.IniFilename = nullptr; EMSCRIPTEN_MAINLOOP_BEGIN #else + std::cout << "Checkpoint" << std::endl; while (!glfwWindowShouldClose(window)) #endif { @@ -163,7 +195,6 @@ int main(int, char**) if (width != wgpu_swap_chain_width || height != wgpu_swap_chain_height) { ImGui_ImplWGPU_InvalidateDeviceObjects(); - CreateSwapChain(width, height); ImGui_ImplWGPU_CreateDeviceObjects(); } @@ -214,7 +245,7 @@ int main(int, char**) #ifndef __EMSCRIPTEN__ // Tick needs to be called in Dawn to display validation errors - wgpuDeviceTick(wgpu_device); + //wgpuDeviceTick(wgpu_device); #endif WGPURenderPassColorAttachment color_attachments = {}; @@ -222,7 +253,21 @@ int main(int, char**) color_attachments.loadOp = WGPULoadOp_Clear; color_attachments.storeOp = WGPUStoreOp_Store; color_attachments.clearValue = { clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w }; - color_attachments.view = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); + WGPUSurfaceTexture sTexture{}; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &sTexture); + const WGPUTextureViewDescriptor viewDescriptor = { + .nextInChain = nullptr, + .label = WGPUStringView{}, + .format = WGPUTextureFormat_BGRA8Unorm, + .dimension = WGPUTextureViewDimension_2D, + .baseMipLevel = 0, + .mipLevelCount = 1, + .baseArrayLayer = 0, + .arrayLayerCount = 1, + .aspect = WGPUTextureAspect_All, + .usage = WGPUTextureUsage_RenderAttachment, + }; + color_attachments.view = wgpuTextureCreateView(sTexture.texture, &viewDescriptor); WGPURenderPassDescriptor render_pass_desc = {}; render_pass_desc.colorAttachmentCount = 1; @@ -240,9 +285,10 @@ int main(int, char**) WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); wgpuQueueSubmit(queue, 1, &cmd_buffer); + //wgpuQueueWaitIdle(queue); #ifndef __EMSCRIPTEN__ - wgpuSwapChainPresent(wgpu_swap_chain); + wgpuSurfacePresent(wgpu_surface); #endif wgpuTextureViewRelease(color_attachments.view); @@ -268,48 +314,79 @@ int main(int, char**) #ifndef __EMSCRIPTEN__ static WGPUAdapter RequestAdapter(WGPUInstance instance) { - auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message, void* pUserData) + auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void* pUserData, void* userdata2) { if (status == WGPURequestAdapterStatus_Success) *(WGPUAdapter*)(pUserData) = adapter; - else - printf("Could not get WebGPU adapter: %s\n", message); + //else + // printf("Could not get WebGPU adapter: %s\n", message); }; WGPUAdapter adapter; - wgpuInstanceRequestAdapter(instance, nullptr, onAdapterRequestEnded, (void*)&adapter); + WGPURequestAdapterCallbackInfo info = {}; + info.callback = onAdapterRequestEnded; + info.userdata1 = (void*)&adapter; + info.mode = WGPUCallbackMode_WaitAnyOnly; + WGPURequestAdapterOptions options{}; + WGPUFuture future = wgpuInstanceRequestAdapter(instance, &options, info); + WGPUFutureWaitInfo finfo{ + .future = future, + .completed = 0 + }; + wgpuInstanceWaitAny(instance, 1, &finfo, UINT32_MAX); return adapter; } static WGPUDevice RequestDevice(WGPUAdapter& adapter) { - auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* pUserData) + auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void* pUserData, void* pUserData2) { if (status == WGPURequestDeviceStatus_Success) *(WGPUDevice*)(pUserData) = device; - else - printf("Could not get WebGPU device: %s\n", message); + //else + // printf("Could not get WebGPU device: %s\n", message); }; WGPUDevice device; - wgpuAdapterRequestDevice(adapter, nullptr, onDeviceRequestEnded, (void*)&device); + WGPURequestDeviceCallbackInfo info = {}; + info.callback = onDeviceRequestEnded; + info.userdata1 = (void*)&device; + info.mode = WGPUCallbackMode_WaitAnyOnly; + WGPUDeviceDescriptor ddesc{}; + WGPUFuture future = wgpuAdapterRequestDevice(adapter, &ddesc, info); + WGPUFutureWaitInfo finfo{ + .future = future, + .completed = 0 + }; + wgpuInstanceWaitAny(wgpu_instance, 1, &finfo, UINT32_MAX); return device; } #endif static bool InitWGPU(GLFWwindow* window) { - wgpu::Instance instance = wgpuCreateInstance(nullptr); + WGPUInstanceLayerSelection lsel{ + .chain = { + nullptr, + WGPUSType_InstanceValidationLayerSelection, + } + }; + lsel.instanceLayerCount = 1; + const char* validation = "VK_LAYER_KHRONOS_validation"; + lsel.instanceLayers = &validation; + WGPUInstanceDescriptor idesc{.nextInChain = &lsel.chain}; + + wgpu_instance = wgpuCreateInstance(&idesc); #ifdef __EMSCRIPTEN__ wgpu_device = emscripten_webgpu_get_device(); if (!wgpu_device) return false; #else - WGPUAdapter adapter = RequestAdapter(instance.Get()); + WGPUAdapter adapter = RequestAdapter(wgpu_instance); if (!adapter) return false; wgpu_device = RequestDevice(adapter); #endif - + #ifdef __EMSCRIPTEN__ wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; html_surface_desc.selector = "#canvas"; @@ -320,31 +397,178 @@ static bool InitWGPU(GLFWwindow* window) wgpu::Adapter adapter = {}; wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter); #else - wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, window); - if (!surface) + wgpu_surface = glfwCreateWindowWGPUSurface(wgpu_instance, window); + if (!wgpu_surface) return false; - wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; + int width, height; + WGPUTextureFormat surfaceViewFormat = WGPUTextureFormat_BGRA8Unorm; + glfwGetFramebufferSize((GLFWwindow*)window, &width, &height); + WGPUSurfaceConfiguration surfaceConfig = { + .nextInChain = nullptr, + .device = wgpu_device, + .format = WGPUTextureFormat_BGRA8Unorm, + .usage = WGPUTextureUsage_RenderAttachment, + .width = (uint32_t)width, + .height = (uint32_t)height, + .viewFormatCount = 1, + .viewFormats = &surfaceViewFormat, + .alphaMode = WGPUCompositeAlphaMode_Opaque, + .presentMode = WGPUPresentMode_Fifo, + }; + wgpuSurfaceConfigure(wgpu_surface, &surfaceConfig); + #endif - wgpu_instance = instance.MoveToCHandle(); - wgpu_surface = surface.MoveToCHandle(); - - wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); + //wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); return true; } -static void CreateSwapChain(int width, int height) -{ - if (wgpu_swap_chain) - wgpuSwapChainRelease(wgpu_swap_chain); - wgpu_swap_chain_width = width; - wgpu_swap_chain_height = height; - WGPUSwapChainDescriptor swap_chain_desc = {}; - swap_chain_desc.usage = WGPUTextureUsage_RenderAttachment; - swap_chain_desc.format = wgpu_preferred_fmt; - swap_chain_desc.width = width; - swap_chain_desc.height = height; - swap_chain_desc.presentMode = WGPUPresentMode_Fifo; - wgpu_swap_chain = wgpuDeviceCreateSwapChain(wgpu_device, wgpu_surface, &swap_chain_desc); -} + +/** + * This is an extension of GLFW for WebGPU, abstracting away the details of + * OS-specific operations. + * + * This file is part of the "Learn WebGPU for C++" book. + * https://eliemichel.github.io/LearnWebGPU + * + * Most of this code comes from the wgpu-native triangle example: + * https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c + * + * MIT License + * Copyright (c) 2022-2025 Elie Michel and the wgpu-native authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + + +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window) { +#ifndef __EMSCRIPTEN__ + switch (glfwGetPlatform()) { +#else + // glfwGetPlatform is not available in older versions of emscripten + switch (GLFW_PLATFORM_EMSCRIPTEN) { +#endif + +#ifdef GLFW_EXPOSE_NATIVE_X11 + case GLFW_PLATFORM_X11: { + Display* x11_display = glfwGetX11Display(); + Window x11_window = glfwGetX11Window(window); + + WGPUSurfaceSourceXlibWindow fromXlibWindow; + fromXlibWindow.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + fromXlibWindow.chain.next = NULL; + fromXlibWindow.display = x11_display; + fromXlibWindow.window = x11_window; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromXlibWindow.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_X11 + +#ifdef GLFW_EXPOSE_NATIVE_WAYLAND + case GLFW_PLATFORM_WAYLAND: { + struct wl_display* wayland_display = glfwGetWaylandDisplay(); + struct wl_surface* wayland_surface = glfwGetWaylandWindow(window); + + WGPUSurfaceSourceWaylandSurface fromWaylandSurface; + fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + fromWaylandSurface.chain.next = NULL; + fromWaylandSurface.display = wayland_display; + fromWaylandSurface.surface = wayland_surface; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWaylandSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WAYLAND + +#ifdef GLFW_EXPOSE_NATIVE_COCOA + case GLFW_PLATFORM_COCOA: { + id metal_layer = [CAMetalLayer layer]; + NSWindow* ns_window = glfwGetCocoaWindow(window); + [ns_window.contentView setWantsLayer : YES] ; + [ns_window.contentView setLayer : metal_layer] ; + + WGPUSurfaceSourceMetalLayer fromMetalLayer; + fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + fromMetalLayer.chain.next = NULL; + fromMetalLayer.layer = metal_layer; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromMetalLayer.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_COCOA + +#ifdef GLFW_EXPOSE_NATIVE_WIN32 + case GLFW_PLATFORM_WIN32: { + HWND hwnd = glfwGetWin32Window(window); + HINSTANCE hinstance = GetModuleHandle(NULL); + + WGPUSurfaceSourceWindowsHWND fromWindowsHWND; + fromWindowsHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + fromWindowsHWND.chain.next = NULL; + fromWindowsHWND.hinstance = hinstance; + fromWindowsHWND.hwnd = hwnd; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWindowsHWND.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WIN32 + +#ifdef GLFW_EXPOSE_NATIVE_EMSCRIPTEN + case GLFW_PLATFORM_EMSCRIPTEN: { +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = (WGPUStringView){ "canvas", WGPU_STRLEN }; +# else + WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = "canvas"; +# endif + fromCanvasHTMLSelector.chain.next = NULL; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain; +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; +# else + surfaceDescriptor.label = NULL; +# endif + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_EMSCRIPTEN + + default: + // Unsupported platform + return NULL; + } +} \ No newline at end of file From f0640b8f6a50a658a4a9634680d919a544bbba88 Mon Sep 17 00:00:00 2001 From: manuel Date: Wed, 23 Jul 2025 13:04:51 +0200 Subject: [PATCH 2/2] Update example and Makefile Makefile has been updated for the current state of emscripten, featuring --use-port=emdawnwebgpu and REMOVING -sUSE_WEBGPU=1 The example is again up to date with webgpu.h and doesn't require webgpu_cpp anymore --- backends/imgui_impl_wgpu.cpp | 11 ++-- .../example_glfw_wgpu/Makefile.emscripten | 5 +- examples/example_glfw_wgpu/README.md | 31 ++++++++- examples/example_glfw_wgpu/main.cpp | 65 +++++++++++++------ 4 files changed, 79 insertions(+), 33 deletions(-) diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index a30112c3d8b6..c7c9d97f8955 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -18,6 +18,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2025-07-23: Update to latest version of webgpu.h // 2025-06-12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. (#8465) // 2025-02-26: Recreate image bind groups during render. (#8426, #8046, #7765, #8027) + Update for latest webgpu-native changes. // 2024-10-14: Update Dawn support for change of string usages. (#8082, #8083) @@ -54,7 +55,7 @@ //#endif #else #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) - #error neither IMGUI_IMPL_WEBGPU_BACKEND_DAWN nor IMGUI_IMPL_WEBGPU_BACKEND_WGPU may be defined if targeting emscripten! + //#error neither IMGUI_IMPL_WEBGPU_BACKEND_DAWN nor IMGUI_IMPL_WEBGPU_BACKEND_WGPU may be defined if targeting emscripten! #endif #endif @@ -259,7 +260,7 @@ static WGPUShaderModule ImGui_ImplWGPU_CreateShaderModule(const char* wgsl_sourc { ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPLE_WEBGPU_BACKEND_WGVK) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; wgsl_desc.code = { wgsl_source, WGPU_STRLEN }; @@ -694,7 +695,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() // Vertex input configuration WGPUVertexAttribute attribute_desc[] = { -#ifdef IMGUI_IMPL_WEBGPU_BACKEND_DAWN +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(WEBGPU_BACKEND_EMDAWNWEBGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGVK) { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, pos), 0 }, { nullptr, WGPUVertexFormat_Float32x2, (uint64_t)offsetof(ImDrawVert, uv), 1 }, { nullptr, WGPUVertexFormat_Unorm8x4, (uint64_t)offsetof(ImDrawVert, col), 2 }, @@ -742,11 +743,7 @@ bool ImGui_ImplWGPU_CreateDeviceObjects() // Create depth-stencil State WGPUDepthStencilState depth_stencil_state = {}; depth_stencil_state.format = bd->depthStencilFormat; -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) depth_stencil_state.depthWriteEnabled = WGPUOptionalBool_False; -#else - depth_stencil_state.depthWriteEnabled = false; -#endif depth_stencil_state.depthCompare = WGPUCompareFunction_Always; depth_stencil_state.stencilFront.compare = WGPUCompareFunction_Always; depth_stencil_state.stencilFront.failOp = WGPUStencilOperation_Keep; diff --git a/examples/example_glfw_wgpu/Makefile.emscripten b/examples/example_glfw_wgpu/Makefile.emscripten index 8ee398bc8d9c..1cc9e482431c 100644 --- a/examples/example_glfw_wgpu/Makefile.emscripten +++ b/examples/example_glfw_wgpu/Makefile.emscripten @@ -33,9 +33,8 @@ EMS = # ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) # Note: For glfw, we use emscripten-glfw port (contrib.glfw3) instead of (-s USE_GLFW=3) to get a better support for High DPI displays. -EMS += -s DISABLE_EXCEPTION_CATCHING=1 --use-port=contrib.glfw3 -LDFLAGS += -s USE_WEBGPU=1 -LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 +EMS += -DWEBGPU_BACKEND_EMDAWNWEBGPU -DIMGUI_IMPL_WEBGPU_BACKEND_DAWN -sDISABLE_EXCEPTION_CATCHING=1 --use-port=contrib.glfw3 --use-port=emdawnwebgpu +LDFLAGS += -sASYNCIFY=2 -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 # Build as single file (binary text encoded in .html file) #LDFLAGS += -sSINGLE_FILE diff --git a/examples/example_glfw_wgpu/README.md b/examples/example_glfw_wgpu/README.md index 399d431ffaa1..6fe988dcd3ef 100644 --- a/examples/example_glfw_wgpu/README.md +++ b/examples/example_glfw_wgpu/README.md @@ -1,5 +1,28 @@ ## How to Build +### Desktop Builds + +- Download and compile one of the three WebGPU on desktop implementations: + - [Dawn](https://dawn.googlesource.com/dawn) + - [WGPU](https://github.com/gfx-rs/wgpu-native) (requires Rust) + - [WGVK](https://github.com/manuel5975p/WGVK) (Lightweight, Vulkan only) + + +Once compiled, imgui's backend code can be compiled and linked to the webgpu implementation library, as an example to WGVK on X11: + +``` +g++ -o example -D_GLFW_X11 -DIMGUI_IMPL_WEBGPU_BACKEND_DAWN imgui_demo.cpp imgui.cpp imgui_draw.cpp imgui_widgets.cpp imgui_tables.cpp examples/example_glfw_wgpu/main.cpp backends/imgui_impl_wgpu.cpp backends/imgui_impl_glfw.cpp -I . -I backends -I /include/ /build/libwgvk.a +``` + +This command is assumed to be run from imgui's root directory. + +Explanation of the options: +- `-D_GLFW_X11` required to expose X11 handles. Replace with `-D_GLFW_WAYLAND` for wayland or `-D_GLFW_WIN32` for windows +- `-DIMGUI_IMPL_WEBGPU_BACKEND_DAWN` because WGVK mimics dawn +- `-I . -I backends` Include paths for imgui +- `-I /include/` include path for `` + +### Web Builds - You need to install Emscripten from https://emscripten.org/docs/getting_started/downloads.html, and have the environment variables set, as described in https://emscripten.org/docs/getting_started/downloads.html#installation-instructions - Depending on your configuration, in Windows you may need to run `emsdk/emsdk_env.bat` in your console to access the Emscripten command-line tools. @@ -10,10 +33,14 @@ - Requires recent Emscripten as WGPU is still a work-in-progress API. -## How to Run +## How to run Web Builds To run on a local machine: -- Make sure your browse supports WGPU and it is enabled. WGPU is still WIP not enabled by default in most browser. +- For Chrome: + - Enable the experimental flags JSPI and WebGPU in chrome://flags/ +- For Firefox: + - Enable the experimental flags wasm_js_promise_integration and WebGPU in about:config + - `make serve` will use Python3 to spawn a local webserver, you can then browse http://localhost:8000 to access your build. - Otherwise, generally you will need a local webserver: - Quoting [https://emscripten.org/docs/getting_started](https://emscripten.org/docs/getting_started/Tutorial.html#generating-html):
diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 5d2348859f1c..c7b3abde49f2 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -12,14 +12,18 @@ #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" -#include "wgvk.h" +#include + +#ifdef __EMSCRIPTEN__ +#include +#endif + #include #include #ifdef __EMSCRIPTEN__ #include #include -#include #else #endif #include @@ -361,8 +365,26 @@ static WGPUDevice RequestDevice(WGPUAdapter& adapter) } #endif +static void resizeCallback(GLFWwindow* window, int width, int height){ + WGPUTextureFormat surfaceViewFormat = WGPUTextureFormat_BGRA8Unorm; + + WGPUSurfaceConfiguration surfaceConfig = { + .nextInChain = nullptr, + .device = wgpu_device, + .format = WGPUTextureFormat_BGRA8Unorm, + .usage = WGPUTextureUsage_RenderAttachment, + .width = (uint32_t)width, + .height = (uint32_t)height, + .viewFormatCount = 1, + .viewFormats = &surfaceViewFormat, + .alphaMode = WGPUCompositeAlphaMode_Opaque, + .presentMode = WGPUPresentMode_Fifo, + }; + wgpuSurfaceConfigure(wgpu_surface, &surfaceConfig); +} static bool InitWGPU(GLFWwindow* window) { + #ifdef IMGUI_IMPL_WEBGPU_BACKEND_WGVK WGPUInstanceLayerSelection lsel{ .chain = { nullptr, @@ -372,9 +394,13 @@ static bool InitWGPU(GLFWwindow* window) lsel.instanceLayerCount = 1; const char* validation = "VK_LAYER_KHRONOS_validation"; lsel.instanceLayers = &validation; - WGPUInstanceDescriptor idesc{.nextInChain = &lsel.chain}; + WGPUInstanceDescriptor idesc{.nextInChain = &lsel.chain}; wgpu_instance = wgpuCreateInstance(&idesc); + #else + wgpu_instance = wgpuCreateInstance(nullptr); + #endif + #ifdef __EMSCRIPTEN__ wgpu_device = emscripten_webgpu_get_device(); @@ -388,14 +414,20 @@ static bool InitWGPU(GLFWwindow* window) #endif #ifdef __EMSCRIPTEN__ - wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; - html_surface_desc.selector = "#canvas"; - wgpu::SurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = &html_surface_desc; - wgpu::Surface surface = instance.CreateSurface(&surface_desc); - - wgpu::Adapter adapter = {}; - wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter); + + WGPUSurfaceDescriptor surface_desc; + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector html_surface_desc{}; + html_surface_desc.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + html_surface_desc.selector = WGPUStringView{ + .data = "#canvas", + .length = 7 + }; + + surface_desc.nextInChain = &html_surface_desc.chain; + wgpu_surface = wgpuInstanceCreateSurface(wgpu_instance, &surface_desc); + + // wgpu::Adapter adapter = {}; // wgpuSurfaceGetPreferredFormat is no more, the correct way would be to pick the first format set by wgpuSurfaceGetCapabilities + wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; #else wgpu_surface = glfwCreateWindowWGPUSurface(wgpu_instance, window); if (!wgpu_surface) @@ -418,7 +450,7 @@ static bool InitWGPU(GLFWwindow* window) wgpuSurfaceConfigure(wgpu_surface, &surfaceConfig); #endif - + glfwSetWindowSizeCallback(window, resizeCallback); //wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); return true; @@ -426,15 +458,6 @@ static bool InitWGPU(GLFWwindow* window) /** - * This is an extension of GLFW for WebGPU, abstracting away the details of - * OS-specific operations. - * - * This file is part of the "Learn WebGPU for C++" book. - * https://eliemichel.github.io/LearnWebGPU - * - * Most of this code comes from the wgpu-native triangle example: - * https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c - * * MIT License * Copyright (c) 2022-2025 Elie Michel and the wgpu-native authors *