diff options
-rw-r--r-- | cmake/core-files.cmake | 5 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 568 | ||||
-rw-r--r-- | src/mbgl/layout/symbol_layout.hpp | 127 | ||||
-rw-r--r-- | src/mbgl/renderer/bucket.hpp | 4 | ||||
-rw-r--r-- | src/mbgl/renderer/symbol_bucket.cpp | 595 | ||||
-rw-r--r-- | src/mbgl/renderer/symbol_bucket.hpp | 143 | ||||
-rw-r--r-- | src/mbgl/style/bucket_parameters.hpp | 3 | ||||
-rw-r--r-- | src/mbgl/style/layers/symbol_layer_impl.cpp | 71 | ||||
-rw-r--r-- | src/mbgl/style/layers/symbol_layer_impl.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/tile/geometry_tile.cpp | 8 | ||||
-rw-r--r-- | src/mbgl/tile/tile_worker.cpp | 191 | ||||
-rw-r--r-- | src/mbgl/tile/tile_worker.hpp | 41 | ||||
-rw-r--r-- | src/mbgl/util/clip_lines.cpp | 4 | ||||
-rw-r--r-- | src/mbgl/util/clip_lines.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/util/merge_lines.hpp | 2 | ||||
-rw-r--r-- | src/mbgl/util/worker.cpp | 10 | ||||
-rw-r--r-- | src/mbgl/util/worker.hpp | 3 |
17 files changed, 908 insertions, 871 deletions
diff --git a/cmake/core-files.cmake b/cmake/core-files.cmake index eafe513d22..20aa4dfd0c 100644 --- a/cmake/core-files.cmake +++ b/cmake/core-files.cmake @@ -79,6 +79,10 @@ set(MBGL_CORE_FILES src/mbgl/gl/object_store.cpp src/mbgl/gl/object_store.hpp + # layout + src/mbgl/layout/symbol_layout.cpp + src/mbgl/layout/symbol_layout.hpp + # map include/mbgl/map/camera.hpp include/mbgl/map/map.hpp @@ -250,6 +254,7 @@ set(MBGL_CORE_FILES src/mbgl/style/tile_source_impl.cpp src/mbgl/style/tile_source_impl.hpp src/mbgl/style/types.cpp + src/mbgl/style/update_batch.hpp src/mbgl/style/update_parameters.hpp # style/conversion diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp new file mode 100644 index 0000000000..aa22eb428d --- /dev/null +++ b/src/mbgl/layout/symbol_layout.cpp @@ -0,0 +1,568 @@ +#include <mbgl/layout/symbol_layout.hpp> +#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/style/filter_evaluator.hpp> +#include <mbgl/sprite/sprite_store.hpp> +#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/geometry/glyph_atlas.hpp> +#include <mbgl/text/get_anchors.hpp> +#include <mbgl/text/glyph_store.hpp> +#include <mbgl/text/collision_tile.hpp> +#include <mbgl/util/utf.hpp> +#include <mbgl/util/token.hpp> +#include <mbgl/util/math.hpp> +#include <mbgl/util/merge_lines.hpp> +#include <mbgl/util/clip_lines.hpp> +#include <mbgl/util/std.hpp> +#include <mbgl/util/constants.hpp> +#include <mbgl/util/string.hpp> +#include <mbgl/math/minmax.hpp> +#include <mbgl/platform/platform.hpp> +#include <mbgl/platform/log.hpp> + +namespace mbgl { + +using namespace style; + +SymbolInstance::SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, + const Shaping& shapedText, const PositionedIcon& shapedIcon, + const SymbolLayoutProperties& layout, const bool addToBuffers, const uint32_t index_, + const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, + const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, + const GlyphPositions& face, const IndexedSubfeature& indexedFeature) : + point(anchor.point), + index(index_), + hasText(shapedText), + hasIcon(shapedIcon), + + // Create the quads used for rendering the glyphs. + glyphQuads(addToBuffers && shapedText ? + getGlyphQuads(anchor, shapedText, textBoxScale, line, layout, textPlacement, face) : + SymbolQuads()), + + // Create the quad used for rendering the icon. + iconQuads(addToBuffers && shapedIcon ? + getIconQuads(anchor, shapedIcon, line, layout, iconPlacement, shapedText) : + SymbolQuads()), + + // Create the collision features that will be used to check whether this symbol instance can be placed + textCollisionFeature(line, anchor, shapedText, textBoxScale, textPadding, textPlacement, indexedFeature), + iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature) + {} + + +SymbolLayout::SymbolLayout(std::string bucketName_, + std::string sourceLayerName_, + uint32_t overscaling_, + float zoom_, + const MapMode mode_, + const GeometryTileLayer& layer, + const style::Filter& filter, + style::SymbolLayoutProperties layout_, + float textMaxSize_, + SpriteAtlas& spriteAtlas_) + : bucketName(std::move(bucketName_)), + sourceLayerName(std::move(sourceLayerName_)), + overscaling(overscaling_), + zoom(zoom_), + mode(mode_), + layout(std::move(layout_)), + textMaxSize(textMaxSize_), + spriteAtlas(spriteAtlas_), + tileSize(util::tileSize * overscaling_), + tilePixelRatio(float(util::EXTENT) / tileSize) { + + const bool hasText = !layout.textField.value.empty() && !layout.textFont.value.empty(); + const bool hasIcon = !layout.iconImage.value.empty(); + + if (!hasText && !hasIcon) { + return; + } + + auto layerName = layer.getName(); + + // Determine and load glyph ranges + const GLsizei featureCount = static_cast<GLsizei>(layer.featureCount()); + for (GLsizei i = 0; i < featureCount; i++) { + auto feature = layer.getFeature(i); + if (!filter(feature->getType(), feature->getID(), [&] (const auto& key) { return feature->getValue(key); })) + continue; + + SymbolFeature ft; + ft.index = i; + + auto getValue = [&feature](const std::string& key) -> std::string { + auto value = feature->getValue(key); + if (!value) + return std::string(); + if (value->is<std::string>()) + return value->get<std::string>(); + if (value->is<bool>()) + return value->get<bool>() ? "true" : "false"; + if (value->is<int64_t>()) + return util::toString(value->get<int64_t>()); + if (value->is<uint64_t>()) + return util::toString(value->get<uint64_t>()); + if (value->is<double>()) + return util::toString(value->get<double>()); + return "null"; + }; + + if (hasText) { + std::string u8string = util::replaceTokens(layout.textField, getValue); + + if (layout.textTransform == TextTransformType::Uppercase) { + u8string = platform::uppercase(u8string); + } else if (layout.textTransform == TextTransformType::Lowercase) { + u8string = platform::lowercase(u8string); + } + + ft.label = util::utf8_to_utf32::convert(u8string); + + if (!ft.label.empty()) { + // Loop through all characters of this text and collect unique codepoints. + for (char32_t chr : ft.label) { + ranges.insert(getGlyphRange(chr)); + } + } + } + + if (hasIcon) { + ft.sprite = util::replaceTokens(layout.iconImage, getValue); + } + + if (ft.label.length() || ft.sprite.length()) { + + auto &multiline = ft.geometry; + + GeometryCollection geometryCollection = feature->getGeometries(); + for (auto& line : geometryCollection) { + multiline.emplace_back(); + for (auto& point : line) { + multiline.back().emplace_back(point.x, point.y); + } + } + + features.push_back(std::move(ft)); + } + } + + if (layout.symbolPlacement == SymbolPlacementType::Line) { + util::mergeLines(features); + } +} + +bool SymbolLayout::hasSymbolInstances() const { + return !symbolInstances.empty(); +} + +bool SymbolLayout::canPrepare(GlyphStore& glyphStore, SpriteStore& spriteStore) { + if (!layout.textField.value.empty() && !layout.textFont.value.empty() && !glyphStore.hasGlyphRanges(layout.textFont, ranges)) { + return false; + } + + if (!layout.iconImage.value.empty() && !spriteStore.isLoaded()) { + return false; + } + + return true; +} + +void SymbolLayout::prepare(uintptr_t tileUID, + GlyphAtlas& glyphAtlas, + GlyphStore& glyphStore) { + float horizontalAlign = 0.5; + float verticalAlign = 0.5; + + switch (layout.textAnchor) { + case TextAnchorType::Top: + case TextAnchorType::Bottom: + case TextAnchorType::Center: + break; + case TextAnchorType::Right: + case TextAnchorType::TopRight: + case TextAnchorType::BottomRight: + horizontalAlign = 1; + break; + case TextAnchorType::Left: + case TextAnchorType::TopLeft: + case TextAnchorType::BottomLeft: + horizontalAlign = 0; + break; + } + + switch (layout.textAnchor) { + case TextAnchorType::Left: + case TextAnchorType::Right: + case TextAnchorType::Center: + break; + case TextAnchorType::Bottom: + case TextAnchorType::BottomLeft: + case TextAnchorType::BottomRight: + verticalAlign = 1; + break; + case TextAnchorType::Top: + case TextAnchorType::TopLeft: + case TextAnchorType::TopRight: + verticalAlign = 0; + break; + } + + const float justify = layout.textJustify == TextJustifyType::Right ? 1 : + layout.textJustify == TextJustifyType::Left ? 0 : + 0.5; + + auto glyphSet = glyphStore.getGlyphSet(layout.textFont); + + for (const auto& feature : features) { + if (feature.geometry.empty()) continue; + + Shaping shapedText; + PositionedIcon shapedIcon; + GlyphPositions face; + + // if feature has text, shape the text + if (feature.label.length()) { + shapedText = glyphSet->getShaping( + /* string */ feature.label, + /* maxWidth: ems */ layout.symbolPlacement != SymbolPlacementType::Line ? + layout.textMaxWidth * 24 : 0, + /* lineHeight: ems */ layout.textLineHeight * 24, + /* horizontalAlign */ horizontalAlign, + /* verticalAlign */ verticalAlign, + /* justify */ justify, + /* spacing: ems */ layout.textLetterSpacing * 24, + /* translate */ Point<float>(layout.textOffset.value[0], layout.textOffset.value[1])); + + // Add the glyphs we need for this label to the glyph atlas. + if (shapedText) { + glyphAtlas.addGlyphs(tileUID, feature.label, layout.textFont, **glyphSet, face); + } + } + + // if feature has icon, get sprite atlas position + if (feature.sprite.length()) { + auto image = spriteAtlas.getImage(feature.sprite, SpritePatternMode::Single); + if (image) { + shapedIcon = shapeIcon(*image, layout); + assert((*image).spriteImage); + if ((*image).spriteImage->sdf) { + sdfIcons = true; + } + if ((*image).relativePixelRatio != 1.0f) { + iconsNeedLinear = true; + } else if (layout.iconRotate != 0) { + iconsNeedLinear = true; + } + } + } + + // if either shapedText or icon position is present, add the feature + if (shapedText || shapedIcon) { + addFeature(feature.geometry, shapedText, shapedIcon, face, feature.index); + } + } + + features.clear(); +} + + +void SymbolLayout::addFeature(const GeometryCollection &lines, + const Shaping &shapedText, const PositionedIcon &shapedIcon, const GlyphPositions &face, const size_t index) { + + const float minScale = 0.5f; + const float glyphSize = 24.0f; + + const float fontScale = layout.textSize / glyphSize; + const float textBoxScale = tilePixelRatio * fontScale; + const float textMaxBoxScale = tilePixelRatio * textMaxSize / glyphSize; + const float iconBoxScale = tilePixelRatio * layout.iconSize; + const float symbolSpacing = tilePixelRatio * layout.symbolSpacing; + const bool avoidEdges = layout.symbolAvoidEdges && layout.symbolPlacement != SymbolPlacementType::Line; + const float textPadding = layout.textPadding * tilePixelRatio; + const float iconPadding = layout.iconPadding * tilePixelRatio; + const float textMaxAngle = layout.textMaxAngle * util::DEG2RAD; + const SymbolPlacementType textPlacement = layout.textRotationAlignment != AlignmentType::Map + ? SymbolPlacementType::Point + : layout.symbolPlacement; + const SymbolPlacementType iconPlacement = layout.iconRotationAlignment != AlignmentType::Map + ? SymbolPlacementType::Point + : layout.symbolPlacement; + const bool mayOverlap = layout.textAllowOverlap || layout.iconAllowOverlap || + layout.textIgnorePlacement || layout.iconIgnorePlacement; + const bool isLine = layout.symbolPlacement == SymbolPlacementType::Line; + const float textRepeatDistance = symbolSpacing / 2; + + auto& clippedLines = isLine ? + util::clipLines(lines, 0, 0, util::EXTENT, util::EXTENT) : + lines; + + IndexedSubfeature indexedFeature = {index, sourceLayerName, bucketName, symbolInstances.size()}; + + for (const auto& line : clippedLines) { + if (line.empty()) continue; + + // Calculate the anchor points around which you want to place labels + Anchors anchors = isLine ? + getAnchors(line, symbolSpacing, textMaxAngle, shapedText.left, shapedText.right, shapedIcon.left, shapedIcon.right, glyphSize, textMaxBoxScale, overscaling) : + Anchors({ Anchor(float(line[0].x), float(line[0].y), 0, minScale) }); + + // For each potential label, create the placement features used to check for collisions, and the quads use for rendering. + for (Anchor &anchor : anchors) { + if (shapedText && isLine) { + if (anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) { + continue; + } + } + + const bool inside = !(anchor.point.x < 0 || anchor.point.x > util::EXTENT || anchor.point.y < 0 || anchor.point.y > util::EXTENT); + + if (avoidEdges && !inside) continue; + + // Normally symbol layers are drawn across tile boundaries. Only symbols + // with their anchors within the tile boundaries are added to the buffers + // to prevent symbols from being drawn twice. + // + // Symbols in layers with overlap are sorted in the y direction so that + // symbols lower on the canvas are drawn on top of symbols near the top. + // To preserve this order across tile boundaries these symbols can't + // be drawn across tile boundaries. Instead they need to be included in + // the buffers for both tiles and clipped to tile boundaries at draw time. + // + // TODO remove the `&& false` when is #1673 implemented + const bool addToBuffers = (mode == MapMode::Still) || inside || (mayOverlap && false); + + symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, symbolInstances.size(), + textBoxScale, textPadding, textPlacement, + iconBoxScale, iconPadding, iconPlacement, + face, indexedFeature); + } + } +} + +bool SymbolLayout::anchorIsTooClose(const std::u32string &text, const float repeatDistance, Anchor &anchor) { + if (compareText.find(text) == compareText.end()) { + compareText.emplace(text, Anchors()); + } else { + auto otherAnchors = compareText.find(text)->second; + for (Anchor &otherAnchor : otherAnchors) { + if (util::dist<float>(anchor.point, otherAnchor.point) < repeatDistance) { + return true; + } + } + } + compareText[text].push_back(anchor); + return false; +} + +std::unique_ptr<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) { + auto bucket = std::make_unique<SymbolBucket>(mode, layout, sdfIcons, iconsNeedLinear); + + // Calculate which labels can be shown and when they can be shown and + // create the bufers used for rendering. + + const SymbolPlacementType textPlacement = layout.textRotationAlignment != AlignmentType::Map + ? SymbolPlacementType::Point + : layout.symbolPlacement; + const SymbolPlacementType iconPlacement = layout.iconRotationAlignment != AlignmentType::Map + ? SymbolPlacementType::Point + : layout.symbolPlacement; + + const bool mayOverlap = layout.textAllowOverlap || layout.iconAllowOverlap || + layout.textIgnorePlacement || layout.iconIgnorePlacement; + + // Sort symbols by their y position on the canvas so that they lower symbols + // are drawn on top of higher symbols. + // Don't sort symbols that won't overlap because it isn't necessary and + // because it causes more labels to pop in and out when rotating. + if (mayOverlap) { + const float sin = std::sin(collisionTile.config.angle); + const float cos = std::cos(collisionTile.config.angle); + + std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { + const int32_t aRotated = sin * a.point.x + cos * a.point.y; + const int32_t bRotated = sin * b.point.x + cos * b.point.y; + return aRotated != bRotated ? + aRotated < bRotated : + a.index > b.index; + }); + } + + for (SymbolInstance &symbolInstance : symbolInstances) { + + const bool hasText = symbolInstance.hasText; + const bool hasIcon = symbolInstance.hasIcon; + + const bool iconWithoutText = layout.textOptional || !hasText; + const bool textWithoutIcon = layout.iconOptional || !hasIcon; + + // Calculate the scales at which the text and icon can be placed without collision. + + float glyphScale = hasText ? + collisionTile.placeFeature(symbolInstance.textCollisionFeature, + layout.textAllowOverlap, layout.symbolAvoidEdges) : + collisionTile.minScale; + float iconScale = hasIcon ? + collisionTile.placeFeature(symbolInstance.iconCollisionFeature, + layout.iconAllowOverlap, layout.symbolAvoidEdges) : + collisionTile.minScale; + + + // Combine the scales for icons and text. + + if (!iconWithoutText && !textWithoutIcon) { + iconScale = glyphScale = util::max(iconScale, glyphScale); + } else if (!textWithoutIcon && glyphScale) { + glyphScale = util::max(iconScale, glyphScale); + } else if (!iconWithoutText && iconScale) { + iconScale = util::max(iconScale, glyphScale); + } + + + // Insert final placement into collision tree and add glyphs/icons to buffers + + if (hasText) { + collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.textIgnorePlacement); + if (glyphScale < collisionTile.maxScale) { + addSymbols<SymbolBucket::TextBuffer, SymbolBucket::TextElementGroup>( + bucket->text, symbolInstance.glyphQuads, glyphScale, + layout.textKeepUpright, textPlacement, collisionTile.config.angle); + } + } + + if (hasIcon) { + collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.iconIgnorePlacement); + if (iconScale < collisionTile.maxScale) { + addSymbols<SymbolBucket::IconBuffer, SymbolBucket::IconElementGroup>( + bucket->icon, symbolInstance.iconQuads, iconScale, + layout.iconKeepUpright, iconPlacement, collisionTile.config.angle); + } + } + } + + if (collisionTile.config.debug) { + addToDebugBuffers(collisionTile, *bucket); + } + + return bucket; +} + +template <typename Buffer, typename GroupType> +void SymbolLayout::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, const bool keepUpright, const style::SymbolPlacementType placement, const float placementAngle) { + + const float placementZoom = ::fmax(std::log(scale) / std::log(2) + zoom, 0); + + for (const auto& symbol : symbols) { + const auto &tl = symbol.tl; + const auto &tr = symbol.tr; + const auto &bl = symbol.bl; + const auto &br = symbol.br; + const auto &tex = symbol.tex; + + float minZoom = + util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom); + float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f); + const auto &anchorPoint = symbol.anchorPoint; + + // drop upside down versions of glyphs + const float a = std::fmod(symbol.anchorAngle + placementAngle + M_PI, M_PI * 2); + if (keepUpright && placement == style::SymbolPlacementType::Line && + (a <= M_PI / 2 || a > M_PI * 3 / 2)) { + continue; + } + + if (maxZoom <= minZoom) + continue; + + // Lower min zoom so that while fading out the label + // it can be shown outside of collision-free zoom levels + if (minZoom == placementZoom) { + minZoom = 0; + } + + const int glyph_vertex_length = 4; + + if (buffer.groups.empty() || (buffer.groups.back()->vertex_length + glyph_vertex_length > 65535)) { + // Move to a new group because the old one can't hold the geometry. + buffer.groups.emplace_back(std::make_unique<GroupType>()); + } + + // We're generating triangle fans, so we always start with the first + // coordinate in this polygon. + assert(buffer.groups.back()); + auto &triangleGroup = *buffer.groups.back(); + GLsizei triangleIndex = triangleGroup.vertex_length; + + // Encode angle of glyph + uint8_t glyphAngle = std::round((symbol.glyphAngle / (M_PI * 2)) * 256); + + // coordinates (2 triangles) + buffer.vertices.add(anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, + maxZoom, placementZoom, glyphAngle); + buffer.vertices.add(anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, + minZoom, maxZoom, placementZoom, glyphAngle); + buffer.vertices.add(anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, + minZoom, maxZoom, placementZoom, glyphAngle); + buffer.vertices.add(anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, + minZoom, maxZoom, placementZoom, glyphAngle); + + // add the two triangles, referencing the four coordinates we just inserted. + buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); + buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); + + triangleGroup.vertex_length += glyph_vertex_length; + triangleGroup.elements_length += 2; + } +} + +void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) { + + const float yStretch = collisionTile.yStretch; + const float angle = collisionTile.config.angle; + float angle_sin = std::sin(-angle); + float angle_cos = std::cos(-angle); + std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; + + for (const SymbolInstance &symbolInstance : symbolInstances) { + for (int i = 0; i < 2; i++) { + auto& feature = i == 0 ? + symbolInstance.textCollisionFeature : + symbolInstance.iconCollisionFeature; + + for (const CollisionBox &box : feature.boxes) { + auto& anchor = box.anchor; + + Point<float> tl{box.x1, box.y1 * yStretch}; + Point<float> tr{box.x2, box.y1 * yStretch}; + Point<float> bl{box.x1, box.y2 * yStretch}; + Point<float> br{box.x2, box.y2 * yStretch}; + tl = util::matrixMultiply(matrix, tl); + tr = util::matrixMultiply(matrix, tr); + bl = util::matrixMultiply(matrix, bl); + br = util::matrixMultiply(matrix, br); + + const float maxZoom = util::max(0.0f, util::min(25.0f, static_cast<float>(zoom + log(box.maxScale) / log(2)))); + const float placementZoom= util::max(0.0f, util::min(25.0f, static_cast<float>(zoom + log(box.placementScale) / log(2)))); + + auto& collisionBox = bucket.collisionBox; + if (collisionBox.groups.empty()) { + // Move to a new group because the old one can't hold the geometry. + collisionBox.groups.emplace_back(std::make_unique<SymbolBucket::CollisionBoxElementGroup>()); + } + + collisionBox.vertices.add(anchor.x, anchor.y, tl.x, tl.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, tr.x, tr.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, tr.x, tr.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, br.x, br.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, br.x, br.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, bl.x, bl.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, bl.x, bl.y, maxZoom, placementZoom); + collisionBox.vertices.add(anchor.x, anchor.y, tl.x, tl.y, maxZoom, placementZoom); + + auto &group= *collisionBox.groups.back(); + group.vertex_length += 8; + } + } + } +} + +} // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp new file mode 100644 index 0000000000..83a3735061 --- /dev/null +++ b/src/mbgl/layout/symbol_layout.hpp @@ -0,0 +1,127 @@ +#pragma once + +#include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/map/mode.hpp> +#include <mbgl/text/collision_feature.hpp> +#include <mbgl/text/quads.hpp> +#include <mbgl/style/layers/symbol_layer_properties.hpp> + +#include <memory> +#include <map> +#include <set> +#include <vector> + +namespace mbgl { + +class CollisionTile; +class SpriteAtlas; +class SpriteStore; +class GlyphAtlas; +class GlyphStore; +class IndexedSubfeature; +class SymbolBucket; + +namespace style { +class Filter; +} // namespace style + +class SymbolFeature { +public: + GeometryCollection geometry; + std::u32string label; + std::string sprite; + std::size_t index; +}; + +struct Anchor; + +class SymbolInstance { +public: + explicit SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, + const Shaping& shapedText, const PositionedIcon& shapedIcon, + const style::SymbolLayoutProperties&, const bool inside, const uint32_t index, + const float textBoxScale, const float textPadding, style::SymbolPlacementType textPlacement, + const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, + const GlyphPositions& face, const IndexedSubfeature& indexedfeature); + + Point<float> point; + uint32_t index; + bool hasText; + bool hasIcon; + SymbolQuads glyphQuads; + SymbolQuads iconQuads; + CollisionFeature textCollisionFeature; + CollisionFeature iconCollisionFeature; +}; + +class SymbolLayout { +public: + SymbolLayout(std::string bucketName_, + std::string sourceLayerName_, + uint32_t overscaling, + float zoom, + const MapMode, + const GeometryTileLayer&, + const style::Filter&, + style::SymbolLayoutProperties, + float textMaxSize, + SpriteAtlas&); + + bool canPrepare(GlyphStore&, SpriteStore&); + + void prepare(uintptr_t tileUID, + GlyphAtlas&, + GlyphStore&); + + std::unique_ptr<SymbolBucket> place(CollisionTile&); + + bool hasSymbolInstances() const; + + enum State { + Pending, // Waiting for the necessary glyphs or icons to be available. + Prepared, // The potential positions of text and icons have been determined. + Placed // The final positions have been determined, taking into account prior layers. + }; + + State state = Pending; + + const std::string bucketName; + const std::string sourceLayerName; + +private: + void addFeature(const GeometryCollection&, + const Shaping& shapedText, + const PositionedIcon& shapedIcon, + const GlyphPositions& face, + const size_t index); + + bool anchorIsTooClose(const std::u32string& text, const float repeatDistance, Anchor&); + std::map<std::u32string, std::vector<Anchor>> compareText; + + void addToDebugBuffers(CollisionTile&, SymbolBucket&); + + // Adds placed items to the buffer. + template <typename Buffer, typename GroupType> + void addSymbols(Buffer&, const SymbolQuads&, float scale, + const bool keepUpright, const style::SymbolPlacementType, const float placementAngle); + + const float overscaling; + const float zoom; + const MapMode mode; + const style::SymbolLayoutProperties layout; + const float textMaxSize; + + SpriteAtlas& spriteAtlas; + + const uint32_t tileSize; + const float tilePixelRatio; + + bool sdfIcons = false; + bool iconsNeedLinear = false; + + std::set<GlyphRange> ranges; + std::vector<SymbolInstance> symbolInstances; + std::vector<SymbolFeature> features; +}; + +} // namespace mbgl diff --git a/src/mbgl/renderer/bucket.hpp b/src/mbgl/renderer/bucket.hpp index cf461e4389..0efcd2845e 100644 --- a/src/mbgl/renderer/bucket.hpp +++ b/src/mbgl/renderer/bucket.hpp @@ -13,7 +13,6 @@ namespace mbgl { class Painter; class PaintParameters; -class CollisionTile; class RenderTile; namespace gl { @@ -47,9 +46,6 @@ public: return !uploaded; } - virtual void placeFeatures(CollisionTile&) {} - virtual void swapRenderData() {} - protected: std::atomic<bool> uploaded { false }; }; diff --git a/src/mbgl/renderer/symbol_bucket.cpp b/src/mbgl/renderer/symbol_bucket.cpp index 9d0cb14609..39fcb91e0c 100644 --- a/src/mbgl/renderer/symbol_bucket.cpp +++ b/src/mbgl/renderer/symbol_bucket.cpp @@ -1,86 +1,32 @@ #include <mbgl/renderer/symbol_bucket.hpp> -#include <mbgl/style/filter_evaluator.hpp> -#include <mbgl/style/layers/symbol_layer.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> -#include <mbgl/sprite/sprite_image.hpp> -#include <mbgl/sprite/sprite_store.hpp> -#include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/geometry/text_buffer.hpp> -#include <mbgl/geometry/icon_buffer.hpp> -#include <mbgl/geometry/glyph_atlas.hpp> -#include <mbgl/geometry/anchor.hpp> -#include <mbgl/text/get_anchors.hpp> #include <mbgl/renderer/painter.hpp> -#include <mbgl/text/glyph_store.hpp> -#include <mbgl/text/glyph_set.hpp> -#include <mbgl/platform/log.hpp> -#include <mbgl/text/collision_tile.hpp> +#include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/shader/sdf_shader.hpp> #include <mbgl/shader/icon_shader.hpp> #include <mbgl/shader/collision_box_shader.hpp> -#include <mbgl/util/utf.hpp> -#include <mbgl/util/token.hpp> -#include <mbgl/util/math.hpp> -#include <mbgl/util/merge_lines.hpp> -#include <mbgl/util/clip_lines.hpp> -#include <mbgl/util/std.hpp> -#include <mbgl/util/constants.hpp> -#include <mbgl/util/string.hpp> -#include <mbgl/math/minmax.hpp> - namespace mbgl { using namespace style; -SymbolInstance::SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, - const Shaping& shapedText, const PositionedIcon& shapedIcon, - const SymbolLayoutProperties& layout, const bool addToBuffers, const uint32_t index_, - const float textBoxScale, const float textPadding, const SymbolPlacementType textPlacement, - const float iconBoxScale, const float iconPadding, const SymbolPlacementType iconPlacement, - const GlyphPositions& face, const IndexedSubfeature& indexedFeature) : - point(anchor.point), - index(index_), - hasText(shapedText), - hasIcon(shapedIcon), - - // Create the quads used for rendering the glyphs. - glyphQuads(addToBuffers && shapedText ? - getGlyphQuads(anchor, shapedText, textBoxScale, line, layout, textPlacement, face) : - SymbolQuads()), - - // Create the quad used for rendering the icon. - iconQuads(addToBuffers && shapedIcon ? - getIconQuads(anchor, shapedIcon, line, layout, iconPlacement, shapedText) : - SymbolQuads()), - - // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line, anchor, shapedText, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature) - {} - - -SymbolBucket::SymbolBucket(uint32_t overscaling_, float zoom_, const MapMode mode_, std::string bucketName_, std::string sourceLayerName_) - : overscaling(overscaling_), - zoom(zoom_), - tileSize(util::tileSize * overscaling_), - tilePixelRatio(float(util::EXTENT) / tileSize), - mode(mode_), - bucketName(std::move(bucketName_)), - sourceLayerName(std::move(sourceLayerName_)) {} - -SymbolBucket::~SymbolBucket() { - // Do not remove. header file only contains forward definitions to unique pointers. +SymbolBucket::SymbolBucket(const MapMode mode_, + style::SymbolLayoutProperties layout_, + bool sdfIcons_, + bool iconsNeedLinear_) + : mode(mode_), + layout(std::move(layout_)), + sdfIcons(sdfIcons_), + iconsNeedLinear(iconsNeedLinear_) { } void SymbolBucket::upload(gl::ObjectStore& store, gl::Config&) { if (hasTextData()) { - renderData->text.vertices.upload(store); - renderData->text.triangles.upload(store); + text.vertices.upload(store); + text.triangles.upload(store); } if (hasIconData()) { - renderData->icon.vertices.upload(store); - renderData->icon.triangles.upload(store); + icon.vertices.upload(store); + icon.triangles.upload(store); } uploaded = true; @@ -93,520 +39,29 @@ void SymbolBucket::render(Painter& painter, painter.renderSymbol(parameters, *this, *layer.as<SymbolLayer>(), tile); } -bool SymbolBucket::hasData() const { return hasTextData() || hasIconData() || !symbolInstances.empty(); } - -bool SymbolBucket::hasTextData() const { return renderData && !renderData->text.groups.empty(); } - -bool SymbolBucket::hasIconData() const { return renderData && !renderData->icon.groups.empty(); } - -bool SymbolBucket::hasCollisionBoxData() const { return renderData && !renderData->collisionBox.groups.empty(); } - -bool SymbolBucket::needsClipping() const { - return mode == MapMode::Still; -} - -void SymbolBucket::parseFeatures(const GeometryTileLayer& layer, const Filter& filter) { - const bool has_text = !layout.textField.value.empty() && !layout.textFont.value.empty(); - const bool has_icon = !layout.iconImage.value.empty(); - - if (!has_text && !has_icon) { - return; - } - - auto layerName = layer.getName(); - - // Determine and load glyph ranges - const GLsizei featureCount = static_cast<GLsizei>(layer.featureCount()); - for (GLsizei i = 0; i < featureCount; i++) { - auto feature = layer.getFeature(i); - if (!filter(feature->getType(), feature->getID(), [&] (const auto& key) { return feature->getValue(key); })) - continue; - - SymbolFeature ft; - ft.index = i; - - auto getValue = [&feature](const std::string& key) -> std::string { - auto value = feature->getValue(key); - if (!value) - return std::string(); - if (value->is<std::string>()) - return value->get<std::string>(); - if (value->is<bool>()) - return value->get<bool>() ? "true" : "false"; - if (value->is<int64_t>()) - return util::toString(value->get<int64_t>()); - if (value->is<uint64_t>()) - return util::toString(value->get<uint64_t>()); - if (value->is<double>()) - return util::toString(value->get<double>()); - return "null"; - }; - - if (has_text) { - std::string u8string = util::replaceTokens(layout.textField, getValue); - - if (layout.textTransform == TextTransformType::Uppercase) { - u8string = platform::uppercase(u8string); - } else if (layout.textTransform == TextTransformType::Lowercase) { - u8string = platform::lowercase(u8string); - } - - ft.label = util::utf8_to_utf32::convert(u8string); - - if (!ft.label.empty()) { - // Loop through all characters of this text and collect unique codepoints. - for (char32_t chr : ft.label) { - ranges.insert(getGlyphRange(chr)); - } - } - } - - if (has_icon) { - ft.sprite = util::replaceTokens(layout.iconImage, getValue); - } - - if (ft.label.length() || ft.sprite.length()) { - - auto &multiline = ft.geometry; - - GeometryCollection geometryCollection = feature->getGeometries(); - for (auto& line : geometryCollection) { - multiline.emplace_back(); - for (auto& point : line) { - multiline.back().emplace_back(point.x, point.y); - } - } - - features.push_back(std::move(ft)); - } - } - - if (layout.symbolPlacement == SymbolPlacementType::Line) { - util::mergeLines(features); - } -} - -bool SymbolBucket::needsDependencies(GlyphStore& glyphStore, SpriteStore& spriteStore) { - if (!layout.textField.value.empty() && !layout.textFont.value.empty() && !glyphStore.hasGlyphRanges(layout.textFont, ranges)) { - return true; - } - - if (!layout.iconImage.value.empty() && !spriteStore.isLoaded()) { - return true; - } - - return false; -} - -void SymbolBucket::addFeatures(uintptr_t tileUID, - SpriteAtlas& spriteAtlas, - GlyphAtlas& glyphAtlas, - GlyphStore& glyphStore) { - float horizontalAlign = 0.5; - float verticalAlign = 0.5; - - switch (layout.textAnchor) { - case TextAnchorType::Top: - case TextAnchorType::Bottom: - case TextAnchorType::Center: - break; - case TextAnchorType::Right: - case TextAnchorType::TopRight: - case TextAnchorType::BottomRight: - horizontalAlign = 1; - break; - case TextAnchorType::Left: - case TextAnchorType::TopLeft: - case TextAnchorType::BottomLeft: - horizontalAlign = 0; - break; - } - - switch (layout.textAnchor) { - case TextAnchorType::Left: - case TextAnchorType::Right: - case TextAnchorType::Center: - break; - case TextAnchorType::Bottom: - case TextAnchorType::BottomLeft: - case TextAnchorType::BottomRight: - verticalAlign = 1; - break; - case TextAnchorType::Top: - case TextAnchorType::TopLeft: - case TextAnchorType::TopRight: - verticalAlign = 0; - break; - } - - const float justify = layout.textJustify == TextJustifyType::Right ? 1 : - layout.textJustify == TextJustifyType::Left ? 0 : - 0.5; - - auto glyphSet = glyphStore.getGlyphSet(layout.textFont); - - for (const auto& feature : features) { - if (feature.geometry.empty()) continue; - - Shaping shapedText; - PositionedIcon shapedIcon; - GlyphPositions face; - - // if feature has text, shape the text - if (feature.label.length()) { - shapedText = glyphSet->getShaping( - /* string */ feature.label, - /* maxWidth: ems */ layout.symbolPlacement != SymbolPlacementType::Line ? - layout.textMaxWidth * 24 : 0, - /* lineHeight: ems */ layout.textLineHeight * 24, - /* horizontalAlign */ horizontalAlign, - /* verticalAlign */ verticalAlign, - /* justify */ justify, - /* spacing: ems */ layout.textLetterSpacing * 24, - /* translate */ Point<float>(layout.textOffset.value[0], layout.textOffset.value[1])); - - // Add the glyphs we need for this label to the glyph atlas. - if (shapedText) { - glyphAtlas.addGlyphs(tileUID, feature.label, layout.textFont, **glyphSet, face); - } - } - - // if feature has icon, get sprite atlas position - if (feature.sprite.length()) { - auto image = spriteAtlas.getImage(feature.sprite, SpritePatternMode::Single); - if (image) { - shapedIcon = shapeIcon(*image, layout); - assert((*image).spriteImage); - if ((*image).spriteImage->sdf) { - sdfIcons = true; - } - if ((*image).relativePixelRatio != 1.0f) { - iconsNeedLinear = true; - } else if (layout.iconRotate != 0) { - iconsNeedLinear = true; - } - } - } - - // if either shapedText or icon position is present, add the feature - if (shapedText || shapedIcon) { - addFeature(feature.geometry, shapedText, shapedIcon, face, feature.index); - } - } - - features.clear(); -} - - -void SymbolBucket::addFeature(const GeometryCollection &lines, - const Shaping &shapedText, const PositionedIcon &shapedIcon, const GlyphPositions &face, const size_t index) { - - const float minScale = 0.5f; - const float glyphSize = 24.0f; - - const float fontScale = layout.textSize / glyphSize; - const float textBoxScale = tilePixelRatio * fontScale; - const float textMaxBoxScale = tilePixelRatio * textMaxSize / glyphSize; - const float iconBoxScale = tilePixelRatio * layout.iconSize; - const float symbolSpacing = tilePixelRatio * layout.symbolSpacing; - const bool avoidEdges = layout.symbolAvoidEdges && layout.symbolPlacement != SymbolPlacementType::Line; - const float textPadding = layout.textPadding * tilePixelRatio; - const float iconPadding = layout.iconPadding * tilePixelRatio; - const float textMaxAngle = layout.textMaxAngle * util::DEG2RAD; - const SymbolPlacementType textPlacement = layout.textRotationAlignment != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.symbolPlacement; - const SymbolPlacementType iconPlacement = layout.iconRotationAlignment != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.symbolPlacement; - const bool mayOverlap = layout.textAllowOverlap || layout.iconAllowOverlap || - layout.textIgnorePlacement || layout.iconIgnorePlacement; - const bool isLine = layout.symbolPlacement == SymbolPlacementType::Line; - const float textRepeatDistance = symbolSpacing / 2; - - auto& clippedLines = isLine ? - util::clipLines(lines, 0, 0, util::EXTENT, util::EXTENT) : - lines; - - IndexedSubfeature indexedFeature = {index, sourceLayerName, bucketName, symbolInstances.size()}; - - for (const auto& line : clippedLines) { - if (line.empty()) continue; - - // Calculate the anchor points around which you want to place labels - Anchors anchors = isLine ? - getAnchors(line, symbolSpacing, textMaxAngle, shapedText.left, shapedText.right, shapedIcon.left, shapedIcon.right, glyphSize, textMaxBoxScale, overscaling) : - Anchors({ Anchor(float(line[0].x), float(line[0].y), 0, minScale) }); - - // For each potential label, create the placement features used to check for collisions, and the quads use for rendering. - for (Anchor &anchor : anchors) { - if (shapedText && isLine) { - if (anchorIsTooClose(shapedText.text, textRepeatDistance, anchor)) { - continue; - } - } - - const bool inside = !(anchor.point.x < 0 || anchor.point.x > util::EXTENT || anchor.point.y < 0 || anchor.point.y > util::EXTENT); - - if (avoidEdges && !inside) continue; - - // Normally symbol layers are drawn across tile boundaries. Only symbols - // with their anchors within the tile boundaries are added to the buffers - // to prevent symbols from being drawn twice. - // - // Symbols in layers with overlap are sorted in the y direction so that - // symbols lower on the canvas are drawn on top of symbols near the top. - // To preserve this order across tile boundaries these symbols can't - // be drawn across tile boundaries. Instead they need to be included in - // the buffers for both tiles and clipped to tile boundaries at draw time. - // - // TODO remove the `&& false` when is #1673 implemented - const bool addToBuffers = (mode == MapMode::Still) || inside || (mayOverlap && false); - - symbolInstances.emplace_back(anchor, line, shapedText, shapedIcon, layout, addToBuffers, symbolInstances.size(), - textBoxScale, textPadding, textPlacement, - iconBoxScale, iconPadding, iconPlacement, - face, indexedFeature); - } - } -} - -bool SymbolBucket::anchorIsTooClose(const std::u32string &text, const float repeatDistance, Anchor &anchor) { - if (compareText.find(text) == compareText.end()) { - compareText.emplace(text, Anchors()); - } else { - auto otherAnchors = compareText.find(text)->second; - for (Anchor &otherAnchor : otherAnchors) { - if (util::dist<float>(anchor.point, otherAnchor.point) < repeatDistance) { - return true; - } - } - } - compareText[text].push_back(anchor); - return false; +bool SymbolBucket::hasData() const { + return hasTextData() || hasIconData(); } -void SymbolBucket::placeFeatures(CollisionTile& collisionTile) { - - renderDataInProgress = std::make_unique<SymbolRenderData>(); - - // Calculate which labels can be shown and when they can be shown and - // create the bufers used for rendering. - - const SymbolPlacementType textPlacement = layout.textRotationAlignment != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.symbolPlacement; - const SymbolPlacementType iconPlacement = layout.iconRotationAlignment != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.symbolPlacement; - - const bool mayOverlap = layout.textAllowOverlap || layout.iconAllowOverlap || - layout.textIgnorePlacement || layout.iconIgnorePlacement; - - // Sort symbols by their y position on the canvas so that they lower symbols - // are drawn on top of higher symbols. - // Don't sort symbols that won't overlap because it isn't necessary and - // because it causes more labels to pop in and out when rotating. - if (mayOverlap) { - const float sin = std::sin(collisionTile.config.angle); - const float cos = std::cos(collisionTile.config.angle); - - std::sort(symbolInstances.begin(), symbolInstances.end(), [sin, cos](SymbolInstance &a, SymbolInstance &b) { - const int32_t aRotated = sin * a.point.x + cos * a.point.y; - const int32_t bRotated = sin * b.point.x + cos * b.point.y; - return aRotated != bRotated ? - aRotated < bRotated : - a.index > b.index; - }); - } - - for (SymbolInstance &symbolInstance : symbolInstances) { - - const bool hasText = symbolInstance.hasText; - const bool hasIcon = symbolInstance.hasIcon; - - const bool iconWithoutText = layout.textOptional || !hasText; - const bool textWithoutIcon = layout.iconOptional || !hasIcon; - - // Calculate the scales at which the text and icon can be placed without collision. - - float glyphScale = hasText ? - collisionTile.placeFeature(symbolInstance.textCollisionFeature, - layout.textAllowOverlap, layout.symbolAvoidEdges) : - collisionTile.minScale; - float iconScale = hasIcon ? - collisionTile.placeFeature(symbolInstance.iconCollisionFeature, - layout.iconAllowOverlap, layout.symbolAvoidEdges) : - collisionTile.minScale; - - - // Combine the scales for icons and text. - - if (!iconWithoutText && !textWithoutIcon) { - iconScale = glyphScale = util::max(iconScale, glyphScale); - } else if (!textWithoutIcon && glyphScale) { - glyphScale = util::max(iconScale, glyphScale); - } else if (!iconWithoutText && iconScale) { - iconScale = util::max(iconScale, glyphScale); - } - - - // Insert final placement into collision tree and add glyphs/icons to buffers - - if (hasText) { - collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.textIgnorePlacement); - if (glyphScale < collisionTile.maxScale) { - addSymbols<SymbolRenderData::TextBuffer, TextElementGroup>( - renderDataInProgress->text, symbolInstance.glyphQuads, glyphScale, - layout.textKeepUpright, textPlacement, collisionTile.config.angle); - } - } - - if (hasIcon) { - collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.iconIgnorePlacement); - if (iconScale < collisionTile.maxScale) { - addSymbols<SymbolRenderData::IconBuffer, IconElementGroup>( - renderDataInProgress->icon, symbolInstance.iconQuads, iconScale, - layout.iconKeepUpright, iconPlacement, collisionTile.config.angle); - } - } - } - - if (collisionTile.config.debug) { - addToDebugBuffers(collisionTile); - } +bool SymbolBucket::hasTextData() const { + return !text.groups.empty(); } -template <typename Buffer, typename GroupType> -void SymbolBucket::addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, const bool keepUpright, const style::SymbolPlacementType placement, const float placementAngle) { - - const float placementZoom = ::fmax(std::log(scale) / std::log(2) + zoom, 0); - - for (const auto& symbol : symbols) { - const auto &tl = symbol.tl; - const auto &tr = symbol.tr; - const auto &bl = symbol.bl; - const auto &br = symbol.br; - const auto &tex = symbol.tex; - - float minZoom = - util::max(static_cast<float>(zoom + log(symbol.minScale) / log(2)), placementZoom); - float maxZoom = util::min(static_cast<float>(zoom + log(symbol.maxScale) / log(2)), 25.0f); - const auto &anchorPoint = symbol.anchorPoint; - - // drop upside down versions of glyphs - const float a = std::fmod(symbol.anchorAngle + placementAngle + M_PI, M_PI * 2); - if (keepUpright && placement == style::SymbolPlacementType::Line && - (a <= M_PI / 2 || a > M_PI * 3 / 2)) { - continue; - } - - if (maxZoom <= minZoom) - continue; - - // Lower min zoom so that while fading out the label - // it can be shown outside of collision-free zoom levels - if (minZoom == placementZoom) { - minZoom = 0; - } - - const int glyph_vertex_length = 4; - - if (buffer.groups.empty() || (buffer.groups.back()->vertex_length + glyph_vertex_length > 65535)) { - // Move to a new group because the old one can't hold the geometry. - buffer.groups.emplace_back(std::make_unique<GroupType>()); - } - - // We're generating triangle fans, so we always start with the first - // coordinate in this polygon. - assert(buffer.groups.back()); - auto &triangleGroup = *buffer.groups.back(); - GLsizei triangleIndex = triangleGroup.vertex_length; - - // Encode angle of glyph - uint8_t glyphAngle = std::round((symbol.glyphAngle / (M_PI * 2)) * 256); - - // coordinates (2 triangles) - buffer.vertices.add(anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, - maxZoom, placementZoom, glyphAngle); - buffer.vertices.add(anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, - minZoom, maxZoom, placementZoom, glyphAngle); - buffer.vertices.add(anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle); - buffer.vertices.add(anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, - minZoom, maxZoom, placementZoom, glyphAngle); - - // add the two triangles, referencing the four coordinates we just inserted. - buffer.triangles.add(triangleIndex + 0, triangleIndex + 1, triangleIndex + 2); - buffer.triangles.add(triangleIndex + 1, triangleIndex + 2, triangleIndex + 3); - - triangleGroup.vertex_length += glyph_vertex_length; - triangleGroup.elements_length += 2; - } +bool SymbolBucket::hasIconData() const { + return !icon.groups.empty(); } -void SymbolBucket::addToDebugBuffers(CollisionTile &collisionTile) { - - const float yStretch = collisionTile.yStretch; - const float angle = collisionTile.config.angle; - float angle_sin = std::sin(-angle); - float angle_cos = std::cos(-angle); - std::array<float, 4> matrix = {{angle_cos, -angle_sin, angle_sin, angle_cos}}; - - for (const SymbolInstance &symbolInstance : symbolInstances) { - for (int i = 0; i < 2; i++) { - auto& feature = i == 0 ? - symbolInstance.textCollisionFeature : - symbolInstance.iconCollisionFeature; - - for (const CollisionBox &box : feature.boxes) { - auto& anchor = box.anchor; - - Point<float> tl{box.x1, box.y1 * yStretch}; - Point<float> tr{box.x2, box.y1 * yStretch}; - Point<float> bl{box.x1, box.y2 * yStretch}; - Point<float> br{box.x2, box.y2 * yStretch}; - tl = util::matrixMultiply(matrix, tl); - tr = util::matrixMultiply(matrix, tr); - bl = util::matrixMultiply(matrix, bl); - br = util::matrixMultiply(matrix, br); - - const float maxZoom = util::max(0.0f, util::min(25.0f, static_cast<float>(zoom + log(box.maxScale) / log(2)))); - const float placementZoom= util::max(0.0f, util::min(25.0f, static_cast<float>(zoom + log(box.placementScale) / log(2)))); - - auto& collisionBox = renderDataInProgress->collisionBox; - if (collisionBox.groups.empty()) { - // Move to a new group because the old one can't hold the geometry. - collisionBox.groups.emplace_back(std::make_unique<CollisionBoxElementGroup>()); - } - - collisionBox.vertices.add(anchor.x, anchor.y, tl.x, tl.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, tr.x, tr.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, tr.x, tr.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, br.x, br.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, br.x, br.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, bl.x, bl.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, bl.x, bl.y, maxZoom, placementZoom); - collisionBox.vertices.add(anchor.x, anchor.y, tl.x, tl.y, maxZoom, placementZoom); - - auto &group= *collisionBox.groups.back(); - group.vertex_length += 8; - } - } - } +bool SymbolBucket::hasCollisionBoxData() const { + return !collisionBox.groups.empty(); } -void SymbolBucket::swapRenderData() { - if (renderDataInProgress) { - renderData = std::move(renderDataInProgress); - uploaded = false; - } +bool SymbolBucket::needsClipping() const { + return mode == MapMode::Still; } void SymbolBucket::drawGlyphs(SDFShader& shader, gl::ObjectStore& store, PaintMode paintMode) { GLbyte *vertex_index = BUFFER_OFFSET_0; GLbyte *elements_index = BUFFER_OFFSET_0; - auto& text = renderData->text; for (auto &group : text.groups) { assert(group); group->array[paintMode == PaintMode::Overdraw ? 1 : 0].bind( @@ -621,7 +76,6 @@ void SymbolBucket::drawGlyphs(SDFShader& shader, gl::ObjectStore& store, PaintMo void SymbolBucket::drawIcons(SDFShader& shader, gl::ObjectStore& store, PaintMode paintMode) { GLbyte *vertex_index = BUFFER_OFFSET_0; GLbyte *elements_index = BUFFER_OFFSET_0; - auto& icon = renderData->icon; for (auto &group : icon.groups) { assert(group); group->array[paintMode == PaintMode::Overdraw ? 1 : 0].bind( @@ -636,7 +90,6 @@ void SymbolBucket::drawIcons(SDFShader& shader, gl::ObjectStore& store, PaintMod void SymbolBucket::drawIcons(IconShader& shader, gl::ObjectStore& store, PaintMode paintMode) { GLbyte *vertex_index = BUFFER_OFFSET_0; GLbyte *elements_index = BUFFER_OFFSET_0; - auto& icon = renderData->icon; for (auto &group : icon.groups) { assert(group); group->array[paintMode == PaintMode::Overdraw ? 3 : 2].bind( @@ -650,10 +103,10 @@ void SymbolBucket::drawIcons(IconShader& shader, gl::ObjectStore& store, PaintMo void SymbolBucket::drawCollisionBoxes(CollisionBoxShader& shader, gl::ObjectStore& store) { GLbyte *vertex_index = BUFFER_OFFSET_0; - auto& collisionBox = renderData->collisionBox; for (auto &group : collisionBox.groups) { group->array[0].bind(shader, collisionBox.vertices, vertex_index, store); MBGL_CHECK_ERROR(glDrawArrays(GL_LINES, 0, group->vertex_length)); } } + } // namespace mbgl diff --git a/src/mbgl/renderer/symbol_bucket.hpp b/src/mbgl/renderer/symbol_bucket.hpp index 8c76bd8045..8f0c21bc95 100644 --- a/src/mbgl/renderer/symbol_bucket.hpp +++ b/src/mbgl/renderer/symbol_bucket.hpp @@ -1,22 +1,15 @@ #pragma once #include <mbgl/renderer/bucket.hpp> -#include <mbgl/tile/geometry_tile_data.hpp> #include <mbgl/map/mode.hpp> -#include <mbgl/geometry/vao.hpp> #include <mbgl/geometry/elements_buffer.hpp> #include <mbgl/geometry/text_buffer.hpp> #include <mbgl/geometry/icon_buffer.hpp> #include <mbgl/geometry/collision_box_buffer.hpp> -#include <mbgl/text/glyph.hpp> -#include <mbgl/text/collision_feature.hpp> -#include <mbgl/text/shaping.hpp> -#include <mbgl/text/quads.hpp> -#include <mbgl/style/filter.hpp> +#include <mbgl/text/glyph_range.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> #include <memory> -#include <map> #include <set> #include <vector> @@ -25,49 +18,13 @@ namespace mbgl { class SDFShader; class IconShader; class CollisionBoxShader; -class CollisionTile; -class SpriteAtlas; -class SpriteStore; -class GlyphAtlas; -class GlyphStore; -class IndexedSubfeature; - -class SymbolFeature { -public: - GeometryCollection geometry; - std::u32string label; - std::string sprite; - std::size_t index; -}; - -struct Anchor; - -class SymbolInstance { - public: - explicit SymbolInstance(Anchor& anchor, const GeometryCoordinates& line, - const Shaping& shapedText, const PositionedIcon& shapedIcon, - const style::SymbolLayoutProperties&, const bool inside, const uint32_t index, - const float textBoxScale, const float textPadding, style::SymbolPlacementType textPlacement, - const float iconBoxScale, const float iconPadding, style::SymbolPlacementType iconPlacement, - const GlyphPositions& face, const IndexedSubfeature& indexedfeature); - Point<float> point; - uint32_t index; - bool hasText; - bool hasIcon; - SymbolQuads glyphQuads; - SymbolQuads iconQuads; - CollisionFeature textCollisionFeature; - CollisionFeature iconCollisionFeature; -}; class SymbolBucket : public Bucket { - typedef ElementGroup<2> TextElementGroup; - typedef ElementGroup<4> IconElementGroup; - typedef ElementGroup<1> CollisionBoxElementGroup; - public: - SymbolBucket(uint32_t overscaling, float zoom, const MapMode, std::string bucketName_, std::string sourceLayerName_); - ~SymbolBucket() override; + SymbolBucket(const MapMode, + style::SymbolLayoutProperties, + bool sdfIcons, + bool iconsNeedLinear); void upload(gl::ObjectStore&, gl::Config&) override; void render(Painter&, PaintParameters&, const style::Layer&, const RenderTile&) override; @@ -77,81 +34,39 @@ public: bool hasCollisionBoxData() const; bool needsClipping() const override; - void addFeatures(uintptr_t tileUID, - SpriteAtlas&, - GlyphAtlas&, - GlyphStore&); - void drawGlyphs(SDFShader&, gl::ObjectStore&, PaintMode); void drawIcons(SDFShader&, gl::ObjectStore&, PaintMode); void drawIcons(IconShader&, gl::ObjectStore&, PaintMode); void drawCollisionBoxes(CollisionBoxShader&, gl::ObjectStore&); - void parseFeatures(const GeometryTileLayer&, const style::Filter&); - bool needsDependencies(GlyphStore&, SpriteStore&); - void placeFeatures(CollisionTile&) override; - -private: - void addFeature(const GeometryCollection &lines, - const Shaping &shapedText, const PositionedIcon &shapedIcon, - const GlyphPositions &face, - const size_t index); - bool anchorIsTooClose(const std::u32string &text, const float repeatDistance, Anchor &anchor); - std::map<std::u32string, std::vector<Anchor>> compareText; - - void addToDebugBuffers(CollisionTile &collisionTile); - - void swapRenderData() override; - - // Adds placed items to the buffer. - template <typename Buffer, typename GroupType> - void addSymbols(Buffer &buffer, const SymbolQuads &symbols, float scale, - const bool keepUpright, const style::SymbolPlacementType placement, const float placementAngle); - -public: - style::SymbolLayoutProperties layout; - - float iconMaxSize = 1.0f; - float textMaxSize = 16.0f; - - bool sdfIcons = false; - bool iconsNeedLinear = false; - -private: - - const float overscaling; - const float zoom; - const uint32_t tileSize; - const float tilePixelRatio; const MapMode mode; - const std::string bucketName; - const std::string sourceLayerName; - - std::set<GlyphRange> ranges; - std::vector<SymbolInstance> symbolInstances; - std::vector<SymbolFeature> features; + const style::SymbolLayoutProperties layout; + const bool sdfIcons; + const bool iconsNeedLinear; - struct SymbolRenderData { - struct TextBuffer { - TextVertexBuffer vertices; - TriangleElementsBuffer triangles; - std::vector<std::unique_ptr<TextElementGroup>> groups; - } text; - - struct IconBuffer { - IconVertexBuffer vertices; - TriangleElementsBuffer triangles; - std::vector<std::unique_ptr<IconElementGroup>> groups; - } icon; +private: + friend class SymbolLayout; - struct CollisionBoxBuffer { - CollisionBoxVertexBuffer vertices; - std::vector<std::unique_ptr<CollisionBoxElementGroup>> groups; - } collisionBox; - }; + typedef ElementGroup<2> TextElementGroup; + typedef ElementGroup<4> IconElementGroup; + typedef ElementGroup<1> CollisionBoxElementGroup; - std::unique_ptr<SymbolRenderData> renderData; - std::unique_ptr<SymbolRenderData> renderDataInProgress; + struct TextBuffer { + TextVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector<std::unique_ptr<TextElementGroup>> groups; + } text; + + struct IconBuffer { + IconVertexBuffer vertices; + TriangleElementsBuffer triangles; + std::vector<std::unique_ptr<IconElementGroup>> groups; + } icon; + + struct CollisionBoxBuffer { + CollisionBoxVertexBuffer vertices; + std::vector<std::unique_ptr<CollisionBoxElementGroup>> groups; + } collisionBox; }; } // namespace mbgl diff --git a/src/mbgl/style/bucket_parameters.hpp b/src/mbgl/style/bucket_parameters.hpp index 3b42e7c41b..ccd5f9a0e4 100644 --- a/src/mbgl/style/bucket_parameters.hpp +++ b/src/mbgl/style/bucket_parameters.hpp @@ -26,7 +26,6 @@ public: const GeometryTileLayer& layer_, const std::atomic<bool>& obsolete_, uintptr_t tileUID_, - bool& partialParse_, SpriteStore& spriteStore_, GlyphAtlas& glyphAtlas_, GlyphStore& glyphStore_, @@ -36,7 +35,6 @@ public: layer(layer_), obsolete(obsolete_), tileUID(tileUID_), - partialParse(partialParse_), spriteStore(spriteStore_), glyphAtlas(glyphAtlas_), glyphStore(glyphStore_), @@ -53,7 +51,6 @@ public: const GeometryTileLayer& layer; const std::atomic<bool>& obsolete; uintptr_t tileUID; - bool& partialParse; SpriteStore& spriteStore; GlyphAtlas& glyphAtlas; GlyphStore& glyphStore; diff --git a/src/mbgl/style/layers/symbol_layer_impl.cpp b/src/mbgl/style/layers/symbol_layer_impl.cpp index 0243b1dfa5..1039951f5e 100644 --- a/src/mbgl/style/layers/symbol_layer_impl.cpp +++ b/src/mbgl/style/layers/symbol_layer_impl.cpp @@ -1,6 +1,7 @@ #include <mbgl/style/layers/symbol_layer_impl.hpp> -#include <mbgl/renderer/symbol_bucket.hpp> #include <mbgl/style/bucket_parameters.hpp> +#include <mbgl/layout/symbol_layout.hpp> +#include <mbgl/renderer/bucket.hpp> namespace mbgl { namespace style { @@ -25,54 +26,44 @@ bool SymbolLayer::Impl::recalculate(const CalculationParameters& parameters) { return hasTransitions; } -std::unique_ptr<Bucket> SymbolLayer::Impl::createBucket(BucketParameters& parameters) const { - auto bucket = std::make_unique<SymbolBucket>(parameters.tileID.overscaleFactor(), - parameters.tileID.overscaledZ, - parameters.mode, - id, - parameters.layer.getName()); +std::unique_ptr<Bucket> SymbolLayer::Impl::createBucket(BucketParameters&) const { + assert(false); // Should be calling createLayout() instead. + return nullptr; +} - bucket->layout = layout; +std::unique_ptr<SymbolLayout> SymbolLayer::Impl::createLayout(BucketParameters& parameters) const { + SymbolLayoutProperties layoutProperties = layout; CalculationParameters p(parameters.tileID.overscaledZ); - bucket->layout.symbolPlacement.calculate(p); - if (bucket->layout.symbolPlacement.value == SymbolPlacementType::Line) { - bucket->layout.iconRotationAlignment.value = AlignmentType::Map; - bucket->layout.textRotationAlignment.value = AlignmentType::Map; - }; + layoutProperties.symbolPlacement.calculate(p); + if (layoutProperties.symbolPlacement.value == SymbolPlacementType::Line) { + layoutProperties.iconRotationAlignment.value = AlignmentType::Map; + layoutProperties.textRotationAlignment.value = AlignmentType::Map; + } // If unspecified `text-pitch-alignment` inherits `text-rotation-alignment` - if (bucket->layout.textPitchAlignment.value == AlignmentType::Undefined) { - bucket->layout.textPitchAlignment.value = bucket->layout.textRotationAlignment.value; - }; - - bucket->layout.recalculate(p); - - bucket->layout.iconSize.calculate(CalculationParameters(18)); - bucket->layout.textSize.calculate(CalculationParameters(18)); - bucket->iconMaxSize = bucket->layout.iconSize; - bucket->textMaxSize = bucket->layout.textSize; - bucket->layout.iconSize.calculate(CalculationParameters(p.z + 1)); - bucket->layout.textSize.calculate(CalculationParameters(p.z + 1)); + if (layoutProperties.textPitchAlignment.value == AlignmentType::Undefined) { + layoutProperties.textPitchAlignment.value = layoutProperties.textRotationAlignment.value; + } - bucket->parseFeatures(parameters.layer, filter); + layoutProperties.recalculate(p); - if (bucket->needsDependencies(parameters.glyphStore, parameters.spriteStore)) { - parameters.partialParse = true; - } + layoutProperties.textSize.calculate(CalculationParameters(18)); + float textMaxSize = layoutProperties.textSize; - // We do not add features if the parser is in a "partial" state because - // the layer ordering needs to be respected when calculating text - // collisions. Although, at this point, we requested all the resources - // needed by this tile. - if (!parameters.partialParse) { - bucket->addFeatures(parameters.tileUID, - *spriteAtlas, - parameters.glyphAtlas, - parameters.glyphStore); - } + layoutProperties.iconSize.calculate(CalculationParameters(p.z + 1)); + layoutProperties.textSize.calculate(CalculationParameters(p.z + 1)); - return std::move(bucket); + return std::make_unique<SymbolLayout>(id, + parameters.layer.getName(), + parameters.tileID.overscaleFactor(), + parameters.tileID.overscaledZ, + parameters.mode, + parameters.layer, + filter, + layoutProperties, + textMaxSize, + *spriteAtlas); } } // namespace style diff --git a/src/mbgl/style/layers/symbol_layer_impl.hpp b/src/mbgl/style/layers/symbol_layer_impl.hpp index 7765d6790e..fe37ba86ea 100644 --- a/src/mbgl/style/layers/symbol_layer_impl.hpp +++ b/src/mbgl/style/layers/symbol_layer_impl.hpp @@ -7,6 +7,7 @@ namespace mbgl { class SpriteAtlas; +class SymbolLayout; namespace style { @@ -19,6 +20,7 @@ public: bool recalculate(const CalculationParameters&) override; std::unique_ptr<Bucket> createBucket(BucketParameters&) const override; + std::unique_ptr<SymbolLayout> createLayout(BucketParameters&) const; SymbolLayoutProperties layout; SymbolPaintProperties paint; diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index f74f813e48..c432869fa5 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -193,19 +193,19 @@ void GeometryTile::redoPlacement() { return; } - workRequest = worker.redoPlacement(tileWorker, buckets, targetConfig, [this, config = targetConfig](std::unique_ptr<CollisionTile> collisionTile) { + workRequest = worker.redoPlacement(tileWorker, targetConfig, [this, config = targetConfig](TilePlacementResult result) { workRequest.reset(); // Persist the configuration we just placed so that we can later check whether we need to // place again in case the configuration has changed. placedConfig = config; - for (auto& bucket : buckets) { - bucket.second->swapRenderData(); + for (auto& bucket : result.buckets) { + buckets[bucket.first] = std::move(bucket.second); } if (featureIndex) { - featureIndex->setCollisionTile(std::move(collisionTile)); + featureIndex->setCollisionTile(std::move(result.collisionTile)); } // The target configuration could have changed since we started placement. In this case, diff --git a/src/mbgl/tile/tile_worker.cpp b/src/mbgl/tile/tile_worker.cpp index d5e551d118..572f9054cd 100644 --- a/src/mbgl/tile/tile_worker.cpp +++ b/src/mbgl/tile/tile_worker.cpp @@ -1,6 +1,7 @@ #include <mbgl/text/collision_tile.hpp> #include <mbgl/tile/tile_worker.hpp> #include <mbgl/tile/geometry_tile_data.hpp> +#include <mbgl/layout/symbol_layout.hpp> #include <mbgl/style/bucket_parameters.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> @@ -12,7 +13,7 @@ #include <mbgl/util/string.hpp> #include <mbgl/util/exception.hpp> -#include <utility> +#include <unordered_set> namespace mbgl { @@ -38,149 +39,141 @@ TileWorker::~TileWorker() { TileParseResult TileWorker::parseAllLayers(std::vector<std::unique_ptr<Layer>> layers_, std::unique_ptr<const GeometryTileData> tileData_, - PlacementConfig config) { + const PlacementConfig& config) { tileData = std::move(tileData_); return redoLayout(std::move(layers_), config); } TileParseResult TileWorker::redoLayout(std::vector<std::unique_ptr<Layer>> layers_, - const PlacementConfig config) { + const PlacementConfig& config) { layers = std::move(layers_); // We're doing a fresh parse of the tile, because the underlying data or style has changed. - pending.clear(); - placementPending.clear(); - partialParse = false; featureIndex = std::make_unique<FeatureIndex>(); + symbolLayouts.clear(); // We're storing a set of bucket names we've parsed to avoid parsing a bucket twice that is // referenced from more than one layer - std::set<std::string> parsed; + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + std::unordered_set<std::string> parsed; for (auto i = layers.rbegin(); i != layers.rend(); i++) { - const Layer* layer = i->get(); - if (parsed.find(layer->baseImpl->bucketName()) == parsed.end()) { - parsed.emplace(layer->baseImpl->bucketName()); - parseLayer(layer); + if (obsolete) { + break; } - featureIndex->addBucketLayerName(layer->baseImpl->bucketName(), layer->baseImpl->id); - } - return prepareResult(config); -} + // Temporary prevention for crashing due to https://github.com/mapbox/mapbox-gl-native/issues/6263. + // Instead, the race condition will produce a blank tile. + if (!tileData) { + break; + } -TileParseResult TileWorker::parsePendingLayers(const PlacementConfig config) { - // Try parsing the remaining layers that we couldn't parse in the first step due to missing - // dependencies. - for (auto it = pending.begin(); it != pending.end();) { - const SymbolLayer& symbolLayer = *it->first; - SymbolBucket* symbolBucket = dynamic_cast<SymbolBucket*>(it->second.get()); + const Layer* layer = i->get(); + const std::string& bucketName = layer->baseImpl->bucketName(); - if (!symbolBucket->needsDependencies(glyphStore, spriteStore)) { - symbolBucket->addFeatures(reinterpret_cast<uintptr_t>(this), - *symbolLayer.impl->spriteAtlas, - glyphAtlas, - glyphStore); - placementPending.emplace(symbolLayer.impl->bucketName(), std::move(it->second)); - pending.erase(it++); + featureIndex->addBucketLayerName(bucketName, layer->baseImpl->id); + + if (parsed.find(bucketName) != parsed.end()) { + continue; + } + + parsed.emplace(bucketName); + + auto geometryLayer = tileData->getLayer(layer->baseImpl->sourceLayer); + if (!geometryLayer) { continue; } - // Advance the iterator here; we're skipping this when erasing an element from this list. - ++it; + BucketParameters parameters(id, + *geometryLayer, + obsolete, + reinterpret_cast<uintptr_t>(this), + spriteStore, + glyphAtlas, + glyphStore, + *featureIndex, + mode); + + if (layer->is<SymbolLayer>()) { + symbolLayouts.push_back(layer->as<SymbolLayer>()->impl->createLayout(parameters)); + } else { + std::unique_ptr<Bucket> bucket = layer->baseImpl->createBucket(parameters); + if (bucket->hasData()) { + buckets.emplace(layer->baseImpl->bucketName(), std::move(bucket)); + } + } } - return prepareResult(config); + return parsePendingLayers(config, std::move(buckets)); } -TileParseResult TileWorker::prepareResult(const PlacementConfig& config) { - result.complete = pending.empty(); +TileParseResult TileWorker::parsePendingLayers(const PlacementConfig& config) { + return parsePendingLayers(config, std::unordered_map<std::string, std::unique_ptr<Bucket>>()); +} - if (result.complete) { - featureIndex->setCollisionTile(placeLayers(config)); - result.featureIndex = std::move(featureIndex); - result.tileData = tileData ? tileData->clone() : nullptr; - } +TileParseResult TileWorker::parsePendingLayers(const PlacementConfig& config, + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets) { + TileParseResultData result; - return std::move(result); -} + result.complete = true; + result.buckets = std::move(buckets); -std::unique_ptr<CollisionTile> TileWorker::placeLayers(const PlacementConfig config) { - auto collisionTile = redoPlacement(&placementPending, config); - for (auto &p : placementPending) { - p.second->swapRenderData(); - insertBucket(p.first, std::move(p.second)); + // Prepare as many SymbolLayouts as possible. + for (auto& symbolLayout : symbolLayouts) { + if (symbolLayout->state == SymbolLayout::Pending) { + if (symbolLayout->canPrepare(glyphStore, spriteStore)) { + symbolLayout->state = SymbolLayout::Prepared; + symbolLayout->prepare(reinterpret_cast<uintptr_t>(this), + glyphAtlas, + glyphStore); + } else { + result.complete = false; + } + } } - placementPending.clear(); - return collisionTile; -} -std::unique_ptr<CollisionTile> TileWorker::redoPlacement( - const std::unordered_map<std::string, std::unique_ptr<Bucket>>* buckets, - PlacementConfig config) { + // If all SymbolLayouts are prepared, then perform placement. Otherwise, parsePendingLayers + // will eventually be re-run. + if (result.complete) { + TilePlacementResult placementResult = redoPlacement(config); - auto collisionTile = std::make_unique<CollisionTile>(config); + featureIndex->setCollisionTile(std::move(placementResult.collisionTile)); - for (auto i = layers.rbegin(); i != layers.rend(); i++) { - const auto it = buckets->find((*i)->baseImpl->id); - if (it != buckets->end()) { - it->second->placeFeatures(*collisionTile); + for (auto& bucket : placementResult.buckets) { + result.buckets.emplace(std::move(bucket)); + } + + result.featureIndex = std::move(featureIndex); + + if (tileData) { + result.tileData = tileData->clone(); } } - return collisionTile; + return std::move(result); } -void TileWorker::parseLayer(const Layer* layer) { - // Cancel early when parsing. - if (obsolete) - return; +TilePlacementResult TileWorker::redoPlacement(const PlacementConfig& config) { + TilePlacementResult result; - // Temporary prevention for crashing due to https://github.com/mapbox/mapbox-gl-native/issues/6263. - // Instead, the race condition will produce a blank tile. - if (!tileData) { - return; - } + result.collisionTile = std::make_unique<CollisionTile>(config); - auto geometryLayer = tileData->getLayer(layer->baseImpl->sourceLayer); - if (!geometryLayer) { - // The layer specified in the bucket does not exist. Do nothing. - if (debug::tileParseWarnings) { - Log::Warning(Event::ParseTile, "layer '%s' does not exist in tile %s", - layer->baseImpl->sourceLayer.c_str(), util::toString(id).c_str()); + for (auto& symbolLayout : symbolLayouts) { + if (symbolLayout->state == SymbolLayout::Pending) { + // Can't do placement until all layouts are prepared. + return result; } - return; } - BucketParameters parameters(id, - *geometryLayer, - obsolete, - reinterpret_cast<uintptr_t>(this), - partialParse, - spriteStore, - glyphAtlas, - glyphStore, - *featureIndex, - mode); - - std::unique_ptr<Bucket> bucket = layer->baseImpl->createBucket(parameters); - - if (layer->is<SymbolLayer>()) { - if (partialParse) { - // We cannot parse this bucket yet. Instead, we're saving it for later. - pending.emplace_back(layer->as<SymbolLayer>(), std::move(bucket)); - } else { - placementPending.emplace(layer->baseImpl->bucketName(), std::move(bucket)); + for (auto& symbolLayout : symbolLayouts) { + symbolLayout->state = SymbolLayout::Placed; + std::unique_ptr<Bucket> bucket = symbolLayout->place(*result.collisionTile); + if (bucket->hasData() || symbolLayout->hasSymbolInstances()) { + result.buckets.emplace(symbolLayout->bucketName, std::move(bucket)); } - } else { - insertBucket(layer->baseImpl->bucketName(), std::move(bucket)); } -} -void TileWorker::insertBucket(const std::string& name, std::unique_ptr<Bucket> bucket) { - if (bucket->hasData()) { - result.buckets.emplace(name, std::move(bucket)); - } + return result; } } // namespace mbgl diff --git a/src/mbgl/tile/tile_worker.hpp b/src/mbgl/tile/tile_worker.hpp index 5f38c898ff..e64e7dee19 100644 --- a/src/mbgl/tile/tile_worker.hpp +++ b/src/mbgl/tile/tile_worker.hpp @@ -4,7 +4,6 @@ #include <mbgl/tile/tile_id.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/variant.hpp> -#include <mbgl/text/placement_config.hpp> #include <mbgl/geometry/feature_index.hpp> #include <atomic> @@ -22,10 +21,11 @@ class SpriteStore; class GlyphAtlas; class GlyphStore; class Bucket; +class SymbolLayout; +class PlacementConfig; namespace style { class Layer; -class SymbolLayer; } // namespace style // We're using this class to shuttle the resulting buckets from the worker thread to the MapContext @@ -42,6 +42,12 @@ using TileParseResult = variant< TileParseResultData, // success std::exception_ptr>; // error +class TilePlacementResult { +public: + std::unordered_map<std::string, std::unique_ptr<Bucket>> buckets; + std::unique_ptr<CollisionTile> collisionTile; +}; + class TileWorker : public util::noncopyable { public: TileWorker(OverscaledTileID, @@ -54,21 +60,18 @@ public: TileParseResult parseAllLayers(std::vector<std::unique_ptr<style::Layer>>, std::unique_ptr<const GeometryTileData>, - PlacementConfig); + const PlacementConfig&); - TileParseResult parsePendingLayers(PlacementConfig); + TileParseResult parsePendingLayers(const PlacementConfig&); TileParseResult redoLayout(std::vector<std::unique_ptr<style::Layer>>, - PlacementConfig); + const PlacementConfig&); - std::unique_ptr<CollisionTile> redoPlacement(const std::unordered_map<std::string, std::unique_ptr<Bucket>>*, - PlacementConfig); + TilePlacementResult redoPlacement(const PlacementConfig&); private: - TileParseResult prepareResult(const PlacementConfig& config); - void parseLayer(const style::Layer*); - void insertBucket(const std::string& name, std::unique_ptr<Bucket>); - std::unique_ptr<CollisionTile> placeLayers(PlacementConfig); + TileParseResult parsePendingLayers(const PlacementConfig&, + std::unordered_map<std::string, std::unique_ptr<Bucket>>); const OverscaledTileID id; @@ -78,23 +81,11 @@ private: const std::atomic<bool>& obsolete; const MapMode mode; - bool partialParse = false; - std::vector<std::unique_ptr<style::Layer>> layers; - - std::unique_ptr<FeatureIndex> featureIndex; std::unique_ptr<const GeometryTileData> tileData; - // Contains buckets that we couldn't parse so far due to missing resources. - // They will be attempted on subsequent parses. - std::list<std::pair<const style::SymbolLayer*, std::unique_ptr<Bucket>>> pending; - - // Contains buckets that have been parsed, but still need placement. - // They will be placed when all buckets have been parsed. - std::unordered_map<std::string, std::unique_ptr<Bucket>> placementPending; - - // Temporary holder - TileParseResultData result; + std::unique_ptr<FeatureIndex> featureIndex; + std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; }; } // namespace mbgl diff --git a/src/mbgl/util/clip_lines.cpp b/src/mbgl/util/clip_lines.cpp index 41965876cb..81021f4b5b 100644 --- a/src/mbgl/util/clip_lines.cpp +++ b/src/mbgl/util/clip_lines.cpp @@ -1,4 +1,6 @@ -#include "clip_lines.hpp" +#include <mbgl/util/clip_lines.hpp> + +#include <cmath> namespace mbgl { namespace util { diff --git a/src/mbgl/util/clip_lines.hpp b/src/mbgl/util/clip_lines.hpp index 19ab3ac201..c1cfd4bbb9 100644 --- a/src/mbgl/util/clip_lines.hpp +++ b/src/mbgl/util/clip_lines.hpp @@ -3,7 +3,7 @@ #include <map> #include <string> #include <vector> -#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/tile/geometry_tile_data.hpp> namespace mbgl { namespace util { diff --git a/src/mbgl/util/merge_lines.hpp b/src/mbgl/util/merge_lines.hpp index b1f216148d..8db8c284cd 100644 --- a/src/mbgl/util/merge_lines.hpp +++ b/src/mbgl/util/merge_lines.hpp @@ -3,7 +3,7 @@ #include <map> #include <string> #include <vector> -#include <mbgl/renderer/symbol_bucket.hpp> +#include <mbgl/layout/symbol_layout.hpp> namespace mbgl { namespace util { diff --git a/src/mbgl/util/worker.cpp b/src/mbgl/util/worker.cpp index d62029acc3..8245628e84 100644 --- a/src/mbgl/util/worker.cpp +++ b/src/mbgl/util/worker.cpp @@ -63,10 +63,9 @@ public: } void redoPlacement(TileWorker* worker, - const std::unordered_map<std::string, std::unique_ptr<Bucket>>* buckets, PlacementConfig config, - std::function<void(std::unique_ptr<CollisionTile>)> callback) { - callback(worker->redoPlacement(buckets, config)); + std::function<void(TilePlacementResult)> callback) { + callback(worker->redoPlacement(config)); } }; @@ -120,12 +119,11 @@ Worker::redoLayout(TileWorker& worker, std::unique_ptr<AsyncRequest> Worker::redoPlacement(TileWorker& worker, - const std::unordered_map<std::string, std::unique_ptr<Bucket>>& buckets, PlacementConfig config, - std::function<void(std::unique_ptr<CollisionTile>)> callback) { + std::function<void(TilePlacementResult)> callback) { current = (current + 1) % threads.size(); return threads[current]->invokeWithCallback(&Worker::Impl::redoPlacement, &worker, - &buckets, config, callback); + config, callback); } } // end namespace mbgl diff --git a/src/mbgl/util/worker.hpp b/src/mbgl/util/worker.hpp index 31c41debf2..5b2ea06525 100644 --- a/src/mbgl/util/worker.hpp +++ b/src/mbgl/util/worker.hpp @@ -55,9 +55,8 @@ public: std::function<void(TileParseResult)> callback); Request redoPlacement(TileWorker&, - const std::unordered_map<std::string, std::unique_ptr<Bucket>>&, PlacementConfig config, - std::function<void(std::unique_ptr<CollisionTile>)> callback); + std::function<void(TilePlacementResult)> callback); private: class Impl; |