From b2c59b0e0317f8801660bde8129150f410704a8d Mon Sep 17 00:00:00 2001 From: Birk Skyum <74932975+birkskyum@users.noreply.github.com> Date: Thu, 23 Oct 2025 14:03:33 +0200 Subject: [PATCH] WebGPU - Add support for wgpu (#3899) --- .github/workflows/macos-ci.yml | 16 +- .gitmodules | 6 + CMakeLists.txt | 16 +- CMakePresets.json | 8 +- include/mbgl/webgpu/draw_scope_resource.hpp | 2 +- include/mbgl/webgpu/renderer_backend.hpp | 7 + include/mbgl/webgpu/wgpu_cpp_compat.hpp | 6 + platform/glfw/CMakeLists.txt | 3 + platform/glfw/glfw_view.cpp | 3 + platform/glfw/glfw_webgpu_backend.hpp | 19 +- platform/glfw/glfw_webgpu_backend_impl.ipp | 516 ++++++++++++-------- src/mbgl/webgpu/drawable.cpp | 52 +- src/mbgl/webgpu/drawable_builder.cpp | 2 +- src/mbgl/webgpu/headless_backend.cpp | 90 +++- src/mbgl/webgpu/offscreen_texture.cpp | 46 +- src/mbgl/webgpu/render_pass.cpp | 29 +- src/mbgl/webgpu/upload_pass.cpp | 2 +- src/mbgl/webgpu/webgpu_cpp_impl.cpp | 6 + vendor/dawn.cmake | 6 - vendor/webgpu-cpp | 1 + vendor/webgpu.cmake | 14 +- vendor/wgpu-native | 1 + vendor/wgpu.cmake | 262 ++++++++++ 23 files changed, 856 insertions(+), 257 deletions(-) create mode 100644 src/mbgl/webgpu/webgpu_cpp_impl.cpp create mode 160000 vendor/webgpu-cpp create mode 160000 vendor/wgpu-native create mode 100644 vendor/wgpu.cmake diff --git a/.github/workflows/macos-ci.yml b/.github/workflows/macos-ci.yml index 82dd3eba6..b605acbdb 100644 --- a/.github/workflows/macos-ci.yml +++ b/.github/workflows/macos-ci.yml @@ -102,8 +102,8 @@ jobs: variant: - impl: dawn name: Dawn - # - impl: wgpu - # name: wgpu-native + - impl: wgpu + name: wgpu steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v4 @@ -134,7 +134,17 @@ jobs: - name: Build targets run: | cmake --build build-macos-webgpu-${{ matrix.variant.impl }} --target \ - mbgl-render-test-runner + mbgl-render-test-runner \ + mbgl-expression-test \ + mbgl-test-runner + + - name: Run unit tests + id: unit_test + run: build-macos-webgpu-${{ matrix.variant.impl }}/mbgl-test-runner --gtest_filter=-Map.StyleNetworkErrorRetry:LocalGlyphRasterizer.PingFang:LocalGlyphRasterizer.PingFangWithBoldInStyle:LocalGlyphRasterizer.PingFangSemibold:Map.Offline + + - name: Run expression tests + id: expression_test + run: build-macos-webgpu-${{ matrix.variant.impl }}/expression-test/mbgl-expression-test - name: Prepare render test output directory run: mkdir -p metrics/macos-webgpu-${{ matrix.variant.impl }} diff --git a/.gitmodules b/.gitmodules index a5b138d33..18819c997 100644 --- a/.gitmodules +++ b/.gitmodules @@ -103,3 +103,9 @@ [submodule "vendor/kdbush.hpp"] path = vendor/kdbush.hpp url = https://github.com/mourner/kdbush.hpp.git +[submodule "vendor/webgpu-cpp"] + path = vendor/webgpu-cpp + url = https://github.com/eliemichel/WebGPU-Cpp.git +[submodule "vendor/wgpu-native"] + path = vendor/wgpu-native + url = https://github.com/gfx-rs/wgpu-native.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 765de0d8b..b768f3442 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1415,10 +1415,18 @@ if(MLN_WITH_WEBGPU) include(${PROJECT_SOURCE_DIR}/vendor/webgpu.cmake) # Include Dawn integration when requested - if(MLN_WEBGPU_IMPL STREQUAL "dawn") + if(MLN_WEBGPU_IMPL_DAWN) include(${PROJECT_SOURCE_DIR}/vendor/dawn.cmake) if(TARGET mbgl-vendor-dawn) - target_link_libraries(mbgl-core PUBLIC mbgl-vendor-dawn) + target_link_libraries(mbgl-core PRIVATE mbgl-vendor-dawn) + endif() + elseif(MLN_WEBGPU_IMPL_WGPU) + # Include wgpu-native integration + include(${PROJECT_SOURCE_DIR}/vendor/wgpu.cmake) + if(TARGET mbgl-vendor-wgpu) + target_link_libraries(mbgl-core PRIVATE mbgl-vendor-wgpu) + # Add WebGPU-Cpp implementation file (required for wgpu-native backend) + list(APPEND SRC_FILES ${PROJECT_SOURCE_DIR}/src/mbgl/webgpu/webgpu_cpp_impl.cpp) endif() endif() endif() @@ -1633,6 +1641,10 @@ if(TARGET mbgl-vendor-dawn) list(APPEND EXPORT_TARGETS mbgl-vendor-dawn) endif() +if(TARGET mbgl-vendor-wgpu) + list(APPEND EXPORT_TARGETS mbgl-vendor-wgpu) +endif() + export(TARGETS ${EXPORT_TARGETS} FILE MapboxCoreTargets.cmake) if(MLN_WITH_VULKAN) diff --git a/CMakePresets.json b/CMakePresets.json index 21afe39d7..74569b967 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -89,7 +89,7 @@ "inherits": "macos", "cacheVariables": { "MLN_WITH_WEBGPU": "ON", - "MLN_WEBGPU_IMPL": "dawn", + "MLN_WEBGPU_IMPL_DAWN": "ON", "MLN_DARWIN_USE_LIBUV": "ON" } }, @@ -101,7 +101,7 @@ "inherits": "macos", "cacheVariables": { "MLN_WITH_WEBGPU": "ON", - "MLN_WEBGPU_IMPL": "wgpu", + "MLN_WEBGPU_IMPL_WGPU": "ON", "MLN_DARWIN_USE_LIBUV": "ON" } }, @@ -149,7 +149,7 @@ "inherits": "linux", "cacheVariables": { "MLN_WITH_WEBGPU": "ON", - "MLN_WEBGPU_IMPL": "dawn", + "MLN_WEBGPU_IMPL_DAWN": "ON", "MLN_WITH_WAYLAND": "OFF", "MLN_WITH_X11": "ON" } @@ -160,7 +160,7 @@ "inherits": "linux", "cacheVariables": { "MLN_WITH_WEBGPU": "ON", - "MLN_WEBGPU_IMPL": "wgpu", + "MLN_WEBGPU_IMPL_WGPU": "ON", "MLN_WITH_WAYLAND": "OFF", "MLN_WITH_X11": "ON" } diff --git a/include/mbgl/webgpu/draw_scope_resource.hpp b/include/mbgl/webgpu/draw_scope_resource.hpp index cf0a6662d..9b0746345 100644 --- a/include/mbgl/webgpu/draw_scope_resource.hpp +++ b/include/mbgl/webgpu/draw_scope_resource.hpp @@ -13,7 +13,7 @@ public: ~DrawScopeResource() override = default; private: - Context& context; + [[maybe_unused]] Context& context; }; } // namespace webgpu diff --git a/include/mbgl/webgpu/renderer_backend.hpp b/include/mbgl/webgpu/renderer_backend.hpp index ace9750a0..062e09433 100644 --- a/include/mbgl/webgpu/renderer_backend.hpp +++ b/include/mbgl/webgpu/renderer_backend.hpp @@ -2,6 +2,13 @@ #include #include + +#if MLN_WEBGPU_IMPL_DAWN +#include +#elif MLN_WEBGPU_IMPL_WGPU +#include +#endif + #include #include diff --git a/include/mbgl/webgpu/wgpu_cpp_compat.hpp b/include/mbgl/webgpu/wgpu_cpp_compat.hpp index d249b2796..29bae7bb9 100644 --- a/include/mbgl/webgpu/wgpu_cpp_compat.hpp +++ b/include/mbgl/webgpu/wgpu_cpp_compat.hpp @@ -10,7 +10,13 @@ #pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif +#if MLN_WEBGPU_IMPL_DAWN +// For Dawn backend, use Dawn's C++ wrapper #include +#elif MLN_WEBGPU_IMPL_WGPU +// For wgpu-native backend, use the WebGPU-Cpp wrapper +#include +#endif #if defined(__clang__) #pragma clang diagnostic pop diff --git a/platform/glfw/CMakeLists.txt b/platform/glfw/CMakeLists.txt index 82d2ebf3b..24cc00654 100644 --- a/platform/glfw/CMakeLists.txt +++ b/platform/glfw/CMakeLists.txt @@ -84,6 +84,9 @@ if(MLN_WITH_WEBGPU) if(TARGET mbgl-vendor-dawn) target_link_libraries(mbgl-glfw PRIVATE mbgl-vendor-dawn) endif() + if(TARGET mbgl-vendor-wgpu) + target_link_libraries(mbgl-glfw PRIVATE mbgl-vendor-wgpu) + endif() endif() if(DEFINED MLN_WITH_X11 AND MLN_WITH_X11) diff --git a/platform/glfw/glfw_view.cpp b/platform/glfw/glfw_view.cpp index c33d42e2b..6cc585296 100644 --- a/platform/glfw/glfw_view.cpp +++ b/platform/glfw/glfw_view.cpp @@ -295,7 +295,10 @@ GLFWView::GLFWView(bool fullscreen_, // "... applications will typically want to set the swap interval to one" // https://www.glfw.org/docs/latest/quick.html#quick_swap_buffers + +#if defined(MLN_RENDER_BACKEND_OPENGL) glfwSwapInterval(1); +#endif glfwGetWindowSize(window, &width, &height); diff --git a/platform/glfw/glfw_webgpu_backend.hpp b/platform/glfw/glfw_webgpu_backend.hpp index 43106bd2d..872b022eb 100644 --- a/platform/glfw/glfw_webgpu_backend.hpp +++ b/platform/glfw/glfw_webgpu_backend.hpp @@ -15,10 +15,12 @@ struct GLFWwindow; struct WGPUDeviceImpl; struct WGPUSurfaceImpl; +#if MLN_WEBGPU_IMPL_DAWN namespace dawn::native { class Instance; class Adapter; } // namespace dawn::native +#endif // Multiple inheritance: GLFWBackend for window management, // webgpu::RendererBackend for rendering, gfx::Renderable for framebuffer @@ -63,7 +65,12 @@ private: mutable SpinLock textureStateLock; GLFWwindow* window; + +#if MLN_WEBGPU_IMPL_DAWN std::unique_ptr instance; +#elif MLN_WEBGPU_IMPL_WGPU + wgpu::Instance wgpuInstance; +#endif wgpu::Device wgpuDevice; // This owns the device wgpu::Queue queue; wgpu::Surface wgpuSurface; @@ -100,6 +107,9 @@ private: std::atomic consecutiveErrors{0}; static constexpr int maxConsecutiveErrors = 3; + // VSync control + bool enableVSync = true; + // Helper methods void reconfigureSurface(); void createDepthStencilTexture(uint32_t width, uint32_t height); @@ -107,13 +117,4 @@ private: void signalFrameComplete(); void periodicMaintenance(); void processEvents(); - - // Debug triangle utilities - void ensureDebugTriangleResources(); - void drawDebugTriangle(const wgpu::TextureView& targetView); - - bool debugTriangleInitialized = false; - wgpu::RenderPipeline debugTrianglePipeline; - wgpu::Buffer debugTriangleVertexBuffer; - uint64_t debugTriangleVertexBufferSize = 0; }; diff --git a/platform/glfw/glfw_webgpu_backend_impl.ipp b/platform/glfw/glfw_webgpu_backend_impl.ipp index 28a03157a..1dab871cd 100644 --- a/platform/glfw/glfw_webgpu_backend_impl.ipp +++ b/platform/glfw/glfw_webgpu_backend_impl.ipp @@ -17,22 +17,39 @@ #import #import #import +#import + +#if MLN_WEBGPU_IMPL_DAWN #include +#endif + #elif defined(__linux__) #define GLFW_EXPOSE_NATIVE_X11 #define GLFW_EXPOSE_NATIVE_WAYLAND #include + +#if MLN_WEBGPU_IMPL_DAWN #include #endif +#endif + #ifdef None #undef None #endif +#if MLN_WEBGPU_IMPL_DAWN #include +#endif + #include #include + +#if MLN_WEBGPU_IMPL_DAWN #include +#elif MLN_WEBGPU_IMPL_WGPU +#include +#endif #include #include @@ -47,8 +64,24 @@ #include #include +// Helper macro for label assignment (wgpu-native needs WGPUStringView) +#if MLN_WEBGPU_IMPL_DAWN +// Dawn uses simple const char* labels - just assign directly inline +#define WGPU_LABEL(desc, labelStr) \ + (desc.label = labelStr) +#elif MLN_WEBGPU_IMPL_WGPU +#define WGPU_LABEL(desc, labelStr) \ + do { \ + static constexpr char label_text[] = labelStr; \ + WGPUStringView label_view = {label_text, sizeof(label_text) - 1}; \ + desc.label = label_view; \ + } while(0) + +#endif + namespace { +#if MLN_WEBGPU_IMPL_DAWN void logUncapturedError(const wgpu::Device&, wgpu::ErrorType type, wgpu::StringView message) { mbgl::Log::Error(mbgl::Event::Render, std::string("Dawn validation error [") + @@ -82,6 +115,14 @@ std::optional desiredBackendFromEnv() { return std::nullopt; } +bool backendMatches(WGPUBackendType candidate, WGPUBackendType desired) { + if (desired == WGPUBackendType_OpenGL) { + return candidate == WGPUBackendType_OpenGL || candidate == WGPUBackendType_OpenGLES; + } + return candidate == desired; +} +#endif // MLN_WEBGPU_IMPL_DAWN + const char* backendTypeToString(WGPUBackendType type) { switch (type) { case WGPUBackendType_Null: @@ -105,13 +146,6 @@ const char* backendTypeToString(WGPUBackendType type) { } } -bool backendMatches(WGPUBackendType candidate, WGPUBackendType desired) { - if (desired == WGPUBackendType_OpenGL) { - return candidate == WGPUBackendType_OpenGL || candidate == WGPUBackendType_OpenGLES; - } - return candidate == desired; -} - #ifdef __APPLE__ MTLPixelFormat toMetalPixelFormat(wgpu::TextureFormat format) { switch (format) { @@ -214,8 +248,8 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) static_cast(std::max(fbHeight, 0))}; }(), std::make_unique(*this)), - window(window_) { - static_cast(capFrameRate); + window(window_), + enableVSync(capFrameRate) { // Add small delay to let previous resources clean up std::this_thread::sleep_for(std::chrono::milliseconds(100)); @@ -230,6 +264,9 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) } + +#if MLN_WEBGPU_IMPL_DAWN + // Create Dawn instance instance = std::make_unique(); @@ -249,7 +286,27 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) throw std::runtime_error("No WebGPU adapters found after retries"); } +#elif MLN_WEBGPU_IMPL_WGPU + // Create wgpu-native instance + // Try without extras first to see if the instance can be created at all + wgpuInstance = wgpu::createInstance(); + if (!wgpuInstance) { + throw std::runtime_error("Failed to create WebGPU instance"); + } + // Request adapter + wgpu::RequestAdapterOptions adapterOpts = {}; + adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance; + + wgpu::Adapter selectedAdapter = wgpuInstance.requestAdapter(adapterOpts); + if (!selectedAdapter) { + throw std::runtime_error("No WebGPU adapter found"); + } + +#endif + + +#if MLN_WEBGPU_IMPL_DAWN std::size_t selectedIndex = 0; if (auto targettedBackend = desiredBackendFromEnv()) { bool matched = false; @@ -291,9 +348,32 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) } } +#elif MLN_WEBGPU_IMPL_WGPU + // Log adapter info for wgpu-native + { + WGPUAdapterInfo info = {}; + if (wgpuAdapterGetInfo(static_cast(selectedAdapter), &info) == WGPUStatus_Success) { + mbgl::Log::Info(mbgl::Event::Render, + std::string("WebGPU: Selected adapter backend = ") + + backendTypeToString(info.backendType) + + ", name = " + + (info.device.data ? std::string(info.device.data, info.device.length) + : std::string(""))); + wgpuAdapterInfoFreeMembers(info); + } + } +#endif + std::vector supportedFeatures; - WGPUSupportedFeatures features = WGPU_SUPPORTED_FEATURES_INIT; + + WGPUSupportedFeatures features = {}; + +#if MLN_WEBGPU_IMPL_DAWN wgpuAdapterGetFeatures(selectedAdapter.Get(), &features); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuAdapterGetFeatures(static_cast(selectedAdapter), &features); +#endif + if (features.featureCount > 0 && features.features) { supportedFeatures.reserve(features.featureCount); for (size_t i = 0; i < features.featureCount; ++i) { @@ -314,12 +394,24 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) wgpu::DeviceDescriptor deviceDesc = {}; - deviceDesc.label = "MapLibre WebGPU Device"; +#if MLN_WEBGPU_IMPL_DAWN deviceDesc.requiredFeatures = requiredFeatures.data(); +#elif MLN_WEBGPU_IMPL_WGPU + deviceDesc.requiredFeatures = reinterpret_cast(requiredFeatures.data()); +#endif deviceDesc.requiredFeatureCount = requiredFeatures.size(); + +#if MLN_WEBGPU_IMPL_DAWN + // Dawn supports error callbacks at device creation time + deviceDesc.label = "MapLibre WebGPU Device"; deviceDesc.SetUncapturedErrorCallback(logUncapturedError); deviceDesc.SetDeviceLostCallback(wgpu::CallbackMode::AllowSpontaneous, logDeviceLost); +#elif MLN_WEBGPU_IMPL_WGPU + // wgpu-native: Use label through descriptor, callbacks are set after device creation + WGPU_LABEL(deviceDesc, "MapLibre WebGPU Device"); +#endif +#if MLN_WEBGPU_IMPL_DAWN // Create device with descriptor WGPUDevice rawDevice = selectedAdapter.CreateDevice(&deviceDesc); if (!rawDevice) { @@ -333,6 +425,13 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) } wgpuDevice = wgpu::Device::Acquire(rawDevice); +#elif MLN_WEBGPU_IMPL_WGPU + // Create device with wgpu-native + wgpuDevice = selectedAdapter.requestDevice(deviceDesc); + if (!wgpuDevice) { + throw std::runtime_error("Failed to create WebGPU device"); + } +#endif setDepthStencilFormat(depthStencilFormat); @@ -350,7 +449,14 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) // Create CAMetalLayer CAMetalLayer* layer = [CAMetalLayer layer]; + +#if MLN_WEBGPU_IMPL_DAWN + // For Dawn, use Dawn's native API to get Metal device layer.device = dawn::native::metal::GetMTLDevice(wgpuDevice.Get()); +#elif MLN_WEBGPU_IMPL_WGPU + // For wgpu-native, don't set the device - the surface configuration will handle it + // Setting it to a different device could cause performance issues +#endif CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceSRGB); if (colorSpace) { layer.colorspace = colorSpace; @@ -369,20 +475,40 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) [view setLayer:layer]; +#if MLN_WEBGPU_IMPL_DAWN // Create surface from metal layer wgpu::SurfaceSourceMetalLayer metalDesc; metalDesc.layer = (__bridge void*)layer; wgpu::SurfaceDescriptor surfaceDesc; surfaceDesc.nextInChain = &metalDesc; +#elif MLN_WEBGPU_IMPL_WGPU + wgpu::SurfaceSourceMetalLayer metalDesc = {}; + metalDesc.chain.sType = wgpu::SType::SurfaceSourceMetalLayer; + metalDesc.chain.next = nullptr; + metalDesc.layer = (__bridge void*)layer; + wgpu::SurfaceDescriptor surfaceDesc = {}; + surfaceDesc.nextInChain = reinterpret_cast(&metalDesc); +#endif + +#if MLN_WEBGPU_IMPL_DAWN // Create surface using the instance wgpu::Instance wgpuInstance(instance->Get()); wgpuSurface = wgpuInstance.CreateSurface(&surfaceDesc); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface = wgpuInstance.createSurface(surfaceDesc); +#endif if (wgpuSurface) { - WGPUSurfaceCapabilities capabilities = WGPU_SURFACE_CAPABILITIES_INIT; + + WGPUSurfaceCapabilities capabilities = {}; + +#if MLN_WEBGPU_IMPL_DAWN if (wgpuSurfaceGetCapabilities(wgpuSurface.Get(), selectedAdapter.Get(), &capabilities) == WGPUStatus_Success) { +#elif MLN_WEBGPU_IMPL_WGPU + if (wgpuSurfaceGetCapabilities(static_cast(wgpuSurface), static_cast(selectedAdapter), &capabilities) == WGPUStatus_Success) { +#endif const auto pickFormat = [&]() { const wgpu::TextureFormat preferredFormats[] = { wgpu::TextureFormat::BGRA8Unorm, @@ -410,7 +536,11 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) layer.pixelFormat = toMetalPixelFormat(swapChainFormat); // Get the queue +#if MLN_WEBGPU_IMPL_DAWN queue = wgpuDevice.GetQueue(); +#elif MLN_WEBGPU_IMPL_WGPU + queue = wgpuDevice.getQueue(); +#endif // Configure surface wgpu::SurfaceConfiguration config = {}; @@ -420,12 +550,20 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) config.alphaMode = wgpu::CompositeAlphaMode::Auto; config.width = width; config.height = height; - config.presentMode = wgpu::PresentMode::Fifo; + // Use Immediate mode (no vsync) in benchmark mode, Fifo (vsync) otherwise + config.presentMode = enableVSync ? wgpu::PresentMode::Fifo : wgpu::PresentMode::Immediate; configuredViewFormats[0] = swapChainFormat; config.viewFormatCount = 1; + +#if MLN_WEBGPU_IMPL_DAWN config.viewFormats = configuredViewFormats.data(); wgpuSurface.Configure(&config); +#elif MLN_WEBGPU_IMPL_WGPU + config.viewFormats = reinterpret_cast(configuredViewFormats.data()); + wgpuSurface.configure(config); +#endif + surfaceConfigured = true; lastConfiguredSize = {static_cast(width), static_cast(height)}; @@ -437,7 +575,9 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) std::string windowSizeMsg = "Window size: " + std::to_string(width) + "x" + std::to_string(height); // Create surface for WebGPU +#if MLN_WEBGPU_IMPL_DAWN wgpu::Instance wgpuInstance(instance->Get()); +#endif bool surfaceCreated = false; @@ -447,7 +587,9 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) Window x11Window = glfwGetX11Window(window); if (x11Display && x11Window) { - wgpu::SurfaceDescriptorFromXlibWindow x11Desc = {}; +#if MLN_WEBGPU_IMPL_DAWN + // Dawn uses a different structure without explicit chain member + wgpu::SurfaceSourceXlibWindow x11Desc = {}; x11Desc.display = x11Display; x11Desc.window = x11Window; @@ -455,6 +597,19 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) surfaceDesc.nextInChain = &x11Desc; wgpuSurface = wgpuInstance.CreateSurface(&surfaceDesc); +#elif MLN_WEBGPU_IMPL_WGPU + // wgpu-native uses explicit chain members + wgpu::SurfaceSourceXlibWindow x11Desc = {}; + x11Desc.chain.sType = wgpu::SType::SurfaceSourceXlibWindow; + x11Desc.chain.next = nullptr; + x11Desc.display = x11Display; + x11Desc.window = x11Window; + + wgpu::SurfaceDescriptor surfaceDesc = {}; + surfaceDesc.nextInChain = reinterpret_cast(&x11Desc); + + wgpuSurface = wgpuInstance.createSurface(surfaceDesc); +#endif surfaceCreated = true; } } @@ -475,7 +630,11 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) wgpu::SurfaceDescriptor surfaceDesc = {}; surfaceDesc.nextInChain = &waylandDesc; +#if MLN_WEBGPU_IMPL_DAWN wgpuSurface = wgpuInstance.CreateSurface(&surfaceDesc); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface = wgpuInstance.createSurface(surfaceDesc); +#endif surfaceCreated = true; } } @@ -494,8 +653,13 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) } if (wgpuSurface) { - WGPUSurfaceCapabilities capabilities = WGPU_SURFACE_CAPABILITIES_INIT; + + WGPUSurfaceCapabilities capabilities = {}; +#if MLN_WEBGPU_IMPL_DAWN if (wgpuSurfaceGetCapabilities(wgpuSurface.Get(), selectedAdapter.Get(), &capabilities) == WGPUStatus_Success) { +#elif MLN_WEBGPU_IMPL_WGPU + if (wgpuSurfaceGetCapabilities(static_cast(wgpuSurface), static_cast(selectedAdapter), &capabilities) == WGPUStatus_Success) { +#endif const auto pickFormat = [&]() { const wgpu::TextureFormat preferredFormats[] = { wgpu::TextureFormat::BGRA8Unorm, @@ -521,7 +685,11 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) setColorFormat(swapChainFormat); // Get the queue +#if MLN_WEBGPU_IMPL_DAWN queue = wgpuDevice.GetQueue(); +#elif MLN_WEBGPU_IMPL_WGPU + queue = wgpuDevice.getQueue(); +#endif // Configure surface wgpu::SurfaceConfiguration config = {}; @@ -531,20 +699,34 @@ GLFWWebGPUBackend::GLFWWebGPUBackend(GLFWwindow* window_, bool capFrameRate) config.alphaMode = wgpu::CompositeAlphaMode::Auto; config.width = width; config.height = height; - config.presentMode = wgpu::PresentMode::Fifo; + // Use Immediate mode (no vsync) in benchmark mode, Fifo (vsync) otherwise + config.presentMode = enableVSync ? wgpu::PresentMode::Fifo : wgpu::PresentMode::Immediate; configuredViewFormats[0] = swapChainFormat; config.viewFormatCount = 1; - config.viewFormats = configuredViewFormats.data(); +#if MLN_WEBGPU_IMPL_DAWN + config.viewFormats = configuredViewFormats.data(); wgpuSurface.Configure(&config); +#elif MLN_WEBGPU_IMPL_WGPU + config.viewFormats = reinterpret_cast(configuredViewFormats.data()); + wgpuSurface.configure(config); +#endif + surfaceConfigured = true; lastConfiguredSize = {static_cast(width), static_cast(height)}; #endif // Store WebGPU instance, device and queue in the base class + +#if MLN_WEBGPU_IMPL_DAWN setInstance(instance->Get()); setDevice(reinterpret_cast(wgpuDevice.Get())); setQueue(reinterpret_cast(queue.Get())); +#elif MLN_WEBGPU_IMPL_WGPU + setInstance(static_cast(wgpuInstance)); + setDevice(static_cast(wgpuDevice)); + setQueue(static_cast(queue)); +#endif // Make sure the window is visible and focused glfwShowWindow(window); @@ -591,7 +773,12 @@ GLFWWebGPUBackend::~GLFWWebGPUBackend() { // Unconfigure surface before releasing device if (wgpuSurface && surfaceConfigured) { + +#if MLN_WEBGPU_IMPL_DAWN wgpuSurface.Unconfigure(); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface.unconfigure(); +#endif surfaceConfigured = false; } @@ -600,8 +787,13 @@ GLFWWebGPUBackend::~GLFWWebGPUBackend() { queue = nullptr; // Queue is owned by device wgpuDevice = nullptr; // Device depends on instance +#if MLN_WEBGPU_IMPL_DAWN // Release Dawn instance last (it owns the backend) instance.reset(); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuInstance = nullptr; +#endif + } mbgl::gfx::RendererBackend& GLFWWebGPUBackend::getRendererBackend() { @@ -626,7 +818,7 @@ void GLFWWebGPUBackend::swap() { } // Keep track of swap calls for debugging - static int swapCount = 0; + [[maybe_unused]] static int swapCount = 0; static auto lastSwapTime = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now(); auto timeSinceLastSwap = std::chrono::duration_cast(now - lastSwapTime).count(); @@ -643,12 +835,6 @@ void GLFWWebGPUBackend::swap() { lastSwapTime = now; swapCount++; - // Process any pending device work - processEvents(); - - // Run periodic maintenance - periodicMaintenance(); - // Note: Command buffer is already submitted in CommandEncoder::present() // before swap() is called, so we don't need to submit it again here. @@ -670,16 +856,19 @@ void GLFWWebGPUBackend::swap() { std::lock_guard guard(textureStateLock); if (currentTextureView && !framePresented) { + +#if MLN_WEBGPU_IMPL_DAWN wgpuSurface.Present(); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface.present(); +#endif framePresented = true; - // Move current resources to previous (keep them alive) - previousTextureView = std::move(currentTextureView); - previousTexture = std::move(currentTexture); - - // Clear current references + // Release resources immediately (wgpu-native performs better with immediate release) currentTextureView = nullptr; currentTexture = nullptr; + previousTextureView = nullptr; + previousTexture = nullptr; } } @@ -745,12 +934,6 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { return nullptr; } - // Process any pending device work before acquiring texture - processEvents(); - - // Run periodic maintenance - periodicMaintenance(); - // Check error threshold if (consecutiveErrors >= maxConsecutiveErrors) { // Too many errors, need to reconfigure @@ -762,18 +945,18 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { std::unique_lock lock(textureStateLock); if (currentTextureView && !framePresented) { + +#if MLN_WEBGPU_IMPL_DAWN return reinterpret_cast(currentTextureView.Get()); +#elif MLN_WEBGPU_IMPL_WGPU + return reinterpret_cast(static_cast(currentTextureView)); +#endif + } - if (framePresented) { - currentTextureView = nullptr; - currentTexture = nullptr; - } - - bool expected = false; - if (!frameInProgress.compare_exchange_strong(expected, true)) { - return nullptr; - } + // Always release textures before acquiring new ones to avoid "already acquired" error + currentTextureView = nullptr; + currentTexture = nullptr; framePresented = false; @@ -782,17 +965,20 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { wgpu::SurfaceTexture surfaceTexture; try { +#if MLN_WEBGPU_IMPL_DAWN wgpuSurface.GetCurrentTexture(&surfaceTexture); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface.getCurrentTexture(&surfaceTexture); +#endif + } catch (...) { consecutiveErrors++; - frameInProgress = false; return nullptr; } if (!surfaceTexture.texture) { surfaceNeedsReconfigure = true; consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -801,7 +987,6 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { status == wgpu::SurfaceGetCurrentTextureStatus::Lost) { surfaceNeedsReconfigure = true; consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -809,7 +994,6 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { (status != wgpu::SurfaceGetCurrentTextureStatus::SuccessOptimal && status != wgpu::SurfaceGetCurrentTextureStatus::SuccessSuboptimal)) { consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -818,13 +1002,18 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { // Create texture view with explicit descriptor wgpu::TextureViewDescriptor viewDesc = {}; viewDesc.format = swapChainFormat; +#if MLN_WEBGPU_IMPL_DAWN viewDesc.dimension = wgpu::TextureViewDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + viewDesc.dimension = wgpu::TextureViewDimension::_2D; +#endif + viewDesc.baseMipLevel = 0; viewDesc.mipLevelCount = 1; viewDesc.baseArrayLayer = 0; viewDesc.arrayLayerCount = 1; viewDesc.aspect = wgpu::TextureAspect::All; - viewDesc.label = "SwapChain TextureView"; + WGPU_LABEL(viewDesc, "SwapChain TextureView"); // Store the texture to keep it alive currentTexture = surfaceTexture.texture; @@ -832,12 +1021,15 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { // Create the texture view with validation wgpu::TextureView newView; try { +#if MLN_WEBGPU_IMPL_DAWN newView = currentTexture.CreateView(&viewDesc); +#elif MLN_WEBGPU_IMPL_WGPU + newView = currentTexture.createView(viewDesc); +#endif } catch (...) { // Handle any exceptions from Dawn currentTexture = nullptr; consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -845,7 +1037,6 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { if (!newView) { currentTexture = nullptr; consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -856,7 +1047,6 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { if (!currentTextureView) { currentTexture = nullptr; consecutiveErrors++; - frameInProgress = false; return nullptr; } @@ -867,7 +1057,11 @@ void* GLFWWebGPUBackend::getCurrentTextureView() { } #endif +#if MLN_WEBGPU_IMPL_DAWN return reinterpret_cast(currentTextureView.Get()); +#elif MLN_WEBGPU_IMPL_WGPU + return reinterpret_cast(static_cast(currentTextureView)); +#endif } mbgl::Size GLFWWebGPUBackend::getFramebufferSize() const { @@ -885,7 +1079,14 @@ void* GLFWWebGPUBackend::getDepthStencilView() { createDepthStencilTexture(static_cast(std::max(width, 0)), static_cast(std::max(height, 0))); } + +#if MLN_WEBGPU_IMPL_DAWN return depthStencilView ? reinterpret_cast(depthStencilView.Get()) : nullptr; +#elif MLN_WEBGPU_IMPL_WGPU + return depthStencilView ? reinterpret_cast(static_cast(depthStencilView)) : nullptr; +#endif + + } void GLFWWebGPUBackend::reconfigureSurface() { @@ -933,9 +1134,14 @@ void GLFWWebGPUBackend::reconfigureSurface() { config.alphaMode = wgpu::CompositeAlphaMode::Auto; config.width = width; config.height = height; - config.presentMode = wgpu::PresentMode::Fifo; + // Use Immediate mode (no vsync) in benchmark mode, Fifo (vsync) otherwise + config.presentMode = enableVSync ? wgpu::PresentMode::Fifo : wgpu::PresentMode::Immediate; +#if MLN_WEBGPU_IMPL_DAWN wgpuSurface.Configure(&config); +#elif MLN_WEBGPU_IMPL_WGPU + wgpuSurface.configure(config); +#endif createDepthStencilTexture(static_cast(width), static_cast(height)); @@ -947,9 +1153,22 @@ void GLFWWebGPUBackend::reconfigureSurface() { } void GLFWWebGPUBackend::processEvents() { - // TODO(webgpu): hook Dawn event processing once the backend relies on it for - // resource lifecycle management. For now, MapLibre doesn't depend on these - // callbacks and invoking them triggers instability in the native backend. + +#if MLN_WEBGPU_IMPL_DAWN + // Dawn: Process device events for resource lifecycle management + // This ticks the Dawn device to process pending callbacks and resource cleanup + if (wgpuDevice) { + wgpuDeviceTick(wgpuDevice.Get()); + } +#elif MLN_WEBGPU_IMPL_WGPU + // wgpu-native: Use wgpuDevicePoll to process GPU work + // This is the lightweight equivalent of Dawn's wgpuDeviceTick + // wgpuDevicePoll with wait=false just processes pending GPU work without blocking + if (wgpuDevice) { + wgpuDevicePoll(static_cast(wgpuDevice), false, nullptr); + } +#endif + } void GLFWWebGPUBackend::createDepthStencilTexture(uint32_t width, uint32_t height) { @@ -961,15 +1180,27 @@ void GLFWWebGPUBackend::createDepthStencilTexture(uint32_t width, uint32_t heigh } wgpu::TextureDescriptor depthDesc = {}; - depthDesc.label = "DepthStencilTexture"; + WGPU_LABEL(depthDesc, "DepthStencilTexture"); depthDesc.usage = wgpu::TextureUsage::RenderAttachment; + +#if MLN_WEBGPU_IMPL_DAWN depthDesc.dimension = wgpu::TextureDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + depthDesc.dimension = wgpu::TextureDimension::_2D; +#endif + + depthDesc.size = {width, height, 1}; depthDesc.format = depthStencilFormat; depthDesc.mipLevelCount = 1; depthDesc.sampleCount = 1; +#if MLN_WEBGPU_IMPL_DAWN depthStencilTexture = wgpuDevice.CreateTexture(&depthDesc); +#elif MLN_WEBGPU_IMPL_WGPU + depthStencilTexture = wgpuDevice.createTexture(depthDesc); +#endif + if (!depthStencilTexture) { mbgl::Log::Warning(mbgl::Event::Render, "WebGPU: Failed to create depth/stencil texture"); depthStencilFormat = wgpu::TextureFormat::Undefined; @@ -978,16 +1209,27 @@ void GLFWWebGPUBackend::createDepthStencilTexture(uint32_t width, uint32_t heigh } wgpu::TextureViewDescriptor viewDesc = {}; - viewDesc.label = "DepthStencilTextureView"; + WGPU_LABEL(viewDesc, "DepthStencilTextureView"); viewDesc.format = depthDesc.format; + +#if MLN_WEBGPU_IMPL_DAWN viewDesc.dimension = wgpu::TextureViewDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + viewDesc.dimension = wgpu::TextureViewDimension::_2D; +#endif + viewDesc.baseMipLevel = 0; viewDesc.mipLevelCount = 1; viewDesc.baseArrayLayer = 0; viewDesc.arrayLayerCount = 1; viewDesc.aspect = wgpu::TextureAspect::All; +#if MLN_WEBGPU_IMPL_DAWN depthStencilView = depthStencilTexture.CreateView(&viewDesc); +#elif MLN_WEBGPU_IMPL_WGPU + depthStencilView = depthStencilTexture.createView(viewDesc); +#endif + if (!depthStencilView) { mbgl::Log::Warning(mbgl::Event::Render, "WebGPU: Failed to create depth/stencil view"); depthStencilTexture = nullptr; @@ -1004,8 +1246,6 @@ bool GLFWWebGPUBackend::waitForFrame(std::chrono::milliseconds timeout) { return true; } - processEvents(); - if (std::chrono::steady_clock::now() - start >= timeout) { return false; } @@ -1040,159 +1280,3 @@ void GLFWWebGPUBackend::periodicMaintenance() { lastMaintenanceTime = now; } } - -void GLFWWebGPUBackend::ensureDebugTriangleResources() { - if (debugTriangleInitialized || !wgpuDevice || !queue) { - return; - } - - static constexpr char shaderSource[] = R"( -struct VertexOut { - @builtin(position) position : vec4, - @location(0) color : vec3, -}; - -@vertex -fn vs_main(@location(0) position : vec2, - @location(1) color : vec3) -> VertexOut { - var out : VertexOut; - out.position = vec4(position, 0.0, 1.0); - out.color = color; - return out; -} - -@fragment -fn fs_main(input : VertexOut) -> @location(0) vec4 { - return vec4(input.color, 1.0); -} -)"; - - wgpu::ShaderSourceWGSL wgslDesc{}; - wgslDesc.code = shaderSource; - - wgpu::ShaderModuleDescriptor shaderDesc{}; - shaderDesc.nextInChain = &wgslDesc; - shaderDesc.label = "DebugTriangleShader"; - - auto shaderModule = wgpuDevice.CreateShaderModule(&shaderDesc); - if (!shaderModule) { - return; - } - - wgpu::VertexAttribute attributes[2]; - attributes[0].format = wgpu::VertexFormat::Float32x2; - attributes[0].offset = 0; - attributes[0].shaderLocation = 0; - attributes[1].format = wgpu::VertexFormat::Float32x3; - attributes[1].offset = sizeof(float) * 2; - attributes[1].shaderLocation = 1; - - wgpu::VertexBufferLayout vertexLayout{}; - vertexLayout.arrayStride = sizeof(float) * 5; - vertexLayout.attributeCount = 2; - vertexLayout.attributes = attributes; - vertexLayout.stepMode = wgpu::VertexStepMode::Vertex; - - wgpu::VertexState vertexState{}; - vertexState.module = shaderModule; - vertexState.entryPoint = "vs_main"; - vertexState.bufferCount = 1; - vertexState.buffers = &vertexLayout; - - wgpu::ColorTargetState colorTarget{}; - colorTarget.format = swapChainFormat; - colorTarget.writeMask = wgpu::ColorWriteMask::All; - - wgpu::FragmentState fragmentState{}; - fragmentState.module = shaderModule; - fragmentState.entryPoint = "fs_main"; - fragmentState.targetCount = 1; - fragmentState.targets = &colorTarget; - - wgpu::PrimitiveState primitiveState{}; - primitiveState.topology = wgpu::PrimitiveTopology::TriangleList; - primitiveState.stripIndexFormat = wgpu::IndexFormat::Undefined; - primitiveState.frontFace = wgpu::FrontFace::CCW; - primitiveState.cullMode = wgpu::CullMode::None; - - wgpu::MultisampleState multisample{}; - multisample.count = 1; - multisample.mask = ~0u; - multisample.alphaToCoverageEnabled = false; - - wgpu::RenderPipelineDescriptor pipelineDesc{}; - pipelineDesc.label = "DebugTrianglePipeline"; - pipelineDesc.vertex = vertexState; - pipelineDesc.fragment = &fragmentState; - pipelineDesc.primitive = primitiveState; - pipelineDesc.multisample = multisample; - - debugTrianglePipeline = wgpuDevice.CreateRenderPipeline(&pipelineDesc); - if (!debugTrianglePipeline) { - return; - } - - const float vertices[] = { - 0.0f, 0.8f, 1.0f, 0.0f, 0.0f, - -0.8f, -0.8f, 0.0f, 1.0f, 0.0f, - 0.8f, -0.8f, 0.0f, 0.0f, 1.0f, - }; - - debugTriangleVertexBufferSize = sizeof(vertices); - - wgpu::BufferDescriptor bufferDesc{}; - bufferDesc.label = "DebugTriangleVertexBuffer"; - bufferDesc.size = debugTriangleVertexBufferSize; - bufferDesc.usage = wgpu::BufferUsage::Vertex | wgpu::BufferUsage::CopyDst; - - debugTriangleVertexBuffer = wgpuDevice.CreateBuffer(&bufferDesc); - if (!debugTriangleVertexBuffer) { - debugTrianglePipeline = nullptr; - debugTriangleVertexBufferSize = 0; - return; - } - - queue.WriteBuffer(debugTriangleVertexBuffer, 0, vertices, debugTriangleVertexBufferSize); - - debugTriangleInitialized = true; -} - -void GLFWWebGPUBackend::drawDebugTriangle(const wgpu::TextureView& targetView) { - if (!targetView) { - return; - } - - ensureDebugTriangleResources(); - if (!debugTriangleInitialized || !debugTrianglePipeline || !debugTriangleVertexBuffer) { - return; - } - - wgpu::CommandEncoderDescriptor encoderDesc{}; - encoderDesc.label = "DebugTriangleEncoder"; - auto encoder = wgpuDevice.CreateCommandEncoder(&encoderDesc); - if (!encoder) { - return; - } - - wgpu::RenderPassColorAttachment colorAttachment{}; - colorAttachment.view = targetView; - colorAttachment.loadOp = wgpu::LoadOp::Load; - colorAttachment.storeOp = wgpu::StoreOp::Store; - - wgpu::RenderPassDescriptor passDesc{}; - passDesc.label = "DebugTrianglePass"; - passDesc.colorAttachmentCount = 1; - passDesc.colorAttachments = &colorAttachment; - - auto pass = encoder.BeginRenderPass(&passDesc); - pass.SetViewport(0.0f, 0.0f, static_cast(lastConfiguredSize.width), static_cast(lastConfiguredSize.height), 0.0f, 1.0f); - pass.SetScissorRect(0, 0, lastConfiguredSize.width, lastConfiguredSize.height); - pass.SetPipeline(debugTrianglePipeline); - pass.SetVertexBuffer(0, debugTriangleVertexBuffer, 0, debugTriangleVertexBufferSize); - pass.Draw(3); - pass.End(); - - auto commandBuffer = encoder.Finish(); - - queue.Submit(1, &commandBuffer); -} diff --git a/src/mbgl/webgpu/drawable.cpp b/src/mbgl/webgpu/drawable.cpp index bf6d7aae1..73564db58 100644 --- a/src/mbgl/webgpu/drawable.cpp +++ b/src/mbgl/webgpu/drawable.cpp @@ -69,6 +69,52 @@ std::size_t hashStencilMode(const gfx::StencilMode& mode) { return hash; } +std::size_t hashPipelineState(const std::vector& vertexLayouts, + const gfx::ColorMode& colorMode, + const gfx::DepthMode& depthMode, + const gfx::StencilMode& stencilMode, + gfx::DrawModeType drawModeType) { + std::size_t hash = 0; + + // Hash vertex layouts + for (const auto& layout : vertexLayouts) { + hash = util::hash(hash, static_cast(layout.arrayStride)); + hash = util::hash(hash, static_cast(layout.stepMode)); + hash = util::hash(hash, static_cast(layout.attributeCount)); + for (uint32_t i = 0; i < layout.attributeCount && i < 16; ++i) { + hash = util::hash(hash, + static_cast(layout.attributes[i].format), + static_cast(layout.attributes[i].shaderLocation), + static_cast(layout.attributes[i].offset)); + } + } + + // Hash color mode + hash = util::hash(hash, colorMode.mask.r, colorMode.mask.g, colorMode.mask.b, colorMode.mask.a); + colorMode.blendFunction.match([&](const auto& blendFunc) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + hash = util::hash(hash, 0); + } else { + hash = util::hash(hash, + static_cast(blendFunc.equation), + static_cast(blendFunc.srcFactor), + static_cast(blendFunc.dstFactor)); + } + }); + + // Hash depth mode + hash = util::hash(hash, static_cast(depthMode.func), static_cast(depthMode.mask)); + + // Hash stencil mode + hash = util::hash(hash, hashStencilMode(stencilMode)); + + // Hash draw mode + hash = util::hash(hash, static_cast(drawModeType)); + + return hash; +} + WGPUVertexFormat wgpuVertexFormatOf(gfx::AttributeDataType type) { switch (type) { case gfx::AttributeDataType::Byte: @@ -762,6 +808,10 @@ void Drawable::draw(PaintParameters& parameters) const { drawModeType = impl->segments.front()->getMode().type; } + // Compute pipeline state hash for caching + const std::size_t pipelineHash = hashPipelineState( + vertexLayouts, colorMode, depthMode, stencilMode, drawModeType); + impl->pipelineState = shaderWebGPU.getRenderPipeline(renderable, vertexLayouts.empty() ? nullptr : vertexLayouts.data(), vertexLayouts.size(), @@ -769,7 +819,7 @@ void Drawable::draw(PaintParameters& parameters) const { depthMode, stencilMode, drawModeType, - std::nullopt); + pipelineHash); if (!impl->pipelineState) { Log::Warning(Event::Render, "WebGPU: skipping pipeline creation for drawable '" + getName() + "' (shader='" + diff --git a/src/mbgl/webgpu/drawable_builder.cpp b/src/mbgl/webgpu/drawable_builder.cpp index fa7b1d1ee..7a43340b7 100644 --- a/src/mbgl/webgpu/drawable_builder.cpp +++ b/src/mbgl/webgpu/drawable_builder.cpp @@ -15,7 +15,7 @@ DrawableBuilder::DrawableBuilder(std::string name_) DrawableBuilder::~DrawableBuilder() = default; std::unique_ptr DrawableBuilder::createDrawable() const { - return std::make_unique(name); + return std::make_unique(drawableName); } std::unique_ptr DrawableBuilder::createSegment(gfx::DrawMode drawMode, diff --git a/src/mbgl/webgpu/headless_backend.cpp b/src/mbgl/webgpu/headless_backend.cpp index 64bfc5048..e4330f7a0 100644 --- a/src/mbgl/webgpu/headless_backend.cpp +++ b/src/mbgl/webgpu/headless_backend.cpp @@ -4,7 +4,12 @@ #include #include #include + +#if MLN_WEBGPU_IMPL_DAWN #include +#elif MLN_WEBGPU_IMPL_WGPU +#include +#endif namespace mbgl { namespace webgpu { @@ -87,7 +92,12 @@ private: class HeadlessBackend::Impl { public: +#if MLN_WEBGPU_IMPL_DAWN std::unique_ptr instance; +#elif MLN_WEBGPU_IMPL_WGPU + wgpu::Instance instance; + wgpu::Adapter adapter; +#endif wgpu::Device device; wgpu::Queue queue; wgpu::Texture offscreenTexture; @@ -105,6 +115,7 @@ HeadlessBackend::HeadlessBackend(Size size_, SwapBehaviour swapBehaviour_, gfx:: impl->framebufferSize = size_; +#if MLN_WEBGPU_IMPL_DAWN // Initialize Dawn instance with TimedWaitAny feature enabled wgpu::InstanceFeatureName timedWaitAnyFeature = wgpu::InstanceFeatureName::TimedWaitAny; wgpu::InstanceDescriptor instanceDesc = {}; @@ -142,6 +153,44 @@ HeadlessBackend::HeadlessBackend(Size size_, SwapBehaviour swapBehaviour_, gfx:: setDevice(impl->device.Get()); setQueue(impl->queue.Get()); setInstance(impl->instance->Get()); +#elif MLN_WEBGPU_IMPL_WGPU + // wgpu-native backend initialization + wgpu::InstanceDescriptor instanceDesc = {}; + impl->instance = wgpu::createInstance(instanceDesc); + + if (!impl->instance) { + Log::Error(Event::Render, "WebGPU HeadlessBackend: Failed to create instance"); + return; + } + + // Request adapter using simpler blocking API + wgpu::RequestAdapterOptions adapterOpts = {}; + adapterOpts.powerPreference = wgpu::PowerPreference::HighPerformance; + + impl->adapter = impl->instance.requestAdapter(adapterOpts); + + if (!impl->adapter) { + Log::Error(Event::Render, "WebGPU HeadlessBackend: No adapter found"); + return; + } + + // Request device using simpler blocking API + wgpu::DeviceDescriptor deviceDesc = {}; + + impl->device = impl->adapter.requestDevice(deviceDesc); + + if (!impl->device) { + Log::Error(Event::Render, "WebGPU HeadlessBackend: Failed to create device"); + return; + } + + impl->queue = impl->device.getQueue(); + + // Store device, queue, and instance in base class + setDevice(static_cast(impl->device)); + setQueue(static_cast(impl->queue)); + setInstance(static_cast(impl->instance)); +#endif // Create offscreen render texture createOffscreenTextures(); @@ -177,55 +226,94 @@ void HeadlessBackend::createOffscreenTextures() { // Create offscreen color texture wgpu::TextureDescriptor colorDesc = {}; colorDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; + +#if MLN_WEBGPU_IMPL_DAWN colorDesc.dimension = wgpu::TextureDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + colorDesc.dimension = wgpu::TextureDimension::_2D; +#endif colorDesc.size = { static_cast(impl->framebufferSize.width), static_cast(impl->framebufferSize.height), 1}; colorDesc.format = wgpu::TextureFormat::RGBA8Unorm; colorDesc.mipLevelCount = 1; colorDesc.sampleCount = 1; - colorDesc.label = "Headless Color Texture"; +#if MLN_WEBGPU_IMPL_DAWN + colorDesc.label = "Headless Color Texture"; impl->offscreenTexture = impl->device.CreateTexture(&colorDesc); +#elif MLN_WEBGPU_IMPL_WGPU + impl->offscreenTexture = impl->device.createTexture(colorDesc); + +#endif if (impl->offscreenTexture) { wgpu::TextureViewDescriptor viewDesc = {}; viewDesc.format = wgpu::TextureFormat::RGBA8Unorm; + +#if MLN_WEBGPU_IMPL_DAWN viewDesc.dimension = wgpu::TextureViewDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + viewDesc.dimension = wgpu::TextureViewDimension::_2D; +#endif viewDesc.baseMipLevel = 0; viewDesc.mipLevelCount = 1; viewDesc.baseArrayLayer = 0; viewDesc.arrayLayerCount = 1; viewDesc.aspect = wgpu::TextureAspect::All; +#if MLN_WEBGPU_IMPL_DAWN viewDesc.label = "Headless Color TextureView"; impl->offscreenTextureView = impl->offscreenTexture.CreateView(&viewDesc); +#elif MLN_WEBGPU_IMPL_WGPU + impl->offscreenTextureView = impl->offscreenTexture.createView(viewDesc); +#endif } // Create depth/stencil texture wgpu::TextureDescriptor depthDesc = {}; depthDesc.usage = wgpu::TextureUsage::RenderAttachment; + +#if MLN_WEBGPU_IMPL_DAWN depthDesc.dimension = wgpu::TextureDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + depthDesc.dimension = wgpu::TextureDimension::_2D; +#endif depthDesc.size = { static_cast(impl->framebufferSize.width), static_cast(impl->framebufferSize.height), 1}; depthDesc.format = wgpu::TextureFormat::Depth24PlusStencil8; depthDesc.mipLevelCount = 1; depthDesc.sampleCount = 1; + +#if MLN_WEBGPU_IMPL_DAWN depthDesc.label = "Headless Depth/Stencil Texture"; impl->depthTexture = impl->device.CreateTexture(&depthDesc); +#elif MLN_WEBGPU_IMPL_WGPU + impl->depthTexture = impl->device.createTexture(depthDesc); +#endif if (impl->depthTexture) { wgpu::TextureViewDescriptor viewDesc = {}; viewDesc.format = wgpu::TextureFormat::Depth24PlusStencil8; + +#if MLN_WEBGPU_IMPL_DAWN viewDesc.dimension = wgpu::TextureViewDimension::e2D; +#elif MLN_WEBGPU_IMPL_WGPU + viewDesc.dimension = wgpu::TextureViewDimension::_2D; +#endif viewDesc.baseMipLevel = 0; viewDesc.mipLevelCount = 1; viewDesc.baseArrayLayer = 0; viewDesc.arrayLayerCount = 1; viewDesc.aspect = wgpu::TextureAspect::All; + +#if MLN_WEBGPU_IMPL_DAWN viewDesc.label = "Headless Depth/Stencil TextureView"; impl->depthTextureView = impl->depthTexture.CreateView(&viewDesc); +#elif MLN_WEBGPU_IMPL_WGPU + impl->depthTextureView = impl->depthTexture.createView(viewDesc); +#endif } } diff --git a/src/mbgl/webgpu/offscreen_texture.cpp b/src/mbgl/webgpu/offscreen_texture.cpp index 830f6dbcc..1a4642cec 100644 --- a/src/mbgl/webgpu/offscreen_texture.cpp +++ b/src/mbgl/webgpu/offscreen_texture.cpp @@ -7,6 +7,10 @@ #include #include +#if MLN_WEBGPU_IMPL_WGPU +#include +#endif + namespace { constexpr uint32_t bytesPerRowAlignment = 256u; } @@ -145,7 +149,9 @@ public: } // Process all pending device operations to ensure rendering is complete +#if MLN_WEBGPU_IMPL_DAWN wgpuDeviceTick(device); +#endif // Create staging buffer for readback WGPUBufferDescriptor bufferDesc = {}; @@ -198,17 +204,19 @@ public: // Use WaitAny for proper synchronous blocking instead of polling struct MapContext { WGPUMapAsyncStatus status = WGPUMapAsyncStatus_Error; + bool completed = false; }; MapContext mapContext; +#if MLN_WEBGPU_IMPL_DAWN + // Dawn: Use WaitAny for proper synchronous blocking WGPUBufferMapCallbackInfo callbackInfo = {}; callbackInfo.mode = WGPUCallbackMode_WaitAnyOnly; callbackInfo.callback = - [](WGPUMapAsyncStatus status, WGPUStringView message, void* userdata1, void* userdata2) { + [](WGPUMapAsyncStatus status, WGPUStringView /*message*/, void* userdata1, void* /*userdata2*/) { auto* ctx = static_cast(userdata1); ctx->status = status; - (void)message; - (void)userdata2; + ctx->completed = true; }; callbackInfo.userdata1 = &mapContext; callbackInfo.userdata2 = nullptr; @@ -229,6 +237,38 @@ public: mbgl::Log::Error(mbgl::Event::Render, "WebGPU: Buffer mapping wait failed"); } } +#elif MLN_WEBGPU_IMPL_WGPU + + // wgpu-native: Use polling approach (WaitAny not implemented yet) + // Use AllowProcessEvents callback mode for synchronous polling + WGPUBufferMapCallbackInfo callbackInfo = {}; + callbackInfo.mode = WGPUCallbackMode_AllowProcessEvents; + callbackInfo.callback = + [](WGPUMapAsyncStatus status, WGPUStringView /*message*/, void* userdata1, void* /*userdata2*/) { + auto* ctx = static_cast(userdata1); + ctx->status = status; + ctx->completed = true; + }; + callbackInfo.userdata1 = &mapContext; + callbackInfo.userdata2 = nullptr; + + wgpuBufferMapAsync(stagingBuffer, WGPUMapMode_Read, 0, alignedDataSize, callbackInfo); + + // Poll device until mapping completes + // wgpu-native processes callbacks during wgpuDevicePoll + constexpr int maxIterations = 1000; + for (int i = 0; i < maxIterations && !mapContext.completed; ++i) { + wgpuDevicePoll(device, true, nullptr); + if (!mapContext.completed) { + // Small sleep to avoid busy-waiting + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + } + + if (!mapContext.completed) { + mbgl::Log::Error(mbgl::Event::Render, "WebGPU: Buffer mapping timeout after polling"); + } +#endif // Check if mapping was successful and read the data if (mapContext.status == WGPUMapAsyncStatus_Success) { diff --git a/src/mbgl/webgpu/render_pass.cpp b/src/mbgl/webgpu/render_pass.cpp index e42aa47a4..0930607a4 100644 --- a/src/mbgl/webgpu/render_pass.cpp +++ b/src/mbgl/webgpu/render_pass.cpp @@ -1,5 +1,9 @@ -// Include Dawn headers first to define WEBGPU_H_ +// Include WebGPU headers first to define WEBGPU_H_ +#if MLN_WEBGPU_IMPL_DAWN #include +#elif MLN_WEBGPU_IMPL_WGPU +#include +#endif #include #include @@ -72,7 +76,12 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const } wgpuTextureViewAddRef(colorViewHandle); + +#if MLN_WEBGPU_IMPL_DAWN impl->colorView = wgpu::TextureView::Acquire(colorViewHandle); +#elif MLN_WEBGPU_IMPL_WGPU + impl->colorView = wgpu::TextureView(colorViewHandle); +#endif if (auto colorFormat = renderableResource.getColorTextureFormat()) { backend.setColorFormat(*colorFormat); @@ -80,7 +89,13 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const } WGPURenderPassColorAttachment colorAttachment = {}; + +#if MLN_WEBGPU_IMPL_DAWN colorAttachment.view = impl->colorView.Get(); +#elif MLN_WEBGPU_IMPL_WGPU + colorAttachment.view = static_cast(impl->colorView); + +#endif colorAttachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; colorAttachment.resolveTarget = nullptr; colorAttachment.storeOp = WGPUStoreOp_Store; @@ -101,7 +116,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const depthAttachment.depthLoadOp = descriptor.clearDepth ? WGPULoadOp_Clear : WGPULoadOp_Load; depthAttachment.depthStoreOp = WGPUStoreOp_Store; depthAttachment.depthClearValue = descriptor.clearDepth ? descriptor.clearDepth.value() : 1.0f; - depthAttachment.depthReadOnly = WGPU_FALSE; + depthAttachment.depthReadOnly = false; if (descriptor.clearStencil) { depthAttachment.stencilLoadOp = WGPULoadOp_Clear; depthAttachment.stencilStoreOp = WGPUStoreOp_Store; @@ -111,7 +126,7 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const depthAttachment.stencilStoreOp = WGPUStoreOp_Undefined; depthAttachment.stencilClearValue = 0u; } - depthAttachment.stencilReadOnly = WGPU_FALSE; + depthAttachment.stencilReadOnly = false; depthAttachment.view = nullptr; WGPURenderPassDepthStencilAttachment* depthAttachmentPtr = nullptr; @@ -125,9 +140,17 @@ RenderPass::RenderPass(CommandEncoder& commandEncoder_, const char* name, const if (depthViewHandle) { wgpuTextureViewAddRef(depthViewHandle); +#if MLN_WEBGPU_IMPL_DAWN impl->depthStencilView = wgpu::TextureView::Acquire(depthViewHandle); +#elif MLN_WEBGPU_IMPL_WGPU + impl->depthStencilView = wgpu::TextureView(depthViewHandle); +#endif if (impl->depthStencilView) { +#if MLN_WEBGPU_IMPL_DAWN depthAttachment.view = impl->depthStencilView.Get(); +#elif MLN_WEBGPU_IMPL_WGPU + depthAttachment.view = static_cast(impl->depthStencilView); +#endif depthAttachmentPtr = &depthAttachment; } } diff --git a/src/mbgl/webgpu/upload_pass.cpp b/src/mbgl/webgpu/upload_pass.cpp index b9bf8987c..e611bcbb2 100644 --- a/src/mbgl/webgpu/upload_pass.cpp +++ b/src/mbgl/webgpu/upload_pass.cpp @@ -14,7 +14,7 @@ namespace mbgl { namespace webgpu { -UploadPass::UploadPass(gfx::Renderable& renderable, CommandEncoder& commandEncoder_, const char* name) +UploadPass::UploadPass([[maybe_unused]] gfx::Renderable& renderable, CommandEncoder& commandEncoder_, const char* name) : commandEncoder(commandEncoder_) { // Push debug groups (Metal pattern) commandEncoder.visitDebugGroups([this](const auto& group) { diff --git a/src/mbgl/webgpu/webgpu_cpp_impl.cpp b/src/mbgl/webgpu/webgpu_cpp_impl.cpp new file mode 100644 index 000000000..a4f599cea --- /dev/null +++ b/src/mbgl/webgpu/webgpu_cpp_impl.cpp @@ -0,0 +1,6 @@ +// Implementation file for WebGPU-Cpp wrapper +// This file must be compiled exactly once to provide implementations +// for the WebGPU C++ wrapper functions + +#define WEBGPU_CPP_IMPLEMENTATION +#include diff --git a/vendor/dawn.cmake b/vendor/dawn.cmake index 5f80cb736..36c658dd3 100644 --- a/vendor/dawn.cmake +++ b/vendor/dawn.cmake @@ -76,12 +76,6 @@ target_include_directories(mbgl-vendor-dawn ${DAWN_BUILD_DIR}/gen/include ) -target_compile_definitions(mbgl-vendor-dawn - INTERFACE - MLN_WITH_DAWN=1 - WEBGPU_BACKEND_DAWN=1 -) - if(APPLE) target_compile_definitions(mbgl-vendor-dawn INTERFACE DAWN_ENABLE_BACKEND_METAL=1) else() diff --git a/vendor/webgpu-cpp b/vendor/webgpu-cpp new file mode 160000 index 000000000..b9507d975 --- /dev/null +++ b/vendor/webgpu-cpp @@ -0,0 +1 @@ +Subproject commit b9507d9753960e6ca0adabd7e91c97d8c8f6e2de diff --git a/vendor/webgpu.cmake b/vendor/webgpu.cmake index a08c596e6..dad2a525b 100644 --- a/vendor/webgpu.cmake +++ b/vendor/webgpu.cmake @@ -1,18 +1,20 @@ # WebGPU implementation configuration # This file handles the integration of either Dawn or wgpu -set(MLN_WEBGPU_IMPL "dawn" CACHE STRING "WebGPU backend implementation (dawn or wgpu)") -set_property(CACHE MLN_WEBGPU_IMPL PROPERTY STRINGS dawn wgpu stub) +set(MLN_WEBGPU_IMPL_DAWN OFF CACHE STRING "WebGPU with Dawn implementation") +set(MLN_WEBGPU_IMPL_WGPU OFF CACHE STRING "WebGPU with wgpu implementation") if(MLN_WITH_WEBGPU) - if(MLN_WEBGPU_IMPL STREQUAL "dawn") + if(MLN_WEBGPU_IMPL_DAWN) message(STATUS "Using Dawn as WebGPU backend") + add_compile_definitions(MLN_WEBGPU_IMPL_DAWN) - elseif(MLN_WEBGPU_IMPL STREQUAL "wgpu") - message(FATAL_ERROR "Using wgpu as WebGPU backend is not implemented yet") + elseif(MLN_WEBGPU_IMPL_WGPU) + message(STATUS "Using wgpu as WebGPU backend") + add_compile_definitions(MLN_WEBGPU_IMPL_WGPU) else() - message(FATAL_ERROR "MLN_WEBGPU_IMPL can take values \"dawn\" or \"wgpu\"") + message(FATAL_ERROR "MLN_WEBGPU_IMPL_DAWN or MLN_WEBGPU_IMPL_WGPU must be set") endif() endif() diff --git a/vendor/wgpu-native b/vendor/wgpu-native new file mode 160000 index 000000000..74f8c24c9 --- /dev/null +++ b/vendor/wgpu-native @@ -0,0 +1 @@ +Subproject commit 74f8c24c903b6352d09f1928c56962ce06f77a4d diff --git a/vendor/wgpu.cmake b/vendor/wgpu.cmake new file mode 100644 index 000000000..58c7f65d6 --- /dev/null +++ b/vendor/wgpu.cmake @@ -0,0 +1,262 @@ +# wgpu-native WebGPU Implementation Configuration + +if(TARGET mbgl-vendor-wgpu) + return() +endif() + +if(NOT MLN_WITH_WEBGPU) + return() +endif() + +message(STATUS "Configuring wgpu-native WebGPU implementation") + +set(_mln_wgpu_source_dir "${PROJECT_SOURCE_DIR}/vendor/wgpu-native") + +# Check if wgpu-native exists +if(NOT EXISTS "${_mln_wgpu_source_dir}/ffi/webgpu-headers/webgpu.h") + message(FATAL_ERROR + "wgpu-native not found at ${_mln_wgpu_source_dir}. " + "Please ensure the wgpu-native submodule is initialized: " + "git submodule update --init --recursive vendor/wgpu-native") +endif() + +# wgpu-native provides pre-built binaries or can be built from source +# We'll look for prebuilt binaries first, then fall back to building from source + +# Platform-specific library names and paths +if(APPLE) + if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64") + set(_wgpu_lib_arch "aarch64-apple-darwin") + else() + set(_wgpu_lib_arch "x86_64-apple-darwin") + endif() + set(_wgpu_lib_name "libwgpu_native.a") + set(_wgpu_lib_suffix ".dylib") +elseif(WIN32) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(_wgpu_lib_arch "x86_64-pc-windows-msvc") + else() + set(_wgpu_lib_arch "i686-pc-windows-msvc") + endif() + set(_wgpu_lib_name "wgpu_native.lib") + set(_wgpu_lib_suffix ".dll") +else() # Linux + if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64") + set(_wgpu_lib_arch "aarch64-unknown-linux-gnu") + else() + set(_wgpu_lib_arch "x86_64-unknown-linux-gnu") + endif() + set(_wgpu_lib_name "libwgpu_native.a") + set(_wgpu_lib_suffix ".so") +endif() + +# Look for prebuilt wgpu-native library +set(_wgpu_lib_search_paths + "${_mln_wgpu_source_dir}/target/release" + "${_mln_wgpu_source_dir}/target/${_wgpu_lib_arch}/release" + "${_mln_wgpu_source_dir}/lib" + "${_mln_wgpu_source_dir}/build/release" +) + +find_library(WGPU_LIBRARY + NAMES wgpu_native libwgpu_native + PATHS ${_wgpu_lib_search_paths} + NO_DEFAULT_PATH +) + +# If not found, try to build it using cargo +if(NOT WGPU_LIBRARY) + message(STATUS "Pre-built wgpu-native library not found, attempting to build from source...") + + # Check if cargo is available + find_program(CARGO_EXECUTABLE cargo) + + if(NOT CARGO_EXECUTABLE) + message(FATAL_ERROR + "wgpu-native library not found and cargo is not available to build it. " + "Please either:\n" + " 1. Install Rust and cargo (https://rustup.rs/), or\n" + " 2. Download pre-built wgpu-native binaries from https://github.com/gfx-rs/wgpu-native/releases\n" + " and place them in ${_mln_wgpu_source_dir}/target/release/") + endif() + + # Build wgpu-native using cargo + message(STATUS "Building wgpu-native with cargo (this may take a few minutes)...") + + execute_process( + COMMAND ${CARGO_EXECUTABLE} build --release + WORKING_DIRECTORY ${_mln_wgpu_source_dir} + RESULT_VARIABLE _cargo_result + OUTPUT_VARIABLE _cargo_output + ERROR_VARIABLE _cargo_error + ) + + if(NOT _cargo_result EQUAL 0) + message(FATAL_ERROR + "Failed to build wgpu-native:\n${_cargo_error}\n" + "You can try building manually:\n" + " cd ${_mln_wgpu_source_dir} && cargo build --release") + endif() + + # Try to find the library again + find_library(WGPU_LIBRARY + NAMES wgpu_native libwgpu_native + PATHS ${_wgpu_lib_search_paths} + NO_DEFAULT_PATH + ) + + if(NOT WGPU_LIBRARY) + message(FATAL_ERROR "Failed to locate wgpu-native library after building") + endif() + + message(STATUS "Successfully built wgpu-native: ${WGPU_LIBRARY}") +else() + message(STATUS "Found wgpu-native library: ${WGPU_LIBRARY}") +endif() + +# Generate WebGPU-Cpp wrapper if needed +set(_webgpu_cpp_dir "${PROJECT_SOURCE_DIR}/vendor/webgpu-cpp") +set(_webgpu_cpp_header "${_webgpu_cpp_dir}/webgpu.hpp") + +if(NOT EXISTS "${_webgpu_cpp_header}") + message(STATUS "Generating WebGPU-Cpp wrapper...") + + # Check if Python 3 is available + find_program(PYTHON_EXECUTABLE python3 python) + if(NOT PYTHON_EXECUTABLE) + message(FATAL_ERROR + "Python is required to generate WebGPU-Cpp wrapper. " + "Please install Python 3.") + endif() + + # Run the generator script using the local webgpu.h from wgpu-native + execute_process( + COMMAND ${PYTHON_EXECUTABLE} generate.py + --use-init-macros + --header ${_mln_wgpu_source_dir}/ffi/webgpu-headers/webgpu.h + WORKING_DIRECTORY ${_webgpu_cpp_dir} + RESULT_VARIABLE _gen_result + OUTPUT_VARIABLE _gen_output + ERROR_VARIABLE _gen_error + ) + + if(NOT _gen_result EQUAL 0) + message(FATAL_ERROR + "Failed to generate WebGPU-Cpp wrapper:\n${_gen_error}") + endif() + + message(STATUS "Successfully generated WebGPU-Cpp wrapper") +endif() + +# Create compatibility shims for Dawn header paths if they don't exist +set(_compat_shim_dir "${_webgpu_cpp_dir}/wgpu-native/webgpu") +set(_compat_shim_header "${_compat_shim_dir}/webgpu_cpp.h") +set(_compat_shim_webgpu_h "${_compat_shim_dir}/webgpu.h") + +if(NOT EXISTS "${_compat_shim_header}") + message(STATUS "Creating WebGPU-Cpp compatibility shim...") + + file(MAKE_DIRECTORY "${_compat_shim_dir}") + file(WRITE "${_compat_shim_header}" +"#pragma once +// Compatibility shim for Dawn header paths +// This file allows MapLibre code to use #include +// for both Dawn and wgpu-native backends + +// Include the WebGPU-Cpp wrapper +#include \"../../webgpu.hpp\" +") + + message(STATUS "Successfully created WebGPU-Cpp compatibility shim") +endif() + +if(NOT EXISTS "${_compat_shim_webgpu_h}") + message(STATUS "Creating webgpu.h compatibility shim...") + + file(WRITE "${_compat_shim_webgpu_h}" +"#pragma once +// Compatibility shim for Dawn header paths +// This file allows MapLibre code to use #include +// for both Dawn and wgpu-native backends + +// Include the actual webgpu.h header +#include +") + + message(STATUS "Successfully created webgpu.h compatibility shim") +endif() + +# Also create wgpu.h shim for wgpu-native specific includes +set(_compat_shim_wgpu_h "${_compat_shim_dir}/wgpu.h") +if(NOT EXISTS "${_compat_shim_wgpu_h}") + message(STATUS "Creating wgpu.h compatibility shim...") + + file(WRITE "${_compat_shim_wgpu_h}" +"#pragma once +// Compatibility shim for wgpu-native specific header +// This file allows MapLibre code to use #include +// for both Dawn and wgpu-native backends + +// Include the actual wgpu.h header +#include +") + + message(STATUS "Successfully created wgpu.h compatibility shim") +endif() + +# Create interface library +add_library(mbgl-vendor-wgpu INTERFACE) + +target_link_libraries(mbgl-vendor-wgpu INTERFACE ${WGPU_LIBRARY}) + +# Add include directories for webgpu.h, wgpu.h, and C++ wrapper +target_include_directories(mbgl-vendor-wgpu + SYSTEM INTERFACE + ${_webgpu_cpp_dir} # For generated webgpu.hpp + ${_webgpu_cpp_dir}/wgpu-native # For webgpu/webgpu_cpp.h compatibility shim + ${_mln_wgpu_source_dir}/ffi/webgpu-headers + ${_mln_wgpu_source_dir}/ffi +) + +# Platform-specific system libraries that wgpu-native needs +if(APPLE) + target_link_libraries(mbgl-vendor-wgpu INTERFACE + "-framework Foundation" + "-framework QuartzCore" + "-framework Metal" + ) +elseif(WIN32) + target_link_libraries(mbgl-vendor-wgpu INTERFACE + d3d12 + dxgi + d3dcompiler + ws2_32 + userenv + bcrypt + ntdll + ) +else() # Linux + # wgpu-native on Linux might need these depending on the build configuration + find_package(Threads REQUIRED) + target_link_libraries(mbgl-vendor-wgpu INTERFACE + Threads::Threads + ${CMAKE_DL_LIBS} + ) +endif() + +# Suppress warnings from wgpu headers +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang|GNU") + target_compile_options(mbgl-vendor-wgpu INTERFACE + -Wno-strict-aliasing + -Wno-error=strict-aliasing + ) +endif() + +set_target_properties( + mbgl-vendor-wgpu + PROPERTIES + INTERFACE_MAPLIBRE_NAME "wgpu-native" + INTERFACE_MAPLIBRE_URL "https://github.com/gfx-rs/wgpu-native" + INTERFACE_MAPLIBRE_AUTHOR "gfx-rs developers" + INTERFACE_MAPLIBRE_LICENSE "${PROJECT_SOURCE_DIR}/vendor/wgpu-native/LICENSE.MIT" +)