From 46ca8bc16fa2a815f6666b9f204876fe943aa29c Mon Sep 17 00:00:00 2001 From: BrutPitt Date: Fri, 7 Feb 2025 06:00:17 +0100 Subject: [PATCH] Backends, Examples: WGPU: wip refactor. (8381) Rebased and squashed 47 commits. - ImGui WebGPU examples: removed swap-chain in favor of surfaceConfigure (8191) - New SDL2 WGPU example - Code optimization: removed the redundancies of assignment - Changes: ImGui code style - Lambdas RequestAdapter and RequestDevice callbacks declarated as signature - Validation Layer callbacks moved from lambdas to standard functions - Same changes of GLFW example: ImGui code style - Lambdas RequestAdapter and RequestDevice callbacks declarated as signature - Validation Layer callbacks moved from lambdas to standard functions - Scrollbars inhibition - Use of new direct CreateDevice function (w/o callback), added release of all resources used (not present in original example) - If the O.S. is Linux/Unix check if Wayland is the current active session and set DAWN_USE_WAYLAND=ON option (otherwise is always OFF) - example_glfw_wgpu: removed all workarounds - example_sdl2_wgpu: same style and functionality as GLFW example - sdl2wgpu tool to acquire surfaceDescriptor via SDL_syswm and to pass to wgpuInstanceCreateSurface to create WGPU Surface - css style to avoid the scrollbar - added `chek_surface_texture_status` function to check the `WGPUSurfaceTexture .status` and recreate the `Surface` in case of "not optimal" (bad) status. - Changed comment reference to `emwgpudawn` an EMSCRIPTEN WGPU binding maintained by Google - Adaptation to the last Google DAWN commit (1411699ba) Adaptation to the last EMSCRIPTEN 4.0.10, using new "--use-port=emdawnwebgpu" compiler/linker flag - Support for EMSCRIPTEN >= 4.0.10 (using "--use-port=emdawnwebgpu") and NEW support for WGPU-Native - Finalized the support of WGPU-Native for MacOS and minimal code adjustment - WebGPU examples - DAWN / WGPU native and EMSCRIPTEN - for GLFW/SDL2/SDL3 frameworks - "index.html" no more necessary (now the common one is used), "sdl2wgpu.cpp" It has been moved and renamed (sdl2_wgpu.c), "sdl2wgpu.h" no more necessary - added procedure for using CMake - added procedure for using CMake - Updated example_sdl3_wgpu build procedure for EMSCRIPTEN - WGPU+GLFW: Helper to create a WebGPU surface for Native application. Used only with WGPU-Native SDK: DAWN-Native already has a built-in function - WGPU+SDL2: helper to create a WebGPU surface (exclusively!) for Native/Desktop applications and available only together with WebGPU/WGPU backend - WGPU+SDL3: helper to create a WebGPU surface (exclusively!) for Native/Desktop applications and available only together with WebGPU/WGPU backend - WebGPU Helper functions and differentiation between the 4 compilation methods (via defines): Google DAWN (Native/Emscripten) / WGPU (Native/EMscripten) - example_glfw_wgpu: ImGui_ImplGLFW_CreateWGPUSurface_Helper (new helper function in imgui_impl_glfw backend), check status and error callback functions in imgui_impl_wgpu backend - example_sdl2_wgpu: ImGui_ImplSDL2_CreateWGPUSurface_Helper (new helper function in imgui_impl_sdl2 backend), check status and error callback functions in imgui_impl_wgpu backend - example_sdl3_wgpu: ImGui_ImplSDL3_CreateWGPUSurface_Helper (new helper function in imgui_impl_sdl3 backend), check status and error callback functions in imgui_impl_wgpu backend - Functions ImGui_ImplXXXX_CreateWGPUSurface_Helper were inserted into imgui_impl_xxxx (xxxx = GLFW / SDL2 / SDL3), and initialization has been integrated into every example: no more necessary, removed --- backends/imgui_impl_glfw.cpp | 91 ++++ backends/imgui_impl_glfw.h | 6 + backends/imgui_impl_sdl2.cpp | 86 +++ backends/imgui_impl_sdl2.h | 6 + backends/imgui_impl_sdl3.cpp | 85 +++ backends/imgui_impl_sdl3.h | 6 + backends/imgui_impl_wgpu.cpp | 149 +++++- backends/imgui_impl_wgpu.h | 24 + examples/example_glfw_wgpu/CMakeLists.txt | 192 +++++-- examples/example_glfw_wgpu/README.md | 36 +- examples/example_glfw_wgpu/main.cpp | 408 +++++++++----- examples/example_glfw_wgpu/web/index.html | 84 --- examples/example_sdl2_wgpu/CMakeLists.txt | 194 +++++++ .../example_sdl2_wgpu/Makefile.emscripten | 91 ++++ examples/example_sdl2_wgpu/README.md | 58 ++ examples/example_sdl2_wgpu/main.cpp | 492 +++++++++++++++++ examples/example_sdl3_wgpu/CMakeLists.txt | 214 ++++++++ .../example_sdl3_wgpu/Makefile.emscripten | 91 ++++ examples/example_sdl3_wgpu/README.md | 63 +++ examples/example_sdl3_wgpu/main.cpp | 499 ++++++++++++++++++ 20 files changed, 2585 insertions(+), 290 deletions(-) mode change 100644 => 100755 examples/example_glfw_wgpu/CMakeLists.txt delete mode 100644 examples/example_glfw_wgpu/web/index.html create mode 100755 examples/example_sdl2_wgpu/CMakeLists.txt create mode 100644 examples/example_sdl2_wgpu/Makefile.emscripten create mode 100644 examples/example_sdl2_wgpu/README.md create mode 100755 examples/example_sdl2_wgpu/main.cpp create mode 100755 examples/example_sdl3_wgpu/CMakeLists.txt create mode 100644 examples/example_sdl3_wgpu/Makefile.emscripten create mode 100644 examples/example_sdl3_wgpu/README.md create mode 100755 examples/example_sdl3_wgpu/main.cpp diff --git a/backends/imgui_impl_glfw.cpp b/backends/imgui_impl_glfw.cpp index cfe5dc6d3..fd7cabe27 100644 --- a/backends/imgui_impl_glfw.cpp +++ b/backends/imgui_impl_glfw.cpp @@ -1049,6 +1049,97 @@ void ImGui_ImplGlfw_InstallEmscriptenCallbacks(GLFWwindow* window, const char* c } #endif // #ifdef EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3 +// GLFW helper to create a WebGPU surface, used only in WGPU-Native, DAWN-Native already has a built-in function +// At current date (jun/2025) there is no "official" support in GLFW to create a surface for WebGPU backend +// This stub uses "low level" GLFW calls to acquire information from a specific Window Manager. +// Currently supported platforms: Windows / Linux (X11 and Wayland) / MacOS +// Not necessary/available with EMSCRIPTEN +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) +// GLFW native necessary to get information about current platform / Window Manager +#include +// MacOS specific: is necessary to compile with "-x objective-c++" flags +// (e.g. using cmake: set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_glfw.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") ) +#if defined(GLFW_EXPOSE_NATIVE_COCOA) +#include +#include +#endif + +WGPUSurface ImGui_ImplGLFW_CreateWGPUSurface_Helper(WGPUInstance instance, GLFWwindow* window) +{ + WGPUSurfaceDescriptor surfaceDescriptor = {}; + WGPUChainedStruct chainedStruct = {}; + + WGPUSurface surface = {}; + +#if defined(GLFW_EXPOSE_NATIVE_COCOA) + { + id metal_layer = NULL; + NSWindow *ns_window = glfwGetCocoaWindow(window); + [ns_window.contentView setWantsLayer:YES]; + metal_layer = [CAMetalLayer layer]; + [ns_window.contentView setLayer:metal_layer]; + + chainedStruct.sType = WGPUSType_SurfaceSourceMetalLayer; + + WGPUSurfaceSourceMetalLayer surfaceMetal = {}; + surfaceMetal.chain = chainedStruct; + surfaceMetal.layer = metal_layer; + + surfaceDescriptor.nextInChain = &surfaceMetal.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif defined(GLFW_EXPOSE_NATIVE_WAYLAND) && defined(GLFW_EXPOSE_NATIVE_X11) + if (glfwGetPlatform() == GLFW_PLATFORM_X11) { + Display *x11_display = glfwGetX11Display(); + Window x11_window = glfwGetX11Window(window); + + chainedStruct.sType = WGPUSType_SurfaceSourceXlibWindow; + + WGPUSurfaceSourceXlibWindow surfaceXlib = {}; + surfaceXlib.chain = chainedStruct; + surfaceXlib.display = x11_display; + surfaceXlib.window = x11_window; + + surfaceDescriptor.nextInChain = &surfaceXlib.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + struct wl_display *wayland_display = glfwGetWaylandDisplay(); + struct wl_surface *wayland_surface = glfwGetWaylandWindow(window); + + chainedStruct.sType = WGPUSType_SurfaceSourceWaylandSurface; + + WGPUSurfaceSourceWaylandSurface surfaceWayland = {}; + surfaceWayland.chain = chainedStruct; + surfaceWayland.display = wayland_display; + surfaceWayland.surface = wayland_surface; + + surfaceDescriptor.nextInChain = &surfaceWayland.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif defined(GLFW_EXPOSE_NATIVE_WIN32) + { + HWND hwnd = glfwGetWin32Window(window); + HINSTANCE hinstance = GetModuleHandle(NULL); + + chainedStruct.sType = WGPUSType_SurfaceSourceWindowsHWND; + + WGPUSurfaceSourceWindowsHWND surfaceHWND = {}; + surfaceHWND.chain = chainedStruct; + surfaceHWND.hinstance = hinstance; + surfaceHWND.hwnd = hwnd; + + surfaceDescriptor.nextInChain = &surfaceHWND.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif +#error "Unsupported GLFW/WebGPU native platform" +#endif + return surface; +} +#endif // defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) + + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_glfw.h b/backends/imgui_impl_glfw.h index 80e2b55ef..65f21d29c 100644 --- a/backends/imgui_impl_glfw.h +++ b/backends/imgui_impl_glfw.h @@ -66,5 +66,11 @@ IMGUI_IMPL_API void ImGui_ImplGlfw_Sleep(int milliseconds); IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window); IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor); +// GLFW helper to create a WebGPU surface for Native/Desktop applications: used only in WGPU-Native, DAWN-Native already has a built-in function +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) +#include +WGPUSurface ImGui_ImplGLFW_CreateWGPUSurface_Helper(WGPUInstance instance, GLFWwindow* window); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdl2.cpp b/backends/imgui_impl_sdl2.cpp index c2dae4053..48eab80d1 100644 --- a/backends/imgui_impl_sdl2.cpp +++ b/backends/imgui_impl_sdl2.cpp @@ -899,6 +899,92 @@ void ImGui_ImplSDL2_NewFrame() ImGui_ImplSDL2_UpdateGamepads(); } +// SDL2 helper to create a WebGPU surface (exclusively!) for Native/Desktop applications: available only together with WebGPU/WGPU backend +// At current date (jun/2025) there is no "official" support in SDL2 to create a surface for WebGPU backend +// This stub uses "low level" SDL2 calls to acquire information from a specific Window Manager. +// Currently supported platforms: Windows / Linux (X11 and Wayland) / MacOS +// Not necessary/available with EMSCRIPTEN +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) +#ifdef SDL_VIDEO_DRIVER_COCOA +// MacOS specific: is necessary to compile with "-x objective-c++" flags +// (e.g. using cmake: set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") ) +#include +#include +#endif + +WGPUSurface ImGui_ImplSDL2_CreateWGPUSurface_Helper(WGPUInstance instance, SDL_Window* window) +{ + WGPUSurfaceDescriptor surfaceDescriptor = {}; + WGPUChainedStruct chainedStruct = {}; + SDL_SysWMinfo sysWMInfo; + SDL_VERSION(&sysWMInfo.version); + SDL_GetWindowWMInfo(window, &sysWMInfo); + + WGPUSurface surface = {}; + +#if defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_X11) + const char *vidDrv = SDL_GetHint(SDL_HINT_VIDEODRIVER); + if(!vidDrv) return NULL; + + if(tolower(vidDrv[0])=='w' && tolower(vidDrv[1])=='a' && tolower(vidDrv[2])=='y' && + tolower(vidDrv[3])=='l' && tolower(vidDrv[4])=='a' && tolower(vidDrv[5])=='n' && tolower(vidDrv[6])=='d') { // wayland + + chainedStruct.sType = WGPUSType_SurfaceSourceWaylandSurface; + + WGPUSurfaceSourceWaylandSurface surfaceWayland = {}; + surfaceWayland.chain = chainedStruct; + surfaceWayland.display = sysWMInfo.info.wl.display; + surfaceWayland.surface = sysWMInfo.info.wl.surface; + + surfaceDescriptor.nextInChain = &surfaceWayland.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + + } else { // x11 + chainedStruct.sType = WGPUSType_SurfaceSourceXlibWindow; + + WGPUSurfaceSourceXlibWindow surfaceXlib = {}; + surfaceXlib.chain = chainedStruct; + surfaceXlib.display = sysWMInfo.info.x11.display; + surfaceXlib.window = sysWMInfo.info.x11.window; + + surfaceDescriptor.nextInChain = &surfaceXlib.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif defined(SDL_VIDEO_DRIVER_WINDOWS) + { + chainedStruct.sType = WGPUSType_SurfaceSourceWindowsHWND; + + WGPUSurfaceSourceWindowsHWND surfaceHWND = {}; + surfaceHWND.chain = chainedStruct; + surfaceHWND.hinstance = sysWMInfo.info.win.hinstance; + surfaceHWND.hwnd = sysWMInfo.info.win.window; + + surfaceDescriptor.nextInChain = &surfaceHWND.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif defined(SDL_VIDEO_DRIVER_COCOA) + { + id metal_layer = [CAMetalLayer layer]; + NSWindow *ns_window = sysWMInfo.info.cocoa.window; + [ns_window.contentView setWantsLayer:YES]; + [ns_window.contentView setLayer:metal_layer]; + + chainedStruct.sType = WGPUSType_SurfaceSourceMetalLayer; + + WGPUSurfaceSourceMetalLayer surfaceMetal = {}; + surfaceMetal.chain = chainedStruct; + surfaceMetal.layer = metal_layer; + + surfaceDescriptor.nextInChain = &surfaceMetal.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#else + #error "Unsupported SDL2/WebGPU Backend" +#endif + return surface; +} +#endif //defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_sdl2.h b/backends/imgui_impl_sdl2.h index 3c0a4a7e4..fe7806a84 100644 --- a/backends/imgui_impl_sdl2.h +++ b/backends/imgui_impl_sdl2.h @@ -47,4 +47,10 @@ IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_ind enum ImGui_ImplSDL2_GamepadMode { ImGui_ImplSDL2_GamepadMode_AutoFirst, ImGui_ImplSDL2_GamepadMode_AutoAll, ImGui_ImplSDL2_GamepadMode_Manual }; IMGUI_IMPL_API void ImGui_ImplSDL2_SetGamepadMode(ImGui_ImplSDL2_GamepadMode mode, struct _SDL_GameController** manual_gamepads_array = nullptr, int manual_gamepads_count = -1); +// SDL2 helper to create a WebGPU surface (exclusively!) for Native/Desktop applications: available only together with WebGPU/WGPU backend +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) +#include +WGPUSurface ImGui_ImplSDL2_CreateWGPUSurface_Helper(WGPUInstance instance, SDL_Window* window); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_sdl3.cpp b/backends/imgui_impl_sdl3.cpp index 564131a4d..28b1df81a 100644 --- a/backends/imgui_impl_sdl3.cpp +++ b/backends/imgui_impl_sdl3.cpp @@ -839,6 +839,91 @@ void ImGui_ImplSDL3_NewFrame() ImGui_ImplSDL3_UpdateGamepads(); } +// SDL3 helper to create a WebGPU surface (exclusively!) for Native/Desktop applications: available only together with WebGPU/WGPU backend +// At current date (jun/2025) there is no "official" support in SDL3 to create a surface for WebGPU backend +// This stub uses "low level" SDL3 calls to acquire information from a specific Window Manager. +// Currently supported platforms: Windows / Linux (X11 and Wayland) / MacOS +// Not necessary/available with EMSCRIPTEN +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) +#if defined(SDL_PLATFORM_MACOS) +// MacOS specific: is necessary to compile with "-x objective-c++" flags +// (e.g. using cmake: set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") ) +# include +# include +#elif defined(SDL_PLATFORM_WIN32) +# include +#endif + +WGPUSurface ImGui_ImplSDL3_CreateWGPUSurface_Helper(WGPUInstance instance, SDL_Window* window) { + SDL_PropertiesID propertiesID = SDL_GetWindowProperties(window); + WGPUSurfaceDescriptor surfaceDescriptor = {}; + + WGPUSurface surface = {}; + +#if defined(SDL_PLATFORM_MACOS) + { + id metal_layer = NULL; + NSWindow *ns_window = (__bridge NSWindow *)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + if (!ns_window) return NULL; + [ns_window.contentView setWantsLayer : YES]; + metal_layer = [CAMetalLayer layer]; + [ns_window.contentView setLayer : metal_layer]; + + WGPUSurfaceSourceMetalLayer surfaceMetal = {}; + surfaceMetal.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + surfaceMetal.layer = metal_layer; + + surfaceDescriptor.nextInChain = &surfaceMetal.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#elif defined(SDL_PLATFORM_LINUX) + if (!SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland")) { + void *w_display = SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + void *w_surface = SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + if (!w_display || !w_surface) return NULL; + + WGPUSurfaceSourceWaylandSurface surfaceWayland = {}; + surfaceWayland.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + surfaceWayland.display = w_display; + surfaceWayland.surface = w_surface; + + surfaceDescriptor.nextInChain = &surfaceWayland.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } else if (!SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11")) { + void *x_display = SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + uint64_t x_window = SDL_GetNumberProperty(propertiesID, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + if (!x_display || !x_window) return NULL; + + WGPUSurfaceSourceXlibWindow surfaceXlib = {}; + surfaceXlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + surfaceXlib.display = x_display; + surfaceXlib.window = x_window; + + surfaceDescriptor.nextInChain = &surfaceXlib.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } + +#elif defined(SDL_PLATFORM_WIN32) + { + HWND hwnd = (HWND)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + if (!hwnd) return NULL; + HINSTANCE hinstance = GetModuleHandle(NULL); + + WGPUSurfaceSourceWindowsHWND surfaceHWND = {}; + surfaceHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + surfaceHWND.hinstance = hinstance; + surfaceHWND.hwnd = hwnd; + + surfaceDescriptor.nextInChain = &surfaceHWND.chain; + surface = wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#else + #error "Unsupported SDL3/WebGPU Backend" +#endif + return surface; +} +#endif // defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) + //----------------------------------------------------------------------------- #if defined(__clang__) diff --git a/backends/imgui_impl_sdl3.h b/backends/imgui_impl_sdl3.h index a822a259b..d515077f1 100644 --- a/backends/imgui_impl_sdl3.h +++ b/backends/imgui_impl_sdl3.h @@ -44,4 +44,10 @@ IMGUI_IMPL_API bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event); enum ImGui_ImplSDL3_GamepadMode { ImGui_ImplSDL3_GamepadMode_AutoFirst, ImGui_ImplSDL3_GamepadMode_AutoAll, ImGui_ImplSDL3_GamepadMode_Manual }; IMGUI_IMPL_API void ImGui_ImplSDL3_SetGamepadMode(ImGui_ImplSDL3_GamepadMode mode, SDL_Gamepad** manual_gamepads_array = nullptr, int manual_gamepads_count = -1); +// SDL3 helper to create a WebGPU surface for Native/Desktop applications: available only with WebGPU/WGPU backend +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) +#include +WGPUSurface ImGui_ImplSDL3_CreateWGPUSurface_Helper(WGPUInstance instance, SDL_Window* window); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index 1f997a10a..5d3d0a7bf 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -52,10 +52,12 @@ #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! - #endif +#endif + +// This condition is TRUE: when it's built with EMSCRIPTEN using -sUSE_WEBGPU=1 flag (deprecated from 4.0.10) +// This condition is FALSE for all other 3 cases: WGPU-Native, DAWN-Native or DAWN-EMSCRIPTEN (using --use-port=emdawnwebgpu flag) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && defined(__EMSCRIPTEN__) + #define IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN #endif #ifndef IMGUI_DISABLE @@ -63,6 +65,9 @@ #include #include +#include +#include + #ifdef IMGUI_IMPL_WEBGPU_BACKEND_DAWN // Dawn renamed WGPUProgrammableStageDescriptor to WGPUComputeState (see: https://github.com/webgpu-native/webgpu-headers/pull/413) // Using type alias until WGPU adopts the same naming convention (#8369) @@ -259,7 +264,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const c { 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_WGPU_EMSCRIPTEN) WGPUShaderSourceWGSL wgsl_desc = {}; wgsl_desc.chain.sType = WGPUSType_ShaderSourceWGSL; wgsl_desc.code = { wgsl_source, WGPU_STRLEN }; @@ -275,7 +280,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const c WGPUProgrammableStageDescriptor stage_desc = {}; stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) stage_desc.entryPoint = { "main", WGPU_STRLEN }; #else stage_desc.entryPoint = "main"; @@ -399,7 +404,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder { nullptr, "Dear ImGui Vertex buffer", -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPU_STRLEN, #endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Vertex, @@ -426,7 +431,7 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder { nullptr, "Dear ImGui Index buffer", -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPU_STRLEN, #endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Index, @@ -561,7 +566,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) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) tex_desc.label = { "Dear ImGui Texture", WGPU_STRLEN }; #else tex_desc.label = "Dear ImGui Texture"; @@ -606,7 +611,7 @@ void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex) // Update full texture or selected blocks. We only ever write to textures regions which have never been used before! // This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions. -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPUTexelCopyTextureInfo dst_view = {}; #else WGPUImageCopyTexture dst_view = {}; @@ -615,7 +620,7 @@ void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex) dst_view.mipLevel = 0; dst_view.origin = { (uint32_t)upload_x, (uint32_t)upload_y, 0 }; dst_view.aspect = WGPUTextureAspect_All; -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPUTexelCopyBufferLayout layout = {}; #else WGPUTextureDataLayout layout = {}; @@ -638,7 +643,7 @@ static void ImGui_ImplWGPU_CreateUniformBuffer() { nullptr, "Dear ImGui Uniform buffer", -#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) WGPU_STRLEN, #endif WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform, @@ -752,7 +757,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) +#if !defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN) depth_stencil_state.depthWriteEnabled = WGPUOptionalBool_False; #else depth_stencil_state.depthWriteEnabled = false; @@ -834,14 +839,18 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info) // Setup backend capabilities flags ImGui_ImplWGPU_Data* bd = IM_NEW(ImGui_ImplWGPU_Data)(); io.BackendRendererUserData = (void*)bd; -#if defined(__EMSCRIPTEN__) - io.BackendRendererName = "imgui_impl_webgpu_emscripten"; -#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) - io.BackendRendererName = "imgui_impl_webgpu_dawn"; +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + #if defined(__EMSCRIPTEN__) + io.BackendRendererName = "imgui_impl_webgpu_dawn_emscripten"; // compiled & linked using EMSCRIPTEN with "--use-port=emdawnwebgpu" flag + #else + io.BackendRendererName = "imgui_impl_webgpu_dawn"; // DAWN-Native + #endif #elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) - io.BackendRendererName = "imgui_impl_webgpu_wgpu"; -#else - io.BackendRendererName = "imgui_impl_webgpu"; + #if defined(__EMSCRIPTEN__) + io.BackendRendererName = "imgui_impl_webgpu_wgpu_emscripten"; // linked using EMSCRIPTEN with "-sUSE_WEBGPU=1" flag, deprecated from EMSCRIPTEN 4.0.10 + #else + io.BackendRendererName = "imgui_impl_webgpu_wgpu"; // WGPU-Native + #endif #endif io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render. @@ -906,6 +915,106 @@ void ImGui_ImplWGPU_NewFrame() IM_ASSERT(0 && "ImGui_ImplWGPU_CreateDeviceObjects() failed!"); } +// WebGPU Helpers + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +// DAWN Validation Layer callback: reason for device loss +void ImGui_ImplWGPU_DAWN_DeviceLostCallback_Helper(const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message) +{ + const char* reasonName = ""; + switch (reason) { + case wgpu::DeviceLostReason::Unknown: reasonName = "Unknown"; break; + case wgpu::DeviceLostReason::Destroyed: reasonName = "Destroyed"; break; + case wgpu::DeviceLostReason::CallbackCancelled: reasonName = "InstanceDropped"; break; + case wgpu::DeviceLostReason::FailedCreation: reasonName = "FailedCreation"; break; + default: reasonName = "UNREACHABLE"; break; + } + fprintf(stderr, "%s device message: %s\n", reasonName, message.data); +} +// DAWN Validation Layer callback: print error type +void ImGui_ImplWGPU_DAWN_ErrorCallback_Helper(const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message) +{ + const char* errorTypeName = ""; + switch (type) { + case wgpu::ErrorType::Validation: errorTypeName = "Validation"; break; + case wgpu::ErrorType::OutOfMemory: errorTypeName = "Out of memory"; break; + case wgpu::ErrorType::Unknown: errorTypeName = "Unknown"; break; + case wgpu::ErrorType::Internal: errorTypeName = "Internal"; break; + default: errorTypeName = "UNREACHABLE"; break; + } + fprintf(stderr, "%s error: %s\n", errorTypeName, message.data); +} +#elif !defined(__EMSCRIPTEN__) +// WGPU-Native LOG callback: print information based on request level +void ImGui_ImplWGPU_WGPU_LogCallback_Helper(WGPULogLevel level, WGPUStringView message, void *userdata) +{ + const char *level_str = ""; + switch (level) { + case WGPULogLevel_Error: level_str = "error"; break; + case WGPULogLevel_Warn: level_str = "warn"; break; + case WGPULogLevel_Info: level_str = "info"; break; + case WGPULogLevel_Debug: level_str = "debug"; break; + case WGPULogLevel_Trace: level_str = "trace"; break; + default: level_str = "unknown_level"; + } + fprintf(stderr, "[wgpu] [%s] %.*s\n", level_str, (int) message.length, message.data); +} +#endif + +/// Print Adapter info +///@param[in] adapter const WGPUAdapter & : reference to acquired and valid WGPUAdapter +///@note The function prints: "selected Adapter - drivers version, BackendType (#)" +void ImGui_ImplWGPU_PrintAdapterInfo_Helper(const WGPUAdapter &adapter) +{ + WGPUAdapterInfo info = {}; + wgpuAdapterGetInfo(adapter, &info); +#ifdef __EMSCRIPTEN__ + printf("BackendType (%u)\n", info.backendType); +#else + printf("Using: %.*s - %.*s, BackendType (%u)\n", (int) info.device.length, info.device.data, (int) info.description.length, info.description.data, info.backendType); +#endif +} + +/// Check if the Status of SurfaceTexture is Optimal +///@param[in] status WGPUSurfaceGetCurrentTextureStatus : current WGPUSurfaceTexture .status (value to check) +///@return true (bool) : SurfaceTexture have an optimal status and we can use it +///@return false (bool) : it's necessary to re-configure the SurfaceTexture +///@note : with "unrecoverable error" the program aborts +bool ImGui_ImplWGPU_CheckSurfaceTextureOptimalStatus_Helper(WGPUSurfaceGetCurrentTextureStatus status) +{ + switch ( status ) + { +#if defined(__EMSCRIPTEN__) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + case WGPUSurfaceGetCurrentTextureStatus_Success: + return true; +#else + case WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal: + return true; + case WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal: +#endif + case WGPUSurfaceGetCurrentTextureStatus_Timeout: + case WGPUSurfaceGetCurrentTextureStatus_Outdated: + case WGPUSurfaceGetCurrentTextureStatus_Lost: + // if the status is NOT Optimal it's necessary try to reconfigure the surface + return false; + // Unrecoverable errors +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + case WGPUSurfaceGetCurrentTextureStatus_Error: +#else // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + case WGPUSurfaceGetCurrentTextureStatus_OutOfMemory: + case WGPUSurfaceGetCurrentTextureStatus_DeviceLost: +#endif + case WGPUSurfaceGetCurrentTextureStatus_Force32: + // Fatal error + fprintf(stderr, "Unrecoverable Error Check Surface Texture status=%#.8x\n", status); + abort(); + + default: // should never be reached + fprintf(stderr, "Unexpected Error Check Surface Texture status=%#.8x\n", status); + abort(); + } +} + //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 61d2d23c0..51be03f96 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -29,6 +29,14 @@ #include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +// DAWN "wgpu::" classes (e.g. used in Validation Layers Callbacks) +#include +#elif !defined(__EMSCRIPTEN__) +// WGPU-Native specific data structure (e.g. used in WGPULogLevel) +#include +#endif + // Initialization data, for ImGui_ImplWGPU_Init() struct ImGui_ImplWGPU_InitInfo { @@ -68,4 +76,20 @@ struct ImGui_ImplWGPU_RenderState WGPURenderPassEncoder RenderPassEncoder; }; +// WebGPU Helpers + +// Check if the Status of SurfaceTexture is Optimal +bool ImGui_ImplWGPU_CheckSurfaceTextureOptimalStatus_Helper(WGPUSurfaceGetCurrentTextureStatus status); +// Print Adapter info +void ImGui_ImplWGPU_PrintAdapterInfo_Helper(const WGPUAdapter &adapter); +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) // DAWN both Native / EMSCRIPTEN +// DAWN Validation Layer callback: reason for device loss +void ImGui_ImplWGPU_DAWN_DeviceLostCallback_Helper(const wgpu::Device&, wgpu::DeviceLostReason reason, wgpu::StringView message); +// DAWN Validation Layer callback: print error type +void ImGui_ImplWGPU_DAWN_ErrorCallback_Helper(const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message); +#elif !defined(__EMSCRIPTEN__) // WGPU-Native +// WGPU-Native LOG callback: print information based on request level +void ImGui_ImplWGPU_WGPU_LogCallback_Helper(WGPULogLevel level, WGPUStringView message, void *userdata); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/examples/example_glfw_wgpu/CMakeLists.txt b/examples/example_glfw_wgpu/CMakeLists.txt old mode 100644 new mode 100755 index 8e164e488..f7c5d65d2 --- a/examples/example_glfw_wgpu/CMakeLists.txt +++ b/examples/example_glfw_wgpu/CMakeLists.txt @@ -6,6 +6,16 @@ # * build/Debug/example_glfw_wgpu[.exe] # * build/example_glfw_wgpu[.exe] +# Building for desktop (WGPU-Native) with WGPU-Native: +# 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases +# 2. unzip the downloaded file in your_preferred_folder +# 3. move into your_preferred_folder (e.g. typing: `cd your_preferred_folder`) +# 4. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 5. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/Debug/example_glfw_wgpu[.exe] +# * build/example_glfw_wgpu[.exe] + # Building for Emscripten: # 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html # 2. Install Ninja build system @@ -16,6 +26,8 @@ cmake_minimum_required(VERSION 3.10.2) project(imgui_example_glfw_wgpu C CXX) +set(IMGUI_EXECUTABLE example_glfw_wgpu) + if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() @@ -25,93 +37,165 @@ set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 # Dear ImGui set(IMGUI_DIR ../../) +# ImGui example commons source files +set(IMGUI_EXAMPLE_SOURCE_FILES + main.cpp + # backend files + ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp + ${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp + # Dear ImGui files + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_demo.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp) + # Libraries if(EMSCRIPTEN) + if(EMSCRIPTEN_VERSION VERSION_GREATER_EQUAL "4.0.10") + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "--use-port=emdawnwebgpu" CACHE STRING "Choose between --use-port=emdawnwebgpu (Dawn implementation of EMSCRIPTEN) and -sUSE_WEBGPU=1 (WGPU implementation of EMSCRIPTEN, deprecated in 4.0.10): default to --use-port=emdawnwebgpu for EMSCRIPTEN >= 4.0.10") + else() + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "-sUSE_WEBGPU=1" CACHE STRING "Use -sUSE_WEBGPU=1 for EMSCRIPTEN WGPU implementation" FORCE) + endif() + if(EMSCRIPTEN_VERSION VERSION_GREATER_EQUAL "3.1.57") set(IMGUI_EMSCRIPTEN_GLFW3 "--use-port=contrib.glfw3" CACHE STRING "Choose between --use-port=contrib.glfw3 and -sUSE_GLFW=3 for GLFW implementation (default to --use-port=contrib.glfw3)") else() # cannot use contrib.glfw3 prior to 3.1.57 set(IMGUI_EMSCRIPTEN_GLFW3 "-sUSE_GLFW=3" CACHE STRING "Use -sUSE_GLFW=3 for GLFW implementation" FORCE) endif() + set(LIBRARIES glfw) add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1) else() - # Dawn wgpu desktop - set(DAWN_FETCH_DEPENDENCIES ON) - set(IMGUI_DAWN_DIR CACHE PATH "Path to Dawn repository") - if (NOT IMGUI_DAWN_DIR) - message(FATAL_ERROR "Please specify the Dawn repository by setting IMGUI_DAWN_DIR") + + # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + if (NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") endif() - option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) - - # Dawn builds many things by default - disable things we don't need - option(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" OFF) - option(TINT_BUILD_CMD_TOOLS "Build the Tint command line tools" OFF) - option(TINT_BUILD_DOCS "Build documentation" OFF) - option(TINT_BUILD_TESTS "Build tests" OFF) - if (NOT APPLE) - option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" OFF) - endif() - if(WIN32) - option(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" OFF) - option(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) - option(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" OFF) - option(TINT_BUILD_GLSL_VALIDATOR "Build the GLSL output validator" OFF) - option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" OFF) - option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON) + # IMGUI_DAWN_DIR and IMGUI_WGPU_DIR both cannot be set + if (IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify only one of Dawn / WGPU base directory") endif() - add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn" EXCLUDE_FROM_ALL) + # Native DAWN build settings + if(IMGUI_DAWN_DIR) + # Dawn wgpu desktop + set(DAWN_FETCH_DEPENDENCIES ON) + set(IMGUI_DAWN_DIR CACHE PATH "Path to Dawn repository") + if (NOT IMGUI_DAWN_DIR) + message(FATAL_ERROR "Please specify the Dawn repository by setting IMGUI_DAWN_DIR") + endif() - set(LIBRARIES webgpu_dawn webgpu_cpp webgpu_glfw glfw) + option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) + + # Dawn builds many things by default - disable things we don't need + option(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" OFF) + option(TINT_BUILD_CMD_TOOLS "Build the Tint command line tools" OFF) + option(TINT_BUILD_DOCS "Build documentation" OFF) + option(TINT_BUILD_TESTS "Build tests" OFF) + if (NOT APPLE) + option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" OFF) + endif() + if(WIN32) + option(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" OFF) + option(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) + option(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" OFF) + option(TINT_BUILD_GLSL_VALIDATOR "Build the GLSL output validator" OFF) + option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" OFF) + option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON) + endif() + # check if WAYLAND is the current Session Type and enable DAWN_USE_WAYLAND Wayland option @compile time + # You can override this using: cmake -DDAWN_USE_WAYLAND=X (X = ON | OFF) + if(LINUX) + if ($ENV{XDG_SESSION_TYPE} MATCHES wayland) + option(DAWN_USE_WAYLAND "Enable support for Wayland surface" ON) + endif() + endif() + + add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn" EXCLUDE_FROM_ALL) + + set(LIBRARIES webgpu_dawn webgpu_cpp webgpu_glfw glfw) + else() + # Native WGPU build settings + find_package(glfw3 CONFIG) + + set(WGPU_NATIVE_LIB_DIR ${IMGUI_WGPU_DIR}/lib) + find_library(WGPU_LIBRARY NAMES libwgpu_native.a wgpu_native.lib wgpu_native + HINTS ${WGPU_NATIVE_LIB_DIR} REQUIRED) + if (WIN32) + add_definitions(-DGLFW_EXPOSE_NATIVE_WIN32) + set(OS_LIBRARIES d3dcompiler ws2_32 userenv bcrypt ntdll opengl32 Propsys RuntimeObject) + elseif(UNIX AND NOT APPLE) + add_definitions(-DGLFW_EXPOSE_NATIVE_X11) + add_definitions(-DGLFW_EXPOSE_NATIVE_WAYLAND) + set(OS_LIBRARIES "-lm -ldl") + elseif(APPLE) + add_definitions(-DGLFW_EXPOSE_NATIVE_COCOA) + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_glfw.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + set(LIBRARIES glfw ${WGPU_LIBRARY} ${OS_LIBRARIES}) + endif() endif() -add_executable(example_glfw_wgpu - main.cpp - # backend files - ${IMGUI_DIR}/backends/imgui_impl_glfw.cpp - ${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp - # Dear ImGui files - ${IMGUI_DIR}/imgui.cpp - ${IMGUI_DIR}/imgui_draw.cpp - ${IMGUI_DIR}/imgui_demo.cpp - ${IMGUI_DIR}/imgui_tables.cpp - ${IMGUI_DIR}/imgui_widgets.cpp -) -IF(NOT EMSCRIPTEN) - target_compile_definitions(example_glfw_wgpu PUBLIC - "IMGUI_IMPL_WEBGPU_BACKEND_DAWN" - ) -endif() -target_include_directories(example_glfw_wgpu PUBLIC +add_executable(${IMGUI_EXECUTABLE} ${IMGUI_EXAMPLE_SOURCE_FILES}) + +target_include_directories(${IMGUI_EXECUTABLE} PUBLIC ${IMGUI_DIR} ${IMGUI_DIR}/backends ) -target_link_libraries(example_glfw_wgpu PUBLIC ${LIBRARIES}) +target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_EXAMPLE_GLFW_WGPU") +# In this example IMGUI_IMPL_WEBGPU_BACKEND_DAWN / IMGUI_IMPL_WEBGPU_BACKEND_WGPU internal define is set according to: +# EMSCRIPTEN: by used FLAG +# --use-port=emdawnwebgpu --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN defined +# -sUSE_WEBGPU=1 --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU defined +# NATIVE: by used SDK installation directory +# if IMGUI_DAWN_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN defined +# if IMGUI_WGPU_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU defined + +# Native settings +if(NOT EMSCRIPTEN) + if(IMGUI_DAWN_DIR) + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + target_include_directories(${IMGUI_EXECUTABLE} PUBLIC ${IMGUI_WGPU_DIR}/include) + endif() + + target_link_libraries(${IMGUI_EXECUTABLE} PUBLIC ${LIBRARIES}) # Emscripten settings -if(EMSCRIPTEN) +else() + set(CMAKE_EXECUTABLE_SUFFIX ".html") + if("${IMGUI_EMSCRIPTEN_GLFW3}" STREQUAL "--use-port=contrib.glfw3") - target_compile_options(example_glfw_wgpu PUBLIC - "${IMGUI_EMSCRIPTEN_GLFW3}" - ) + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "${IMGUI_EMSCRIPTEN_GLFW3}") endif() message(STATUS "Using ${IMGUI_EMSCRIPTEN_GLFW3} GLFW implementation") - target_link_options(example_glfw_wgpu PRIVATE - "-sUSE_WEBGPU=1" + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" STREQUAL "--use-port=emdawnwebgpu") + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}") + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + endif() + message(STATUS "Using ${IMGUI_EMSCRIPTEN_WEBGPU_FLAG} WebGPU implementation") + + target_link_options(${IMGUI_EXECUTABLE} PRIVATE + "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" "${IMGUI_EMSCRIPTEN_GLFW3}" "-sWASM=1" + "-sASYNCIFY=1" "-sALLOW_MEMORY_GROWTH=1" "-sNO_EXIT_RUNTIME=0" "-sASSERTIONS=1" "-sDISABLE_EXCEPTION_CATCHING=1" "-sNO_FILESYSTEM=1" + "--shell-file=${CMAKE_CURRENT_LIST_DIR}/../libs/emscripten/shell_minimal.html" ) - set_target_properties(example_glfw_wgpu PROPERTIES OUTPUT_NAME "index") - # copy our custom index.html to build directory - add_custom_command(TARGET example_glfw_wgpu POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_LIST_DIR}/web/index.html" $ - ) + set_target_properties(${IMGUI_EXECUTABLE} PROPERTIES OUTPUT_NAME "index") endif() diff --git a/examples/example_glfw_wgpu/README.md b/examples/example_glfw_wgpu/README.md index 399d431ff..1354fe77f 100644 --- a/examples/example_glfw_wgpu/README.md +++ b/examples/example_glfw_wgpu/README.md @@ -1,4 +1,38 @@ -## How to Build +## How to Build + +### Using CMake +#### Building for desktop (WebGPU-native) with Google Dawn: + 1. `git clone https://github.com/google/dawn dawn` + 2. `cmake -B build -DIMGUI_DAWN_DIR=dawn` + 3. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for desktop (WebGPU-Native) with WGPU: + 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases + 2. unzip the downloaded file in `your_preferred_folder` + 3. move into `your_preferred_folder` (e.g. typing: `cd your_preferred_folder`) + 4. `cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder` ("full path" or "relative" starting from current directory) + 5. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for Emscripten: + 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html + 2. Install Ninja build system + 3. `emcmake cmake -G Ninja -B build` + 4. `cmake --build build` + +To run: + - `emrun build/index.html` + +or + - `python -m http.server` then open WGPU browser with url: `http://localhost:8000/build` + + +### Using makefile - 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 diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 670dd08c2..0c1570958 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -12,54 +12,55 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" #include - -#ifdef __EMSCRIPTEN__ -#include -#include -#include -#else -#include -#endif +#include #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" + #include + #include + #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + #include + #endif + #include + #include + #include "../libs/emscripten/emscripten_mainloop_stub.h" +#else + #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + #include + #endif #endif // Global WebGPU required states -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 int wgpu_swap_chain_width = 1280; -static int wgpu_swap_chain_height = 800; +static WGPUInstance wgpu_instance = nullptr; +static WGPUDevice wgpu_device = nullptr; +static WGPUSurface wgpu_surface = nullptr; +static WGPUQueue wgpu_queue = nullptr; +static WGPUSurfaceConfiguration wgpu_surface_configuration {}; +static int wgpu_surface_width = 1280; +static int wgpu_surface_height = 800; // Forward declarations -static bool InitWGPU(GLFWwindow* window); -static void CreateSwapChain(int width, int height); +static bool InitWGPU(void* window); 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*) +static void ResizeSurface(int width, int height) { - 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); + wgpu_surface_configuration.width = wgpu_surface_width = width; + wgpu_surface_configuration.height = wgpu_surface_height = height; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); +} + +static void ReleaseTextureAndConfigureSurface(WGPUTexture &texture, int fb_width, int fb_height) +{ + if (texture) + wgpuTextureRelease(texture); + if ( fb_width > 0 && fb_height > 0 ) + ResizeSurface(fb_width, fb_height); } // Main code @@ -67,24 +68,23 @@ int main(int, char**) { glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) - return 1; + return EXIT_FAILURE; // 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); + GLFWwindow* window = glfwCreateWindow(wgpu_surface_width, wgpu_surface_height, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr); if (window == nullptr) - return 1; + return EXIT_FAILURE; // Initialize the WebGPU environment if (!InitWGPU(window)) { - if (window) - glfwDestroyWindow(window); + glfwDestroyWindow(window); glfwTerminate(); - return 1; + return EXIT_FAILURE; } - CreateSwapChain(wgpu_swap_chain_width, wgpu_swap_chain_height); + glfwShowWindow(window); // Setup Dear ImGui context @@ -106,7 +106,7 @@ int main(int, char**) ImGui_ImplWGPU_InitInfo init_info; init_info.Device = wgpu_device; init_info.NumFramesInFlight = 3; - init_info.RenderTargetFormat = wgpu_preferred_fmt; + init_info.RenderTargetFormat = wgpu_surface_configuration.format; init_info.DepthStencilFormat = WGPUTextureFormat_Undefined; ImGui_ImplWGPU_Init(&init_info); @@ -114,19 +114,19 @@ int main(int, char**) // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. // - Read 'docs/FONTS.md' for more instructions and details. If you like the default font but want it to scale better, consider using the 'ProggyVector' from the same author! // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details. //io.Fonts->AddFontDefault(); - //style.FontSizeBase = 20.0f; #ifndef IMGUI_DISABLE_FILE_FUNCTIONS - //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf"); - //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf"); - //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf"); - //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf"); - //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf"); - //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf"); + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); //IM_ASSERT(font != nullptr); #endif @@ -160,13 +160,22 @@ int main(int, char**) // 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) + if (width != wgpu_surface_width || height != wgpu_surface_height) { ImGui_ImplWGPU_InvalidateDeviceObjects(); - CreateSwapChain(width, height); + ResizeSurface(width, height); ImGui_ImplWGPU_CreateDeviceObjects(); } + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surfaceTexture); + + // Check SurfaceTexture status, if NOT optimal status we try to re-configure Surface + if (!ImGui_ImplWGPU_CheckSurfaceTextureOptimalStatus_Helper(surfaceTexture.status)) { + ReleaseTextureAndConfigureSurface(surfaceTexture.texture, width, height); + continue; + } + // Start the Dear ImGui frame ImGui_ImplWGPU_NewFrame(); ImGui_ImplGlfw_NewFrame(); @@ -212,21 +221,26 @@ int main(int, char**) // Rendering ImGui::Render(); -#ifndef __EMSCRIPTEN__ - // Tick needs to be called in Dawn to display validation errors - wgpuDeviceTick(wgpu_device); -#endif + WGPUTextureViewDescriptor viewDescriptor {}; + viewDescriptor.format = wgpu_surface_configuration.format; + viewDescriptor.dimension = WGPUTextureViewDimension_2D ; + viewDescriptor.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + viewDescriptor.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + viewDescriptor.aspect = WGPUTextureAspect_All; - WGPURenderPassColorAttachment color_attachments = {}; + WGPUTextureView textureView = wgpuTextureCreateView(surfaceTexture.texture, &viewDescriptor); + + + WGPURenderPassColorAttachment color_attachments {}; color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; - color_attachments.loadOp = WGPULoadOp_Clear; - color_attachments.storeOp = WGPUStoreOp_Store; + 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); + color_attachments.view = textureView; - WGPURenderPassDescriptor render_pass_desc = {}; - render_pass_desc.colorAttachmentCount = 1; - render_pass_desc.colorAttachments = &color_attachments; + WGPURenderPassDescriptor render_pass_desc {}; + render_pass_desc.colorAttachmentCount = 1; + render_pass_desc.colorAttachments = &color_attachments; render_pass_desc.depthStencilAttachment = nullptr; WGPUCommandEncoderDescriptor enc_desc = {}; @@ -236,16 +250,18 @@ int main(int, char**) ImGui_ImplWGPU_RenderDrawData(ImGui::GetDrawData(), pass); wgpuRenderPassEncoderEnd(pass); - WGPUCommandBufferDescriptor cmd_buffer_desc = {}; + WGPUCommandBufferDescriptor cmd_buffer_desc {}; WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); - WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device); - wgpuQueueSubmit(queue, 1, &cmd_buffer); + wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); #ifndef __EMSCRIPTEN__ - wgpuSwapChainPresent(wgpu_swap_chain); + wgpuSurfacePresent(wgpu_surface); + // Tick needs to be called in Dawn to display validation errors +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpuDeviceTick(wgpu_device); #endif - - wgpuTextureViewRelease(color_attachments.view); +#endif + wgpuTextureViewRelease(textureView); wgpuRenderPassEncoderRelease(pass); wgpuCommandEncoderRelease(encoder); wgpuCommandBufferRelease(cmd_buffer); @@ -259,92 +275,222 @@ int main(int, char**) ImGui_ImplGlfw_Shutdown(); ImGui::DestroyContext(); + wgpuSurfaceUnconfigure(wgpu_surface); + wgpuSurfaceRelease(wgpu_surface); + wgpuQueueRelease(wgpu_queue); + wgpuDeviceRelease(wgpu_device); + wgpuInstanceRelease(wgpu_instance); + glfwDestroyWindow(window); glfwTerminate(); - return 0; + return EXIT_SUCCESS; } -#ifndef __EMSCRIPTEN__ -static WGPUAdapter RequestAdapter(WGPUInstance instance) -{ - auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message, void* pUserData) - { - if (status == WGPURequestAdapterStatus_Success) - *(WGPUAdapter*)(pUserData) = adapter; - else - printf("Could not get WebGPU adapter: %s\n", message); -}; - WGPUAdapter adapter; - wgpuInstanceRequestAdapter(instance, nullptr, onAdapterRequestEnded, (void*)&adapter); - return adapter; -} -static WGPUDevice RequestDevice(WGPUAdapter& adapter) +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter GetAdapter(wgpu::Instance &instance) { - auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* pUserData) - { - if (status == WGPURequestDeviceStatus_Success) - *(WGPUDevice*)(pUserData) = device; - else - printf("Could not get WebGPU device: %s\n", message); + wgpu::Adapter acquiredAdapter; + wgpu::RequestAdapterOptions adapterOptions; + + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) { + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquiredAdapter = std::move(adapter); }; - WGPUDevice device; - wgpuAdapterRequestDevice(adapter, nullptr, onDeviceRequestEnded, (void*)&device); - return device; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapterOptions, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + assert(acquiredAdapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(acquiredAdapter.Get()); +#endif + return acquiredAdapter.MoveToCHandle(); } -#endif -static bool InitWGPU(GLFWwindow* window) +static WGPUDevice GetDevice(wgpu::Instance &instance, wgpu::Adapter &adapter) { - wgpu::Instance instance = wgpuCreateInstance(nullptr); + // Set device callback functions + wgpu::DeviceDescriptor deviceDesc; + deviceDesc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, ImGui_ImplWGPU_DAWN_DeviceLostCallback_Helper); + deviceDesc.SetUncapturedErrorCallback(ImGui_ImplWGPU_DAWN_ErrorCallback_Helper); + wgpu::Device acquiredDevice; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device localDevice, wgpu::StringView message) { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquiredDevice = std::move(localDevice); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&deviceDesc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + assert(acquiredDevice != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquiredDevice.MoveToCHandle(); +} +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #ifdef __EMSCRIPTEN__ - wgpu_device = emscripten_webgpu_get_device(); - if (!wgpu_device) - return false; -#else - WGPUAdapter adapter = RequestAdapter(instance.Get()); - if (!adapter) - return false; - wgpu_device = RequestDevice(adapter); +// Adapter and device initialization via JS +EM_ASYNC_JS( void, getAdapterAndDeviceViaJS, (), +{ + if (!navigator.gpu) throw Error("WebGPU not supported."); + + const adapter = await navigator.gpu.requestAdapter(); + const device = await adapter.requestDevice(); + Module.preinitializedWebGPUDevice = device; +} ); +#else // __EMSCRIPTEN__ +static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestAdapterStatus_Success) + { + WGPUAdapter *extAdapter = (WGPUAdapter *) userdata1; + *extAdapter = adapter; + } + else + printf("Request_adapter status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestDeviceStatus_Success) + { + WGPUDevice *extDevice = (WGPUDevice *) userdata1; + *extDevice = device; + } + else + printf("Request_device status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static WGPUAdapter GetAdapter(WGPUInstance &instance) +{ + WGPURequestAdapterOptions adapterOptions = {}; + + static WGPUAdapter localAdapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &localAdapter; + + wgpuInstanceRequestAdapter(wgpu_instance, &adapterOptions, adapterCallbackInfo); + assert(localAdapter && "Error on Adapter request"); + +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(localAdapter); #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); + return localAdapter; +} - wgpu::Adapter adapter = {}; - wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter); +static WGPUDevice GetDevice(WGPUAdapter &adapter) +{ + static WGPUDevice localDevice; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &localDevice; + + wgpuAdapterRequestDevice(adapter, NULL, deviceCallbackInfo); + assert(localDevice && "Error on Device request"); + + return localDevice; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + +static bool InitWGPU(void* window) +{ + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities + + // Google DAWN backend: Adapter and Device acquisition, Surface creation +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpu::InstanceDescriptor instanceDescriptor = {}; + instanceDescriptor.capabilities.timedWaitAnyEnable = true; + wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor); + + wgpu::Adapter adapter { GetAdapter(instance) }; + wgpu_device = GetDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc{}; + canvasDesc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surfaceDesc = {}; + surfaceDesc.nextInChain = &canvasDesc; + wgpu::Surface surface = instance.CreateSurface(&surfaceDesc); #else - wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, window); + wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, (GLFWwindow *) window); +#endif if (!surface) return false; - wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; -#endif + // Moving Dawn objects into WGPU handles wgpu_instance = instance.MoveToCHandle(); - wgpu_surface = surface.MoveToCHandle(); + wgpu_surface = surface.MoveToCHandle(); - wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; + + // WGPU backend: Adapter and Device acquisition, Surface creation +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + wgpu_instance = wgpuCreateInstance(nullptr); + +#ifdef __EMSCRIPTEN__ + getAdapterAndDeviceViaJS(); + + wgpu_device = emscripten_webgpu_get_device(); + assert(wgpu_device != nullptr && "Error creating the Device"); + + WGPUSurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; + html_surface_desc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + html_surface_desc.selector = "#canvas"; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &html_surface_desc.chain; + + // Create the surface. + wgpu_surface = wgpuInstanceCreateSurface(wgpu_instance, &surface_desc); + preferred_fmt = wgpuSurfaceGetPreferredFormat(wgpu_surface, {} /* adapter */); +#else // __EMSCRIPTEN__ + wgpuSetLogCallback(ImGui_ImplWGPU_WGPU_LogCallback_Helper, NULL); + wgpuSetLogLevel(WGPULogLevel_Warn); + + static WGPUAdapter adapter = GetAdapter(wgpu_instance); + wgpu_device = GetDevice(adapter); + + // Create the surface. + wgpu_surface = ImGui_ImplGLFW_CreateWGPUSurface_Helper( wgpu_instance, (GLFWwindow*) window); + + if (!wgpu_surface) + return false; + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter, &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + + wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; + wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + wgpu_surface_configuration.width = wgpu_surface_width; + wgpu_surface_configuration.height = wgpu_surface_height; + wgpu_surface_configuration.device = wgpu_device; + wgpu_surface_configuration.format = preferred_fmt; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); + + wgpu_queue = wgpuDeviceGetQueue(wgpu_device); 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); -} diff --git a/examples/example_glfw_wgpu/web/index.html b/examples/example_glfw_wgpu/web/index.html deleted file mode 100644 index a2a91c4a7..000000000 --- a/examples/example_glfw_wgpu/web/index.html +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - Dear ImGui Emscripten+GLFW+WebGPU example - - - - - - - diff --git a/examples/example_sdl2_wgpu/CMakeLists.txt b/examples/example_sdl2_wgpu/CMakeLists.txt new file mode 100755 index 000000000..c6554092c --- /dev/null +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -0,0 +1,194 @@ +# Building for desktop (WebGPU-native) with Dawn: +# 1. git clone https://github.com/google/dawn dawn +# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn +# 3. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/Debug/example_sdl2_wgpu[.exe] +# * build/example_sdl2_wgpu[.exe] + +# Building for desktop (WGPU-Native) with WGPU-Native: +# 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases +# 2. unzip the downloaded file in your_preferred_folder +# 3. move into your_preferred_folder (e.g. typing: `cd your_preferred_folder`) +# 4. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 5. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/Debug/example_sdl2_wgpu[.exe] +# * build/example_sdl2_wgpu[.exe] + +# Building for Emscripten: +# 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html +# 2. Install Ninja build system +# 3. emcmake cmake -G Ninja -B build +# 3. cmake --build build +# 4. emrun build/index.html + +cmake_minimum_required(VERSION 3.10.2) +project(imgui_example_sdl2_wgpu C CXX) + +set(IMGUI_EXECUTABLE example_sdl2_wgpu) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) +endif() + +set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 + +# Dear ImGui +set(IMGUI_DIR ../../) + +# ImGui example commons source files +set(IMGUI_EXAMPLE_SOURCE_FILES + main.cpp + # backend files + ${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp + ${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp + # Dear ImGui files + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_demo.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp) + +# SDL_MAIN_HANDLED + find_package(SDL2 REQUIRED) + +# Libraries +if(EMSCRIPTEN) + if(EMSCRIPTEN_VERSION VERSION_GREATER_EQUAL "4.0.10") + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "--use-port=emdawnwebgpu" CACHE STRING "Choose between --use-port=emdawnwebgpu (Dawn implementation of EMSCRIPTEN) and -sUSE_WEBGPU=1 (WGPU implementation of EMSCRIPTEN, deprecated in 4.0.10): default to --use-port=emdawnwebgpu for EMSCRIPTEN >= 4.0.10") + else() + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "-sUSE_WEBGPU=1" CACHE STRING "Use -sUSE_WEBGPU=1 for EMSCRIPTEN WGPU implementation" FORCE) + endif() + + add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1) +else() + # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + if (NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") + endif() + + # IMGUI_DAWN_DIR and IMGUI_WGPU_DIR both cannot be set + if (IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify only one of Dawn / WGPU base directory") + endif() + + # Add SDL2 module to get Surface, with libs and file property for MacOS build + if(APPLE) + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_sdl2.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + # Native DAWN build settings + if(IMGUI_DAWN_DIR) + # Dawn wgpu desktop + set(DAWN_FETCH_DEPENDENCIES ON) + set(IMGUI_DAWN_DIR CACHE PATH "Path to Dawn repository") + if (NOT IMGUI_DAWN_DIR) + message(FATAL_ERROR "Please specify the Dawn repository by setting IMGUI_DAWN_DIR") + endif() + + option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) + + # disable buildin GLFW in DAWN when we use SDL2 / SDL3 build + option(DAWN_USE_GLFW OFF) + + # Dawn builds many things by default - disable things we don't need + option(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" OFF) + option(TINT_BUILD_CMD_TOOLS "Build the Tint command line tools" OFF) + option(TINT_BUILD_DOCS "Build documentation" OFF) + option(TINT_BUILD_TESTS "Build tests" OFF) + if (NOT APPLE) + option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" OFF) + endif() + if(WIN32) + option(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" OFF) + option(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) + option(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" OFF) + option(TINT_BUILD_GLSL_VALIDATOR "Build the GLSL output validator" OFF) + option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" OFF) + option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON) + endif() + # check if WAYLAND is the current Session Type and enable DAWN_USE_WAYLAND Wayland option @compile time + # You can override this using: cmake -DDAWN_USE_WAYLAND=X (X = ON | OFF) + if(LINUX) + if ($ENV{XDG_SESSION_TYPE} MATCHES wayland) + option(DAWN_USE_WAYLAND "Enable support for Wayland surface" ON) + endif() + endif() + + add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn" EXCLUDE_FROM_ALL) + + set(LIBRARIES webgpu_dawn webgpu_cpp ${OS_LIBRARIES}) + else() + # Native WGPU build settings + + set(WGPU_NATIVE_LIB_DIR ${IMGUI_WGPU_DIR}/lib) + find_library(WGPU_LIBRARY NAMES libwgpu_native.a wgpu_native.lib wgpu_native + HINTS ${WGPU_NATIVE_LIB_DIR} REQUIRED) + if (WIN32) + set(OS_LIBRARIES d3dcompiler ws2_32 userenv bcrypt ntdll opengl32 Propsys RuntimeObject) + elseif(UNIX AND NOT APPLE) + set(OS_LIBRARIES "-lm -ldl") + endif() + + set(LIBRARIES ${WGPU_LIBRARY} ${OS_LIBRARIES}) + + endif() +endif() + +add_executable(${IMGUI_EXECUTABLE} ${IMGUI_EXAMPLE_SOURCE_FILES}) + +target_include_directories(${IMGUI_EXECUTABLE} PUBLIC + ${IMGUI_DIR} + ${IMGUI_DIR}/backends + ${SDL2_INCLUDE_DIRS} +) + +target_link_libraries(${IMGUI_EXECUTABLE} PUBLIC ${LIBRARIES} ${SDL2_LIBRARIES}) + +target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_EXAMPLE_SDL2_WGPU") + +# IMGUI_IMPL_WEBGPU_BACKEND_DAWN/WGPU internal define is set according to: +# EMSCRIPTEN: by used FLAG +# --use-port=emdawnwebgpu --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN enabled (+EMSCRIPTEN) +# -sUSE_WEBGPU=1 --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU enabled (+EMSCRIPTEN) +# NATIVE: by used SDK installation directory +# if IMGUI_DAWN_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN enabled +# if IMGUI_WGPU_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU enabled + +# Native settings +IF(NOT EMSCRIPTEN) + if(IMGUI_DAWN_DIR) + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + target_include_directories(${IMGUI_EXECUTABLE} PUBLIC ${IMGUI_WGPU_DIR}/include) + endif() +# Emscripten settings +else() + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" STREQUAL "--use-port=emdawnwebgpu") + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}") + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + endif() + message(STATUS "Using ${IMGUI_EMSCRIPTEN_WEBGPU_FLAG} WebGPU implementation") + + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "-sUSE_SDL=2" ) + target_link_options(${IMGUI_EXECUTABLE} PRIVATE + "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" + "-sUSE_SDL=2" + "-sWASM=1" + "-sASYNCIFY=1" + "-sALLOW_MEMORY_GROWTH=1" + "-sNO_EXIT_RUNTIME=0" + "-sASSERTIONS=1" + "-sDISABLE_EXCEPTION_CATCHING=1" + "-sNO_FILESYSTEM=1" + "--shell-file=${CMAKE_CURRENT_LIST_DIR}/../libs/emscripten/shell_minimal.html" + ) + set_target_properties(${IMGUI_EXECUTABLE} PROPERTIES OUTPUT_NAME "index") +endif() diff --git a/examples/example_sdl2_wgpu/Makefile.emscripten b/examples/example_sdl2_wgpu/Makefile.emscripten new file mode 100644 index 000000000..78d64b4d3 --- /dev/null +++ b/examples/example_sdl2_wgpu/Makefile.emscripten @@ -0,0 +1,91 @@ +# +# Makefile to use with emscripten +# See https://emscripten.org/docs/getting_started/downloads.html +# for installation instructions. +# +# This Makefile assumes you have loaded emscripten's environment. +# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead) +# +# Running `make` will produce three files: +# - web/index.html (current stored in the repository) +# - web/index.js +# - web/index.wasm +# +# All three are needed to run the demo. + +CC = emcc +CXX = em++ +WEB_DIR = web +EXE = $(WEB_DIR)/index.js +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +CPPFLAGS = +LDFLAGS = +EMS = + +##--------------------------------------------------------------------- +## EMSCRIPTEN OPTIONS +##--------------------------------------------------------------------- + +# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s USE_GLFW=3 -s USE_WEBGPU=1 +LDFLAGS += -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 + +# Emscripten allows preloading a file or folder to be accessible at runtime. +# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts" +# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html +# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.) +USE_FILE_SYSTEM ?= 0 +ifeq ($(USE_FILE_SYSTEM), 0) +LDFLAGS += -s NO_FILESYSTEM=1 +CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS +endif +ifeq ($(USE_FILE_SYSTEM), 1) +LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts +endif + +##--------------------------------------------------------------------- +## FINAL BUILD FLAGS +##--------------------------------------------------------------------- + +CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +#CPPFLAGS += -g +CPPFLAGS += -Wall -Wformat -Os $(EMS) +#LDFLAGS += --shell-file shell_minimal.html +LDFLAGS += $(EMS) + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(EXE) + +$(WEB_DIR): + mkdir $@ + +serve: all + python3 -m http.server -d $(WEB_DIR) + +$(EXE): $(OBJS) $(WEB_DIR) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre diff --git a/examples/example_sdl2_wgpu/README.md b/examples/example_sdl2_wgpu/README.md new file mode 100644 index 000000000..36de11e18 --- /dev/null +++ b/examples/example_sdl2_wgpu/README.md @@ -0,0 +1,58 @@ +## How to Build + +### Using CMake +#### Building for desktop (WebGPU-native) with Google Dawn: + 1. `git clone https://github.com/google/dawn dawn` + 2. `cmake -B build -DIMGUI_DAWN_DIR=dawn` + 3. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for desktop (WebGPU-Native) with WGPU: + 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases + 2. unzip the downloaded file in `your_preferred_folder` + 3. move into `your_preferred_folder` (e.g. typing: `cd your_preferred_folder`) + 4. `cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder` ("full path" or "relative" starting from current directory) + 5. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for Emscripten: + 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html + 2. Install Ninja build system + 3. `emcmake cmake -G Ninja -B build` + 4. `cmake --build build` + +To run: + - `emrun build/index.html` + +or + - `python -m http.server` then open WGPU browser with url: `http://localhost:8000/build` + + +### Using makefile + +- 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. + +- 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 -f Makefile.emscripten` while in the `example_glfw_wgpu/` directory. + +- Requires recent Emscripten as WGPU is still a work-in-progress API. + +## How to Run + +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. +- `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):
+_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can’t load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you’ll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_ + - Emscripten SDK has a handy `emrun` command: `emrun web/example_glfw_wgpu.html --browser firefox` which will spawn a temporary local webserver (in Firefox). See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. + - You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses). + - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. + - If you are accessing the files over a network, certain browsers, such as Firefox, will restrict Gamepad API access to secure contexts only (e.g. https only). diff --git a/examples/example_sdl2_wgpu/main.cpp b/examples/example_sdl2_wgpu/main.cpp new file mode 100755 index 000000000..bdcc5a6c6 --- /dev/null +++ b/examples/example_sdl2_wgpu/main.cpp @@ -0,0 +1,492 @@ +// Dear ImGui: standalone example application for using GLFW + WebGPU +// - Emscripten is supported for publishing on web. See https://emscripten.org. +// - Dawn is used as a WebGPU implementation on desktop. + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - 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_sdl2.h" +#include "imgui_impl_wgpu.h" +#include + +#ifdef __EMSCRIPTEN__ +#include +#include + #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + #include + #endif + +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + +#include +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + #include +#endif + +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#endif + +// Global WebGPU required states +WGPUInstance wgpu_instance = nullptr; +WGPUDevice wgpu_device = nullptr; +WGPUSurface wgpu_surface = nullptr; +WGPUQueue wgpu_queue = nullptr; +WGPUSurfaceConfiguration wgpu_surface_configuration {}; +int wgpu_surface_width = 1280; +int wgpu_surface_height = 720; + +// Forward declarations +static bool InitWGPU(void* window); + +static void ResizeSurface(int width, int height) +{ + wgpu_surface_configuration.width = wgpu_surface_width = width; + wgpu_surface_configuration.height = wgpu_surface_height = height; + + wgpuSurfaceConfigure( wgpu_surface, (WGPUSurfaceConfiguration *) &wgpu_surface_configuration ); +} + +static void ReleaseTextureAndConfigureSurface(WGPUTexture &texture, int fb_width, int fb_height) +{ + if (texture) + wgpuTextureRelease(texture); + if ( fb_width > 0 && fb_height > 0 ) + ResizeSurface(fb_width, fb_height); +} + +// Main code +int main(int, char**) +{ + +#if defined(__linux__) + // it's necessary to specify "x11" or "wayland": default is "x11" it works also in wayland + SDL_SetHint(SDL_HINT_VIDEODRIVER, "x11"); + // or comment the previous line and export SDL_VIDEODRIVER environment variable: + // export SDL_VIDEODRIVER=wayland (to set wayland session type) + // export SDL_VIDEODRIVER=$XDG_SESSION_TYPE (to get current session type from WM: x11 | wayland) +#endif + + // Init SDL + SDL_Init(SDL_INIT_VIDEO); + SDL_Window *window = SDL_CreateWindow("Dear ImGui SDL2+WebGPU example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wgpu_surface_width, wgpu_surface_height, SDL_WINDOW_RESIZABLE); + + // Initialize WGPU + InitWGPU(window); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL2_InitForOther(window); + + ImGui_ImplWGPU_InitInfo init_info; + init_info.Device = wgpu_device; + init_info.NumFramesInFlight = 3; + init_info.RenderTargetFormat = wgpu_surface_configuration.format; + init_info.DepthStencilFormat = WGPUTextureFormat_Undefined; + ImGui_ImplWGPU_Init(&init_info); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details. + //io.Fonts->AddFontDefault(); +#ifndef IMGUI_DISABLE_FILE_FUNCTIONS + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != nullptr); +#endif + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + SDL_Event event; + bool canCloseWindow = false; + // 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 (!canCloseWindow) +#endif + { + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT || + (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && + event.window.windowID == SDL_GetWindowID(window))) + canCloseWindow = true; + } + // 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. + + // React to changes in screen size + int width, height; + SDL_GetWindowSize(window, &width, &height); + if (width != wgpu_surface_width || height != wgpu_surface_height) + { + ImGui_ImplWGPU_InvalidateDeviceObjects(); + ResizeSurface(width, height); + ImGui_ImplWGPU_CreateDeviceObjects(); + //continue; + } + + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surfaceTexture); + + // Check SurfaceTexture status, if NOT optimal status we try to re-configure Surface + if (!ImGui_ImplWGPU_CheckSurfaceTextureOptimalStatus_Helper(surfaceTexture.status)) { + ReleaseTextureAndConfigureSurface(surfaceTexture.texture, width, height); + continue; + } + + // Start the Dear ImGui frame + ImGui_ImplWGPU_NewFrame(); + ImGui_ImplSDL2_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(); + + WGPUTextureViewDescriptor viewDescriptor {}; + viewDescriptor.format = wgpu_surface_configuration.format; + viewDescriptor.dimension = WGPUTextureViewDimension_2D; + viewDescriptor.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + viewDescriptor.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + viewDescriptor.aspect = WGPUTextureAspect_All; + + WGPUTextureView textureView = wgpuTextureCreateView(surfaceTexture.texture, &viewDescriptor); + + WGPURenderPassColorAttachment color_attachments {}; + color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + 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 = textureView; + + 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); + wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); + +#ifndef __EMSCRIPTEN__ + wgpuSurfacePresent(wgpu_surface); + // Tick needs to be called in Dawn to display validation errors +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpuDeviceTick(wgpu_device); +#endif +#endif + wgpuTextureViewRelease(textureView); + wgpuRenderPassEncoderRelease(pass); + wgpuCommandEncoderRelease(encoder); + wgpuCommandBufferRelease(cmd_buffer); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + // Cleanup + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + + wgpuSurfaceUnconfigure(wgpu_surface); + wgpuSurfaceRelease(wgpu_surface); + wgpuQueueRelease(wgpu_queue); + wgpuDeviceRelease(wgpu_device); + wgpuInstanceRelease(wgpu_instance); + + // Terminate SDL + SDL_DestroyWindow(window); + SDL_Quit(); + + return EXIT_SUCCESS; +} + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter GetAdapter(wgpu::Instance &instance) +{ + wgpu::Adapter acquiredAdapter; + wgpu::RequestAdapterOptions adapterOptions; + + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) { + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquiredAdapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapterOptions, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + assert(acquiredAdapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(acquiredAdapter.Get()); +#endif + return acquiredAdapter.MoveToCHandle(); +} + +static WGPUDevice GetDevice(wgpu::Instance &instance, wgpu::Adapter &adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor deviceDesc; + deviceDesc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, ImGui_ImplWGPU_DAWN_DeviceLostCallback_Helper); + deviceDesc.SetUncapturedErrorCallback(ImGui_ImplWGPU_DAWN_ErrorCallback_Helper); + + wgpu::Device acquiredDevice; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device localDevice, wgpu::StringView message) { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquiredDevice = std::move(localDevice); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&deviceDesc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + assert(acquiredDevice != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquiredDevice.MoveToCHandle(); +} +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#ifdef __EMSCRIPTEN__ +// Adapter and device initialization via JS +EM_ASYNC_JS( void, getAdapterAndDeviceViaJS, (), +{ + if (!navigator.gpu) throw Error("WebGPU not supported."); + + const adapter = await navigator.gpu.requestAdapter(); + const device = await adapter.requestDevice(); + Module.preinitializedWebGPUDevice = device; +} ); +#else // __EMSCRIPTEN__ +static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestAdapterStatus_Success) + { + WGPUAdapter *extAdapter = (WGPUAdapter *) userdata1; + *extAdapter = adapter; + } + else + printf("Request_adapter status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestDeviceStatus_Success) + { + WGPUDevice *extDevice = (WGPUDevice *) userdata1; + *extDevice = device; + } + else + printf("Request_device status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static WGPUAdapter GetAdapter(WGPUInstance &instance) +{ + WGPURequestAdapterOptions adapterOptions = {}; + + static WGPUAdapter localAdapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &localAdapter; + + wgpuInstanceRequestAdapter(wgpu_instance, &adapterOptions, adapterCallbackInfo); + assert(localAdapter && "Error on Adapter request"); + +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(localAdapter); +#endif + + return localAdapter; +} + +static WGPUDevice GetDevice(WGPUAdapter &adapter) +{ + static WGPUDevice localDevice; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &localDevice; + + wgpuAdapterRequestDevice(adapter, NULL, deviceCallbackInfo); + assert(localDevice && "Error on Device request"); + + return localDevice; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + +static bool InitWGPU(void* window) +{ + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities + + // Google DAWN backend: Adapter and Device acquisition, Surface creation +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpu::InstanceDescriptor instanceDescriptor = {}; + instanceDescriptor.capabilities.timedWaitAnyEnable = true; + wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor); + + wgpu::Adapter adapter { GetAdapter(instance) }; + wgpu_device = GetDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc{}; + canvasDesc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surfaceDesc = {}; + surfaceDesc.nextInChain = &canvasDesc; + wgpu::Surface surface = instance.CreateSurface(&surfaceDesc) ; +#else + wgpu::Surface surface = ImGui_ImplSDL2_CreateWGPUSurface_Helper(instance.Get(), (SDL_Window *) window); +#endif + if (!surface) + return false; + + // Moving Dawn objects into WGPU handles + wgpu_instance = instance.MoveToCHandle(); + wgpu_surface = surface.MoveToCHandle(); + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; + + // WGPU backend: Adapter and Device acquisition, Surface creation +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + wgpu_instance = wgpuCreateInstance(nullptr); + +#ifdef __EMSCRIPTEN__ + getAdapterAndDeviceViaJS(); + + wgpu_device = emscripten_webgpu_get_device(); + assert(wgpu_device != nullptr && "Error creating the Device"); + + WGPUSurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; + html_surface_desc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + html_surface_desc.selector = "#canvas"; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &html_surface_desc.chain; + + // Create the surface. + wgpu_surface = wgpuInstanceCreateSurface(wgpu_instance, &surface_desc); + preferred_fmt = wgpuSurfaceGetPreferredFormat(wgpu_surface, {} /* adapter */); +#else // __EMSCRIPTEN__ + wgpuSetLogCallback(ImGui_ImplWGPU_WGPU_LogCallback_Helper, NULL); + wgpuSetLogLevel(WGPULogLevel_Warn); + + static WGPUAdapter adapter = GetAdapter(wgpu_instance); + wgpu_device = GetDevice(adapter); + + // Create the surface. + wgpu_surface = ImGui_ImplSDL2_CreateWGPUSurface_Helper(wgpu_instance, (SDL_Window *) window); + + if (!wgpu_surface) + return false; + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter, &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + + wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; + wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + wgpu_surface_configuration.width = wgpu_surface_width; + wgpu_surface_configuration.height = wgpu_surface_height; + wgpu_surface_configuration.device = wgpu_device; + wgpu_surface_configuration.format = preferred_fmt; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); + + wgpu_queue = wgpuDeviceGetQueue(wgpu_device); + + return true; +} diff --git a/examples/example_sdl3_wgpu/CMakeLists.txt b/examples/example_sdl3_wgpu/CMakeLists.txt new file mode 100755 index 000000000..f74d1b4e3 --- /dev/null +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -0,0 +1,214 @@ +# Building for desktop (WebGPU-native) with Dawn: +# 1. git clone https://github.com/google/dawn dawn +# 2. cmake -B build -DIMGUI_DAWN_DIR=dawn +# 3. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/Debug/example_sdl3_wgpu[.exe] +# * build/example_sdl3_wgpu[.exe] + +# Building for desktop (WGPU-Native) with WGPU-Native: +# 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases +# 2. unzip the downloaded file in your_preferred_folder +# 3. move into your_preferred_folder (e.g. typing: `cd your_preferred_folder`) +# 4. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 5. cmake --build build +# The resulting binary will be found at one of the following locations: +# * build/Debug/example_sdl3_wgpu[.exe] +# * build/example_sdl3_wgpu[.exe] + +# Building for Emscripten: +# At current date (jun/2025) there is no official support for SDL3 in EMSCRIPTEN, +# and its use in EMSCRIPTEN is also in the experimental phase. +# To use SDL3 with EMSCRIPTEN you need to download the SDL3 repository (source code) +# and build the libraries for EMSCRIPTEN following the official page: +# https://wiki.libsdl.org/SDL3/README-emscripten#building-sdlemscripten +# +# the SDL3 library will be built in `your_SDL3_folder/build` +# +# 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html +# 2. Install Ninja build system +# 3. emcmake cmake -DIMGUI_SDL3_EMSCRIPTEN_DIR=your_SDL3_folder -G Ninja -B build +# 3. cmake --build build +# 4. emrun build/index.html + +cmake_minimum_required(VERSION 3.10.2) +project(imgui_example_sdl3_wgpu C CXX) + +set(IMGUI_EXECUTABLE example_sdl3_wgpu) + +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) +endif() + +set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 + +# Dear ImGui +set(IMGUI_DIR ../../) + +# ImGui example commons source files +set(IMGUI_EXAMPLE_SOURCE_FILES + main.cpp + # backend files + ${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp + ${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp + # Dear ImGui files + ${IMGUI_DIR}/imgui.cpp + ${IMGUI_DIR}/imgui_draw.cpp + ${IMGUI_DIR}/imgui_demo.cpp + ${IMGUI_DIR}/imgui_tables.cpp + ${IMGUI_DIR}/imgui_widgets.cpp) + +# Libraries +if(EMSCRIPTEN) + + if(EMSCRIPTEN_VERSION VERSION_GREATER_EQUAL "4.0.10") + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "--use-port=emdawnwebgpu" CACHE STRING "Choose between --use-port=emdawnwebgpu (Dawn implementation of EMSCRIPTEN) and -sUSE_WEBGPU=1 (WGPU implementation of EMSCRIPTEN, deprecated in 4.0.10): default to --use-port=emdawnwebgpu for EMSCRIPTEN >= 4.0.10") + else() + set(IMGUI_EMSCRIPTEN_WEBGPU_FLAG "-sUSE_WEBGPU=1" CACHE STRING "Use -sUSE_WEBGPU=1 for EMSCRIPTEN WGPU implementation" FORCE) + endif() + + add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1) +else() + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) + + # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + if (NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") + endif() + + # IMGUI_DAWN_DIR and IMGUI_WGPU_DIR both cannot be set + if (IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) + message(FATAL_ERROR "Please specify only one of Dawn / WGPU base directory") + endif() + + if(APPLE) + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_sdl3.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + # Native DAWN build settings + if(IMGUI_DAWN_DIR) + # Dawn wgpu desktop + set(DAWN_FETCH_DEPENDENCIES ON) + set(IMGUI_DAWN_DIR CACHE PATH "Path to Dawn repository") + if (NOT IMGUI_DAWN_DIR) + message(FATAL_ERROR "Please specify the Dawn repository by setting IMGUI_DAWN_DIR") + endif() + + # disable buildin GLFW in DAWN when we use SDL2 / SDL3 + option(DAWN_USE_GLFW OFF) + + option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) + + # Dawn builds many things by default - disable things we don't need + option(DAWN_BUILD_SAMPLES "Enables building Dawn's samples" OFF) + option(TINT_BUILD_CMD_TOOLS "Build the Tint command line tools" OFF) + option(TINT_BUILD_DOCS "Build documentation" OFF) + option(TINT_BUILD_TESTS "Build tests" OFF) + if (NOT APPLE) + option(TINT_BUILD_MSL_WRITER "Build the MSL output writer" OFF) + endif() + if(WIN32) + option(TINT_BUILD_SPV_READER "Build the SPIR-V input reader" OFF) + option(TINT_BUILD_WGSL_READER "Build the WGSL input reader" ON) + option(TINT_BUILD_GLSL_WRITER "Build the GLSL output writer" OFF) + option(TINT_BUILD_GLSL_VALIDATOR "Build the GLSL output validator" OFF) + option(TINT_BUILD_SPV_WRITER "Build the SPIR-V output writer" OFF) + option(TINT_BUILD_WGSL_WRITER "Build the WGSL output writer" ON) + endif() + # check if WAYLAND is the current Session Type and enable DAWN_USE_WAYLAND Wayland option @compile time + # You can override this using: cmake -DDAWN_USE_WAYLAND=X (X = ON | OFF) + if(LINUX) + if ($ENV{XDG_SESSION_TYPE} MATCHES wayland) + option(DAWN_USE_WAYLAND "Enable support for Wayland surface" ON) + endif() + endif() + + add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn" EXCLUDE_FROM_ALL) + + set(LIBRARIES webgpu_dawn webgpu_cpp ${OS_LIBRARIES}) + else() + # Native WGPU build settings + + set(WGPU_NATIVE_LIB_DIR ${IMGUI_WGPU_DIR}/lib) + find_library(WGPU_LIBRARY NAMES libwgpu_native.a wgpu_native.lib wgpu_native + HINTS ${WGPU_NATIVE_LIB_DIR} REQUIRED) + if (WIN32) + set(OS_LIBRARIES d3dcompiler ws2_32 userenv bcrypt ntdll opengl32 Propsys RuntimeObject) + elseif(UNIX AND NOT APPLE) + set(OS_LIBRARIES "-lm -ldl") + endif() + + set(LIBRARIES ${WGPU_LIBRARY} ${OS_LIBRARIES}) + + endif() +endif() + +add_executable(${IMGUI_EXECUTABLE} ${IMGUI_EXAMPLE_SOURCE_FILES}) + +target_include_directories(${IMGUI_EXECUTABLE} PUBLIC + ${IMGUI_DIR} + ${IMGUI_DIR}/backends + ${SDL3_INCLUDE_DIRS} + +) + +target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_EXAMPLE_SDL3_WGPU") + +# IMGUI_IMPL_WEBGPU_BACKEND_DAWN/WGPU internal define is set according to: +# EMSCRIPTEN: by used FLAG +# --use-port=emdawnwebgpu --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN enabled (+EMSCRIPTEN) +# -sUSE_WEBGPU=1 --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU enabled (+EMSCRIPTEN) +# NATIVE: by used SDK installation directory +# if IMGUI_DAWN_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_DAWN enabled +# if IMGUI_WGPU_DIR is valid --> IMGUI_IMPL_WEBGPU_BACKEND_WGPU enabled + +# Native settings +IF(NOT EMSCRIPTEN) + target_link_libraries(${IMGUI_EXECUTABLE} PUBLIC ${LIBRARIES} SDL3::SDL3) + + if(IMGUI_DAWN_DIR) + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + target_include_directories(${IMGUI_EXECUTABLE} PUBLIC ${IMGUI_WGPU_DIR}/include) + endif() +# Emscripten settings +else() + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + if(NOT IMGUI_SDL3_EMSCRIPTEN_DIR) + message(FATAL_ERROR "aaaaa") + else() + target_include_directories(${IMGUI_EXECUTABLE} PUBLIC ${IMGUI_SDL3_EMSCRIPTEN_DIR}/include) + find_library(SDL_EMS_LIBRARY NAMES libSDL3.a libSDL3.lib libSDL3 SDL3.a SDL3.lib SDL3 + PATHS ${IMGUI_SDL3_EMSCRIPTEN_DIR}/build REQUIRED NO_CMAKE_FIND_ROOT_PATH) + target_link_libraries(${IMGUI_EXECUTABLE} PUBLIC ${SDL_EMS_LIBRARY}) + endif() + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" STREQUAL "--use-port=emdawnwebgpu") + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}") + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_DAWN") + else() + target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_IMPL_WEBGPU_BACKEND_WGPU") + endif() + message(STATUS "Using ${IMGUI_EMSCRIPTEN_WEBGPU_FLAG} WebGPU implementation") + + + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "-sUSE_SDL=2" ) + target_link_options(${IMGUI_EXECUTABLE} PRIVATE + "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" + #"-sUSE_SDL=3" # There is currently no official support internally at emscripten + "-sWASM=1" + "-sASYNCIFY=1" + "-sALLOW_MEMORY_GROWTH=1" + "-sNO_EXIT_RUNTIME=0" + "-sASSERTIONS=1" + "-sDISABLE_EXCEPTION_CATCHING=1" + #"-sNO_FILESYSTEM=1" # sdl3 integrates a filesystem, you need to disable it at SDL3 build time + "--shell-file=${CMAKE_CURRENT_LIST_DIR}/../libs/emscripten/shell_minimal.html" + ) + set_target_properties(${IMGUI_EXECUTABLE} PROPERTIES OUTPUT_NAME "index") +endif() + +file(WRITE ${CMAKE_SOURCE_DIR}/.idea/.name ${PROJECT_NAME}) # used to rename a Project in clion (run once) \ No newline at end of file diff --git a/examples/example_sdl3_wgpu/Makefile.emscripten b/examples/example_sdl3_wgpu/Makefile.emscripten new file mode 100644 index 000000000..78d64b4d3 --- /dev/null +++ b/examples/example_sdl3_wgpu/Makefile.emscripten @@ -0,0 +1,91 @@ +# +# Makefile to use with emscripten +# See https://emscripten.org/docs/getting_started/downloads.html +# for installation instructions. +# +# This Makefile assumes you have loaded emscripten's environment. +# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead) +# +# Running `make` will produce three files: +# - web/index.html (current stored in the repository) +# - web/index.js +# - web/index.wasm +# +# All three are needed to run the demo. + +CC = emcc +CXX = em++ +WEB_DIR = web +EXE = $(WEB_DIR)/index.js +IMGUI_DIR = ../.. +SOURCES = main.cpp +SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp +SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp +OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) +UNAME_S := $(shell uname -s) +CPPFLAGS = +LDFLAGS = +EMS = + +##--------------------------------------------------------------------- +## EMSCRIPTEN OPTIONS +##--------------------------------------------------------------------- + +# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s USE_GLFW=3 -s USE_WEBGPU=1 +LDFLAGS += -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 + +# Emscripten allows preloading a file or folder to be accessible at runtime. +# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts" +# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html +# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.) +USE_FILE_SYSTEM ?= 0 +ifeq ($(USE_FILE_SYSTEM), 0) +LDFLAGS += -s NO_FILESYSTEM=1 +CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS +endif +ifeq ($(USE_FILE_SYSTEM), 1) +LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts +endif + +##--------------------------------------------------------------------- +## FINAL BUILD FLAGS +##--------------------------------------------------------------------- + +CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends +#CPPFLAGS += -g +CPPFLAGS += -Wall -Wformat -Os $(EMS) +#LDFLAGS += --shell-file shell_minimal.html +LDFLAGS += $(EMS) + +##--------------------------------------------------------------------- +## BUILD RULES +##--------------------------------------------------------------------- + +%.o:%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +%.o:$(IMGUI_DIR)/backends/%.cpp + $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< + +all: $(EXE) + @echo Build complete for $(EXE) + +$(WEB_DIR): + mkdir $@ + +serve: all + python3 -m http.server -d $(WEB_DIR) + +$(EXE): $(OBJS) $(WEB_DIR) + $(CXX) -o $@ $(OBJS) $(LDFLAGS) + +clean: + rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre diff --git a/examples/example_sdl3_wgpu/README.md b/examples/example_sdl3_wgpu/README.md new file mode 100644 index 000000000..1899232c1 --- /dev/null +++ b/examples/example_sdl3_wgpu/README.md @@ -0,0 +1,63 @@ +## How to Build + +### Using CMake +#### Building for desktop (WebGPU-native) with Google Dawn: + 1. `git clone https://github.com/google/dawn dawn` + 2. `cmake -B build -DIMGUI_DAWN_DIR=dawn` + 3. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for desktop (WebGPU-Native) with WGPU: + 1. download WGPU-Native autogenerated binary modules for your platform/compiler from: https://github.com/gfx-rs/wgpu-native/releases + 2. unzip the downloaded file in `your_preferred_folder` + 3. move into `your_preferred_folder` (e.g. typing: `cd your_preferred_folder`) + 4. `cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder` ("full path" or "relative" starting from current directory) + 5. `cmake --build build` +The resulting binary will be found at one of the following locations: + * build/Debug/example_sdl2_wgpu[.exe] + * build/example_sdl2_wgpu[.exe] + +#### Building for Emscripten: +At current date (Jul/2025) there is no official support for SDL3 in EMSCRIPTEN, and its use in EMSCRIPTEN is also in the experimental phase. +To use SDL3 with EMSCRIPTEN you need to download the SDL3 repository (source code) and build the libraries for EMSCRIPTEN following the official page: +https://wiki.libsdl.org/SDL3/README-emscripten#building-sdlemscripten + +- the SDL3 library will be built in `your_SDL3_folder/build` + + 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html + 2. Install Ninja build system + 3. `emcmake cmake -DIMGUI_SDL3_EMSCRIPTEN_DIR=your_SDL3_folder -G Ninja -B build` + 4. `cmake --build build` + +To run: + - `emrun build/index.html` + +or + - `python -m http.server` then open WGPU browser with url: `http://localhost:8000/build` + +### Using makefile + +- 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. + +- 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 -f Makefile.emscripten` while in the `example_glfw_wgpu/` directory. + +- Requires recent Emscripten as WGPU is still a work-in-progress API. + +## How to Run + +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. +- `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):
+_"Unfortunately several browsers (including Chrome, Safari, and Internet Explorer) do not support file:// [XHR](https://emscripten.org/docs/site/glossary.html#term-xhr) requests, and can’t load extra files needed by the HTML (like a .wasm file, or packaged file data as mentioned lower down). For these browsers you’ll need to serve the files using a [local webserver](https://emscripten.org/docs/getting_started/FAQ.html#faq-local-webserver) and then open http://localhost:8000/hello.html."_ + - Emscripten SDK has a handy `emrun` command: `emrun web/example_glfw_wgpu.html --browser firefox` which will spawn a temporary local webserver (in Firefox). See https://emscripten.org/docs/compiling/Running-html-files-with-emrun.html for details. + - You may use Python 3 builtin webserver: `python -m http.server -d web` (this is what `make serve` uses). + - You may use Python 2 builtin webserver: `cd web && python -m SimpleHTTPServer`. + - If you are accessing the files over a network, certain browsers, such as Firefox, will restrict Gamepad API access to secure contexts only (e.g. https only). diff --git a/examples/example_sdl3_wgpu/main.cpp b/examples/example_sdl3_wgpu/main.cpp new file mode 100755 index 000000000..4642de47f --- /dev/null +++ b/examples/example_sdl3_wgpu/main.cpp @@ -0,0 +1,499 @@ +// Dear ImGui: standalone example application for using GLFW + WebGPU +// - Emscripten is supported for publishing on web. See https://emscripten.org. +// - Dawn is used as a WebGPU implementation on desktop. + +// Learn about Dear ImGui: +// - FAQ https://dearimgui.com/faq +// - Getting Started https://dearimgui.com/getting-started +// - 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_sdl3.h" +#include "imgui_impl_wgpu.h" +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#include + #if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + #include + #endif + +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + +#include + +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + #include +#endif + +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#endif + +// Global WebGPU required states +WGPUInstance wgpu_instance = nullptr; +WGPUDevice wgpu_device = nullptr; +WGPUSurface wgpu_surface = nullptr; +WGPUQueue wgpu_queue = nullptr; +WGPUSurfaceConfiguration wgpu_surface_configuration {}; +int wgpu_surface_width = 1280; +int wgpu_surface_height = 720; + +// Forward declarations +static bool InitWGPU(void* window); + +void ResizeSurface(int width, int height) +{ + wgpu_surface_configuration.width = wgpu_surface_width = width; + wgpu_surface_configuration.height = wgpu_surface_height = height; + + wgpuSurfaceConfigure( wgpu_surface, (WGPUSurfaceConfiguration *) &wgpu_surface_configuration ); +} + +static void ReleaseTextureAndConfigureSurface(WGPUTexture &texture, int fb_width, int fb_height) +{ + if (texture) + wgpuTextureRelease(texture); + if ( fb_width > 0 && fb_height > 0 ) + ResizeSurface(fb_width, fb_height); +} + +// Main code +int main(int, char**) +{ +#if defined(__linux__) + // SDL3 default is "x11" (it works also in "wayland"), uncomment the line below to use "wayland" + // SDL_SetHint(SDL_HINT_VIDEO_DRIVER, "wayland"); +#endif + + // Init SDL + if (!SDL_Init(SDL_INIT_VIDEO )) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return EXIT_FAILURE; + } + SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE; + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL3+WebGPU example", wgpu_surface_width, wgpu_surface_height, window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return EXIT_FAILURE; + } + + // Initialize WGPU + InitWGPU(window); + + // Setup Dear ImGui context + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); (void)io; + io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls + io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls + + // Setup Dear ImGui style + ImGui::StyleColorsDark(); + //ImGui::StyleColorsLight(); + + // Setup Platform/Renderer backends + ImGui_ImplSDL3_InitForOther(window); + + ImGui_ImplWGPU_InitInfo init_info; + init_info.Device = wgpu_device; + init_info.NumFramesInFlight = 3; + init_info.RenderTargetFormat = wgpu_surface_configuration.format; + init_info.DepthStencilFormat = WGPUTextureFormat_Undefined; + ImGui_ImplWGPU_Init(&init_info); + + // Load Fonts + // - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them. + // - AddFontFromFileTTF() will return the ImFont* so you can store it if you need to select the font among multiple. + // - If the file cannot be loaded, the function will return a nullptr. Please handle those errors in your application (e.g. use an assertion, or display an error and quit). + // - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call. + // - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering. + // - Read 'docs/FONTS.md' for more instructions and details. + // - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ ! + // - Emscripten allows preloading a file or folder to be accessible at runtime. See Makefile for details. + //io.Fonts->AddFontDefault(); +#ifndef IMGUI_DISABLE_FILE_FUNCTIONS + //io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf", 18.0f); + //io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f); + //io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f); + //io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf", 18.0f, nullptr, io.Fonts->GetGlyphRangesJapanese()); + //IM_ASSERT(font != nullptr); +#endif + + // Our state + bool show_demo_window = true; + bool show_another_window = false; + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + SDL_Event event; + bool canCloseWindow = false; + // 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 (!canCloseWindow) +#endif + { + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT || + (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window))) + canCloseWindow = true; + } + // 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. + + // React to changes in screen size + int width, height; + SDL_GetWindowSize(window, &width, &height); + if (width != wgpu_surface_width || height != wgpu_surface_height) + { + ImGui_ImplWGPU_InvalidateDeviceObjects(); + ResizeSurface(width, height); + ImGui_ImplWGPU_CreateDeviceObjects(); + //continue; + } + + WGPUSurfaceTexture surfaceTexture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surfaceTexture); + + // Check SurfaceTexture status, if NOT optimal status we try to re-configure Surface + if (!ImGui_ImplWGPU_CheckSurfaceTextureOptimalStatus_Helper(surfaceTexture.status)) { + ReleaseTextureAndConfigureSurface(surfaceTexture.texture, width, height); + continue; + } + + // Start the Dear ImGui frame + ImGui_ImplWGPU_NewFrame(); + ImGui_ImplSDL3_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(); + + WGPUTextureViewDescriptor viewDescriptor {}; + viewDescriptor.format = wgpu_surface_configuration.format; + viewDescriptor.dimension = WGPUTextureViewDimension_2D; + viewDescriptor.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + viewDescriptor.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + viewDescriptor.aspect = WGPUTextureAspect_All; + + WGPUTextureView textureView = wgpuTextureCreateView(surfaceTexture.texture, &viewDescriptor); + + WGPURenderPassColorAttachment color_attachments {}; + color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; + 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 = textureView; + + 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); + wgpuQueueSubmit(wgpu_queue, 1, &cmd_buffer); + +#ifndef __EMSCRIPTEN__ + wgpuSurfacePresent(wgpu_surface); + // Tick needs to be called in Dawn to display validation errors +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpuDeviceTick(wgpu_device); +#endif +#endif + wgpuTextureViewRelease(textureView); + wgpuRenderPassEncoderRelease(pass); + wgpuCommandEncoderRelease(encoder); + wgpuCommandBufferRelease(cmd_buffer); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + // Cleanup + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + wgpuSurfaceUnconfigure(wgpu_surface); + wgpuSurfaceRelease(wgpu_surface); + wgpuQueueRelease(wgpu_queue); + wgpuDeviceRelease(wgpu_device); + wgpuInstanceRelease(wgpu_instance); + + // Terminate SDL + SDL_DestroyWindow(window); + SDL_Quit(); + + return EXIT_SUCCESS; +} + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter GetAdapter(wgpu::Instance &instance) +{ + wgpu::Adapter acquiredAdapter; + wgpu::RequestAdapterOptions adapterOptions; + + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) { + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquiredAdapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapterOptions, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + assert(acquiredAdapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(acquiredAdapter.Get()); +#endif + return acquiredAdapter.MoveToCHandle(); +} + +static WGPUDevice GetDevice(wgpu::Instance &instance, wgpu::Adapter &adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor deviceDesc; + deviceDesc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, ImGui_ImplWGPU_DAWN_DeviceLostCallback_Helper); + deviceDesc.SetUncapturedErrorCallback(ImGui_ImplWGPU_DAWN_ErrorCallback_Helper); + + wgpu::Device acquiredDevice; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device localDevice, wgpu::StringView message) { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquiredDevice = std::move(localDevice); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&deviceDesc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + assert(acquiredDevice != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquiredDevice.MoveToCHandle(); +} +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#ifdef __EMSCRIPTEN__ +// Adapter and device initialization via JS +EM_ASYNC_JS( void, getAdapterAndDeviceViaJS, (), +{ + if (!navigator.gpu) throw Error("WebGPU not supported."); + + const adapter = await navigator.gpu.requestAdapter(); + const device = await adapter.requestDevice(); + Module.preinitializedWebGPUDevice = device; +} ); +#else // __EMSCRIPTEN__ +static void handle_request_adapter(WGPURequestAdapterStatus status, WGPUAdapter adapter, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestAdapterStatus_Success) + { + WGPUAdapter *extAdapter = (WGPUAdapter *) userdata1; + *extAdapter = adapter; + } + else + printf("Request_adapter status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static void handle_request_device(WGPURequestDeviceStatus status, WGPUDevice device, WGPUStringView message, void *userdata1, void *userdata2) +{ + if (status == WGPURequestDeviceStatus_Success) + { + WGPUDevice *extDevice = (WGPUDevice *) userdata1; + *extDevice = device; + } + else + printf("Request_device status=%#.8x message=%.*s\n", status, (int) message.length, message.data); +} + +static WGPUAdapter GetAdapter(WGPUInstance &instance) +{ + WGPURequestAdapterOptions adapterOptions = {}; + + static WGPUAdapter localAdapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &localAdapter; + + wgpuInstanceRequestAdapter(wgpu_instance, &adapterOptions, adapterCallbackInfo); + assert(localAdapter && "Error on Adapter request"); + +#ifndef NDEBUG + ImGui_ImplWGPU_PrintAdapterInfo_Helper(localAdapter); +#endif + + return localAdapter; +} + +static WGPUDevice GetDevice(WGPUAdapter &adapter) +{ + static WGPUDevice localDevice; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &localDevice; + + wgpuAdapterRequestDevice(adapter, NULL, deviceCallbackInfo); + assert(localDevice && "Error on Device request"); + + return localDevice; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + +static bool InitWGPU(void* window) +{ + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities + + // Google DAWN backend: Adapter and Device acquisition, Surface creation +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpu::InstanceDescriptor instanceDescriptor = {}; + instanceDescriptor.capabilities.timedWaitAnyEnable = true; + wgpu::Instance instance = wgpu::CreateInstance(&instanceDescriptor); + + wgpu::Adapter adapter { GetAdapter(instance) }; + wgpu_device = GetDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvasDesc{}; + canvasDesc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surfaceDesc = {}; + surfaceDesc.nextInChain = &canvasDesc; + wgpu::Surface surface = instance.CreateSurface(&surfaceDesc) ; +#else + wgpu::Surface surface = ImGui_ImplSDL3_CreateWGPUSurface_Helper(instance.Get(), (SDL_Window *) window); +#endif + if (!surface) + return false; + + // Moving Dawn objects into WGPU handles + wgpu_instance = instance.MoveToCHandle(); + wgpu_surface = surface.MoveToCHandle(); + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter.Get(), &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; + + // WGPU backend: Adapter and Device acquisition, Surface creation +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) + wgpu_instance = wgpuCreateInstance(nullptr); + +#ifdef __EMSCRIPTEN__ + getAdapterAndDeviceViaJS(); + + wgpu_device = emscripten_webgpu_get_device(); + assert(wgpu_device != nullptr && "Error creating the Device"); + + WGPUSurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {}; + html_surface_desc.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + html_surface_desc.selector = "#canvas"; + + WGPUSurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &html_surface_desc.chain; + + // Create the surface. + wgpu_surface = wgpuInstanceCreateSurface(wgpu_instance, &surface_desc); + preferred_fmt = wgpuSurfaceGetPreferredFormat(wgpu_surface, {} /* adapter */); +#else // __EMSCRIPTEN__ + wgpuSetLogCallback(ImGui_ImplWGPU_WGPU_LogCallback_Helper, NULL); + wgpuSetLogLevel(WGPULogLevel_Warn); + + static WGPUAdapter adapter = GetAdapter(wgpu_instance); + wgpu_device = GetDevice(adapter); + + // Create the surface. + wgpu_surface = ImGui_ImplSDL3_CreateWGPUSurface_Helper(wgpu_instance, (SDL_Window *) window); + + if (!wgpu_surface) + return false; + + WGPUSurfaceCapabilities surface_capabilities = {}; + wgpuSurfaceGetCapabilities(wgpu_surface, adapter, &surface_capabilities); + + preferred_fmt = surface_capabilities.formats[0]; +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + + wgpu_surface_configuration.presentMode = WGPUPresentMode_Fifo; + wgpu_surface_configuration.alphaMode = WGPUCompositeAlphaMode_Auto; + wgpu_surface_configuration.usage = WGPUTextureUsage_RenderAttachment; + wgpu_surface_configuration.width = wgpu_surface_width; + wgpu_surface_configuration.height = wgpu_surface_height; + wgpu_surface_configuration.device = wgpu_device; + wgpu_surface_configuration.format = preferred_fmt; + + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); + + wgpu_queue = wgpuDeviceGetQueue(wgpu_device); + + return true; +}