#include #include #include #include #include #include #include #include #include #include #include #if MLN_RENDER_BACKEND_OPENGL #include #endif #if MLN_RENDER_BACKEND_WEBGPU #include #include #endif #if MLN_RENDER_BACKEND_METAL #include #include #endif // MLN_RENDER_BACKEND_METAL #if MLN_RENDER_BACKEND_VULKAN #include #include #include #endif // MLN_RENDER_BACKEND_VULKAN namespace mbgl { TransformParameters::TransformParameters(const TransformState& state_) : state(state_) { // Update the default matrices to the current viewport dimensions. state.getProjMatrix(projMatrix); // Also compute a projection matrix that aligns with the current pixel grid, // taking into account odd viewport sizes. state.getProjMatrix(alignedProjMatrix, 1, true); // Calculate a second projection matrix with the near plane moved further, // to a tenth of the far value, so as not to waste depth buffer precision on // very close empty space, for layer types (fill-extrusion) that use the // depth buffer to emulate real-world space. state.getProjMatrix(nearClippedProjMatrix, static_cast(0.1 * state.getCameraToCenterDistance())); } PaintParameters::PaintParameters(gfx::Context& context_, float pixelRatio_, gfx::RendererBackend& backend_, const EvaluatedLight& evaluatedLight_, MapMode mode_, MapDebugOptions debugOptions_, TimePoint timePoint_, const TransformParameters& transformParams_, RenderStaticData& staticData_, LineAtlas& lineAtlas_, PatternAtlas& patternAtlas_, uint64_t frameCount_, double tileLodMinRadius_, double tileLodScale_, double tileLodPitchThreshold_) : context(context_), backend(backend_), encoder(context.createCommandEncoder()), transformParams(transformParams_), state(transformParams_.state), evaluatedLight(evaluatedLight_), staticData(staticData_), lineAtlas(lineAtlas_), patternAtlas(patternAtlas_), mapMode(mode_), debugOptions(debugOptions_), timePoint(timePoint_), pixelRatio(pixelRatio_), shaders(*staticData_.shaders), frameCount(frameCount_), tileLodMinRadius(tileLodMinRadius_), tileLodScale(tileLodScale_), tileLodPitchThreshold(tileLodPitchThreshold_) { pixelsToGLUnits = {{2.0f / state.getSize().width, -2.0f / state.getSize().height}}; if (state.getViewportMode() == ViewportMode::FlippedY) { pixelsToGLUnits[1] *= -1; } } PaintParameters::~PaintParameters() = default; mat4 PaintParameters::matrixForTile(const UnwrappedTileID& tileID, bool aligned) const { mat4 matrix; state.matrixFor(matrix, tileID); matrix::multiply(matrix, aligned ? transformParams.alignedProjMatrix : transformParams.projMatrix, matrix); return matrix; } gfx::DepthMode PaintParameters::depthModeForSublayer([[maybe_unused]] uint8_t n, gfx::DepthMaskType mask) const { if (currentLayer < opaquePassCutoff) { return gfx::DepthMode::disabled(); } #if MLN_RENDER_BACKEND_OPENGL float depth = depthRangeSize + ((1 + currentLayer) * numSublayers + n) * depthEpsilon; return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, mask, {depth, depth}}; #else return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, mask}; #endif } gfx::DepthMode PaintParameters::depthModeFor3D() const { #if MLN_RENDER_BACKEND_OPENGL return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, gfx::DepthMaskType::ReadWrite, {0.0, depthRangeSize}}; #else return gfx::DepthMode{gfx::DepthFunctionType::LessEqual, gfx::DepthMaskType::ReadWrite}; #endif } namespace { template using GetTileIDFunc = const UnwrappedTileID& (*)(const typename TIter::value_type&); using TileMaskIDMap = std::map; // Check whether we can reuse a clip mask for a new set of tiles bool tileIDsCovered(const RenderTiles& tiles, const TileMaskIDMap& idMap) { return idMap.size() == tiles->size() && std::equal(idMap.cbegin(), idMap.cend(), tiles->cbegin(), tiles->cend(), [=](const auto& a, const auto& b) { return a.first == b.get().id; }); } } // namespace void PaintParameters::clearStencil() { nextStencilID = 1; tileClippingMaskIDs.clear(); #if MLN_RENDER_BACKEND_METAL auto& mtlContext = static_cast(context); // Metal doesn't have an equivalent of `glClear`, so we clear the buffer by drawing zero to (0:0,0) #if !defined(NDEBUG) const auto debugGroup = renderPass->createDebugGroup("tile-clip-mask-clear"); #endif const std::vector tileUBO = { shaders::ClipUBO{/* .matrix = */ util::cast(matrixForTile({0, 0, 0})), /* .stencil_ref = */ 0, /* .pad1 = */ 0, /* .pad2 = */ 0, /* .pad3 = */ 0}}; mtlContext.renderTileClippingMasks(*renderPass, staticData, tileUBO); context.renderingStats().stencilClears++; #elif MLN_RENDER_BACKEND_VULKAN const auto& vulkanRenderPass = static_cast(*renderPass); vulkanRenderPass.clearStencil(); context.renderingStats().stencilClears++; #elif MLN_RENDER_BACKEND_WEBGPU // WebGPU clears stencil through render pass descriptor context.renderingStats().stencilClears++; #elif MLN_RENDER_BACKEND_OPENGL context.clearStencilBuffer(0b00000000); #endif } void PaintParameters::renderTileClippingMasks(const RenderTiles& renderTiles) { // We can avoid updating the mask if it already contains the same set of tiles. if (!renderTiles || !renderPass || tileIDsCovered(renderTiles, tileClippingMaskIDs)) { return; } tileClippingMaskIDs.clear(); // If the stencil value will overflow, clear the target to ensure ensure that none of the new // values remain set somewhere in it. Otherwise we can continue to overwrite it incrementally. const auto count = renderTiles->size(); if (nextStencilID + count > maxStencilValue) { clearStencil(); } #if MLN_RENDER_BACKEND_WEBGPU std::vector tileUBOs; for (const auto& tileRef : *renderTiles) { const auto& tileID = tileRef.get().id; const int32_t stencilID = nextStencilID; const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); if (result.second) { nextStencilID++; } else { continue; } if (tileUBOs.empty()) { tileUBOs.reserve(count); } tileUBOs.emplace_back(shaders::ClipUBO{/* .matrix = */ util::cast(matrixForTile(tileID)), /* .stencil_ref = */ static_cast(stencilID), /* .pad1 = */ 0, /* .pad2 = */ 0, /* .pad3 = */ 0}); } if (!tileUBOs.empty()) { #if !defined(NDEBUG) const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); #endif auto& webgpuContext = static_cast(context); webgpuContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs); webgpuContext.renderingStats().stencilUpdates++; } #elif MLN_RENDER_BACKEND_METAL // Assign a stencil ID and build a UBO for each tile in the set std::vector tileUBOs; for (const auto& tileRef : *renderTiles) { const auto& tileID = tileRef.get().id; const int32_t stencilID = nextStencilID; const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); if (result.second) { // inserted nextStencilID++; } else { // already present continue; } if (tileUBOs.empty()) { tileUBOs.reserve(count); } tileUBOs.emplace_back(shaders::ClipUBO{/* .matrix = */ util::cast(matrixForTile(tileID)), /* .stencil_ref = */ static_cast(stencilID), /* .pad1 = */ 0, /* .pad2 = */ 0, /* .pad3 = */ 0}); } if (!tileUBOs.empty()) { #if !defined(NDEBUG) const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); #endif auto& mtlContext = static_cast(context); mtlContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs); mtlContext.renderingStats().stencilUpdates++; } #elif MLN_RENDER_BACKEND_VULKAN std::vector tileUBOs; for (const auto& tileRef : *renderTiles) { const auto& tileID = tileRef.get().id; const uint32_t stencilID = nextStencilID; const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); if (result.second) { // inserted nextStencilID++; } else { // already present continue; } if (tileUBOs.empty()) { tileUBOs.reserve(count); } tileUBOs.emplace_back(shaders::ClipUBO{matrixForTile(tileID), stencilID}); } if (!tileUBOs.empty()) { #if !defined(NDEBUG) const auto debugGroup = renderPass->createDebugGroup("tile-clip-masks"); #endif auto& vulkanContext = static_cast(context); vulkanContext.renderTileClippingMasks(*renderPass, staticData, tileUBOs); vulkanContext.renderingStats().stencilUpdates++; } #elif MLN_RENDER_BACKEND_OPENGL auto program = staticData.shaders->getLegacyGroup().get(); if (!program) { return; } static_cast(context).renderingStats().stencilUpdates++; const style::Properties<>::PossiblyEvaluated properties{}; const ClippingMaskProgram::Binders paintAttributeData(properties, 0); for (const auto& tileRef : *renderTiles) { const auto& tileID = tileRef.get().id; const int32_t stencilID = nextStencilID; const auto result = tileClippingMaskIDs.insert(std::make_pair(tileID, stencilID)); if (result.second) { // inserted nextStencilID++; } else { // already present continue; } program->draw(context, *renderPass, gfx::Triangles(), gfx::DepthMode::disabled(), gfx::StencilMode{gfx::StencilMode::Always{}, stencilID, 0b11111111, gfx::StencilOpType::Keep, gfx::StencilOpType::Keep, gfx::StencilOpType::Replace}, gfx::ColorMode::disabled(), gfx::CullFaceMode::disabled(), *staticData.quadTriangleIndexBuffer, staticData.clippingMaskSegments, ClippingMaskProgram::computeAllUniformValues( ClippingMaskProgram::LayoutUniformValues{ uniforms::matrix::Value(matrixForTile(tileID)), }, paintAttributeData, properties, static_cast(state.getZoom())), ClippingMaskProgram::computeAllAttributeBindings( *staticData.tileVertexBuffer, paintAttributeData, properties), "clipping/" + util::toString(stencilID)); } #endif // MLN_RENDER_BACKEND_OPENGL } gfx::StencilMode PaintParameters::stencilModeForClipping(const UnwrappedTileID& tileID) const { auto it = tileClippingMaskIDs.find(tileID); assert(it != tileClippingMaskIDs.end()); const int32_t id = it != tileClippingMaskIDs.end() ? it->second : 0b00000000; return gfx::StencilMode{gfx::StencilMode::Equal{0b11111111}, id, 0b00000000, gfx::StencilOpType::Keep, gfx::StencilOpType::Keep, gfx::StencilOpType::Replace}; } gfx::StencilMode PaintParameters::stencilModeFor3D() { if (nextStencilID + 1 > maxStencilValue) { clearStencil(); } // We're potentially destroying the stencil clipping mask in this pass. That // means we'll have to recreate it for the next source if any. tileClippingMaskIDs.clear(); const int32_t id = nextStencilID++; return gfx::StencilMode{gfx::StencilMode::NotEqual{0b11111111}, id, 0b11111111, gfx::StencilOpType::Keep, gfx::StencilOpType::Keep, gfx::StencilOpType::Replace}; } gfx::ColorMode PaintParameters::colorModeForRenderPass() const { if (debugOptions & MapDebugOptions::Overdraw) { constexpr float overdraw = 1.0f / 8.0f; return gfx::ColorMode{ gfx::ColorMode::Add{gfx::ColorBlendFactorType::ConstantColor, gfx::ColorBlendFactorType::One}, Color{overdraw, overdraw, overdraw, 0.0f}, gfx::ColorMode::Mask{true, true, true, true}}; } else if (pass == RenderPass::Translucent) { return gfx::ColorMode::alphaBlended(); } else { return gfx::ColorMode::unblended(); } } } // namespace mbgl