From 7a27b00e7ab443dd0b42cfdc109f19d988c8cc4b Mon Sep 17 00:00:00 2001 From: Elias Daler Date: Sun, 24 Mar 2024 14:18:10 +0100 Subject: [PATCH 1/5] WIP --- .../example_emscripten_wgpu/CMakeLists.txt | 73 ++++++++++++++ .../Makefile.emscripten | 88 ----------------- examples/example_emscripten_wgpu/main.cpp | 96 +++++++++++++++---- .../example_emscripten_wgpu/web/index.html | 77 ++++++--------- 4 files changed, 184 insertions(+), 150 deletions(-) create mode 100644 examples/example_emscripten_wgpu/CMakeLists.txt delete mode 100644 examples/example_emscripten_wgpu/Makefile.emscripten diff --git a/examples/example_emscripten_wgpu/CMakeLists.txt b/examples/example_emscripten_wgpu/CMakeLists.txt new file mode 100644 index 000000000000..0f32d5924bee --- /dev/null +++ b/examples/example_emscripten_wgpu/CMakeLists.txt @@ -0,0 +1,73 @@ +# Building: +# git clone https://github.com/google/dawn dawn +# cmake -B build -DIMGUI_DAWN_DIR=dawn +# cmake --build build + +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 11) +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DVK_PROTOTYPES") +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DVK_PROTOTYPES") + +# set(BUILD_TESTING OFF) +# set(BUILD_TESTING_STATIC OFF) +# set(BUILD_TESTING_SHARED OFF) + +# 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") + add_subdirectory("${IMGUI_DAWN_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/dawn") + 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 +) + +# Libraries +if(EMSCRIPTEN) + set_target_properties(example_emscripten_wgpu PROPERTIES OUTPUT_NAME "index") + set_target_properties(example_emscripten_wgpu PROPERTIES SUFFIX ".html") + set_target_properties(example_emscripten_wgpu PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/web) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --shell-file ${CMAKE_SOURCE_DIR}/../libs/emscripten/shell_minimal.html") + + 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" + "-sASYNCIFY=1" # Required by WebGPU-C++ + ) +endif() + +target_link_libraries(example_emscripten_wgpu ${LIBRARIES}) diff --git a/examples/example_emscripten_wgpu/Makefile.emscripten b/examples/example_emscripten_wgpu/Makefile.emscripten deleted file mode 100644 index 5c79f0c77dbb..000000000000 --- a/examples/example_emscripten_wgpu/Makefile.emscripten +++ /dev/null @@ -1,88 +0,0 @@ -# -# Makefile to use with emscripten -# See https://emscripten.org/docs/getting_started/downloads.html -# for installation instructions. -# -# This Makefile assumes you have loaded emscripten's environment. -# (On Windows, you may need to execute emsdk_env.bat or encmdprompt.bat ahead) -# -# Running `make` will produce three files: -# - web/index.html (current stored in the repository) -# - web/index.js -# - web/index.wasm -# -# All three are needed to run the demo. - -CC = emcc -CXX = em++ -WEB_DIR = web -EXE = $(WEB_DIR)/index.js -IMGUI_DIR = ../.. -SOURCES = main.cpp -SOURCES += $(IMGUI_DIR)/imgui.cpp $(IMGUI_DIR)/imgui_demo.cpp $(IMGUI_DIR)/imgui_draw.cpp $(IMGUI_DIR)/imgui_tables.cpp $(IMGUI_DIR)/imgui_widgets.cpp -SOURCES += $(IMGUI_DIR)/backends/imgui_impl_glfw.cpp $(IMGUI_DIR)/backends/imgui_impl_wgpu.cpp -OBJS = $(addsuffix .o, $(basename $(notdir $(SOURCES)))) -UNAME_S := $(shell uname -s) -CPPFLAGS = -LDFLAGS = -EMS = - -##--------------------------------------------------------------------- -## EMSCRIPTEN OPTIONS -##--------------------------------------------------------------------- - -# ("EMS" options gets added to both CPPFLAGS and LDFLAGS, whereas some options are for linker only) -EMS += -s DISABLE_EXCEPTION_CATCHING=1 -LDFLAGS += -s USE_GLFW=3 -s USE_WEBGPU=1 -LDFLAGS += -s WASM=1 -s ALLOW_MEMORY_GROWTH=1 -s NO_EXIT_RUNTIME=0 -s ASSERTIONS=1 - -# Emscripten allows preloading a file or folder to be accessible at runtime. -# The Makefile for this example project suggests embedding the misc/fonts/ folder into our application, it will then be accessible as "/fonts" -# See documentation for more details: https://emscripten.org/docs/porting/files/packaging_files.html -# (Default value is 0. Set to 1 to enable file-system and include the misc/fonts/ folder as part of the build.) -USE_FILE_SYSTEM ?= 0 -ifeq ($(USE_FILE_SYSTEM), 0) -LDFLAGS += -s NO_FILESYSTEM=1 -CPPFLAGS += -DIMGUI_DISABLE_FILE_FUNCTIONS -endif -ifeq ($(USE_FILE_SYSTEM), 1) -LDFLAGS += --no-heap-copy --preload-file ../../misc/fonts@/fonts -endif - -##--------------------------------------------------------------------- -## FINAL BUILD FLAGS -##--------------------------------------------------------------------- - -CPPFLAGS += -I$(IMGUI_DIR) -I$(IMGUI_DIR)/backends -#CPPFLAGS += -g -CPPFLAGS += -Wall -Wformat -Os $(EMS) -#LDFLAGS += --shell-file shell_minimal.html -LDFLAGS += $(EMS) - -##--------------------------------------------------------------------- -## BUILD RULES -##--------------------------------------------------------------------- - -%.o:%.cpp - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< - -%.o:$(IMGUI_DIR)/%.cpp - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< - -%.o:$(IMGUI_DIR)/backends/%.cpp - $(CXX) $(CPPFLAGS) $(CXXFLAGS) -c -o $@ $< - -all: $(EXE) - @echo Build complete for $(EXE) - -$(WEB_DIR): - mkdir $@ - -serve: all - python3 -m http.server -d $(WEB_DIR) - -$(EXE): $(OBJS) $(WEB_DIR) - $(CXX) -o $@ $(OBJS) $(LDFLAGS) - -clean: - rm -f $(EXE) $(OBJS) $(WEB_DIR)/*.js $(WEB_DIR)/*.wasm $(WEB_DIR)/*.wasm.pre diff --git a/examples/example_emscripten_wgpu/main.cpp b/examples/example_emscripten_wgpu/main.cpp index 43e93a2ad06d..6c529bce0781 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 + #ifdef __EMSCRIPTEN__ #include #include #include +#else +#include #endif + #include #include #include @@ -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 @@ 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(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(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,12 +101,12 @@ 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); @@ -115,7 +150,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); @@ -200,8 +235,15 @@ 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 = {}; +#ifndef __EMSCRIPTEN__ color_attachments.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif 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 }; @@ -223,6 +265,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; @@ -239,29 +290,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; } diff --git a/examples/example_emscripten_wgpu/web/index.html b/examples/example_emscripten_wgpu/web/index.html index 82b1c422151f..8641ccc9e455 100644 --- a/examples/example_emscripten_wgpu/web/index.html +++ b/examples/example_emscripten_wgpu/web/index.html @@ -3,9 +3,10 @@ - Dear ImGui Emscripten+WebGPU example + Dear ImGui Emscripten example