Examples: GLFW+WebGPU: added support for WebGPU-native/Dawn (#7435, #7132)
diff --git a/.gitignore b/.gitignore
index 211d21d..9570dac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,8 @@
examples/example_glfw_opengl3/web/*
examples/example_sdl2_opengl3/web/*
examples/example_emscripten_wgpu/web/*
+## Dawn build dependencies
+examples/example_emscripten_wgpu/external/*
## JetBrains IDE artifacts
.idea
diff --git a/examples/example_emscripten_wgpu/CMakeLists.txt b/examples/example_emscripten_wgpu/CMakeLists.txt
new file mode 100644
index 0000000..646c5b3
--- /dev/null
+++ b/examples/example_emscripten_wgpu/CMakeLists.txt
@@ -0,0 +1,103 @@
+# Building for desktop (WebGPU-native) with Dawn:
+#
+# git clone https://github.com/google/dawn dawn
+# cmake -B build -DIMGUI_DAWN_DIR=dawn
+# cmake --build build
+#
+# The resulting binary will be found at one of the following locations:
+# * build/Debug/example_emscripten_wgpu[.exe]
+# * build/example_emscripten_wgpu[.exe]
+
+# Building for Emscripten:
+#
+# 1. Install Emscripten SDK following the instructions: https://emscripten.org/docs/getting_started/downloads.html
+# 2. Install Ninja build system
+# 3. emcmake cmake -G Ninja -B build
+# 3. cmake --build build
+# 4. emrun build/index.html
+
+cmake_minimum_required(VERSION 3.10.2)
+project(imgui_example_emscripten_wgpu C CXX)
+
+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 ../../)
+
+# Libraries
+if(EMSCRIPTEN)
+ set(LIBRARIES glfw)
+ add_compile_options(-sDISABLE_EXCEPTION_CATCHING=1 -DIMGUI_DISABLE_FILE_FUNCTIONS=1)
+else()
+ # Dawn wgpu desktop
+ set(DAWN_FETCH_DEPENDENCIES ON)
+ set(IMGUI_DAWN_DIR CACHE PATH "Path to Dawn repository")
+ if (NOT IMGUI_DAWN_DIR)
+ message(FATAL_ERROR "Please specify the Dawn repository by setting IMGUI_DAWN_DIR")
+ 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)
+endif()
+
+add_executable(example_emscripten_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
+)
+target_include_directories(example_emscripten_wgpu PUBLIC
+ ${IMGUI_DIR}
+ ${IMGUI_DIR}/backends
+)
+
+target_link_libraries(example_emscripten_wgpu PUBLIC ${LIBRARIES})
+
+# Emscripten settings
+if(EMSCRIPTEN)
+ target_link_options(example_emscripten_wgpu PRIVATE
+ "-sUSE_WEBGPU=1"
+ "-sUSE_GLFW=3"
+ "-sWASM=1"
+ "-sALLOW_MEMORY_GROWTH=1"
+ "-sNO_EXIT_RUNTIME=0"
+ "-sASSERTIONS=1"
+ "-sDISABLE_EXCEPTION_CATCHING=1"
+ "-sNO_FILESYSTEM=1"
+ )
+ set_target_properties(example_emscripten_wgpu PROPERTIES OUTPUT_NAME "index")
+ # copy our custom index.html to build directory
+ add_custom_command(TARGET example_emscripten_wgpu POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy_if_different "${CMAKE_CURRENT_LIST_DIR}/web/index.html" $<TARGET_FILE_DIR:example_emscripten_wgpu>
+ )
+endif()
diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp
index 43e93a2..ad6c3dd 100644
--- a/examples/example_emscripten_wgpu/main.cpp
+++ b/examples/example_emscripten_wgpu/main.cpp
@@ -1,4 +1,6 @@
-// Dear ImGui: standalone example application for Emscripten, using GLFW + WebGPU
+// Dear ImGui: standalone example application for using GLFW + WebGPU
+// Dawn is used as a WebGPU implementation on desktop and Emscripten is supported for
+// publishing on web.
// (Emscripten is a C++-to-javascript compiler, used to publish executables for the web. See https://emscripten.org/)
// Learn about Dear ImGui:
@@ -10,12 +12,17 @@
#include "imgui.h"
#include "imgui_impl_glfw.h"
#include "imgui_impl_wgpu.h"
+
#include <stdio.h>
+
#ifdef __EMSCRIPTEN__
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/html5_webgpu.h>
+#else
+#include <webgpu/webgpu_glfw.h>
#endif
+
#include <GLFW/glfw3.h>
#include <webgpu/webgpu.h>
#include <webgpu/webgpu_cpp.h>
@@ -26,15 +33,16 @@
#endif
// Global WebGPU required states
+static WGPUInstance wgpu_instance = nullptr;
static WGPUDevice wgpu_device = nullptr;
static WGPUSurface wgpu_surface = nullptr;
static WGPUTextureFormat wgpu_preferred_fmt = WGPUTextureFormat_RGBA8Unorm;
static WGPUSwapChain wgpu_swap_chain = nullptr;
-static int wgpu_swap_chain_width = 0;
-static int wgpu_swap_chain_height = 0;
+static int wgpu_swap_chain_width = 1280;
+static int wgpu_swap_chain_height = 720;
// Forward declarations
-static bool InitWGPU();
+static bool InitWGPU(GLFWwindow* window);
static void CreateSwapChain(int width, int height);
static void glfw_error_callback(int error, const char* description)
@@ -56,6 +64,33 @@
printf("%s error: %s\n", error_type_lbl, message);
}
+static WGPUAdapter requestAdapter(WGPUInstance instance) {
+ auto onAdapterRequestEnded = [](WGPURequestAdapterStatus status, WGPUAdapter adapter, char const* message, void* pUserData) {
+ if (status == WGPURequestAdapterStatus_Success) {
+ *static_cast<WGPUAdapter*>(pUserData) = adapter;
+ } else {
+ printf("Could not get WebGPU adapter: %s\n", message);
+ }
+ };
+ WGPUAdapter adapter;
+ wgpuInstanceRequestAdapter(instance, nullptr, onAdapterRequestEnded, (void*)&adapter);
+ return adapter;
+}
+
+static WGPUDevice requestDevice(WGPUAdapter& adapter) {
+ auto onDeviceRequestEnded = [](WGPURequestDeviceStatus status, WGPUDevice device, char const* message, void* pUserData) {
+ if (status == WGPURequestDeviceStatus_Success) {
+ *static_cast<WGPUDevice*>(pUserData) = device;
+ } else {
+ printf("Could not get WebGPU device: %s\n", message);
+ }
+ };
+
+ WGPUDevice device;
+ wgpuAdapterRequestDevice(adapter, nullptr, onDeviceRequestEnded, (void*)&device);
+ return device;
+}
+
// Main code
int main(int, char**)
{
@@ -66,18 +101,19 @@
// Make sure GLFW does not initialize any graphics context.
// This needs to be done explicitly later.
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
- GLFWwindow* window = glfwCreateWindow(1280, 720, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr);
+ GLFWwindow* window = glfwCreateWindow(wgpu_swap_chain_width, wgpu_swap_chain_height, "Dear ImGui GLFW+WebGPU example", nullptr, nullptr);
if (window == nullptr)
return 1;
// Initialize the WebGPU environment
- if (!InitWGPU())
+ if (!InitWGPU(window))
{
if (window)
glfwDestroyWindow(window);
glfwTerminate();
return 1;
}
+ CreateSwapChain(wgpu_swap_chain_width, wgpu_swap_chain_height);
glfwShowWindow(window);
// Setup Dear ImGui context
@@ -115,7 +151,7 @@
//io.Fonts->AddFontDefault();
#ifndef IMGUI_DISABLE_FILE_FUNCTIONS
//io.Fonts->AddFontFromFileTTF("fonts/segoeui.ttf", 18.0f);
- io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f);
+ // io.Fonts->AddFontFromFileTTF("fonts/DroidSans.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("fonts/Roboto-Medium.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("fonts/Cousine-Regular.ttf", 15.0f);
//io.Fonts->AddFontFromFileTTF("fonts/ProggyTiny.ttf", 10.0f);
@@ -200,6 +236,11 @@
// Rendering
ImGui::Render();
+#ifndef __EMSCRIPTEN__
+ // Tick needs to be called in Dawn to display validation errors
+ wgpuDeviceTick(wgpu_device);
+#endif
+
WGPURenderPassColorAttachment color_attachments = {};
color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
color_attachments.loadOp = WGPULoadOp_Clear;
@@ -223,6 +264,15 @@
WGPUCommandBuffer cmd_buffer = wgpuCommandEncoderFinish(encoder, &cmd_buffer_desc);
WGPUQueue queue = wgpuDeviceGetQueue(wgpu_device);
wgpuQueueSubmit(queue, 1, &cmd_buffer);
+
+#ifndef __EMSCRIPTEN__
+ wgpuSwapChainPresent(wgpu_swap_chain);
+#endif
+
+ wgpuTextureViewRelease(color_attachments.view);
+ wgpuRenderPassEncoderRelease(pass);
+ wgpuCommandEncoderRelease(encoder);
+ wgpuCommandBufferRelease(cmd_buffer);
}
#ifdef __EMSCRIPTEN__
EMSCRIPTEN_MAINLOOP_END;
@@ -239,29 +289,42 @@
return 0;
}
-static bool InitWGPU()
+static bool InitWGPU(GLFWwindow* window)
{
+ wgpu::Instance instance = wgpuCreateInstance(nullptr);
+
+#ifdef __EMSCRIPTEN__
wgpu_device = emscripten_webgpu_get_device();
if (!wgpu_device)
return false;
+#else
+ WGPUAdapter adapter = requestAdapter(instance.Get());
+ if (!adapter)
+ return false;
+ wgpu_device = requestDevice(adapter);
+#endif
- wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr);
-
- // Use C++ wrapper due to misbehavior in Emscripten.
- // Some offset computation for wgpuInstanceCreateSurface in JavaScript
- // seem to be inline with struct alignments in the C++ structure
+#ifdef __EMSCRIPTEN__
wgpu::SurfaceDescriptorFromCanvasHTMLSelector html_surface_desc = {};
html_surface_desc.selector = "#canvas";
-
wgpu::SurfaceDescriptor surface_desc = {};
surface_desc.nextInChain = &html_surface_desc;
-
- wgpu::Instance instance = wgpuCreateInstance(nullptr);
wgpu::Surface surface = instance.CreateSurface(&surface_desc);
+
wgpu::Adapter adapter = {};
wgpu_preferred_fmt = (WGPUTextureFormat)surface.GetPreferredFormat(adapter);
+#else
+ wgpu::Surface surface = wgpu::glfw::CreateSurfaceForWindow(instance, window);
+ if (!surface)
+ return false;
+ wgpu_preferred_fmt = WGPUTextureFormat_BGRA8Unorm;
+#endif
+
+ wgpu_instance = instance.MoveToCHandle();
wgpu_surface = surface.MoveToCHandle();
+ wgpuDeviceSetUncapturedErrorCallback(wgpu_device, wgpu_error_callback, nullptr);
+
return true;
}
diff --git a/examples/example_emscripten_wgpu/web/index.html b/examples/example_emscripten_wgpu/web/index.html
index 82b1c42..6fad6b2 100644
--- a/examples/example_emscripten_wgpu/web/index.html
+++ b/examples/example_emscripten_wgpu/web/index.html
@@ -63,6 +63,10 @@
// Initialize the graphics adapter
{
+ if (!navigator.gpu) {
+ throw Error("WebGPU not supported.");
+ }
+
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
Module.preinitializedWebGPUDevice = device;