diff --git a/backends/imgui_impl_wgpu.cpp b/backends/imgui_impl_wgpu.cpp index fe5cd5832bde..9ec2e3592b54 100644 --- a/backends/imgui_impl_wgpu.cpp +++ b/backends/imgui_impl_wgpu.cpp @@ -51,6 +51,9 @@ #ifndef IMGUI_DISABLE #include "imgui_impl_wgpu.h" +#include +#include +#include // One of IMGUI_IMPL_WEBGPU_BACKEND_DAWN or IMGUI_IMPL_WEBGPU_BACKEND_WGPU must be provided. See imgui_impl_wgpu.h for more details. #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) == defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) @@ -63,9 +66,6 @@ #define IMGUI_IMPL_WEBGPU_BACKEND_WGPU_EMSCRIPTEN #endif -#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) @@ -273,7 +273,7 @@ static WGPUProgrammableStageDescriptor ImGui_ImplWGPU_CreateShaderModule(const c #endif WGPUShaderModuleDescriptor desc = {}; - desc.nextInChain = reinterpret_cast(&wgsl_desc); + desc.nextInChain = (WGPUChainedStruct*)&wgsl_desc; WGPUProgrammableStageDescriptor stage_desc = {}; stage_desc.module = wgpuDeviceCreateShaderModule(bd->wgpuDevice, &desc); @@ -915,6 +915,173 @@ void ImGui_ImplWGPU_NewFrame() IM_ASSERT(0 && "ImGui_ImplWGPU_CreateDeviceObjects() failed!"); } +//------------------------------------------------------------------------- +// Internal Helpers +// Those are currently used by our example applications. +//------------------------------------------------------------------------- + +bool ImGui_ImplWGPU_IsSurfaceStatusError(WGPUSurfaceGetCurrentTextureStatus status) +{ +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + return (status == WGPUSurfaceGetCurrentTextureStatus_Error); +#else + return (status == WGPUSurfaceGetCurrentTextureStatus_OutOfMemory || status == WGPUSurfaceGetCurrentTextureStatus_DeviceLost); +#endif +} + +bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus status) +{ +#if defined(__EMSCRIPTEN__) && !defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + return (status == WGPUSurfaceGetCurrentTextureStatus_Timeout || status == WGPUSurfaceGetCurrentTextureStatus_Outdated || status == WGPUSurfaceGetCurrentTextureStatus_Lost); +#else + return (status == WGPUSurfaceGetCurrentTextureStatus_Timeout || status == WGPUSurfaceGetCurrentTextureStatus_Outdated || status == WGPUSurfaceGetCurrentTextureStatus_Lost || status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal); +#endif +} + +// Helpers to obtain a string +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type) +{ + switch (type) + { + case WGPUErrorType_Validation: return "Validation"; + case WGPUErrorType_OutOfMemory: return "OutOfMemory"; + case WGPUErrorType_Unknown: return "Unknown"; + case WGPUErrorType_Internal: return "Internal"; + default: return "Unknown"; + } +} +const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type) +{ + switch (type) + { + case WGPUDeviceLostReason_Unknown: return "Unknown"; + case WGPUDeviceLostReason_Destroyed: return "Destroyed"; + case WGPUDeviceLostReason_CallbackCancelled: return "CallbackCancelled"; + case WGPUDeviceLostReason_FailedCreation: return "FailedCreation"; + default: return "Unknown"; + } +} +#elif !defined(__EMSCRIPTEN__) +const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level) +{ + switch (level) + { + case WGPULogLevel_Error: return "Error"; + case WGPULogLevel_Warn: return "Warn"; + case WGPULogLevel_Info: return "Info"; + case WGPULogLevel_Debug: return "Debug"; + case WGPULogLevel_Trace: return "Trace"; + default: return "Unknown"; + } +} +#endif + +const char* ImGui_ImplWGPU_GetBackendTypeName(WGPUBackendType type) +{ + switch (type) + { + case WGPUBackendType_WebGPU: return "WebGPU"; + case WGPUBackendType_D3D11: return "D3D11"; + case WGPUBackendType_D3D12: return "D3D12"; + case WGPUBackendType_Metal: return "Metal"; + case WGPUBackendType_Vulkan: return "Vulkan"; + case WGPUBackendType_OpenGL: return "OpenGL"; + case WGPUBackendType_OpenGLES: return "OpenGLES"; + default: return "Unknown"; + } +} + +const char* ImGui_ImplWGPU_GetAdapterTypeName(WGPUAdapterType type) +{ + switch (type) + { + case WGPUAdapterType_DiscreteGPU: return "DiscreteGPU"; + case WGPUAdapterType_IntegratedGPU: return "IntegratedGPU"; + case WGPUAdapterType_CPU: return "CPU"; + default: return "Unknown"; + } +} + +void ImGui_ImplWGPU_DebugPrintAdapterInfo(const WGPUAdapter& adapter) +{ + WGPUAdapterInfo info = {}; + wgpuAdapterGetInfo(adapter, &info); + printf("description: \"%.*s\"\n", (int)info.description.length, info.description.data); + printf("vendor: \"%.*s\", vendorID: %x\n", (int)info.vendor.length, info.vendor.data, info.vendorID); + printf("architecture: \"%.*s\"\n", (int) info.architecture.length, info.architecture.data); + printf("device: \"%.*s\", deviceID: %x\n", (int)info.device.length, info.device.data, info.deviceID); + printf("backendType: \"%s\"\n", ImGui_ImplWGPU_GetBackendTypeName(info.backendType)); + printf("adapterType: \"%s\"\n", ImGui_ImplWGPU_GetAdapterTypeName(info.adapterType)); + wgpuAdapterInfoFreeMembers(info); +} + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) + +#if defined(__APPLE__) +// MacOS specific: is necessary to compile with "-x objective-c++" flags +// (e.g. using cmake: set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") ) +#include +#include +#endif + +WGPUSurface ImGui_ImplWGPU_CreateWGPUSurfaceHelper(ImGui_ImplWGPU_CreateSurfaceInfo* info) +{ + WGPUSurfaceDescriptor surface_descriptor = {}; + WGPUSurface surface = {}; +#if defined(__APPLE__) + if (strcmp(info->System, "cocoa") == 0) + { + IM_ASSERT(info->RawWindow != nullptr); + NSWindow* ns_window = (NSWindow*)info->RawWindow; + id metal_layer = [CAMetalLayer layer]; + [ns_window.contentView setWantsLayer : YES] ; + [ns_window.contentView setLayer : metal_layer] ; + WGPUSurfaceSourceMetalLayer surface_src_metal = {}; + surface_src_metal.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + surface_src_metal.layer = metal_layer; + surface_descriptor.nextInChain = &surface_src_metal.chain; + surface = wgpuInstanceCreateSurface(info->Instance, &surface_descriptor); + } +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) + if (strcmp(info->System, "wayland") == 0) + { + IM_ASSERT(info->RawDisplay != nullptr && info->RawSurface != nullptr); + WGPUSurfaceSourceWaylandSurface surface_src_wayland = {}; + surface_src_wayland.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + surface_src_wayland.display = info->RawDisplay; + surface_src_wayland.surface = info->RawSurface; + surface_descriptor.nextInChain = &surface_src_wayland.chain; + surface = wgpuInstanceCreateSurface(info->Instance, &surface_descriptor); + } + else if (strcmp(info->System, "x11") == 0) + { + IM_ASSERT(info->RawDisplay != nullptr && info->RawWindow != nullptr); + WGPUSurfaceSourceXlibWindow surface_src_xlib = {}; + surface_src_xlib.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + surface_src_xlib.display = info->RawDisplay; + surface_src_xlib.window = (uint64_t)info->RawWindow; + surface_descriptor.nextInChain = &surface_src_xlib.chain; + surface = wgpuInstanceCreateSurface(info->Instance, &surface_descriptor); + } +#elif defined(_WIN32) + if (strcmp(info->System, "win32") == 0) + { + IM_ASSERT(info->RawWindow != nullptr && info->RawInstance != nullptr); + WGPUSurfaceSourceWindowsHWND surface_src_hwnd = {}; + surface_src_hwnd.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + surface_src_hwnd.hinstance = info->RawInstance; + surface_src_hwnd.hwnd = info->RawWindow; + surface_descriptor.nextInChain = &surface_src_hwnd.chain; + surface = wgpuInstanceCreateSurface(info->Instance, &surface_descriptor); + } +#else +#error "Unsupported WebGPU native platform!" +#endif + return surface; +} +#endif + //----------------------------------------------------------------------------- #endif // #ifndef IMGUI_DISABLE diff --git a/backends/imgui_impl_wgpu.h b/backends/imgui_impl_wgpu.h index 9443106b8ddf..3e46849b518c 100644 --- a/backends/imgui_impl_wgpu.h +++ b/backends/imgui_impl_wgpu.h @@ -41,6 +41,11 @@ #endif #include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +#include // for wgpu::Device, wgpu::DeviceLostReason, wgpu::ErrorType used by validation layer callbacks. +#elif !defined(__EMSCRIPTEN__) +#include // WGPULogLevel +#endif // Initialization data, for ImGui_ImplWGPU_Init() struct ImGui_ImplWGPU_InitInfo @@ -81,4 +86,38 @@ struct ImGui_ImplWGPU_RenderState WGPURenderPassEncoder RenderPassEncoder; }; +//------------------------------------------------------------------------- +// Internal Helpers +// Those are currently used by our example applications. +//------------------------------------------------------------------------- + +// (Optional) Helper to wrap some of the Dawn/WGPU/Emscripten quirks +bool ImGui_ImplWGPU_IsSurfaceStatusError(WGPUSurfaceGetCurrentTextureStatus status); +bool ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(WGPUSurfaceGetCurrentTextureStatus status); // Return whether the texture is suboptimal and may need to be recreated. + +// (Optional) Helper for debugging/logging +void ImGui_ImplWGPU_DebugPrintAdapterInfo(const WGPUAdapter& adapter); +const char* ImGui_ImplWGPU_GetBackendTypeName(WGPUBackendType type); +const char* ImGui_ImplWGPU_GetAdapterTypeName(WGPUAdapterType type); +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +const char* ImGui_ImplWGPU_GetDeviceLostReasonName(WGPUDeviceLostReason type); +const char* ImGui_ImplWGPU_GetErrorTypeName(WGPUErrorType type); +#elif defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) && !defined(__EMSCRIPTEN__) +const char* ImGui_ImplWGPU_GetLogLevelName(WGPULogLevel level); +#endif + +// (Optional) Helper to create a surface on macOS/Wayland/X11/Window +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) && !defined(__EMSCRIPTEN__) +struct ImGui_ImplWGPU_CreateSurfaceInfo +{ + WGPUInstance Instance; + const char* System; // "cocoa" | "wayland" | "x11" | "win32" + void* RawWindow; // NSWindow* | 0 | Window | HWND + void* RawDisplay; // 0 | wl_display* | Display* | 0 + void* RawSurface; // | wl_surface* | 0 | 0 + void* RawInstance; // 0 | 0 | 0 | HINSTANCE +}; +WGPUSurface ImGui_ImplWGPU_CreateWGPUSurfaceHelper(ImGui_ImplWGPU_CreateSurfaceInfo* info); +#endif + #endif // #ifndef IMGUI_DISABLE diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7b101d6a78c4..bea104171f49 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -82,16 +82,28 @@ Other Changes: was unused in core but might be used by a direct caller). (#9027) [@achabense] - Vulkan: added IMGUI_IMPL_VULKAN_VOLK_FILENAME to configure path to Volk (default to "volk.h"). (#9008, #7722, #6582, #4854) [@mwlasiuk] + - WebGPU: added various internal/optional helpers to wrap some of the + Dawn/WGPU/Emscripten debacle quirks: (#8381) [@brutpitt] + - ImGui_ImplWGPU_CreateWGPUSurfaceHelper(). + - ImGui_ImplWGPU_IsSurfaceStatusError(), ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(). + - ImGui_ImplWGPU_DebugPrintAdapterInfo(), + - ImGui_ImplWGPU_GetBackendTypeName(), ImGui_ImplWGPU_GetAdapterTypeName(), + ImGui_ImplWGPU_GetDeviceLostReasonName(), ImGui_ImplWGPU_GetErrorTypeName(), + ImGui_ImplWGPU_GetLogLevelName(). - WebGPU: update to compile with Dawn and Emscripten's 4.0.10+ - '--use-port=emdawnwebgpu' ports. (#8381, #8898) [@brutpitt, @trbabb] + '--use-port=emdawnwebgpu' ports. (#8381, #8898, #7435) [@brutpitt, @trbabb] When using Emscripten 4.0.10+, backend now defaults to IMGUI_IMPL_WEBGPU_BACKEND_DAWN instead of IMGUI_IMPL_WEBGPU_BACKEND_WGPU, if neither are specified. (note: examples application were not updated yet) - Win32: Revert 1.92.4 change of comparing dwPacketNumber, which prevents refreshing accurate gamepad info after focus-out + io.ClearInputKeys(). (#8556) - Examples: + - GLFW+WebGPU: update example for latest specs, to work on Emscripten 4.0.10+, + latest Dawn-Native and WGPU-Native. (#8381, #8567, #8191, #7435) [@brutpitt] - GLFW+WebGPU: removed unnecessary ImGui_ImplWGPU_InvalidateDeviceObjects() call during surface resize. (#8381) + - SDL2+WebGPU: added new example (Emscripten + native Dawn/WGPU). (#8381) [@brutpitt] + - SDL3+WebGPU: added new example (Emscripten + native Dawn/WGPU). (#8381) [@brutpitt] ----------------------------------------------------------------------- diff --git a/examples/example_glfw_wgpu/CMakeLists.txt b/examples/example_glfw_wgpu/CMakeLists.txt index d21bb091909b..d8c6eae4f3fc 100644 --- a/examples/example_glfw_wgpu/CMakeLists.txt +++ b/examples/example_glfw_wgpu/CMakeLists.txt @@ -6,18 +6,30 @@ # * 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. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 4. 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 # 3. emcmake cmake -G Ninja -B build +# (optional) -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py", see ReadMe.md # 3. cmake --build build # 4. emrun build/index.html -cmake_minimum_required(VERSION 3.10.2) +cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 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) + set(CMAKE_BUILD_TYPE Debug CACHE STRING "" FORCE) endif() set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 @@ -25,93 +37,172 @@ set(CMAKE_CXX_STANDARD 17) # Dawn requires C++17 # Dear ImGui set(IMGUI_DIR ../../) -# Libraries +set(IMGUI_EXAMPLE_SOURCE_FILES + # Example code + main.cpp + # Dear ImGui 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(EMSCRIPTEN) - 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 -DIMGUI_IMPL_WEBGPU_BACKEND_WGPU) -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") - 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) - endif() - - add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn" EXCLUDE_FROM_ALL) - - set(LIBRARIES webgpu_dawn webgpu_cpp webgpu_glfw glfw) + if(NOT IMGUI_EMSCRIPTEN_WEBGPU_FLAG) # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG not used, set by current EMSCRIPTEN version + 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") + endif() + else() # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG used, check correct version + if(EMSCRIPTEN_VERSION VERSION_LESS "4.0.10" AND "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "emdawnwebgpu") + # it's necessary EMSCRIPTEN >= v4.0.10 (although "--use-port=path/to/emdawnwebgpu.port.py" is supported/tested from v4.0.8) + message(FATAL_ERROR "emdawnwebgpu needs EMSCRIPTEN version >= 4.0.10") + endif() + 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() # Native/Desktop build + # Check DAWN/WGPU directory + if(NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") + endif() + + if(IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) # both IMGUI_DAWN_DIR and IMGUI_WGPU_DIR cannot be set + message(FATAL_ERROR "Please specify only one between Dawn / WGPU base directory") + endif() + + if(APPLE) # Add SDL2 module to get Surface, with libs and file property for MacOS build + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") +# set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + if(IMGUI_DAWN_DIR) # DAWN-Native build options + 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) + set(DAWN_BUILD_MONOLITHIC_LIBRARY "STATIC" CACHE STRING "Build monolithic library: SHARED, STATIC, or 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(DAWN_FORCE_SYSTEM_COMPONENT_LOAD "Allow system component fallback" ON) + 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" ON) + 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_glfw glfw) + else() # WGPU-Native build options + 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) + set(OS_LIBRARIES d3dcompiler ws2_32 userenv bcrypt ntdll opengl32 Propsys RuntimeObject) + elseif(UNIX AND NOT APPLE) + set(OS_LIBRARIES "-lm -ldl") + elseif(APPLE) + 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 +add_executable(${IMGUI_EXECUTABLE} ${IMGUI_EXAMPLE_SOURCE_FILES}) + +target_include_directories(${IMGUI_EXECUTABLE} PUBLIC + ${IMGUI_DIR} + ${IMGUI_DIR}/backends ) -IF(NOT EMSCRIPTEN) - target_compile_definitions(example_glfw_wgpu PUBLIC - "IMGUI_IMPL_WEBGPU_BACKEND_DAWN" - ) + +target_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_EXAMPLE_GLFW_WGPU") + +# compiler option only for IMGUI_EXAMPLE_SOURCE_FILES +if (MSVC) + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC /W4) # warning level 4 +else() + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC -Wall) # -Wextra -Wpedantic endif() -target_include_directories(example_glfw_wgpu PUBLIC - ${IMGUI_DIR} - ${IMGUI_DIR}/backends -) -target_link_libraries(example_glfw_wgpu PUBLIC ${LIBRARIES}) +# 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 -# Emscripten settings -if(EMSCRIPTEN) - if("${IMGUI_EMSCRIPTEN_GLFW3}" STREQUAL "--use-port=contrib.glfw3") - target_compile_options(example_glfw_wgpu PUBLIC - "${IMGUI_EMSCRIPTEN_GLFW3}" +if(NOT EMSCRIPTEN) # WegGPU-Native settings + # Set IMGUI_IMPL_WEBGPU_BACKEND_XXXX based on the SDK (directory) used + 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} INTERFACE webgpu_cpp PUBLIC ${LIBRARIES}) +else() # Emscripten settings + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + if("${IMGUI_EMSCRIPTEN_GLFW3}" STREQUAL "--use-port=contrib.glfw3") + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC "${IMGUI_EMSCRIPTEN_GLFW3}") + endif() + message(STATUS "Using ${IMGUI_EMSCRIPTEN_GLFW3} GLFW implementation") + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "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" ) - endif() - message(STATUS "Using ${IMGUI_EMSCRIPTEN_GLFW3} GLFW implementation") - target_link_options(example_glfw_wgpu PRIVATE - "-sUSE_WEBGPU=1" - "${IMGUI_EMSCRIPTEN_GLFW3}" - "-sWASM=1" - "-sALLOW_MEMORY_GROWTH=1" - "-sNO_EXIT_RUNTIME=0" - "-sASSERTIONS=1" - "-sDISABLE_EXCEPTION_CATCHING=1" - "-sNO_FILESYSTEM=1" - ) - 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/Makefile.emscripten b/examples/example_glfw_wgpu/Makefile.emscripten index 545a5057836a..8fee2fc7c980 100644 --- a/examples/example_glfw_wgpu/Makefile.emscripten +++ b/examples/example_glfw_wgpu/Makefile.emscripten @@ -6,8 +6,8 @@ # 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) +# Running `make -f Makefile.emscripten` will produce three files: +# - web/index.html # - web/index.js # - web/index.wasm # @@ -16,7 +16,7 @@ CC = emcc CXX = em++ WEB_DIR = web -EXE = $(WEB_DIR)/index.js +EXE = $(WEB_DIR)/index.html 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 @@ -34,15 +34,19 @@ EMS = # ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) # Note: For glfw, we use emscripten-glfw port (contrib.glfw3) instead of (-s USE_GLFW=3) to get a better support for High DPI displays. EMS += -s DISABLE_EXCEPTION_CATCHING=1 --use-port=contrib.glfw3 -LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 +LDFLAGS += -s WASM=1 +LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 +LDFLAGS += -s ASYNCIFY=1 +LDFLAGS += -s NO_EXIT_RUNTIME=0 +LDFLAGS += -s ASSERTIONS=1 # (1) Using legacy WebGPU implementation (Emscripten < 4.0.10) -EMS += -DIMGUI_IMPL_WEBGPU_BACKEND_WGPU -LDFLAGS += -s USE_WEBGPU=1 +#EMS += -DIMGUI_IMPL_WEBGPU_BACKEND_WGPU +#LDFLAGS += -s USE_WEBGPU=1 -# or (2) Using newer Dawn-based WebGPU port (Emscripten >= 4.0.10) (UNSUPPORTED YET) -#EMS += --use-port=emdawnwebgpu -#LDFLAGS += --use-port=emdawnwebgpu +# or (2) Using newer Dawn-based WebGPU port (Emscripten >= 4.0.10) +EMS += --use-port=emdawnwebgpu +LDFLAGS += --use-port=emdawnwebgpu # Build as single file (binary text encoded in .html file) #LDFLAGS += -sSINGLE_FILE @@ -67,7 +71,7 @@ endif CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends #CPPFLAGS += -g CPPFLAGS += -Wall -Wformat -Os $(EMS) -#LDFLAGS += --shell-file shell_minimal.html +LDFLAGS += --shell-file ../libs/emscripten/shell_minimal.html LDFLAGS += $(EMS) ##--------------------------------------------------------------------- diff --git a/examples/example_glfw_wgpu/README.md b/examples/example_glfw_wgpu/README.md index 399d431ffaa1..119647211073 100644 --- a/examples/example_glfw_wgpu/README.md +++ b/examples/example_glfw_wgpu/README.md @@ -1,4 +1,145 @@ -## 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_glfw_wgpu[.exe] + * build/example_glfw_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. `cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder` ("full path" or "relative" starting from current directory) + 4. `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 + 3. `emcmake cmake -G Ninja -B build` + - (optional) `-DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py"`, see below + 4. `cmake --build build` + +#### Sync Emscripten with latest Google Dawn: +If you want to sync Emscripten with latest DAWN release it's necessary to download the `port-emdawnwgpu-package` (released daily by Google) here: +https://github.com/google/dawn/releases +Unpack it in your preferred folder and to replace the step 3 with: + +3. `emcmake cmake -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py" -G Ninja -B build` + +**N.B.** +For the WASM code produced by Emscripten to work correctly, it will also be necessary to have the "corresponding" (or newer) version of Google Canary (nightly build for developers) that includes the latest changes + + +--- + +### CMake by step + +#### Generate Dawn Native: + +- `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + - Using `IMGUI_DAWN_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + +#### Generate WGPU Native: + +- `cmake -G Ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -B where_to_build_dir` + + - Using `IMGUI_WGPU_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten: + +- `emcmake cmake -G Ninja -B where_to_build_dir`\ +CMake checks the EMSCRIPEN version then: + - if EMS >= 4.0.10 uses `--use-port=emdawnwebgpu` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - if EMS < 4.0.10 uses `-sUSE_WEBGPU=1` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten forcing `-sUSE_WEBGPU=1` deprecated flag even with EMS >= 4.0.10 +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="-sUSE_WEBGPU=1" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten using external WebGPU library (emdawnwebgpu_pkg) +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path_to_emdawnwebgpu_pkg" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - *To use external WebGPU library it's necessary to have EMS >= 4.0.10 or the minimum requirements specified by the package:* + - https://github.com/google/dawn/releases + +#### Build time + +Once the procedure for the specific builder is generated, the build command is **always the same**: + +- Build using CMake + - `cmake --build where_to_build_dir` + - It will use selected builder to build the example. + +- Build explicitly: + - `cd where_to_build_dir` + - `ninja` + - This is the builder chosen during the generation phase + +--- + +### CMake useful options +#### Generator types (alternative to **ninja** bulder): +- `-G Ninja` to build with __ninja__ builder +- `-G "Unix Makefiles"` to build with __make__ builder +- `-G "Visual Studio 17 2022" -A x64` to create a VS 2022 solution (.sln) file, Windows only + - **Native build only** + - Not **officially** supported to build Google Dawn + +Example: +- using **make** instead **ninja**: + - `cmake -G "Unix Makefiles" -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + +**Syntax is case sensitive and the "" are necessary in case of spaces between words* + +#### Directories +- The directory path can be absolute or relative (starting from the current directory) +- It's necessary to use different `where_to_build_dir` for different CMake generations + + +#### Build type +The default build type is **Debug** +It is possible to use a different build type using: +- `-DCMAKE_BUILD_TYPE=Release` +- `-DCMAKE_BUILD_TYPE=MinSizeRel` +- `-DCMAKE_BUILD_TYPE=RelWithDebInfo` + +Example: +- building **Release**: + - `cmake -G ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -DCMAKE_BUILD_TYPE=Release -B where_to_build_dir ` + +#### GLFW / SDL2 / SDL3 includes, libraries, search paths and package manager + +Includes and libraries, by default, are searched in system/compiler paths (environment variables): you can add the path to your development tools to the environment variables without having to modify the `CMakeLists.txt` file. +- e.g. CLang search in path specified from the following environment variables: + - include files: CPATH, C_INCLUDE_PATH, CPLUS_INCLUDE_PATH + - library files: LIBRARY_PATH + +If you are using a package manager (**vcpkg** / **conan** / ... ) you need/can to specify it, adding to cmake command: +- `-DCMAKE_TOOLCHAIN_FILE=path/to/package_manager.cmake` + +Examples: + +- using **vcpkg** package manager it's necessary adding: + - `-DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake` + +- full cmake command using **vcpkg** package manager: + - `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -B where_to_build_dir` + +--- + + +### 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 @@ -10,6 +151,8 @@ - Requires recent Emscripten as WGPU is still a work-in-progress API. +--- + ## How to Run To run on a local machine: diff --git a/examples/example_glfw_wgpu/main.cpp b/examples/example_glfw_wgpu/main.cpp index 88c504007f5d..9ea267154dc5 100644 --- a/examples/example_glfw_wgpu/main.cpp +++ b/examples/example_glfw_wgpu/main.cpp @@ -12,26 +12,31 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_wgpu.h" #include +#include #include // This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. #ifdef __EMSCRIPTEN__ #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 -#include -#include +#endif // Data 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 WGPUQueue wgpu_queue = nullptr; +static WGPUSurfaceConfiguration wgpu_surface_configuration = {}; static int wgpu_surface_width = 1280; static int wgpu_surface_height = 800; @@ -43,33 +48,11 @@ static void glfw_error_callback(int error, const char* description) printf("GLFW Error %d: %s\n", error, description); } -static void wgpu_error_callback(WGPUErrorType error_type, const char* message, void*) -{ - const char* error_type_lbl = ""; - switch (error_type) - { - case WGPUErrorType_Validation: error_type_lbl = "Validation"; break; - case WGPUErrorType_OutOfMemory: error_type_lbl = "Out of memory"; break; - case WGPUErrorType_Unknown: error_type_lbl = "Unknown"; break; - case WGPUErrorType_DeviceLost: error_type_lbl = "Device lost"; break; - default: error_type_lbl = "Unknown"; - } - printf("%s error: %s\n", error_type_lbl, message); -} - static void ResizeSurface(int width, int height) { - if (wgpu_swap_chain) - wgpuSwapChainRelease(wgpu_swap_chain); - wgpu_surface_width = width; - wgpu_surface_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); + wgpu_surface_configuration.width = wgpu_surface_width = width; + wgpu_surface_configuration.height = wgpu_surface_height = height; + wgpuSurfaceConfigure(wgpu_surface, &wgpu_surface_configuration); } // Main code @@ -98,7 +81,7 @@ int main(int, char**) glfwTerminate(); return 1; } - ResizeSurface(wgpu_surface_width, wgpu_surface_height); + glfwShowWindow(window); // Setup Dear ImGui context @@ -125,7 +108,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); @@ -181,6 +164,23 @@ int main(int, char**) if (width != wgpu_surface_width || height != wgpu_surface_height) ResizeSurface(width, height); + // Check surface status for error. If texture is not optimal, try to reconfigure the surface. + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surface_texture); + if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status)) + { + fprintf(stderr, "Unrecoverable Surface Texture status=%#.8x\n", surface_texture.status); + abort(); + } + if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) + { + if (surface_texture.texture) + wgpuTextureRelease(surface_texture.texture); + if (width > 0 && height > 0) + ResizeSurface(width, height); + continue; + } + // Start the Dear ImGui frame ImGui_ImplWGPU_NewFrame(); ImGui_ImplGlfw_NewFrame(); @@ -226,17 +226,21 @@ 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 view_desc = {}; + view_desc.format = wgpu_surface_configuration.format; + view_desc.dimension = WGPUTextureViewDimension_2D ; + view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + view_desc.aspect = WGPUTextureAspect_All; + + WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); 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 = wgpuSwapChainGetCurrentTextureView(wgpu_swap_chain); + color_attachments.view = texture_view; WGPURenderPassDescriptor render_pass_desc = {}; render_pass_desc.colorAttachmentCount = 1; @@ -252,14 +256,16 @@ int main(int, char**) WGPUCommandBufferDescriptor cmd_buffer_desc = {}; WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc); - WGPUQueue wgpu_queue = wgpuDeviceGetQueue(wgpu_device); 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(texture_view); wgpuRenderPassEncoderRelease(pass); wgpuCommandEncoderRelease(encoder); wgpuCommandBufferRelease(cmd_buffer); @@ -273,75 +279,298 @@ 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; } -#ifndef __EMSCRIPTEN__ -static WGPUAdapter RequestAdapter(WGPUInstance instance) +// GLFW helper to create a WebGPU surface, used only in WGPU-Native. DAWN-Native already has a built-in function +// As of today (2025/10) 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 nor available with EMSCRIPTEN. +#if !defined(__EMSCRIPTEN__) && (defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN)) + +#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) +#define GLFW_HAS_X11_OR_WAYLAND 1 +#else +#define GLFW_HAS_X11_OR_WAYLAND 0 +#endif +#ifdef _WIN32 +#undef APIENTRY +#ifndef GLFW_EXPOSE_NATIVE_WIN32 // for glfwGetWin32Window() +#define GLFW_EXPOSE_NATIVE_WIN32 +#endif +#elif defined(__APPLE__) +#ifndef GLFW_EXPOSE_NATIVE_COCOA // for glfwGetCocoaWindow() +#define GLFW_EXPOSE_NATIVE_COCOA +#endif +#elif GLFW_HAS_X11_OR_WAYLAND +#ifndef GLFW_EXPOSE_NATIVE_X11 // for glfwGetX11Display(), glfwGetX11Window() on Freedesktop (Linux, BSD, etc.) +#define GLFW_EXPOSE_NATIVE_X11 +#endif +#ifndef GLFW_EXPOSE_NATIVE_WAYLAND +#if defined(__has_include) && __has_include() +#define GLFW_EXPOSE_NATIVE_WAYLAND +#endif +#endif +#endif +#include +#undef Status // X11 headers are leaking this. + +WGPUSurface CreateWGPUSurface(const WGPUInstance& instance, GLFWwindow* window) +{ + ImGui_ImplWGPU_CreateSurfaceInfo create_info = {}; + create_info.Instance = instance; +#if defined(GLFW_EXPOSE_NATIVE_COCOA) + { + create_info.System = "cocoa"; + create_info.RawWindow = (void*)glfwGetCocoaWindow(window); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(GLFW_EXPOSE_NATIVE_WAYLAND) + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) + { + create_info.System = "wayland"; + create_info.RawDisplay = (void*)glfwGetWaylandDisplay(); + create_info.RawSurface = (void*)glfwGetWaylandWindow(window); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(GLFW_EXPOSE_NATIVE_X11) + if (glfwGetPlatform() == GLFW_PLATFORM_X11) + { + create_info.System = "x11"; + create_info.RawWindow = (void*)glfwGetX11Window(window); + create_info.RawDisplay = (void*)glfwGetX11Display(); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(GLFW_EXPOSE_NATIVE_WIN32) + { + create_info.System = "win32"; + create_info.RawWindow = (void*)glfwGetWin32Window(window); + create_info.RawInstance = (void*)::GetModuleHandle(NULL); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#else +#error "Unsupported WebGPU native platform!" +#endif + return nullptr; +} +#endif + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter RequestAdapter(wgpu::Instance& instance) { - auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, const char* message, void* pUserData) + wgpu::Adapter acquired_adapter; + wgpu::RequestAdapterOptions adapter_options; + auto onRequestAdapter = [&](wgpu::RequestAdapterStatus status, wgpu::Adapter adapter, wgpu::StringView message) { - if (status == WGPURequestAdapterStatus_Success) - *(WGPUAdapter*)pUserData = adapter; - else - printf("Could not get WebGPU adapter: %s\n", message); + if (status != wgpu::RequestAdapterStatus::Success) + { + printf("Failed to get an adapter: %s\n", message.data); + return; + } + acquired_adapter = std::move(adapter); }; - WGPUAdapter adapter; - wgpuInstanceRequestAdapter(instance, nullptr, onAdapterRequestEnded, (void*)&adapter); - return adapter; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + IM_ASSERT(acquired_adapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); + return acquired_adapter.MoveToCHandle(); } -static WGPUDevice RequestDevice(WGPUAdapter adapter) +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) { - auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, const char* message, void* pUserData) + // Set device callback functions + wgpu::DeviceDescriptor device_desc; + device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } + ); + device_desc.SetUncapturedErrorCallback( + [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } + ); + + wgpu::Device acquired_device; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) { - if (status == WGPURequestDeviceStatus_Success) - *(WGPUDevice*)pUserData = device; - else - printf("Could not get WebGPU device: %s\n", message); + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquired_device = std::move(local_device); }; - WGPUDevice device; - wgpuAdapterRequestDevice(adapter, nullptr, onDeviceRequestEnded, (void*)&device); - return device; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + IM_ASSERT(acquired_device != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquired_device.MoveToCHandle(); } -#endif +#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 RequestAdapter(WGPUInstance& instance) +{ + WGPURequestAdapterOptions adapter_options = {}; + + WGPUAdapter local_adapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &local_adapter; + + wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); + IM_ASSERT(local_adapter && "Error on Adapter request"); + return local_adapter; +} + +static WGPUDevice RequestDevice(WGPUAdapter& adapter) +{ + WGPUDevice local_device; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &local_device; + wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); + IM_ASSERT(local_device && "Error on Device request"); + return local_device; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU static bool InitWGPU(GLFWwindow* window) { - wgpu::Instance instance = wgpuCreateInstance(nullptr); + WGPUTextureFormat preferred_fmt = WGPUTextureFormat_Undefined; // acquired from SurfaceCapabilities -#ifdef __EMSCRIPTEN__ - wgpu_device = emscripten_webgpu_get_device(); - if (!wgpu_device) - return false; + // Google DAWN backend: Adapter and Device acquisition, Surface creation +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) + wgpu::InstanceDescriptor instance_desc = {}; + static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; + instance_desc.requiredFeatureCount = 1; + instance_desc.requiredFeatures = &timedWaitAny; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + + wgpu::Adapter adapter = RequestAdapter(instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); - wgpu::SurfaceDescriptorFromCanvasHTMLSelector canvas_desc = {}; + wgpu_device = RequestDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; canvas_desc.selector = "#canvas"; + wgpu::SurfaceDescriptor surface_desc = {}; surface_desc.nextInChain = &canvas_desc; - wgpu::Surface surface = instance.CreateSurface(&surface_desc); - - wgpu::Adapter adapter = {}; - wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter); + wgpu_surface = instance.CreateSurface(&surface_desc).MoveToCHandle(); #else - WGPUAdapter adapter = RequestAdapter(instance.Get()); - if (!adapter) + wgpu_surface = CreateWGPUSurface(instance.Get(), window); +#endif + if (!wgpu_surface) return false; + + // Moving Dawn objects into WGPU handles + wgpu_instance = instance.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(); + IM_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( + [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + ); + wgpuSetLogLevel(WGPULogLevel_Warn); + + WGPUAdapter adapter = RequestAdapter(wgpu_instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter); + wgpu_device = RequestDevice(adapter); - wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, window); - if (!surface) + // Create the surface. + wgpu_surface = CreateWGPUSurface(wgpu_instance, window); + if (!wgpu_surface) return false; - wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm; -#endif - // Moving Dawn objects into WGPU handles - wgpu_instance = instance.MoveToCHandle(); - wgpu_surface = surface.MoveToCHandle(); + 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); - wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr); return true; } diff --git a/examples/example_glfw_wgpu/web/index.html b/examples/example_glfw_wgpu/web/index.html deleted file mode 100644 index a2a91c4a75f8..000000000000 --- 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 100644 index 000000000000..815c702f621c --- /dev/null +++ b/examples/example_sdl2_wgpu/CMakeLists.txt @@ -0,0 +1,197 @@ +# 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. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 4. 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 +# (optional) -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py", see ReadMe.md +# 3. cmake --build build +# 4. emrun build/index.html + +cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 +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 +) + +if(EMSCRIPTEN) + if(NOT IMGUI_EMSCRIPTEN_WEBGPU_FLAG) # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG not used, set by current EMSCRIPTEN version + 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") + endif() + else() # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG used, check correct version + if(EMSCRIPTEN_VERSION VERSION_LESS "4.0.10" AND "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "emdawnwebgpu") + # it's necessary EMSCRIPTEN >= v4.0.10 (although "--use-port=path/to/emdawnwebgpu.port.py" is supported/tested from v4.0.8) + message(FATAL_ERROR "emdawnwebgpu needs EMSCRIPTEN version >= 4.0.10") + endif() + endif() + + add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1) +else() # Native/Desktop build + find_package(SDL2 REQUIRED) # SDL_MAIN_HANDLED + + if(NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") + endif() + + if(IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) # both IMGUI_DAWN_DIR and IMGUI_WGPU_DIR cannot be set + message(FATAL_ERROR "Please specify only one between Dawn / WGPU base directory") + endif() + + if(APPLE) # Add SDL2 module to get Surface, with libs and file property for MacOS build + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + if(IMGUI_DAWN_DIR) # DAWN-Native build options + 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_USE_GLFW OFF) # disable buildin GLFW in DAWN when we use SDL2 / SDL3 + + option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) + set(DAWN_BUILD_MONOLITHIC_LIBRARY "STATIC" CACHE STRING "Build monolithic library: SHARED, STATIC, or 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(DAWN_FORCE_SYSTEM_COMPONENT_LOAD "Allow system component fallback" ON) + 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" ON) + 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 ${OS_LIBRARIES}) + else() # WGPU-Native build options + + 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_compile_definitions(${IMGUI_EXECUTABLE} PUBLIC "IMGUI_EXAMPLE_SDL2_WGPU") + +# compiler option only for IMGUI_EXAMPLE_SOURCE_FILES +if (MSVC) + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC /W4) # warning level 4 +else() + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC -Wall) # -Wextra -Wpedantic +endif() + +# 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 + +if(NOT EMSCRIPTEN) # WegGPU-Native settings + 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} INTERFACE webgpu_cpp PUBLIC ${LIBRARIES} ${SDL2_LIBRARIES}) +else() # Emscripten settings + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "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 000000000000..69bcb00e725a --- /dev/null +++ b/examples/example_sdl2_wgpu/Makefile.emscripten @@ -0,0 +1,103 @@ +# +# 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 -f Makefile.emscripten` will produce three files: +# - web/index.html +# - 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.html +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_sdl2.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 USE_SDL=2 +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s WASM=1 +LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 +LDFLAGS += -s ASYNCIFY=1 +LDFLAGS += -s NO_EXIT_RUNTIME=0 +LDFLAGS += -s ASSERTIONS=1 + +# (1) Using legacy WebGPU implementation (Emscripten < 4.0.10) +#EMS += -DIMGUI_IMPL_WEBGPU_BACKEND_WGPU +#LDFLAGS += -s USE_WEBGPU=1 + +# or (2) Using newer Dawn-based WebGPU port (Emscripten >= 4.0.10) +EMS += --use-port=emdawnwebgpu +LDFLAGS += --use-port=emdawnwebgpu + +# 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 ../libs/emscripten/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 000000000000..cdfd032da2fd --- /dev/null +++ b/examples/example_sdl2_wgpu/README.md @@ -0,0 +1,167 @@ +## 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. `cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder` ("full path" or "relative" starting from current directory) + 4. `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` + - (optional) `-DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py"`, see below + 4. `cmake --build build` + +#### Sync Emscripten with latest Google Dawn: +If you want to sync Emscripten with latest DAWN release it's necessary to download the `port-emdawnwgpu-package` (released daily by Google) here: +https://github.com/google/dawn/releases +Unpack it in your preferred folder and to replace the step 3 with: + +3. `emcmake cmake -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py" -G Ninja -B build` + +**N.B.** +For the WASM code produced by Emscripten to work correctly, it will also be necessary to have the "corresponding" (or newer) version of Google Canary (nightly build for developers) that includes the latest changes + + +--- + +### CMake by step + +#### Generate Dawn Native: + +- `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + - Using `IMGUI_DAWN_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + +#### Generate WGPU Native: + +- `cmake -G Ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -B where_to_build_dir` + + - Using `IMGUI_WGPU_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten: + +- `emcmake cmake -G Ninja -B where_to_build_dir`\ +CMake checks the EMSCRIPEN version then: + - if EMS >= 4.0.10 uses `--use-port=emdawnwebgpu` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - if EMS < 4.0.10 uses `-sUSE_WEBGPU=1` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten forcing `-sUSE_WEBGPU=1` deprecated flag even with EMS >= 4.0.10 +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="-sUSE_WEBGPU=1" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten using external WebGPU library (emdawnwebgpu_pkg) +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path_to_emdawnwebgpu_pkg" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - *To use external WebGPU library it's necessary to have EMS >= 4.0.10 or the minimum requirements specified by the package:* + - https://github.com/google/dawn/releases + +#### Build time + +Once the procedure for the specific builder is generated, the build command is **always the same**: + +- Build using CMake + - `cmake --build where_to_build_dir` + - It will use selected builder to build the example. + +- Build explicitly: + - `cd where_to_build_dir` + - `ninja` + - This is the builder chosen during the generation phase + +--- + +### CMake useful options +#### Generator types (alternative to **ninja** bulder): +- `-G Ninja` to build with __ninja__ builder +- `-G "Unix Makefiles"` to build with __make__ builder +- `-G "Visual Studio 17 2022" -A x64` to create a VS 2022 solution (.sln) file, Windows only + - **Native build only** + - Not **officially** supported to build Google Dawn + +Example: +- using **make** instead **ninja**: + - `cmake -G "Unix Makefiles" -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + +**Syntax is case sensitive and the "" are necessary in case of spaces between words* + +#### Directories +- The directory path can be absolute or relative (starting from the current directory) +- It's necessary to use different `where_to_build_dir` for different CMake generations + + +#### Build type +The default build type is **Debug** +It is possible to use a different build type using: +- `-DCMAKE_BUILD_TYPE=Release` +- `-DCMAKE_BUILD_TYPE=MinSizeRel` +- `-DCMAKE_BUILD_TYPE=RelWithDebInfo` + +Example: +- building **Release**: + - `cmake -G ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -DCMAKE_BUILD_TYPE=Release -B where_to_build_dir ` + +#### GLFW / SDL2 / SDL3 includes, libraries, search paths and package manager + +Includes and libraries, by default, are searched in system/compiler paths (environment variables): you can add the path to your development tools to the environment variables without having to modify the `CMakeLists.txt` file. +- e.g. CLang search in path specified from the following environment variables: + - include files: CPATH, C_INCLUDE_PATH, CPLUS_INCLUDE_PATH + - library files: LIBRARY_PATH + +If you are using a package manager (**vcpkg** / **conan** / ... ) you need/can to specify it, adding to cmake command: +- `-DCMAKE_TOOLCHAIN_FILE=path/to/package_manager.cmake` + +Examples: + +- using **vcpkg** package manager it's necessary adding: + - `-DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake` + +- full cmake command using **vcpkg** package manager: + - `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -B where_to_build_dir` + +--- + + +### 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 100644 index 000000000000..1d95da572b78 --- /dev/null +++ b/examples/example_sdl2_wgpu/main.cpp @@ -0,0 +1,539 @@ +// Dear ImGui: standalone example application for using SDL2 + 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 +#include + +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#include +#endif +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +#include +#endif + +// Data +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 = 800; + +// Forward declarations +static bool InitWGPU(SDL_Window* 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); +} + +// Main code +int main(int, char**) +{ + // Setup SDL + SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMECONTROLLER); + + // Create window with graphics context + float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); + SDL_WindowFlags window_flags = SDL_WINDOW_RESIZABLE; + SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+WebGPU example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, wgpu_surface_width, wgpu_surface_height, window_flags); + if (window == nullptr) + { + printf("Error: SDL_CreateWindow(): %s\n", SDL_GetError()); + return 1; + } + + // 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 scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + + // 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). + // - 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 \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefault(); +#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"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("fonts/ArialUni.ttf"); + //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); + + // Main loop + bool done = false; +#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 (!done) +#endif + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_QUIT) + done = true; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // React to changes in screen size + int width, height; + SDL_GetWindowSize(window, &width, &height); + if (width != wgpu_surface_width || height != wgpu_surface_height) + ResizeSurface(width, height); + + // Check surface status for error. If texture is not optimal, try to reconfigure the surface. + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surface_texture); + if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status)) + { + fprintf(stderr, "Unrecoverable Surface Texture status=%#.8x\n", surface_texture.status); + abort(); + } + if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) + { + if (surface_texture.texture) + wgpuTextureRelease(surface_texture.texture); + if (width > 0 && height > 0) + ResizeSurface(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 view_desc = {}; + view_desc.format = wgpu_surface_configuration.format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + view_desc.aspect = WGPUTextureAspect_All; + + WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); + + 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 = texture_view; + + 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(texture_view); + 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); + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} + +// SDL2 helper to create a WebGPU surface (exclusively!) for Native/Desktop applications: available only together with WebGPU/WGPU backend +// As of today (2025/10/31) 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 nor available with EMSCRIPTEN. +#if !defined(__EMSCRIPTEN__) && (defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN)) + +#include +WGPUSurface CreateWGPUSurface(const WGPUInstance& instance, SDL_Window* window) +{ + SDL_SysWMinfo sysWMInfo; + SDL_VERSION(&sysWMInfo.version); + SDL_GetWindowWMInfo(window, &sysWMInfo); + + ImGui_ImplWGPU_CreateSurfaceInfo create_info = {}; + create_info.Instance = instance; +#if defined(SDL_VIDEO_DRIVER_COCOA) + { + create_info.System = "cocoa"; + create_info.RawWindow = (void*)sysWMInfo.info.cocoa.window; + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(SDL_VIDEO_DRIVER_WAYLAND) || defined(SDL_VIDEO_DRIVER_X11) + const char* sdl_driver = SDL_GetCurrentVideoDriver(); + if (sdl_driver && strcmp(sdl_driver, "wayland") == 0) + { + create_info.System = "wayland"; + create_info.RawDisplay = (void*)sysWMInfo.info.wl.display; + create_info.RawSurface = (void*)sysWMInfo.info.wl.surface; + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } + else + { + create_info.System = "x11"; + create_info.RawWindow = (void*)sysWMInfo.info.x11.window; + create_info.RawDisplay = (void*)sysWMInfo.info.x11.display; + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(SDL_VIDEO_DRIVER_WINDOWS) + { + create_info.System = "win32"; + create_info.RawWindow = (void*)sysWMInfo.info.win.window; + create_info.RawInstance = (void*)sysWMInfo.info.win.hinstance; + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#else +#error "Unsupported WebGPU native platform!" +#endif + return nullptr; +} +#endif + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter RequestAdapter(wgpu::Instance& instance) +{ + wgpu::Adapter acquired_adapter; + wgpu::RequestAdapterOptions adapter_options; + 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; + } + acquired_adapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + IM_ASSERT(acquired_adapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); + return acquired_adapter.MoveToCHandle(); +} + +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor device_desc; + device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } + ); + device_desc.SetUncapturedErrorCallback( + [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } + ); + + wgpu::Device acquired_device; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) + { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquired_device = std::move(local_device); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + IM_ASSERT(acquired_device != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquired_device.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 RequestAdapter(WGPUInstance& instance) +{ + WGPURequestAdapterOptions adapter_options = {}; + + WGPUAdapter local_adapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &local_adapter; + + wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); + IM_ASSERT(local_adapter && "Error on Adapter request"); + return local_adapter; +} + +static WGPUDevice RequestDevice(WGPUAdapter& adapter) +{ + WGPUDevice local_device; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &local_device; + wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); + IM_ASSERT(local_device && "Error on Device request"); + return local_device; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + +static bool InitWGPU(SDL_Window* 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 instance_desc = {}; + static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; + instance_desc.requiredFeatureCount = 1; + instance_desc.requiredFeatures = &timedWaitAny; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + + wgpu::Adapter adapter = RequestAdapter(instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); + + wgpu_device = RequestDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; + canvas_desc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &canvas_desc; + wgpu::Surface surface = instance.CreateSurface(&surface_desc); +#else + wgpu::Surface surface = CreateWGPUSurface(instance.Get(), 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( + [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + ); + wgpuSetLogLevel(WGPULogLevel_Warn); + + WGPUAdapter adapter = RequestAdapter(wgpu_instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter); + + wgpu_device = RequestDevice(adapter); + + // Create the surface. + wgpu_surface = CreateWGPUSurface(wgpu_instance, 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 100644 index 000000000000..05d57650c7da --- /dev/null +++ b/examples/example_sdl3_wgpu/CMakeLists.txt @@ -0,0 +1,199 @@ +# 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. cmake -B build -DIMGUI_WGPU_DIR=your_preferred_folder ("full path" or "relative" starting from current directory) +# 4. 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: +# 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 +# (optional) -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py", see ReadMe.md +# 4. cmake --build build +# 5. emrun build/index.html + +cmake_minimum_required(VERSION 3.22) # Dawn requires CMake >= 3.22 +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 +) + +if(EMSCRIPTEN) + if(EMSCRIPTEN_VERSION VERSION_LESS "4.0.15") + message(FATAL_ERROR "Using Emscripten with SDL3 needs Emscripten version >= 4.0.15") + endif() + if(NOT IMGUI_EMSCRIPTEN_WEBGPU_FLAG) # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG not used, set by current EMSCRIPTEN version + 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") + endif() + else() # if IMGUI_EMSCRIPTEN_WEBGPU_FLAG used, check correct version + if(EMSCRIPTEN_VERSION VERSION_LESS "4.0.10" AND "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "emdawnwebgpu") + # it's necessary EMSCRIPTEN >= v4.0.10 (although "--use-port=path/to/emdawnwebgpu.port.py" is supported/tested from v4.0.8) + message(FATAL_ERROR "emdawnwebgpu needs EMSCRIPTEN version >= 4.0.10") + endif() + endif() + + add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1) +else() # Native/Desktop build + find_package(SDL3 REQUIRED CONFIG REQUIRED COMPONENTS SDL3) + + if(NOT IMGUI_DAWN_DIR AND NOT IMGUI_WGPU_DIR) # if it's Native/Desktop build, IMGUI_DAWN_DIR or IMGUI_WGPU_DIR must be specified + message(FATAL_ERROR "Please specify the Dawn or WGPU base directory") + endif() + + if(IMGUI_DAWN_DIR AND IMGUI_WGPU_DIR) # both IMGUI_DAWN_DIR and IMGUI_WGPU_DIR cannot be set + message(FATAL_ERROR "Please specify only one between Dawn / WGPU base directory") + endif() + + if(APPLE) # Add SDL3 module to get Surface, with libs and file property for MacOS build + set_source_files_properties(${IMGUI_DIR}/backends/imgui_impl_wgpu.cpp PROPERTIES COMPILE_FLAGS "-x objective-c++") + set(OS_LIBRARIES "-framework CoreFoundation -framework QuartzCore -framework Metal -framework MetalKit -framework Cocoa") + endif() + + if(IMGUI_DAWN_DIR) # DAWN-Native build options + 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_USE_GLFW OFF) # disable buildin GLFW in DAWN when we use SDL2 / SDL3 + + option(DAWN_FETCH_DEPENDENCIES "Use fetch_dawn_dependencies.py as an alternative to using depot_tools" ON) + set(DAWN_BUILD_MONOLITHIC_LIBRARY "STATIC" CACHE STRING "Build monolithic library: SHARED, STATIC, or 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(DAWN_FORCE_SYSTEM_COMPONENT_LOAD "Allow system component fallback" ON) + 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" ON) + 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 ${OS_LIBRARIES}) + else() # WGPU-Native build options + 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") + +# Enable warning level compiler option only for IMGUI_EXAMPLE_SOURCE_FILES +if (MSVC) + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC /W4) # warning level 4 +else() + target_compile_options(${IMGUI_EXECUTABLE} PUBLIC -Wall) # -Wextra -Wpedantic +endif() + +# 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 + +if(NOT EMSCRIPTEN) # WegGPU-Native settings + 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} INTERFACE webgpu_cpp PUBLIC ${LIBRARIES} SDL3::SDL3) +else() # Emscripten settings + set(CMAKE_EXECUTABLE_SUFFIX ".html") + + if("${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" MATCHES "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=3") + target_link_options(${IMGUI_EXECUTABLE} PRIVATE + "${IMGUI_EMSCRIPTEN_WEBGPU_FLAG}" + "-sUSE_SDL=3" + "-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_sdl3_wgpu/Makefile.emscripten b/examples/example_sdl3_wgpu/Makefile.emscripten new file mode 100644 index 000000000000..58639a1fd105 --- /dev/null +++ b/examples/example_sdl3_wgpu/Makefile.emscripten @@ -0,0 +1,103 @@ +# +# 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 -f Makefile.emscripten` will produce three files: +# - web/index.html +# - 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.html +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_sdl3.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 USE_SDL=3 +EMS += -s DISABLE_EXCEPTION_CATCHING=1 +LDFLAGS += -s WASM=1 +LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 +LDFLAGS += -s ASYNCIFY=1 +LDFLAGS += -s NO_EXIT_RUNTIME=0 +LDFLAGS += -s ASSERTIONS=1 + +# (1) Using legacy WebGPU implementation (Emscripten < 4.0.10) +#EMS += -DIMGUI_IMPL_WEBGPU_BACKEND_WGPU +#LDFLAGS += -s USE_WEBGPU=1 + +# or (2) Using newer Dawn-based WebGPU port (Emscripten >= 4.0.10) +EMS += --use-port=emdawnwebgpu +LDFLAGS += --use-port=emdawnwebgpu + +# 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 ../libs/emscripten/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 000000000000..bd7c3365216c --- /dev/null +++ b/examples/example_sdl3_wgpu/README.md @@ -0,0 +1,167 @@ +## 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_sdl3_wgpu[.exe] + * build/example_sdl3_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. `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: + 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` + - (optional) `-DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py"`, see below + 4. `cmake --build build` + +#### Sync Emscripten with latest Google Dawn: +If you want to sync Emscripten with latest DAWN release it's necessary to download the `port-emdawnwgpu-package` (released daily by Google) here: +https://github.com/google/dawn/releases +Unpack it in your preferred folder and to replace the step 3 with: + +3. `emcmake cmake -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path/to/emdawnwebgpu_package/emdawnwebgpu.port.py" -G Ninja -B build` + +**N.B.** +For the WASM code produced by Emscripten to work correctly, it will also be necessary to have the "corresponding" (or newer) version of Google Canary (nightly build for developers) that includes the latest changes + + +--- + +### CMake by step + +#### Generate Dawn Native: + +- `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + - Using `IMGUI_DAWN_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + +#### Generate WGPU Native: + +- `cmake -G Ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -B where_to_build_dir` + + - Using `IMGUI_WGPU_DIR` set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten: + +- `emcmake cmake -G Ninja -B where_to_build_dir`\ +CMake checks the EMSCRIPEN version then: + - if EMS >= 4.0.10 uses `--use-port=emdawnwebgpu` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - if EMS < 4.0.10 uses `-sUSE_WEBGPU=1` flag to build + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten forcing `-sUSE_WEBGPU=1` deprecated flag even with EMS >= 4.0.10 +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="-sUSE_WEBGPU=1" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_WGPU` compiler define + +#### Generate Emscripten using external WebGPU library (emdawnwebgpu_pkg) +- `emcmake cmake -G Ninja -DIMGUI_EMSCRIPTEN_WEBGPU_FLAG="--use-port=path_to_emdawnwebgpu_pkg" -B where_to_build_dir` + - it set `IMGUI_IMPL_WEBGPU_BACKEND_DAWN` compiler define + - *To use external WebGPU library it's necessary to have EMS >= 4.0.10 or the minimum requirements specified by the package:* + - https://github.com/google/dawn/releases + +#### Build time + +Once the procedure for the specific builder is generated, the build command is **always the same**: + +- Build using CMake + - `cmake --build where_to_build_dir` + - It will use selected builder to build the example. + +- Build explicitly: + - `cd where_to_build_dir` + - `ninja` + - This is the builder chosen during the generation phase + +--- + +### CMake useful options +#### Generator types (alternative to **ninja** bulder): +- `-G Ninja` to build with __ninja__ builder +- `-G "Unix Makefiles"` to build with __make__ builder +- `-G "Visual Studio 17 2022" -A x64` to create a VS 2022 solution (.sln) file, Windows only + - **Native build only** + - Not **officially** supported to build Google Dawn + +Example: +- using **make** instead **ninja**: + - `cmake -G "Unix Makefiles" -DIMGUI_DAWN_DIR=path_to_sdk_dir -B where_to_build_dir` + +**Syntax is case sensitive and the "" are necessary in case of spaces between words* + +#### Directories +- The directory path can be absolute or relative (starting from the current directory) +- It's necessary to use different `where_to_build_dir` for different CMake generations + + +#### Build type +The default build type is **Debug** +It is possible to use a different build type using: +- `-DCMAKE_BUILD_TYPE=Release` +- `-DCMAKE_BUILD_TYPE=MinSizeRel` +- `-DCMAKE_BUILD_TYPE=RelWithDebInfo` + +Example: +- building **Release**: + - `cmake -G ninja -DIMGUI_WGPU_DIR=path_to_sdk_dir -DCMAKE_BUILD_TYPE=Release -B where_to_build_dir ` + +#### GLFW / SDL2 / SDL3 includes, libraries, search paths and package manager + +Includes and libraries, by default, are searched in system/compiler paths (environment variables): you can add the path to your development tools to the environment variables without having to modify the `CMakeLists.txt` file. +- e.g. CLang search in path specified from the following environment variables: + - include files: CPATH, C_INCLUDE_PATH, CPLUS_INCLUDE_PATH + - library files: LIBRARY_PATH + +If you are using a package manager (**vcpkg** / **conan** / ... ) you need/can to specify it, adding to cmake command: +- `-DCMAKE_TOOLCHAIN_FILE=path/to/package_manager.cmake` + +Examples: + +- using **vcpkg** package manager it's necessary adding: + - `-DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake` + +- full cmake command using **vcpkg** package manager: + - `cmake -G Ninja -DIMGUI_DAWN_DIR=path_to_sdk_dir -DCMAKE_TOOLCHAIN_FILE=/scripts/buildsystems/vcpkg.cmake -B where_to_build_dir` + +--- + + +### 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 100644 index 000000000000..377ffd9bb9e7 --- /dev/null +++ b/examples/example_sdl3_wgpu/main.cpp @@ -0,0 +1,553 @@ +// Dear ImGui: standalone example application for using SDL3 + 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 +#include + +// This example can also compile and run with Emscripten! See 'Makefile.emscripten' for details. +#ifdef __EMSCRIPTEN__ +#include +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) +#include +#endif +#include "../libs/emscripten/emscripten_mainloop_stub.h" +#endif + +#include +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +#include +#endif + +// Data +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 = 800; + +// Forward declarations +static bool InitWGPU(SDL_Window* 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 ); +} + +// Main code +int main(int, char**) +{ + // Setup SDL + // [If using SDL_MAIN_USE_CALLBACKS: all code below until the main loop starts would likely be your SDL_AppInit() function] + if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_GAMEPAD)) + { + printf("Error: SDL_Init(): %s\n", SDL_GetError()); + return 1; + } + + // Create SDL window graphics context + float main_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); + 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 1; + } + + // 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 scaling + ImGuiStyle& style = ImGui::GetStyle(); + style.ScaleAllSizes(main_scale); // Bake a fixed style scale. (until we have a solution for dynamic style scaling, changing this requires resetting Style + calling this again) + style.FontScaleDpi = main_scale; // Set initial font scale. (using io.ConfigDpiScaleFonts=true makes this unnecessary. We leave both here for documentation purpose) + + // 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). + // - 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 \\ ! + // - Our Emscripten build process allows embedding fonts to be accessible at runtime from the "fonts/" folder. See Makefile.emscripten for details. + //style.FontSizeBase = 20.0f; + //io.Fonts->AddFontDefault(); +#ifndef IMGUI_DISABLE_FILE_FUNCTIONS + //io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf"); + //io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf"); + //ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf"); + //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); + + // Main loop + bool done = false; +#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 (!done) +#endif + { + // Poll and handle events (inputs, window resize, etc.) + // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs. + // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data. + // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data. + // Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags. + // [If using SDL_MAIN_USE_CALLBACKS: call ImGui_ImplSDL3_ProcessEvent() from your SDL_AppEvent() function] + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL3_ProcessEvent(&event); + if (event.type == SDL_EVENT_QUIT) + done = true; + if (event.type == SDL_EVENT_WINDOW_CLOSE_REQUESTED && event.window.windowID == SDL_GetWindowID(window)) + done = true; + } + + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppIterate() function] + // React to changes in screen size + int width, height; + SDL_GetWindowSize(window, &width, &height); + if (width != wgpu_surface_width || height != wgpu_surface_height) + ResizeSurface(width, height); + + // Check surface status for error. If texture is not optimal, try to reconfigure the surface. + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(wgpu_surface, &surface_texture); + if (ImGui_ImplWGPU_IsSurfaceStatusError(surface_texture.status)) + { + fprintf(stderr, "Unrecoverable Surface Texture status=%#.8x\n", surface_texture.status); + abort(); + } + if (ImGui_ImplWGPU_IsSurfaceStatusSubOptimal(surface_texture.status)) + { + if (surface_texture.texture) + wgpuTextureRelease(surface_texture.texture); + if (width > 0 && height > 0) + ResizeSurface(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 view_desc = {}; + view_desc.format = wgpu_surface_configuration.format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.mipLevelCount = WGPU_MIP_LEVEL_COUNT_UNDEFINED; + view_desc.arrayLayerCount = WGPU_ARRAY_LAYER_COUNT_UNDEFINED; + view_desc.aspect = WGPUTextureAspect_All; + + WGPUTextureView texture_view = wgpuTextureCreateView(surface_texture.texture, &view_desc); + + 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 = texture_view; + + 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(texture_view); + wgpuRenderPassEncoderRelease(pass); + wgpuCommandEncoderRelease(encoder); + wgpuCommandBufferRelease(cmd_buffer); + } +#ifdef __EMSCRIPTEN__ + EMSCRIPTEN_MAINLOOP_END; +#endif + + // Cleanup + // [If using SDL_MAIN_USE_CALLBACKS: all code below would likely be your SDL_AppQuit() function] + ImGui_ImplWGPU_Shutdown(); + ImGui_ImplSDL3_Shutdown(); + ImGui::DestroyContext(); + + wgpuSurfaceUnconfigure(wgpu_surface); + wgpuSurfaceRelease(wgpu_surface); + wgpuQueueRelease(wgpu_queue); + wgpuDeviceRelease(wgpu_device); + wgpuInstanceRelease(wgpu_instance); + + SDL_DestroyWindow(window); + SDL_Quit(); + + return 0; +} + +// SDL3 helper to create a WebGPU surface (exclusively!) for Native/Desktop applications: available only together with WebGPU/WGPU backend +// As of today (2025/10) 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 nor available with EMSCRIPTEN. +#if !defined(__EMSCRIPTEN__) && (defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) || defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN)) + +#if defined(SDL_PLATFORM_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN 1 +#endif +#include +#endif + +WGPUSurface CreateWGPUSurface(const WGPUInstance& instance, SDL_Window* window) +{ + SDL_PropertiesID propertiesID = SDL_GetWindowProperties(window); + + ImGui_ImplWGPU_CreateSurfaceInfo create_info = {}; + create_info.Instance = instance; +#if defined(SDL_PLATFORM_MACOS) + { + create_info.System = "cocoa"; + create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, NULL); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(SDL_PLATFORM_LINUX) + if (SDL_strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0) + { + create_info.System = "wayland"; + create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_DISPLAY_POINTER, NULL); + create_info.RawSurface = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WAYLAND_SURFACE_POINTER, NULL); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } + else if (!SDL_strcmp(SDL_GetCurrentVideoDriver(), "x11")) + { + create_info.System = "x11"; + create_info.RawWindow = (void*)SDL_GetNumberProperty(propertiesID, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0); + create_info.RawDisplay = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, NULL); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#elif defined(SDL_PLATFORM_WIN32) + { + create_info.System = "win32"; + create_info.RawWindow = (void*)SDL_GetPointerProperty(propertiesID, SDL_PROP_WINDOW_WIN32_HWND_POINTER, NULL); + create_info.RawInstance = (void*)::GetModuleHandle(NULL); + return ImGui_ImplWGPU_CreateWGPUSurfaceHelper(&create_info); + } +#else +#error "Unsupported WebGPU native platform!" +#endif + return nullptr; +} +#endif + +#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) +static WGPUAdapter RequestAdapter(wgpu::Instance& instance) +{ + wgpu::Adapter acquired_adapter; + wgpu::RequestAdapterOptions adapter_options; + 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; + } + acquired_adapter = std::move(adapter); + }; + + // Synchronously (wait until) acquire Adapter + wgpu::Future waitAdapterFunc { instance.RequestAdapter(&adapter_options, wgpu::CallbackMode::WaitAnyOnly, onRequestAdapter) }; + wgpu::WaitStatus waitStatusAdapter = instance.WaitAny(waitAdapterFunc, UINT64_MAX); + IM_ASSERT(acquired_adapter != nullptr && waitStatusAdapter == wgpu::WaitStatus::Success && "Error on Adapter request"); + return acquired_adapter.MoveToCHandle(); +} + +static WGPUDevice RequestDevice(wgpu::Instance& instance, wgpu::Adapter& adapter) +{ + // Set device callback functions + wgpu::DeviceDescriptor device_desc; + device_desc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, + [](const wgpu::Device&, wgpu::DeviceLostReason type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetDeviceLostReasonName((WGPUDeviceLostReason)type), msg.data); } + ); + device_desc.SetUncapturedErrorCallback( + [](const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView msg) { fprintf(stderr, "%s error: %s\n", ImGui_ImplWGPU_GetErrorTypeName((WGPUErrorType)type), msg.data); } + ); + + wgpu::Device acquired_device; + auto onRequestDevice = [&](wgpu::RequestDeviceStatus status, wgpu::Device local_device, wgpu::StringView message) + { + if (status != wgpu::RequestDeviceStatus::Success) + { + printf("Failed to get an device: %s\n", message.data); + return; + } + acquired_device = std::move(local_device); + }; + + // Synchronously (wait until) get Device + wgpu::Future waitDeviceFunc { adapter.RequestDevice(&device_desc, wgpu::CallbackMode::WaitAnyOnly, onRequestDevice) }; + wgpu::WaitStatus waitStatusDevice = instance.WaitAny(waitDeviceFunc, UINT64_MAX); + IM_ASSERT(acquired_device != nullptr && waitStatusDevice == wgpu::WaitStatus::Success && "Error on Device request"); + return acquired_device.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 RequestAdapter(WGPUInstance& instance) +{ + WGPURequestAdapterOptions adapter_options = {}; + + WGPUAdapter local_adapter; + WGPURequestAdapterCallbackInfo adapterCallbackInfo = {}; + adapterCallbackInfo.callback = handle_request_adapter; + adapterCallbackInfo.userdata1 = &local_adapter; + + wgpuInstanceRequestAdapter(instance, &adapter_options, adapterCallbackInfo); + IM_ASSERT(local_adapter && "Error on Adapter request"); + return local_adapter; +} + +static WGPUDevice RequestDevice(WGPUAdapter& adapter) +{ + WGPUDevice local_device; + WGPURequestDeviceCallbackInfo deviceCallbackInfo = {}; + deviceCallbackInfo.callback = handle_request_device; + deviceCallbackInfo.userdata1 = &local_device; + wgpuAdapterRequestDevice(adapter, nullptr, deviceCallbackInfo); + IM_ASSERT(local_device && "Error on Device request"); + return local_device; +} +#endif // __EMSCRIPTEN__ +#endif // IMGUI_IMPL_WEBGPU_BACKEND_WGPU + +static bool InitWGPU(SDL_Window* 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 instance_desc = {}; + static constexpr wgpu::InstanceFeatureName timedWaitAny = wgpu::InstanceFeatureName::TimedWaitAny; + instance_desc.requiredFeatureCount = 1; + instance_desc.requiredFeatures = &timedWaitAny; + wgpu::Instance instance = wgpu::CreateInstance(&instance_desc); + + wgpu::Adapter adapter = RequestAdapter(instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter.Get()); + + wgpu_device = RequestDevice(instance, adapter); + + // Create the surface. +#ifdef __EMSCRIPTEN__ + wgpu::EmscriptenSurfaceSourceCanvasHTMLSelector canvas_desc = {}; + canvas_desc.selector = "#canvas"; + + wgpu::SurfaceDescriptor surface_desc = {}; + surface_desc.nextInChain = &canvas_desc; + wgpu::Surface surface = instance.CreateSurface(&surface_desc); +#else + wgpu::Surface surface = CreateWGPUSurface(instance.Get(), 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(); + IM_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( + [](WGPULogLevel level, WGPUStringView msg, void* userdata) { fprintf(stderr, "%s: %.*s\n", ImGui_ImplWGPU_GetLogLevelName(level), (int)msg.length, msg.data); }, nullptr + ); + wgpuSetLogLevel(WGPULogLevel_Warn); + + WGPUAdapter adapter = RequestAdapter(wgpu_instance); + ImGui_ImplWGPU_DebugPrintAdapterInfo(adapter); + + wgpu_device = RequestDevice(adapter); + + // Create the surface. + wgpu_surface = CreateWGPUSurface(wgpu_instance, 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; +}