summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Firebaugh <john.firebaugh@gmail.com>2016-09-08 12:31:36 -0700
committerJohn Firebaugh <john.firebaugh@gmail.com>2016-09-14 13:41:56 -0700
commitc8217a873940264387a7d8101f968798ac7d543e (patch)
treebf3e32b78cad4681974afa99eb14b6b579dd0343
parent0bdd968d2b6eb0a12c5f2879a6a8801c96a35d85 (diff)
downloadqtlocation-mapboxgl-c8217a873940264387a7d8101f968798ac7d543e.tar.gz
[core] Extract SymbolLayout from SymbolBucket
SymbolLayout lives on the worker thread and contains the persistent data needed for repeated placement. SymbolBucket contains the data generated during placement, and is transferred to the main thread for rendering. This eliminates the risky sharing of GeometryTile::buckets between the main thread and worker thread during TileWorker::redoPlacement. While here, rationalize the names of states a SymbolLayout may be in: * 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. In TileWorker, all SymbolLayouts are stored in a single vector. Each SymbolLayout knows what state it is in, and TileWorker can easily determine how much progress it can make toward a final result.
-rw-r--r--cmake/core-files.cmake5
-rw-r--r--src/mbgl/layout/symbol_layout.cpp568
-rw-r--r--src/mbgl/layout/symbol_layout.hpp127
-rw-r--r--src/mbgl/renderer/bucket.hpp4
-rw-r--r--src/mbgl/renderer/symbol_bucket.cpp595
-rw-r--r--src/mbgl/renderer/symbol_bucket.hpp143
-rw-r--r--src/mbgl/style/bucket_parameters.hpp3
-rw-r--r--src/mbgl/style/layers/symbol_layer_impl.cpp71
-rw-r--r--src/mbgl/style/layers/symbol_layer_impl.hpp2
-rw-r--r--src/mbgl/tile/geometry_tile.cpp8
-rw-r--r--src/mbgl/tile/tile_worker.cpp191
-rw-r--r--src/mbgl/tile/tile_worker.hpp41
-rw-r--r--src/mbgl/util/clip_lines.cpp4
-rw-r--r--src/mbgl/util/clip_lines.hpp2
-rw-r--r--src/mbgl/util/merge_lines.hpp2
-rw-r--r--src/mbgl/util/worker.cpp10
-rw-r--r--src/mbgl/util/worker.hpp3
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;