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;