#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 { using namespace style; namespace { style::SymbolPropertyValues iconPropertyValues(const style::SymbolPaintProperties::PossiblyEvaluated& evaluated_, const style::SymbolLayoutProperties::PossiblyEvaluated& layout_) { return style::SymbolPropertyValues { layout_.get(), layout_.get(), layout_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get().constantOr(Color::black()).a > 0 && evaluated_.get().constantOr(1), evaluated_.get().constantOr(Color::black()).a > 0 }; } style::SymbolPropertyValues textPropertyValues(const style::SymbolPaintProperties::PossiblyEvaluated& evaluated_, const style::SymbolLayoutProperties::PossiblyEvaluated& layout_) { return style::SymbolPropertyValues { layout_.get(), layout_.get(), layout_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get().constantOr(Color::black()).a > 0 && evaluated_.get().constantOr(1), evaluated_.get().constantOr(Color::black()).a > 0 }; } using SegmentWrapper = std::reference_wrapper>; using SegmentVectorWrapper = std::reference_wrapper>; using SegmentsWrapper = variant; struct RenderableSegment { RenderableSegment(SegmentWrapper segment_, const RenderTile& tile_, const LayerRenderData& renderData_, const SymbolBucket::PaintProperties& bucketPaintProperties_, float sortKey_, const SymbolType type_) : segment(segment_), tile(tile_), renderData(renderData_), bucketPaintProperties(bucketPaintProperties_), sortKey(sortKey_), type(type_) {} SegmentWrapper segment; const RenderTile& tile; const LayerRenderData& renderData; const SymbolBucket::PaintProperties& bucketPaintProperties; float sortKey; SymbolType type; friend bool operator < (const RenderableSegment& lhs, const RenderableSegment& rhs) { // Sort renderable segments by a sort key. if (lhs.sortKey < rhs.sortKey) { return true; } // In cases when sort key is the same, sort by the type of a segment (text over icons), // and for segments of the same type with the same sort key, sort by a tile id. if (lhs.sortKey == rhs.sortKey) { if (lhs.type != SymbolType::Text && rhs.type == SymbolType::Text) { return true; } if (lhs.type == rhs.type) { return lhs.tile.id < rhs.tile.id; } } return false; } }; template void drawIcon(const DrawFn& draw, const RenderTile& tile, const LayerRenderData& renderData, SegmentsWrapper iconSegments, const SymbolBucket::PaintProperties& bucketPaintProperties, const PaintParameters& parameters, const bool sdfIcons) { auto& bucket = static_cast(*renderData.bucket); const auto& evaluated = getEvaluated(renderData.layerProperties); const auto& layout = *bucket.layout; auto values = iconPropertyValues(evaluated, layout); const auto& paintPropertyValues = RenderSymbolLayer::iconPaintProperties(evaluated); const bool alongLine = layout.get() != SymbolPlacementType::Point && layout.get() == AlignmentType::Map; const bool iconScaled = layout.get().constantOr(1.0) != 1.0 || bucket.iconsNeedLinear; const bool iconTransformed = values.rotationAlignment == AlignmentType::Map || parameters.state.getPitch() != 0; const gfx::TextureBinding textureBinding{ tile.getIconAtlasTexture().getResource(), sdfIcons || parameters.state.isChanging() || iconScaled || iconTransformed ? gfx::TextureFilterType::Linear : gfx::TextureFilterType::Nearest }; const Size& iconSize = tile.getIconAtlasTexture().size; const bool variablePlacedIcon = bucket.hasVariablePlacement && layout.get() != IconTextFitType::None; if (sdfIcons) { if (values.hasHalo) { draw(parameters.programs.getSymbolLayerPrograms().symbolIconSDF, SymbolSDFIconProgram::layoutUniformValues(false, variablePlacedIcon, values, iconSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), bucket.sdfIcon, iconSegments, bucket.iconSizeBinder, bucketPaintProperties.iconBinders, paintPropertyValues, SymbolSDFIconProgram::TextureBindings{ textureBinding }, "halo"); } if (values.hasFill) { draw(parameters.programs.getSymbolLayerPrograms().symbolIconSDF, SymbolSDFIconProgram::layoutUniformValues(false, variablePlacedIcon, values, iconSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), bucket.sdfIcon, iconSegments, bucket.iconSizeBinder, bucketPaintProperties.iconBinders, paintPropertyValues, SymbolSDFIconProgram::TextureBindings{ textureBinding }, "fill"); } } else { draw(parameters.programs.getSymbolLayerPrograms().symbolIcon, SymbolIconProgram::layoutUniformValues(false, variablePlacedIcon, values, iconSize, parameters.pixelsToGLUnits, alongLine, tile, parameters.state, parameters.symbolFadeChange), bucket.icon, iconSegments, bucket.iconSizeBinder, bucketPaintProperties.iconBinders, paintPropertyValues, SymbolIconProgram::TextureBindings{ textureBinding }, "icon"); } } template void drawText(const DrawFn& draw, const RenderTile& tile, const LayerRenderData& renderData, SegmentsWrapper textSegments, const SymbolBucket::PaintProperties& bucketPaintProperties, const PaintParameters& parameters) { auto& bucket = static_cast(*renderData.bucket); const auto& evaluated = getEvaluated(renderData.layerProperties); const auto& layout = *bucket.layout; auto values = textPropertyValues(evaluated, layout); const auto& paintPropertyValues = RenderSymbolLayer::textPaintProperties(evaluated); const bool alongLine = layout.get() != SymbolPlacementType::Point && layout.get() == AlignmentType::Map; const Size& glyphTexSize = tile.getGlyphAtlasTexture().size; const gfx::TextureBinding glyphTextureBinding{tile.getGlyphAtlasTexture().getResource(), gfx::TextureFilterType::Linear}; const auto drawGlyphs = [&](auto& program, const auto& uniforms, const auto& textures, SymbolSDFPart part) { draw(program, uniforms, bucket.text, textSegments, bucket.textSizeBinder, bucketPaintProperties.textBinders, paintPropertyValues, textures, (part == SymbolSDFPart::Halo) ? "halo" : "fill"); }; if (bucket.iconsInText) { const ZoomEvaluatedSize partiallyEvaluatedTextSize = bucket.textSizeBinder->evaluateForZoom(parameters.state.getZoom()); const bool transformed = values.rotationAlignment == AlignmentType::Map || parameters.state.getPitch() != 0; const Size& iconTexSize = tile.getIconAtlasTexture().size; const gfx::TextureBinding iconTextureBinding{ tile.getIconAtlasTexture().getResource(), parameters.state.isChanging() || transformed || !partiallyEvaluatedTextSize.isZoomConstant ? gfx::TextureFilterType::Linear : gfx::TextureFilterType::Nearest}; if (values.hasHalo) { drawGlyphs(parameters.programs.getSymbolLayerPrograms().symbolTextAndIcon, SymbolTextAndIconProgram::layoutUniformValues(bucket.hasVariablePlacement, values, glyphTexSize, iconTexSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), SymbolTextAndIconProgram::TextureBindings{glyphTextureBinding, iconTextureBinding}, SymbolSDFPart::Halo); } if (values.hasFill) { drawGlyphs(parameters.programs.getSymbolLayerPrograms().symbolTextAndIcon, SymbolTextAndIconProgram::layoutUniformValues(bucket.hasVariablePlacement, values, glyphTexSize, iconTexSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), SymbolTextAndIconProgram::TextureBindings{glyphTextureBinding, iconTextureBinding}, SymbolSDFPart::Fill); } } else { if (values.hasHalo) { drawGlyphs(parameters.programs.getSymbolLayerPrograms().symbolGlyph, SymbolSDFTextProgram::layoutUniformValues(true, bucket.hasVariablePlacement, values, glyphTexSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Halo), SymbolSDFTextProgram::TextureBindings{glyphTextureBinding}, SymbolSDFPart::Halo); } if (values.hasFill) { drawGlyphs(parameters.programs.getSymbolLayerPrograms().symbolGlyph, SymbolSDFTextProgram::layoutUniformValues(true, bucket.hasVariablePlacement, values, glyphTexSize, parameters.pixelsToGLUnits, parameters.pixelRatio, alongLine, tile, parameters.state, parameters.symbolFadeChange, SymbolSDFPart::Fill), SymbolSDFTextProgram::TextureBindings{glyphTextureBinding}, SymbolSDFPart::Fill); } } } inline const SymbolLayer::Impl& impl_cast(const Immutable& impl) { assert(impl->getTypeInfo() == SymbolLayer::Impl::staticTypeInfo()); return static_cast(*impl); } } // namespace RenderSymbolLayer::RenderSymbolLayer(Immutable _impl) : RenderLayer(makeMutable(std::move(_impl))), unevaluated(impl_cast(baseImpl).paint.untransitioned()) {} RenderSymbolLayer::~RenderSymbolLayer() = default; void RenderSymbolLayer::transition(const TransitionParameters& parameters) { unevaluated = impl_cast(baseImpl).paint.transitioned(parameters, std::move(unevaluated)); hasFormatSectionOverrides = SymbolLayerPaintPropertyOverrides::hasOverrides(impl_cast(baseImpl).layout.get()); } void RenderSymbolLayer::evaluate(const PropertyEvaluationParameters& parameters) { auto properties = makeMutable( staticImmutableCast(baseImpl), unevaluated.evaluate(parameters)); auto& evaluated = properties->evaluated; auto& layout = impl_cast(baseImpl).layout; if (hasFormatSectionOverrides) { SymbolLayerPaintPropertyOverrides::setOverrides(layout, evaluated); } auto hasIconOpacity = evaluated.get().constantOr(Color::black()).a > 0 || evaluated.get().constantOr(Color::black()).a > 0; auto hasTextOpacity = evaluated.get().constantOr(Color::black()).a > 0 || evaluated.get().constantOr(Color::black()).a > 0; passes = ((evaluated.get().constantOr(1) > 0 && hasIconOpacity && iconSize > 0) || (evaluated.get().constantOr(1) > 0 && hasTextOpacity && textSize > 0)) ? RenderPass::Translucent : RenderPass::None; properties->renderPasses = mbgl::underlying_type(passes); evaluatedProperties = std::move(properties); } bool RenderSymbolLayer::hasTransition() const { return unevaluated.hasTransition(); } bool RenderSymbolLayer::hasCrossfade() const { return false; } void RenderSymbolLayer::render(PaintParameters& parameters) { assert(renderTiles); if (parameters.pass == RenderPass::Opaque) { return; } const bool sortFeaturesByKey = !impl_cast(baseImpl).layout.get().isUndefined(); std::multiset renderableSegments; const auto draw = [¶meters, this] (auto& programInstance, const auto& uniformValues, const auto& buffers, auto& segments, const auto& symbolSizeBinder, const auto& binders, const auto& paintProperties, const auto& textureBindings, const std::string& suffix) { const auto allUniformValues = programInstance.computeAllUniformValues( uniformValues, *symbolSizeBinder, binders, paintProperties, parameters.state.getZoom() ); const auto allAttributeBindings = programInstance.computeAllAttributeBindings( *buffers.vertexBuffer, *buffers.dynamicVertexBuffer, *buffers.opacityVertexBuffer, binders, paintProperties ); this->checkRenderability(parameters, programInstance.activeBindingCount(allAttributeBindings)); segments.match( [&](const std::reference_wrapper>& segment) { programInstance.draw( parameters.context, *parameters.renderPass, gfx::Triangles(), parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), *buffers.indexBuffer, segment, allUniformValues, allAttributeBindings, textureBindings, this->getID() + "/" + suffix ); }, [&](const std::reference_wrapper>& segmentVector) { programInstance.draw( parameters.context, *parameters.renderPass, gfx::Triangles(), parameters.depthModeForSublayer(0, gfx::DepthMaskType::ReadOnly), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), *buffers.indexBuffer, segmentVector, allUniformValues, allAttributeBindings, textureBindings, this->getID() + "/" + suffix ); } ); }; for (const RenderTile& tile : *renderTiles) { const LayerRenderData* renderData = getRenderDataForPass(tile, parameters.pass); if (!renderData) { continue; } auto& bucket = static_cast(*renderData->bucket); assert(bucket.paintProperties.find(getID()) != bucket.paintProperties.end()); const auto& bucketPaintProperties = bucket.paintProperties.at(getID()); bucket.justReloaded = false; auto addRenderables = [&renderableSegments, &tile, renderData, &bucketPaintProperties, it = renderableSegments.begin()] (auto& segments, const SymbolType type) mutable { for (auto& segment : segments) { it = renderableSegments.emplace_hint(it, SegmentWrapper{std::ref(segment)}, tile, *renderData, bucketPaintProperties, segment.sortKey, type); } }; if (bucket.hasIconData()) { if (sortFeaturesByKey) { addRenderables(bucket.icon.segments, SymbolType::IconRGBA); } else { drawIcon(draw, tile, *renderData, std::ref(bucket.icon.segments), bucketPaintProperties, parameters, false /*sdfIcon*/); } } if (bucket.hasSdfIconData()) { if (sortFeaturesByKey) { addRenderables(bucket.sdfIcon.segments, SymbolType::IconSDF); } else { drawIcon(draw, tile, *renderData, std::ref(bucket.sdfIcon.segments), bucketPaintProperties, parameters, true /*sdfIcon*/); } } if (bucket.hasTextData()) { if (sortFeaturesByKey) { addRenderables(bucket.text.segments, SymbolType::Text); } else { drawText(draw, tile, *renderData, std::ref(bucket.text.segments), bucketPaintProperties, parameters); } } const auto drawCollisonData = [&](const bool isText, const bool hasCollisionBox, const bool hasCollisionCircle) { if (!hasCollisionBox && !hasCollisionCircle) return; static const style::Properties<>::PossiblyEvaluated properties{}; static const CollisionBoxProgram::Binders paintAttributeData(properties, 0); auto pixelRatio = tile.id.pixelsToTileUnits(1, parameters.state.getZoom()); const float scale = std::pow(2, parameters.state.getZoom() - tile.getOverscaledTileID().overscaledZ); std::array extrudeScale = {{ parameters.pixelsToGLUnits[0] / (pixelRatio * scale), parameters.pixelsToGLUnits[1] / (pixelRatio * scale) }}; const auto& evaluated = getEvaluated(renderData->layerProperties); const auto& layout = *bucket.layout; const auto values = isText ? textPropertyValues(evaluated, layout): iconPropertyValues(evaluated, layout); const bool needTranslate = values.translate[0] != 0 || values.translate[1] != 0; if (hasCollisionBox) { const auto& collisionBox = isText ? bucket.textCollisionBox : bucket.iconCollisionBox; parameters.programs.getSymbolLayerPrograms().collisionBox.draw( parameters.context, *parameters.renderPass, gfx::Lines{ 1.0f }, gfx::DepthMode::disabled(), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), CollisionBoxProgram::LayoutUniformValues { uniforms::matrix::Value((needTranslate ? tile.translatedMatrix(values.translate, values.translateAnchor, parameters.state) : tile.matrix)), uniforms::extrude_scale::Value(extrudeScale), uniforms::camera_to_center_distance::Value(parameters.state.getCameraToCenterDistance()) }, *collisionBox->vertexBuffer, *collisionBox->dynamicVertexBuffer, *collisionBox->indexBuffer, collisionBox->segments, paintAttributeData, properties, CollisionBoxProgram::TextureBindings{}, parameters.state.getZoom(), getID()); } if (hasCollisionCircle) { const auto& collisionCircle = isText ? bucket.textCollisionCircle : bucket.iconCollisionCircle; parameters.programs.getSymbolLayerPrograms().collisionCircle.draw( parameters.context, *parameters.renderPass, gfx::Triangles(), gfx::DepthMode::disabled(), gfx::StencilMode::disabled(), parameters.colorModeForRenderPass(), gfx::CullFaceMode::disabled(), CollisionCircleProgram::LayoutUniformValues { uniforms::matrix::Value((needTranslate ? tile.translatedMatrix(values.translate, values.translateAnchor, parameters.state) : tile.matrix)), uniforms::extrude_scale::Value(extrudeScale), uniforms::overscale_factor::Value(float(tile.getOverscaledTileID().overscaleFactor())), uniforms::camera_to_center_distance::Value(parameters.state.getCameraToCenterDistance()) }, *collisionCircle->vertexBuffer, *collisionCircle->dynamicVertexBuffer, *collisionCircle->indexBuffer, collisionCircle->segments, paintAttributeData, properties, CollisionCircleProgram::TextureBindings{}, parameters.state.getZoom(), getID()); } }; drawCollisonData(false /*isText*/, bucket.hasIconCollisionBoxData(), bucket.hasIconCollisionCircleData()); drawCollisonData(true /*isText*/, bucket.hasTextCollisionBoxData(), bucket.hasTextCollisionCircleData()); } if (sortFeaturesByKey) { for (auto& renderable : renderableSegments) { if (renderable.type == SymbolType::Text) { drawText(draw, renderable.tile, renderable.renderData, renderable.segment, renderable.bucketPaintProperties, parameters); } else { drawIcon(draw, renderable.tile, renderable.renderData, renderable.segment, renderable.bucketPaintProperties, parameters, renderable.type == SymbolType::IconSDF); } } } } // static style::IconPaintProperties::PossiblyEvaluated RenderSymbolLayer::iconPaintProperties(const style::SymbolPaintProperties::PossiblyEvaluated& evaluated_) { return style::IconPaintProperties::PossiblyEvaluated { evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get() }; } // static style::TextPaintProperties::PossiblyEvaluated RenderSymbolLayer::textPaintProperties(const style::SymbolPaintProperties::PossiblyEvaluated& evaluated_) { return style::TextPaintProperties::PossiblyEvaluated { evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get(), evaluated_.get() }; } void RenderSymbolLayer::prepare(const LayerPrepareParameters& params) { renderTiles = params.source->getRenderTilesSortedByYPosition(); addRenderPassesFromTiles(); placementData.clear(); for (const RenderTile& renderTile : *renderTiles) { auto* bucket = static_cast(renderTile.getBucket(*baseImpl)); if (bucket && bucket->bucketLeaderID == getID()) { // Only place this layer if it's the "group leader" for the bucket const Tile* tile = params.source->getRenderedTile(renderTile.id); assert(tile); assert(tile->kind == Tile::Kind::Geometry); auto featureIndex = static_cast(tile)->getFeatureIndex(); if (bucket->sortKeyRanges.empty()) { placementData.push_back({*bucket, renderTile, featureIndex, baseImpl->source, nullopt}); } else { for (const auto& sortKeyRange : bucket->sortKeyRanges) { BucketPlacementData layerData{*bucket, renderTile, featureIndex, baseImpl->source, sortKeyRange}; auto sortPosition = std::upper_bound( placementData.cbegin(), placementData.cend(), layerData, [](const auto& lhs, const auto& rhs) { assert(lhs.sortKeyRange && rhs.sortKeyRange); return lhs.sortKeyRange->sortKey < rhs.sortKeyRange->sortKey; }); placementData.insert(sortPosition, std::move(layerData)); } } } } } } // namespace mbgl