#include #include #include #include #include #include #include #if MLN_WEBGPU_IMPL_DAWN #include #elif MLN_WEBGPU_IMPL_WGPU #include #endif namespace mbgl { namespace webgpu { class HeadlessRenderableResource final : public webgpu::RenderableResource { public: HeadlessRenderableResource(HeadlessBackend&, webgpu::Context& context_, Size size_) : context(context_), size(size_) { // Create offscreen texture using the proper WebGPU OffscreenTexture class offscreenTexture = context.createOffscreenTexture(size, gfx::TextureChannelDataType::UnsignedByte, true, true); } ~HeadlessRenderableResource() noexcept override = default; void bind() override { // Bind the offscreen texture if (offscreenTexture) { offscreenTexture->getResource().bind(); } } void swap() override { // For headless rendering, we don't need to swap } const mbgl::webgpu::RendererBackend& getBackend() const override { return context.getBackend(); } const WGPUCommandEncoder& getCommandEncoder() const override { // This will be set by the command encoder when needed static WGPUCommandEncoder dummy = nullptr; return dummy; } WGPURenderPassEncoder getRenderPassEncoder() const override { // This will be set by the render pass when needed return nullptr; } WGPUTextureView getColorTextureView() override { if (offscreenTexture) { return offscreenTexture->getResource().getColorTextureView(); } return nullptr; } std::optional getColorTextureFormat() const override { if (offscreenTexture) { return offscreenTexture->getResource().getColorTextureFormat(); } return std::nullopt; } WGPUTextureView getDepthStencilTextureView() override { if (offscreenTexture) { return offscreenTexture->getResource().getDepthStencilTextureView(); } return nullptr; } std::optional getDepthStencilTextureFormat() const override { if (offscreenTexture) { return offscreenTexture->getResource().getDepthStencilTextureFormat(); } return std::nullopt; } PremultipliedImage readStillImage() { if (offscreenTexture) { return offscreenTexture->readStillImage(); } return PremultipliedImage(size); } gfx::Texture2DPtr takeTexture() { return static_cast(offscreenTexture.get())->takeTexture(); } private: webgpu::Context& context; Size size; std::unique_ptr offscreenTexture; }; 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; wgpu::TextureView offscreenTextureView; wgpu::Texture depthTexture; wgpu::TextureView depthTextureView; Size framebufferSize; }; HeadlessBackend::HeadlessBackend(Size size_, SwapBehaviour swapBehaviour_, gfx::ContextMode mode) : webgpu::RendererBackend(mode), gfx::HeadlessBackend(size_), impl(std::make_unique()) { static_cast(swapBehaviour_); impl->framebufferSize = size_; #if MLN_WEBGPU_IMPL_DAWN // Initialize Dawn instance with TimedWaitAny feature enabled wgpu::InstanceFeatureName timedWaitAnyFeature = wgpu::InstanceFeatureName::TimedWaitAny; wgpu::InstanceDescriptor instanceDesc = {}; instanceDesc.requiredFeatureCount = 1; instanceDesc.requiredFeatures = &timedWaitAnyFeature; impl->instance = std::make_unique(&instanceDesc); // Enumerate adapters using the correct API std::vector adapters = impl->instance->EnumerateAdapters(); if (adapters.empty()) { Log::Error(Event::Render, "WebGPU HeadlessBackend: No adapters found"); return; } // Use the first adapter (typically Vulkan on Linux) dawn::native::Adapter& selectedAdapter = adapters[0]; // Create device descriptor wgpu::DeviceDescriptor deviceDesc = {}; deviceDesc.label = "MapLibre Headless WebGPU Device"; // Create device WGPUDevice rawDevice = selectedAdapter.CreateDevice(&deviceDesc); if (!rawDevice) { Log::Error(Event::Render, "WebGPU HeadlessBackend: Failed to create device"); return; } impl->device = wgpu::Device::Acquire(rawDevice); impl->queue = impl->device.GetQueue(); // Store device and queue in base class setDevice(impl->device.Get()); setQueue(impl->queue.Get()); setInstance(impl->instance->Get()); #elif MLN_WEBGPU_IMPL_WGPU && !MLN_WEBGPU_IMPL_FFI // 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(); } HeadlessBackend::~HeadlessBackend() { // Explicitly reset the renderable resource setResource(nullptr); context.reset(); if (impl) { // Clean up textures impl->offscreenTextureView = nullptr; impl->offscreenTexture = nullptr; impl->depthTextureView = nullptr; impl->depthTexture = nullptr; // Clean up device and queue impl->queue = nullptr; impl->device = nullptr; impl->instance = nullptr; } } void HeadlessBackend::createOffscreenTextures() { if (!impl->device) return; // Release old textures if they exist impl->offscreenTextureView = nullptr; impl->offscreenTexture = nullptr; impl->depthTextureView = nullptr; impl->depthTexture = nullptr; // 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; #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 } } gfx::Renderable& HeadlessBackend::getDefaultRenderable() { if (!hasResource()) { setResource( std::make_unique(*this, static_cast(getContext()), size)); } return *this; } PremultipliedImage HeadlessBackend::readStillImage() { if (hasResource()) { return getResource().readStillImage(); } return PremultipliedImage(getSize()); } gfx::Texture2DPtr HeadlessBackend::takeTexture() { if (hasResource()) { return getResource().takeTexture(); } return nullptr; } RendererBackend* HeadlessBackend::getRendererBackend() { return this; } void HeadlessBackend::activate() { active = true; // Ensure textures are created if size changed if (impl && impl->framebufferSize != getSize()) { impl->framebufferSize = getSize(); createOffscreenTextures(); } } void HeadlessBackend::deactivate() { active = false; } void* HeadlessBackend::getCurrentTextureView() { if (hasResource()) { return getResource().getColorTextureView(); } return nullptr; } void* HeadlessBackend::getDepthStencilView() { if (hasResource()) { return getResource().getDepthStencilTextureView(); } return nullptr; } mbgl::Size HeadlessBackend::getFramebufferSize() const { if (impl) { return impl->framebufferSize; } return getSize(); } } // namespace webgpu namespace gfx { template <> std::unique_ptr Backend::Create( Size size, gfx::Renderable::SwapBehaviour swapBehaviour, gfx::ContextMode contextMode) { return std::make_unique(size, swapBehaviour, contextMode); } } // namespace gfx } // namespace mbgl