From e3c7ff944d52154c3e5e6db6d3b8fba65b1a1547 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 22 Jan 2024 14:20:24 +0100 Subject: [PATCH] Examples: Emscripten+WebGPU: slightly refactor like other Emscripten compatible Desktop examples, as aiming to make this suppot desktop eventually. Also aimed at reducing diff for https://github.com/ocornut/imgui/pull/7132 tho this will lead in conflict. --- .../{Makefile => Makefile.emscripten} | 0 examples/example_emscripten_wgpu/README.md | 2 +- examples/example_emscripten_wgpu/main.cpp | 289 ++++++++++-------- 3 files changed, 156 insertions(+), 135 deletions(-) rename examples/example_emscripten_wgpu/{Makefile => Makefile.emscripten} (100%) diff --git a/examples/example_emscripten_wgpu/Makefile b/examples/example_emscripten_wgpu/Makefile.emscripten similarity index 100% rename from examples/example_emscripten_wgpu/Makefile rename to examples/example_emscripten_wgpu/Makefile.emscripten diff --git a/examples/example_emscripten_wgpu/README.md b/examples/example_emscripten_wgpu/README.md index c4c4dec7..e60025da 100644 --- a/examples/example_emscripten_wgpu/README.md +++ b/examples/example_emscripten_wgpu/README.md @@ -6,7 +6,7 @@ - You may also refer to our [Continuous Integration setup](https://github.com/ocornut/imgui/tree/master/.github/workflows) for Emscripten setup. -- Then build using `make` while in the `example_emscripten_wgpu/` directory. +- Then build using `make -f Makefile.emscripten` while in the `example_emscripten_wgpu/` directory. - Requires recent Emscripten as WGPU is still a work-in-progress API. diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 73791a54..cb09d422 100644 --- a/examples/example_emscripten_wgpu/main.cpp +++ b/examples/example_emscripten_wgpu/main.cpp @@ -11,13 +11,20 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" #include +#ifdef __EMSCRIPTEN__ #include #include #include +#endif #include #include #include +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + // Global WebGPU required states static WGPUDevice wgpu_device = nullptr; static WGPUSurface wgpu_surface = nullptr; @@ -27,15 +34,32 @@ static int wgpu_swap_chain_width = 0; static int wgpu_swap_chain_height = 0; // Forward declarations -static void MainLoopStep(void* window); static bool InitWGPU(); -static void print_glfw_error(int error, const char* description); -static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*); +static void CreateSwapChain(int width, int height); + +static void glfw_error_callback(int error, const char* description) +{ + printf("GLFW Error %d: %s\n", error, description); +} + +static void wgpu_error_callback(WGPUErrorType error_type, const char* message, void*) +{ + const char* error_type_lbl = ""; + switch (error_type) + { + 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; + default: error_type_lbl = "Unknown"; + } + printf("%s error: %s\n", error_type_lbl, message); +} // Main code int main(int, char**) { - glfwSetErrorCallback(print_glfw_error); + glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; @@ -43,11 +67,8 @@ int main(int, char**) // This needs to be done explicitly later. glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr); - if (!window) - { - glfwTerminate(); + if (window == nullptr) return 1; - } // Initialize the WebGPU environment if (!InitWGPU()) @@ -66,17 +87,15 @@ int main(int, char**) io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls - // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. - // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. - io.IniFilename = nullptr; - // Setup Dear ImGui style ImGui::StyleColorsDark(); //ImGui::StyleColorsLight(); // Setup Platform/Renderer backends ImGui_ImplGlfw_InitForOther(window, true); +#ifdef __EMSCRIPTEN__ ImGui_ImplGlfw_InstallEmscriptenCanvasResizeCallback("#canvas"); +#endif ImGui_ImplWGPU_Init(wgpu_device, 3, wgpu_preferred_fmt, WGPUTextureFormat_Undefined); // Load Fonts @@ -99,10 +118,117 @@ int main(int, char**) //IM_ASSERT(font != nullptr); #endif - // This function will directly return and exit the main function. - // Make sure that no required objects get cleaned up. - // This way we can use the browsers 'requestAnimationFrame' to control the rendering. - emscripten_set_main_loop_arg(MainLoopStep, window, 0, false); + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + // Main loop +#ifdef __EMSCRIPTEN__ + // For an Emscripten build we are disabling file-system access, so let's not attempt to do a fopen() of the imgui.ini file. + // You may manually call LoadIniSettingsFromMemory() to load settings from your own storage. + io.IniFilename = nullptr; + EMSCRIPTEN_MAINLOOP_BEGIN +#else + while (!glfwWindowShouldClose(window)) +#endif + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + glfwPollEvents(); + + // React to changes in screen size + int width, height; + glfwGetFramebufferSize((GLFWwindow*)window, &width, &height); + if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height) + { + ImGui_ImplWGPU_InvalidateDeviceObjects(); + CreateSwapChain(width, height); + ImGui_ImplWGPU_CreateDeviceObjects(); + } + + // Start the Dear ImGui frame + ImGui_ImplWGPU_NewFrame(); + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). + if (show_demo_window) + ImGui::ShowDemoWindow(&show_demo_window); + + // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. + { + static float f = 0.0f; + static int counter = 0; + + ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. + + ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) + ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state + ImGui::Checkbox("Another Window", &show_another_window); + + ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f + ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color + + if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) + counter++; + ImGui::SameLine(); + ImGui::Text("counter = %d", counter); + + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); + ImGui::End(); + } + + // 3. Show another simple window. + if (show_another_window) + { + ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) + ImGui::Text("Hello from another window!"); + if (ImGui::Button("Close Me")) + show_another_window = false; + ImGui::End(); + } + + // Rendering + ImGui::Render(); + + WGPURenderPassColorAttachment color_attachments = {}; + 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); + + WGPURenderPassDescriptor render_pass_desc = {}; + render_pass_desc.colorAttachmentCount = 1; + render_pass_desc.colorAttachments = &color_attachments; + render_pass_desc.depthStencilAttachment = nullptr; + + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); + ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); + wgpuRenderPassEncoderEnd(pass); + + WGPUCommandBufferDescriptor cmd_buffer_desc = {}; + WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); + WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); + wgpuQueueSubmit(queue, 1, &cmd_buffer); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + // Cleanup + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplGlfw_Shutdown(); + ImGui::DestroyContext(); + + glfwDestroyWindow(window); + glfwTerminate(); return 0; } @@ -113,7 +239,7 @@ static bool InitWGPU() if (!wgpu_device) return false; - wgpuDeviceSetUncapturedErrorCallback(wgpu_device, print_wgpu_error, nullptr); + wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); // Use C++ wrapper due to misbehavior in Emscripten. // Some offset computation for wgpuInstanceCreateSurface in JavaScript @@ -133,122 +259,17 @@ static bool InitWGPU() return true; } -static void MainLoopStep(void* window) +static void CreateSwapChain(int width, int height) { - ImGuiIO& io = ImGui::GetIO(); - - glfwPollEvents(); - - int width, height; - glfwGetFramebufferSize((GLFWwindow*)window, &width, &height); - - // React to changes in screen size - if (width != wgpu_swap_chain_width && height != wgpu_swap_chain_height) - { - ImGui_ImplWGPU_InvalidateDeviceObjects(); - 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); - ImGui_ImplWGPU_CreateDeviceObjects(); - } - - // Start the Dear ImGui frame - ImGui_ImplWGPU_NewFrame(); - ImGui_ImplGlfw_NewFrame(); - ImGui::NewFrame(); - - // Our state - // (we use static, which essentially makes the variable globals, as a convenience to keep the example code easy to follow) - static bool show_demo_window = true; - static bool show_another_window = false; - static ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); - - // 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!). - if (show_demo_window) - ImGui::ShowDemoWindow(&show_demo_window); - - // 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window. - { - static float f = 0.0f; - static int counter = 0; - - ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it. - - ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too) - ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state - ImGui::Checkbox("Another Window", &show_another_window); - - ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f - ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color - - if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) - counter++; - ImGui::SameLine(); - ImGui::Text("counter = %d", counter); - - ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / io.Framerate, io.Framerate); - ImGui::End(); - } - - // 3. Show another simple window. - if (show_another_window) - { - ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked) - ImGui::Text("Hello from another window!"); - if (ImGui::Button("Close Me")) - show_another_window = false; - ImGui::End(); - } - - // Rendering - ImGui::Render(); - - WGPURenderPassColorAttachment color_attachments = {}; - 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); - WGPURenderPassDescriptor render_pass_desc = {}; - render_pass_desc.colorAttachmentCount = 1; - render_pass_desc.colorAttachments = &color_attachments; - render_pass_desc.depthStencilAttachment = nullptr; - - WGPUCommandEncoderDescriptor enc_desc = {}; - WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(wgpu_device, &enc_desc); - - WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); - ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); - wgpuRenderPassEncoderEnd(pass); - - WGPUCommandBufferDescriptor cmd_buffer_desc = {}; - WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); - WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); - wgpuQueueSubmit(queue, 1, &cmd_buffer); -} - -static void print_glfw_error(int error, const char* description) -{ - printf("GLFW Error %d: %s\n", error, description); -} - -static void print_wgpu_error(WGPUErrorType error_type, const char* message, void*) -{ - const char* error_type_lbl = ""; - switch (error_type) - { - 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; - default: error_type_lbl = "Unknown"; - } - printf("%s error: %s\n", error_type_lbl, message); + 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); }