Skip to content

Commit

Permalink
Examples: GLFW+WebGPU: added support for WebGPU-native/Dawn (#7435, #…
Browse files Browse the repository at this point in the history
  • Loading branch information
eliasdaler authored and ocornut committed Apr 16, 2024
1 parent b475309 commit f9df6bf
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 16 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ examples/*.out.wasm
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
Expand Down
103 changes: 103 additions & 0 deletions examples/example_emscripten_wgpu/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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()
95 changes: 79 additions & 16 deletions examples/example_emscripten_wgpu/main.cpp
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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>
Expand All @@ -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)
Expand All @@ -56,6 +64,33 @@ static void wgpu_error_callback(WGPUErrorType error_type, const char* message, v
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**)
{
Expand All @@ -66,18 +101,19 @@ int main(int, char**)
// 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
Expand Down Expand Up @@ -115,7 +151,7 @@ int main(int, char**)
//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);
Expand Down Expand Up @@ -200,6 +236,11 @@ int main(int, char**)
// 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;
Expand All @@ -223,6 +264,15 @@ int main(int, char**)
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;
Expand All @@ -239,29 +289,42 @@ int main(int, char**)
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;
}

Expand Down
4 changes: 4 additions & 0 deletions examples/example_emscripten_wgpu/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit f9df6bf

Please sign in to comment.