#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mbgl { namespace webgpu { namespace { const auto clipMaskStencilMode = gfx::StencilMode{ /*.test=*/gfx::StencilMode::Always(), /*.ref=*/0, /*.mask=*/0xFF, /*.fail=*/gfx::StencilOpType::Keep, /*.depthFail=*/gfx::StencilOpType::Keep, /*.pass=*/gfx::StencilOpType::Replace, }; const auto clipMaskDepthMode = gfx::DepthMode{.func = gfx::DepthFunctionType::Always, .mask = gfx::DepthMaskType::ReadOnly}; } // namespace Context::Context(RendererBackend& backend_) : gfx::Context(gfx::Context::minimumRequiredVertexBindingCount), backend(backend_), globalUniformBuffers(std::make_unique()) {} Context::~Context() = default; void Context::beginFrame() { // Begin a new frame - WebGPU command recording starts here } void Context::endFrame() { // End the frame - submit WebGPU commands } void Context::performCleanup() { // Clean up unused resources } void Context::reduceMemoryUsage() { // Free cached resources to reduce memory } std::unique_ptr Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type, bool depth, bool stencil) { return std::make_unique(*this, size, type, depth, stencil); } std::unique_ptr Context::createOffscreenTexture(Size size, gfx::TextureChannelDataType type) { return std::make_unique(*this, size, type, true, false); } std::unique_ptr Context::createCommandEncoder() { auto encoder = std::make_unique(*this); currentCommandEncoder = encoder.get(); return encoder; } gfx::VertexAttributeArrayPtr Context::createVertexAttributeArray() const { return std::make_shared(); } gfx::UniqueDrawableBuilder Context::createDrawableBuilder(std::string name) { return std::make_unique(std::move(name)); } gfx::UniformBufferPtr Context::createUniformBuffer(const void* data, std::size_t size, bool persistent, bool) { (void)persistent; return std::make_shared(*this, data, size); } gfx::UniqueUniformBufferArray Context::createLayerUniformBufferArray() { return std::make_unique(); } gfx::ShaderProgramBasePtr Context::getGenericShader(gfx::ShaderRegistry& registry, const std::string& name) { // Align with Metal - just get shader from registry without caching const auto shaderGroup = registry.getShaderGroup(name); auto shader = shaderGroup ? shaderGroup->getOrCreateShader(*this, {}) : gfx::ShaderProgramBasePtr{}; return std::static_pointer_cast(std::move(shader)); } TileLayerGroupPtr Context::createTileLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { auto tileLayerGroup = std::make_shared(layerIndex, initialCapacity, std::move(name)); return tileLayerGroup; } LayerGroupPtr Context::createLayerGroup(int32_t layerIndex, std::size_t initialCapacity, std::string name) { auto layerGroup = std::make_shared(layerIndex, initialCapacity, std::move(name)); return layerGroup; } gfx::Texture2DPtr Context::createTexture2D() { return std::make_shared(*this); } gfx::DynamicTexturePtr Context::createDynamicTexture(Size size, gfx::TexturePixelType pixelType) { return std::make_shared(*this, size, pixelType); } RenderTargetPtr Context::createRenderTarget(const Size size, const gfx::TextureChannelDataType type) { return std::make_shared(*this, size, type); } void Context::resetState(gfx::DepthMode, gfx::ColorMode) { // Reset WebGPU render state } void Context::setDirtyState() { // Mark state as needing update } void Context::clearStencilBuffer(int32_t value) { // Clear stencil buffer to specified value (void)value; } bool Context::emplaceOrUpdateUniformBuffer(gfx::UniformBufferPtr& ptr, const void* data, std::size_t size, bool persistent) { if (!ptr) { ptr = createUniformBuffer(data, size, persistent); return true; } // Update existing buffer auto* buffer = static_cast(ptr.get()); buffer->update(data, size); return false; } const gfx::UniformBufferArray& Context::getGlobalUniformBuffers() const { return *globalUniformBuffers; } gfx::UniformBufferArray& Context::mutableGlobalUniformBuffers() { return *globalUniformBuffers; } void Context::bindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept { // In WebGPU, we can't bind buffers globally to the render pass like Metal does. // Instead, we need to ensure these buffers are available for drawables to include // in their bind groups. The actual binding happens per-drawable. // Store a reference to global buffers in the render pass for drawables to access auto& webgpuRenderPass = static_cast(renderPass); webgpuRenderPass.setGlobalUniformBuffers(globalUniformBuffers.get()); } void Context::unbindGlobalUniformBuffers(gfx::RenderPass& renderPass) const noexcept { // Clear the global buffer reference from the render pass auto& webgpuRenderPass = static_cast(renderPass); webgpuRenderPass.setGlobalUniformBuffers(nullptr); } #if !defined(NDEBUG) void Context::visualizeStencilBuffer() { // Debug visualization of stencil buffer } void Context::visualizeDepthBuffer([[maybe_unused]] float depthRangeSize) { // Debug visualization of depth buffer } #endif std::unique_ptr Context::createRenderbufferResource(gfx::RenderbufferPixelType type, Size size) { (void)type; (void)size; return std::make_unique(); } std::unique_ptr Context::createDrawScopeResource() { return std::make_unique(*this); } // Buffer creation (aligned with Metal) BufferResource Context::createBuffer( const void* data, std::size_t size, uint32_t usage, bool isIndexBuffer, bool persistent) const { return BufferResource(const_cast(*this), data, size, usage, isIndexBuffer, persistent); } // Get reusable tile vertex buffer (aligned with Metal) const BufferResource& Context::getTileVertexBuffer() { if (!tileVertexBuffer) { const auto vertices = RenderStaticData::tileVertices(); const uint32_t usage = WGPUBufferUsage_Vertex; tileVertexBuffer = createBuffer(vertices.data(), vertices.bytes(), usage, false, true); } return *tileVertexBuffer; } // Get reusable tile index buffer (aligned with Metal) const BufferResource& Context::getTileIndexBuffer() { if (!tileIndexBuffer) { const auto indices = RenderStaticData::quadTriangleIndices(); const uint32_t usage = WGPUBufferUsage_Index; tileIndexBuffer = createBuffer(indices.data(), indices.bytes(), usage, true, true); } return *tileIndexBuffer; } bool Context::renderTileClippingMasks(gfx::RenderPass& renderPass, RenderStaticData& staticData, const std::vector& tileUBOs) { using ShaderClass = shaders::ShaderSource; if (tileUBOs.empty()) { return false; } if (!clipMaskShader) { if (auto group = staticData.shaders->getShaderGroup(ShaderClass::name)) { clipMaskShader = std::static_pointer_cast(group->getOrCreateShader(*this, {})); } } if (!clipMaskShader) { Log::Error(Event::Render, "WebGPU: Failed to acquire clipping mask shader"); return false; } auto& shader = static_cast(*clipMaskShader); auto& webgpuRenderPass = static_cast(renderPass); const auto encoder = webgpuRenderPass.getEncoder(); if (!encoder) { Log::Error(Event::Render, "WebGPU: render pass encoder unavailable for clipping masks"); return false; } const auto& renderable = webgpuRenderPass.getDescriptor().renderable; if (clipMaskRenderable != &renderable) { clipMaskPipelineHash.reset(); clipMaskRenderable = &renderable; } const auto& vertexResource = getTileVertexBuffer(); const auto& indexResource = getTileIndexBuffer(); if (!vertexResource.getBuffer() || !indexResource.getBuffer()) { Log::Error(Event::Render, "WebGPU: Missing tile geometry buffers for clipping masks"); return false; } const gfx::ColorMode colorMode = gfx::ColorMode::disabled(); WGPUVertexAttribute vertexAttribute{}; vertexAttribute.format = WGPUVertexFormat_Sint16x2; vertexAttribute.offset = 0; vertexAttribute.shaderLocation = static_cast(ShaderClass::attributes[0].index); WGPUVertexBufferLayout vertexLayout{}; vertexLayout.arrayStride = sizeof(gfx::Vertex); vertexLayout.attributeCount = 1; vertexLayout.attributes = &vertexAttribute; vertexLayout.stepMode = WGPUVertexStepMode_Vertex; if (!clipMaskPipelineHash) { clipMaskPipelineHash = util::hash(colorMode.hash(), vertexLayout.arrayStride, static_cast(vertexAttribute.format), vertexAttribute.shaderLocation); } const auto pipeline = shader.getRenderPipeline(renderable, &vertexLayout, 1, colorMode, clipMaskDepthMode, clipMaskStencilMode, gfx::DrawModeType::Triangles, clipMaskPipelineHash); if (!pipeline) { Log::Error(Event::Render, "WebGPU: Failed to create clipping mask pipeline"); return false; } wgpuRenderPassEncoderSetPipeline(encoder, pipeline); wgpuRenderPassEncoderSetVertexBuffer(encoder, /*slot=*/0, vertexResource.getBuffer(), /*offset=*/0, vertexResource.getSizeInBytes()); wgpuRenderPassEncoderSetIndexBuffer(encoder, indexResource.getBuffer(), WGPUIndexFormat_Uint16, /*offset=*/0, indexResource.getSizeInBytes()); const uint64_t uboSize = sizeof(shaders::ClipUBO); clipMaskUniformBuffers.clear(); clipMaskUniformBuffers.reserve(tileUBOs.size()); for (const auto& ubo : tileUBOs) { auto buffer = createBuffer(&ubo, uboSize, WGPUBufferUsage_Uniform, false, false); if (!buffer.getBuffer()) { Log::Error(Event::Render, "WebGPU: Failed to allocate uniform buffer for clipping mask tile"); return false; } clipMaskUniformBuffers.emplace_back(std::move(buffer)); } auto& rendererBackend = static_cast(getBackend()); WGPUDevice device = static_cast(rendererBackend.getDevice()); if (!device) { Log::Error(Event::Render, "WebGPU: Device unavailable for clipping masks"); return false; } const auto& bindGroupOrder = shader.getBindGroupOrder(); if (bindGroupOrder.empty()) { Log::Error(Event::Render, "WebGPU: No bind group layouts available for clipping mask shader"); return false; } const uint32_t bindGroupIndex = bindGroupOrder.front(); const auto layout = shader.getBindGroupLayout(bindGroupIndex); if (!layout) { Log::Error(Event::Render, "WebGPU: Bind group layout missing for clipping mask shader"); return false; } const auto& bindingInfos = shader.getBindingInfosForGroup(bindGroupIndex); uint32_t uniformBinding = 0; bool bindingFound = false; for (const auto& info : bindingInfos) { if (info.type == webgpu::ShaderProgram::BindingType::UniformBuffer) { uniformBinding = info.binding; bindingFound = true; break; } } if (!bindingFound) { Log::Error(Event::Render, "WebGPU: Uniform binding not found for clipping mask shader"); return false; } clipMaskActiveBindGroups.clear(); clipMaskActiveBindGroups.reserve(tileUBOs.size()); for (std::size_t i = 0; i < tileUBOs.size(); ++i) { WGPUBindGroupEntry entry{}; entry.binding = uniformBinding; const auto& uboBuffer = clipMaskUniformBuffers[i]; entry.buffer = uboBuffer.getBuffer(); entry.offset = 0; entry.size = uboSize; WGPUBindGroupDescriptor descriptor{}; descriptor.layout = layout; descriptor.entryCount = 1; descriptor.entries = &entry; const auto bindGroup = wgpuDeviceCreateBindGroup(device, &descriptor); if (!bindGroup) { Log::Error(Event::Render, "WebGPU: Failed to create bind group for clipping mask draw"); continue; } clipMaskActiveBindGroups.push_back(bindGroup); wgpuRenderPassEncoderSetBindGroup(encoder, bindGroupIndex, bindGroup, 0, nullptr); wgpuRenderPassEncoderSetStencilReference(encoder, tileUBOs[i].stencil_ref); wgpuRenderPassEncoderDrawIndexed(encoder, /*indexCount=*/6, /*instanceCount=*/1, /*firstIndex=*/0, /*baseVertex=*/0, /*firstInstance=*/0); } for (auto bindGroup : clipMaskActiveBindGroups) { if (bindGroup) { wgpuBindGroupRelease(bindGroup); } } clipMaskActiveBindGroups.clear(); auto& renderStats = renderingStats(); renderStats.numDrawCalls += tileUBOs.size(); renderStats.totalDrawCalls += tileUBOs.size(); return true; } gfx::AttributeBindingArray Context::getOrCreateVertexBindings(gfx::Context&, const gfx::AttributeDataType vertexType, const size_t vertexAttributeIndex, const std::vector& vertexData, const gfx::VertexAttributeArray& defaults, const gfx::VertexAttributeArray& overrides, gfx::BufferUsageType, const std::optional>&, std::shared_ptr&) noexcept { static_cast(vertexType); static_cast(vertexAttributeIndex); static_cast(vertexData); gfx::AttributeBindingArray result; result.resize(overrides.allocatedSize()); // Process each vertex attribute overrides.visitAttributes([&](const gfx::VertexAttribute& attr) { const auto index = static_cast(attr.getIndex()); if (index >= result.size()) return; // Get the shared raw data if available gfx::VertexVectorBasePtr sharedRaw = attr.getSharedRawData(); if (!sharedRaw) { // Try default attributes by ID defaults.visitAttributes([&](const gfx::VertexAttribute& defaultAttr) { if (!sharedRaw && defaultAttr.getIndex() == attr.getIndex()) { sharedRaw = defaultAttr.getSharedRawData(); } }); } if (sharedRaw) { // Create vertex buffer from the shared data const auto dataSize = sharedRaw->getRawSize() * sharedRaw->getRawCount(); if (dataSize > 0) { // Create GPU buffer for this vertex attribute uint32_t usage = WGPUBufferUsage_Vertex | WGPUBufferUsage_CopyDst; auto bufferResource = std::make_shared( createBuffer(sharedRaw->getRawData(), dataSize, usage, false, false)); // Create attribute binding result[index] = {/*.attribute = */ {attr.getDataType(), /*offset=*/0}, /*.vertexStride = */ static_cast(attr.getStride()), /*.vertexBufferResource = */ bufferResource.get(), /*.vertexOffset = */ 0, /*.bufferIndex = */ 0}; } } }); return result; } } // namespace webgpu } // namespace mbgl