diff options
| -rw-r--r-- | CMakeLists.txt | 85 | ||||
| -rw-r--r-- | FETCH_DEPS.md | 31 | ||||
| -rw-r--r-- | PROJECT_CONTEXT.md | 5 | ||||
| -rwxr-xr-x | scripts/project_init.sh | 25 | ||||
| -rw-r--r-- | src/platform.cc | 78 | ||||
| -rw-r--r-- | third_party/glfw3webgpu/glfw3webgpu.c | 181 | ||||
| -rw-r--r-- | third_party/glfw3webgpu/glfw3webgpu.h | 62 |
7 files changed, 332 insertions, 135 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index b06bd6d..fc4ea81 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,30 +6,22 @@ option(DEMO_SIZE_OPT "Enable size optimization flags" OFF) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Search in both debug and release -set(WGPU_SEARCH_PATHS - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/wgpu-native/target/debug - ${CMAKE_CURRENT_SOURCE_DIR}/third_party/wgpu-native/target/release -) - -find_library( - WGPU_LIBRARY NAMES libwgpu_native.a wgpu_native.lib wgpu_native - HINTS ${WGPU_SEARCH_PATHS} - REQUIRED -) +# Find wgpu-native (system install) +find_library(WGPU_LIBRARY NAMES wgpu_native libwgpu_native REQUIRED) +find_path(WGPU_INCLUDE_DIR NAMES webgpu.h PATH_SUFFIXES webgpu-headers REQUIRED) include_directories( src third_party - third_party/wgpu-native/ffi - third_party/wgpu-native/ffi/webgpu-headers + ${WGPU_INCLUDE_DIR} + third_party/glfw3webgpu ) find_package(glfw3 REQUIRED) set(DEMO_LIBS glfw ${WGPU_LIBRARY}) -# Platform-specific dependencies for wgpu-native +# Platform-specific dependencies if (APPLE) set_source_files_properties(src/platform.cc PROPERTIES COMPILE_FLAGS "-x objective-c++") list(APPEND DEMO_LIBS @@ -52,6 +44,7 @@ add_executable(demo64k src/audio/idct.cc src/audio/window.cc src/audio/synth.cc + third_party/glfw3webgpu/glfw3webgpu.c ) target_link_libraries(demo64k PRIVATE ${DEMO_LIBS}) @@ -86,31 +79,57 @@ if(DEMO_BUILD_TESTS) target_include_directories(test_synth PRIVATE src) add_test(NAME SynthEngineTest COMMAND test_synth) - add_executable(test_spectool - src/tests/test_spectool.cc - src/audio/audio.cc - src/audio/window.cc - src/audio/fdct.cc - src/audio/synth.cc - src/audio/idct.cc - ) - target_include_directories(test_spectool PRIVATE src third_party) + add_executable(test_spectool + + src/tests/test_spectool.cc + + src/audio/audio.cc + + src/audio/window.cc + + src/audio/fdct.cc + + src/audio/synth.cc + + src/audio/idct.cc + + third_party/glfw3webgpu/glfw3webgpu.c + + ) + + target_include_directories(test_spectool PRIVATE + + src + + third_party + + ${WGPU_INCLUDE_DIR} + + third_party/glfw3webgpu + + ) target_link_libraries(test_spectool PRIVATE ${DEMO_LIBS}) add_test(NAME SpectoolEndToEndTest COMMAND test_spectool) endif() option(DEMO_BUILD_TOOLS "Build tools" OFF) if(DEMO_BUILD_TOOLS) - add_executable(spectool - tools/spectool.cc - src/platform.cc - src/audio/audio.cc - src/audio/fdct.cc - src/audio/idct.cc - src/audio/window.cc - src/audio/synth.cc - ) - target_include_directories(spectool PRIVATE src third_party) + add_executable(spectool + tools/spectool.cc + src/platform.cc + src/audio/audio.cc + src/audio/fdct.cc + src/audio/idct.cc + src/audio/window.cc + src/audio/synth.cc + third_party/glfw3webgpu/glfw3webgpu.c + ) + target_include_directories(spectool PRIVATE + src + third_party + ${WGPU_INCLUDE_DIR} + third_party/glfw3webgpu + ) target_link_libraries(spectool PRIVATE ${DEMO_LIBS}) add_executable(specview diff --git a/FETCH_DEPS.md b/FETCH_DEPS.md index 3d5cc0e..24cbf08 100644 --- a/FETCH_DEPS.md +++ b/FETCH_DEPS.md @@ -35,8 +35,19 @@ third_party/miniaudio.h WebGPU implementation via wgpu-native. -Source: -https://github.com/gfx-rs/wgpu-native +### Installation + +**macOS:** +```bash +brew install wgpu-native +``` + +**Other platforms:** +Please install `wgpu-native` such that `libwgpu_native` (static or shared) is in your library path and headers are in your include path (under `webgpu/`). + +## glfw3webgpu + +Helper library for creating WebGPU surfaces from GLFW windows. ### Automatic fetch @@ -44,18 +55,4 @@ Use one of the provided scripts: - scripts/project_init.sh - scripts/project_init.bat -These scripts will run `git submodule update --init --recursive` to fetch `wgpu-native` and then build its static library. - -### Manual fetch - -Run the following commands in the project root directory: -```bash -git submodule add https://github.com/gfx-rs/wgpu-native third_party/wgpu-native -git submodule update --init --recursive -cd third_party/wgpu-native -make lib-native # Requires Rust toolchain and LLVM/Clang to be installed. -cd ../.. -``` - -Expected static library location (for linking): -- `third_party/wgpu-native/target/release/libwgpu_native.a` (or platform equivalent like `.lib` or `.dylib`) +These scripts will download `glfw3webgpu.h` and `glfw3webgpu.c` into `third_party/glfw3webgpu`. diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index 1ee929c..39f050c 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -49,6 +49,11 @@ Incoming tasks in no particular order: - `spectool`: A command-line tool for analyzing WAV/MP3 files into `.spec` spectrogram format and for playing back `.spec` files through the synth engine. - `specview`: A command-line tool for visualizing `.spec` spectrogram files in ASCII art. +### WebGPU Integration: +- **Strategy**: Uses system-installed `wgpu-native` (e.g., via Homebrew) for the implementation. +- **Surface Creation**: Uses `glfw3webgpu` helper library to abstract platform-specific surface creation from GLFW windows, keeping the codebase clean and cross-platform. +- **Headers**: Uses standard `<webgpu.h>` provided by the system install. + ### Coding Style: - **Standard**: Adopted a consistent coding style enforced by `.clang-format`. - **Rules**: 2-space indentation, no tabs, 80-column line limit. diff --git a/scripts/project_init.sh b/scripts/project_init.sh index db24c8f..549e146 100755 --- a/scripts/project_init.sh +++ b/scripts/project_init.sh @@ -14,19 +14,26 @@ else echo "miniaudio.h already present." fi -# wgpu-native submodule -if [ ! -d third_party/wgpu-native ]; then - echo "Fetching wgpu-native submodule..." - git submodule update --init --recursive +# glfw3webgpu (helper for GLFW + WebGPU) +mkdir -p third_party/glfw3webgpu +if [ ! -f third_party/glfw3webgpu/glfw3webgpu.h ]; then + echo "Fetching glfw3webgpu..." + curl -L https://raw.githubusercontent.com/eliemichel/glfw3webgpu/main/glfw3webgpu.h -o third_party/glfw3webgpu/glfw3webgpu.h + curl -L https://raw.githubusercontent.com/eliemichel/glfw3webgpu/main/glfw3webgpu.c -o third_party/glfw3webgpu/glfw3webgpu.c else - echo "wgpu-native submodule already present." + echo "glfw3webgpu already present." fi -if [ ! -f third_party/wgpu-native/target/release/libwgpu_native.a ]; then - echo "Building wgpu-native static library..." - (cd third_party/wgpu-native && make lib-native) +# Check for wgpu-native (system install) +if command -v brew >/dev/null 2>&1; then + if ! brew list wgpu-native >/dev/null 2>&1; then + echo "Warning: wgpu-native not found via brew. Installing..." + brew install wgpu-native + else + echo "wgpu-native found (via brew)." + fi else - echo "wgpu-native static library already built." + echo "Warning: Homebrew not found. Ensure wgpu-native is installed manually." fi echo "Done." diff --git a/src/platform.cc b/src/platform.cc index 09cc7ac..fa49b53 100644 --- a/src/platform.cc +++ b/src/platform.cc @@ -1,22 +1,7 @@ #include "platform.h" - -#ifdef _WIN32 -#define GLFW_EXPOSE_NATIVE_WIN32 -#elif defined(__APPLE__) -#define GLFW_EXPOSE_NATIVE_COCOA -#else -#define GLFW_EXPOSE_NATIVE_X11 -#define GLFW_EXPOSE_NATIVE_WAYLAND -#endif +#include "glfw3webgpu.h" #include <GLFW/glfw3.h> -#include <GLFW/glfw3native.h> - -#ifdef __APPLE__ -#import <QuartzCore/CAMetalLayer.h> -#import <AppKit/NSWindow.h> -#import <AppKit/NSView.h> -#endif static GLFWwindow *window = nullptr; static int windowed_x, windowed_y, windowed_w, windowed_h; @@ -82,64 +67,5 @@ GLFWwindow *platform_get_window() { return window; } double platform_get_time() { return glfwGetTime(); } WGPUSurface platform_create_wgpu_surface(WGPUInstance instance) { -#if defined(GLFW_EXPOSE_NATIVE_COCOA) - id metal_layer = NULL; - NSWindow *ns_window = glfwGetCocoaWindow(window); - [ns_window.contentView setWantsLayer:YES]; - metal_layer = [CAMetalLayer layer]; - [ns_window.contentView setLayer:metal_layer]; - - WGPUSurfaceSourceMetalLayer metal_src = {}; - metal_src.chain.sType = WGPUSType_SurfaceSourceMetalLayer; - metal_src.layer = metal_layer; - - WGPUSurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = (const WGPUChainedStruct *)&metal_src; - - return wgpuInstanceCreateSurface(instance, &surface_desc); - -#elif defined(GLFW_EXPOSE_NATIVE_WIN32) - HWND hwnd = glfwGetWin32Window(window); - HINSTANCE hinstance = GetModuleHandle(NULL); - - WGPUSurfaceSourceWindowsHWND win_src = {}; - win_src.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; - win_src.hinstance = hinstance; - win_src.hwnd = hwnd; - - WGPUSurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = (const WGPUChainedStruct *)&win_src; - - return wgpuInstanceCreateSurface(instance, &surface_desc); - -#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_WAYLAND) - if (glfwGetPlatform() == GLFW_PLATFORM_X11) { - Display *x11_display = glfwGetX11Display(); - Window x11_window = glfwGetX11Window(window); - - WGPUSurfaceSourceXlibWindow x11_src = {}; - x11_src.chain.sType = WGPUSType_SurfaceSourceXlibWindow; - x11_src.display = x11_display; - x11_src.window = x11_window; - - WGPUSurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = (const WGPUChainedStruct *)&x11_src; - - return wgpuInstanceCreateSurface(instance, &surface_desc); - } else if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { - struct wl_display *wayland_display = glfwGetWaylandDisplay(); - struct wl_surface *wayland_surface = glfwGetWaylandWindow(window); - - WGPUSurfaceSourceWaylandSurface wl_src = {}; - wl_src.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; - wl_src.display = wayland_display; - wl_src.surface = wayland_surface; - - WGPUSurfaceDescriptor surface_desc = {}; - surface_desc.nextInChain = (const WGPUChainedStruct *)&wl_src; - - return wgpuInstanceCreateSurface(instance, &surface_desc); - } -#endif - return nullptr; + return glfwCreateWindowWGPUSurface(instance, window); } diff --git a/third_party/glfw3webgpu/glfw3webgpu.c b/third_party/glfw3webgpu/glfw3webgpu.c new file mode 100644 index 0000000..27bf9ec --- /dev/null +++ b/third_party/glfw3webgpu/glfw3webgpu.c @@ -0,0 +1,181 @@ +/** + * This is an extension of GLFW for WebGPU, abstracting away the details of + * OS-specific operations. + * + * This file is part of the "Learn WebGPU for C++" book. + * https://eliemichel.github.io/LearnWebGPU + * + * Most of this code comes from the wgpu-native triangle example: + * https://github.com/gfx-rs/wgpu-native/blob/master/examples/triangle/main.c + * + * MIT License + * Copyright (c) 2022-2025 Elie Michel and the wgpu-native authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "glfw3webgpu.h" + +#include <webgpu.h> + +#include <GLFW/glfw3.h> + +#ifdef __EMSCRIPTEN__ +# define GLFW_EXPOSE_NATIVE_EMSCRIPTEN +# ifndef GLFW_PLATFORM_EMSCRIPTEN // not defined in older versions of emscripten +# define GLFW_PLATFORM_EMSCRIPTEN 0 +# endif +#else // __EMSCRIPTEN__ +# ifdef _GLFW_X11 +# define GLFW_EXPOSE_NATIVE_X11 +# endif +# ifdef _GLFW_WAYLAND +# define GLFW_EXPOSE_NATIVE_WAYLAND +# endif +# ifdef _GLFW_COCOA +# define GLFW_EXPOSE_NATIVE_COCOA +# endif +# ifdef _GLFW_WIN32 +# define GLFW_EXPOSE_NATIVE_WIN32 +# endif +#endif // __EMSCRIPTEN__ + +#ifdef GLFW_EXPOSE_NATIVE_COCOA +# include <Foundation/Foundation.h> +# include <QuartzCore/CAMetalLayer.h> +#endif + +#ifndef __EMSCRIPTEN__ +# include <GLFW/glfw3native.h> +#endif + +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window) { +#ifndef __EMSCRIPTEN__ + switch (glfwGetPlatform()) { +#else + // glfwGetPlatform is not available in older versions of emscripten + switch (GLFW_PLATFORM_EMSCRIPTEN) { +#endif + +#ifdef GLFW_EXPOSE_NATIVE_X11 + case GLFW_PLATFORM_X11: { + Display* x11_display = glfwGetX11Display(); + Window x11_window = glfwGetX11Window(window); + + WGPUSurfaceSourceXlibWindow fromXlibWindow; + fromXlibWindow.chain.sType = WGPUSType_SurfaceSourceXlibWindow; + fromXlibWindow.chain.next = NULL; + fromXlibWindow.display = x11_display; + fromXlibWindow.window = x11_window; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromXlibWindow.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_X11 + +#ifdef GLFW_EXPOSE_NATIVE_WAYLAND + case GLFW_PLATFORM_WAYLAND: { + struct wl_display* wayland_display = glfwGetWaylandDisplay(); + struct wl_surface* wayland_surface = glfwGetWaylandWindow(window); + + WGPUSurfaceSourceWaylandSurface fromWaylandSurface; + fromWaylandSurface.chain.sType = WGPUSType_SurfaceSourceWaylandSurface; + fromWaylandSurface.chain.next = NULL; + fromWaylandSurface.display = wayland_display; + fromWaylandSurface.surface = wayland_surface; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWaylandSurface.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WAYLAND + +#ifdef GLFW_EXPOSE_NATIVE_COCOA + case GLFW_PLATFORM_COCOA: { + id metal_layer = [CAMetalLayer layer]; + NSWindow* ns_window = glfwGetCocoaWindow(window); + [ns_window.contentView setWantsLayer : YES] ; + [ns_window.contentView setLayer : metal_layer] ; + + WGPUSurfaceSourceMetalLayer fromMetalLayer; + fromMetalLayer.chain.sType = WGPUSType_SurfaceSourceMetalLayer; + fromMetalLayer.chain.next = NULL; + fromMetalLayer.layer = metal_layer; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromMetalLayer.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_COCOA + +#ifdef GLFW_EXPOSE_NATIVE_WIN32 + case GLFW_PLATFORM_WIN32: { + HWND hwnd = glfwGetWin32Window(window); + HINSTANCE hinstance = GetModuleHandle(NULL); + + WGPUSurfaceSourceWindowsHWND fromWindowsHWND; + fromWindowsHWND.chain.sType = WGPUSType_SurfaceSourceWindowsHWND; + fromWindowsHWND.chain.next = NULL; + fromWindowsHWND.hinstance = hinstance; + fromWindowsHWND.hwnd = hwnd; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromWindowsHWND.chain; + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; + + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_WIN32 + +#ifdef GLFW_EXPOSE_NATIVE_EMSCRIPTEN + case GLFW_PLATFORM_EMSCRIPTEN: { +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + WGPUEmscriptenSurfaceSourceCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_EmscriptenSurfaceSourceCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = (WGPUStringView){ "canvas", WGPU_STRLEN }; +# else + WGPUSurfaceDescriptorFromCanvasHTMLSelector fromCanvasHTMLSelector; + fromCanvasHTMLSelector.chain.sType = WGPUSType_SurfaceDescriptorFromCanvasHTMLSelector; + fromCanvasHTMLSelector.selector = "canvas"; +# endif + fromCanvasHTMLSelector.chain.next = NULL; + + WGPUSurfaceDescriptor surfaceDescriptor; + surfaceDescriptor.nextInChain = &fromCanvasHTMLSelector.chain; +# ifdef WEBGPU_BACKEND_EMDAWNWEBGPU + surfaceDescriptor.label = (WGPUStringView){ NULL, WGPU_STRLEN }; +# else + surfaceDescriptor.label = NULL; +# endif + return wgpuInstanceCreateSurface(instance, &surfaceDescriptor); + } +#endif // GLFW_EXPOSE_NATIVE_EMSCRIPTEN + + default: + // Unsupported platform + return NULL; + } +} diff --git a/third_party/glfw3webgpu/glfw3webgpu.h b/third_party/glfw3webgpu/glfw3webgpu.h new file mode 100644 index 0000000..729afcc --- /dev/null +++ b/third_party/glfw3webgpu/glfw3webgpu.h @@ -0,0 +1,62 @@ +/** + * This is an extension of GLFW for WebGPU, abstracting away the details of + * OS-specific operations. + * + * This file is part of the "Learn WebGPU for C++" book. + * https://eliemichel.github.io/LearnWebGPU + * + * MIT License + * Copyright (c) 2022-2024 Elie Michel and the wgpu-native authors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef _glfw3_webgpu_h_ +#define _glfw3_webgpu_h_ + +#include <webgpu.h> +#include <GLFW/glfw3.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/*! @brief Creates a WebGPU surface for the specified window. + * + * This function creates a WGPUSurface object for the specified window. + * + * If the surface cannot be created, this function returns `NULL`. + * + * It is the responsibility of the caller to destroy the window surface. The + * window surface must be destroyed using `wgpuSurfaceRelease`. + * + * @param[in] instance The WebGPU instance to create the surface in. + * @param[in] window The window to create the surface for. + * @return The handle of the surface. This is set to `NULL` if an error + * occurred. + * + * @ingroup webgpu + */ +WGPUSurface glfwCreateWindowWGPUSurface(WGPUInstance instance, GLFWwindow* window); + +#ifdef __cplusplus +} +#endif + +#endif // _glfw3_webgpu_h_ |
