From c0cb210ddca1901a956cd68e9142b7fb04183248 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Thu, 9 Nov 2017 13:24:43 -0800 Subject: [core] Switch from background to foreground placement - Background placement code now just generates static symbol buffers - Don't render GeometryTiles until their symbols are loaded. This is necessary for the CrossTileSymbolIndex to successfully prevent flicker. - SymbolInstances are transferred to SymbolBucket for use on foreground during collision detection - Symbols are sorted on foreground by sorting their index buffer but leaving vertex buffers intact (only works within one segment) - Vertical glyphs are generated at same time as horizontal glyphs. `reprojectLineLabels` chooses which one to use at render time and hides the other. - Icons are now always represented with a single collision box, even if they're placed along a line (this means their rotation alignment may be wrong, but the approach of representing them with multiple collision boxes wasn't very accurate either). - Generate vertices for new debug collision boxes and collision circles - Only add symbols within tile boundaries (reduces work, avoids double-draw) - Update symbol_projection.cpp to support line label projection calls from CollisionIndex. --- src/mbgl/layout/symbol_instance.cpp | 33 ++-- src/mbgl/layout/symbol_instance.hpp | 16 +- src/mbgl/layout/symbol_layout.cpp | 277 ++++++++++++++-------------- src/mbgl/layout/symbol_layout.hpp | 22 +-- src/mbgl/layout/symbol_projection.cpp | 156 +++++++++++----- src/mbgl/layout/symbol_projection.hpp | 34 +++- src/mbgl/renderer/buckets/symbol_bucket.cpp | 166 +++++++++++++++-- src/mbgl/renderer/buckets/symbol_bucket.hpp | 58 ++++-- src/mbgl/renderer/paint_parameters.hpp | 2 + src/mbgl/renderer/render_layer.hpp | 7 +- src/mbgl/renderer/renderer_impl.cpp | 74 +++++--- src/mbgl/renderer/renderer_impl.hpp | 7 +- src/mbgl/renderer/tile_pyramid.cpp | 18 +- src/mbgl/text/collision_feature.cpp | 81 +++----- src/mbgl/text/collision_feature.hpp | 39 ++-- src/mbgl/text/placement.cpp | 4 +- src/mbgl/tile/geometry_tile.cpp | 72 ++++---- src/mbgl/tile/geometry_tile.hpp | 22 +-- src/mbgl/tile/geometry_tile_worker.cpp | 22 ++- src/mbgl/tile/geometry_tile_worker.hpp | 11 +- src/mbgl/tile/tile.hpp | 7 +- 21 files changed, 697 insertions(+), 431 deletions(-) diff --git a/src/mbgl/layout/symbol_instance.cpp b/src/mbgl/layout/symbol_instance.cpp index 02fb800df6..d0398fcd30 100644 --- a/src/mbgl/layout/symbol_instance.cpp +++ b/src/mbgl/layout/symbol_instance.cpp @@ -11,7 +11,6 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, optional shapedIcon, const SymbolLayoutProperties::Evaluated& layout, const float layoutTextSize, - const bool addToBuffers, const uint32_t index_, const float textBoxScale, const float textPadding, @@ -19,11 +18,12 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, const std::array textOffset_, const float iconBoxScale, const float iconPadding, - const SymbolPlacementType iconPlacement, const std::array iconOffset_, const GlyphPositionMap& positions, const IndexedSubfeature& indexedFeature, - const std::size_t featureIndex_) : + const std::size_t featureIndex_, + const std::u16string& key_, + const float overscaling) : anchor(anchor_), line(line_), index(index_), @@ -31,25 +31,22 @@ SymbolInstance::SymbolInstance(Anchor& anchor_, hasIcon(shapedIcon), // Create the collision features that will be used to check whether this symbol instance can be placed - textCollisionFeature(line_, anchor, shapedTextOrientations.second ?: shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature), - iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, iconPlacement, indexedFeature), + textCollisionFeature(line_, anchor, shapedTextOrientations.first, textBoxScale, textPadding, textPlacement, indexedFeature, overscaling), + iconCollisionFeature(line_, anchor, shapedIcon, iconBoxScale, iconPadding, SymbolPlacementType::Point, indexedFeature), featureIndex(featureIndex_), textOffset(textOffset_), - iconOffset(iconOffset_) { + iconOffset(iconOffset_), + key(key_) { // Create the quads used for rendering the icon and glyphs. - if (addToBuffers) { - if (shapedIcon) { - iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); - } - if (shapedTextOrientations.first) { - auto quads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); - glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); - } - if (shapedTextOrientations.second) { - auto quads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); - glyphQuads.insert(glyphQuads.end(), quads.begin(), quads.end()); - } + if (shapedIcon) { + iconQuad = getIconQuad(*shapedIcon, layout, layoutTextSize, shapedTextOrientations.first); + } + if (shapedTextOrientations.first) { + horizontalGlyphQuads = getGlyphQuads(shapedTextOrientations.first, layout, textPlacement, positions); + } + if (shapedTextOrientations.second) { + verticalGlyphQuads = getGlyphQuads(shapedTextOrientations.second, layout, textPlacement, positions); } if (shapedTextOrientations.first && shapedTextOrientations.second) { diff --git a/src/mbgl/layout/symbol_instance.hpp b/src/mbgl/layout/symbol_instance.hpp index f1df416cd1..827a5dbbdb 100644 --- a/src/mbgl/layout/symbol_instance.hpp +++ b/src/mbgl/layout/symbol_instance.hpp @@ -5,6 +5,7 @@ #include #include + namespace mbgl { class Anchor; @@ -18,7 +19,6 @@ public: optional shapedIcon, const style::SymbolLayoutProperties::Evaluated&, const float layoutTextSize, - const bool inside, const uint32_t index, const float textBoxScale, const float textPadding, @@ -26,18 +26,20 @@ public: const std::array textOffset, const float iconBoxScale, const float iconPadding, - style::SymbolPlacementType iconPlacement, const std::array iconOffset, const GlyphPositionMap&, const IndexedSubfeature&, - const std::size_t featureIndex); + const std::size_t featureIndex, + const std::u16string& key, + const float overscaling); Anchor anchor; GeometryCoordinates line; uint32_t index; bool hasText; bool hasIcon; - SymbolQuads glyphQuads; + SymbolQuads horizontalGlyphQuads; + SymbolQuads verticalGlyphQuads; optional iconQuad; CollisionFeature textCollisionFeature; CollisionFeature iconCollisionFeature; @@ -45,6 +47,12 @@ public: std::size_t featureIndex; std::array textOffset; std::array iconOffset; + std::u16string key; + bool isDuplicate; + optional placedTextIndex; + optional placedVerticalTextIndex; + optional placedIconIndex; + uint32_t crossTileID = 0; }; } // namespace mbgl diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index 2c90b69b08..09d8883544 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -43,8 +42,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, std::unique_ptr sourceLayer_, ImageDependencies& imageDependencies, GlyphDependencies& glyphDependencies) - : sourceLayer(std::move(sourceLayer_)), - bucketName(layers.at(0)->getID()), + : bucketName(layers.at(0)->getID()), + sourceLayer(std::move(sourceLayer_)), overscaling(parameters.tileID.overscaleFactor()), zoom(parameters.tileID.overscaledZ), mode(parameters.mode), @@ -179,7 +178,8 @@ bool SymbolLayout::hasSymbolInstances() const { } void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyphPositions, - const ImageMap& imageMap, const ImagePositions& imagePositions) { + const ImageMap& imageMap, const ImagePositions& imagePositions, + const OverscaledTileID& tileID, const std::string& sourceID) { const bool textAlongLine = layout.get() == AlignmentType::Map && layout.get() == SymbolPlacementType::Line; @@ -248,7 +248,7 @@ void SymbolLayout::prepare(const GlyphMap& glyphMap, const GlyphPositions& glyph // if either shapedText or icon position is present, add the feature if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap); + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionMap, tileID, sourceID); } feature.geometry.clear(); @@ -261,7 +261,9 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair& shapedTextOrientations, optional shapedIcon, - const GlyphPositionMap& glyphPositionMap) { + const GlyphPositionMap& glyphPositionMap, + const OverscaledTileID& tileID, + const std::string& sourceID) { const float minScale = 0.5f; const float glyphSize = 24.0f; @@ -288,12 +290,10 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolPlacementType textPlacement = layout.get() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get(); - const SymbolPlacementType iconPlacement = layout.get() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); + const float textRepeatDistance = symbolSpacing / 2; - IndexedSubfeature indexedFeature = { feature.index, sourceLayer->getName(), bucketName, - symbolInstances.size() }; + IndexedSubfeature indexedFeature(feature.index, sourceLayer->getName(), bucketName, symbolInstances.size(), + sourceID, tileID.canonical.z, tileID.canonical.x, tileID.canonical.y); auto addSymbolInstance = [&] (const GeometryCoordinates& line, Anchor& anchor) { // https://github.com/mapbox/vector-tile-spec/tree/master/2.1#41-layers @@ -314,14 +314,16 @@ void SymbolLayout::addFeature(const std::size_t index, if (avoidEdges && !inside) return; - const bool addToBuffers = mode == MapMode::Still || withinPlus0; - - symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, - layout.evaluate(zoom, feature), layoutTextSize, - addToBuffers, symbolInstances.size(), - textBoxScale, textPadding, textPlacement, textOffset, - iconBoxScale, iconPadding, iconPlacement, iconOffset, - glyphPositionMap, indexedFeature, index); + // TODO set this to make api-gl work + const bool singleTileMode = false && mode == MapMode::Still; + if (singleTileMode || withinPlus0) { + symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, + layout.evaluate(zoom, feature), layoutTextSize, + symbolInstances.size(), + textBoxScale, textPadding, textPlacement, textOffset, + iconBoxScale, iconPadding, iconOffset, + glyphPositionMap, indexedFeature, index, feature.text ? *feature.text : std::u16string{}, overscaling); + } }; const auto& type = feature.getType(); @@ -392,108 +394,93 @@ bool SymbolLayout::anchorIsTooClose(const std::u16string& text, const float repe return false; } -std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) { - auto bucket = std::make_unique(layout, layerPaintProperties, textSize, iconSize, zoom, 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.get() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); - const SymbolPlacementType iconPlacement = layout.get() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get(); +// Analog of `addToLineVertexArray` in JS. This version doesn't need to build up a line array like the +// JS version does, but it uses the same logic to calculate tile distances. +std::vector CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) { + std::vector tileDistances(line.size()); + if (anchor.segment != -1) { + auto sumForwardLength = util::dist(anchor.point, line[anchor.segment + 1]); + auto sumBackwardLength = util::dist(anchor.point, line[anchor.segment]); + for (size_t i = anchor.segment + 1; i < line.size(); i++) { + tileDistances[i] = sumForwardLength; + if (i < line.size() - 1) { + sumForwardLength += util::dist(line[i + 1], line[i]); + } + } + for (auto i = anchor.segment; i >= 0; i--) { + tileDistances[i] = sumBackwardLength; + if (i > 0) { + sumBackwardLength += util::dist(line[i - 1], line[i]); + } + } + } + return tileDistances; +} +std::unique_ptr SymbolLayout::place(const bool showCollisionBoxes) { const bool mayOverlap = layout.get() || layout.get() || layout.get() || layout.get(); + + auto bucket = std::make_unique(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances)); - const bool keepUpright = layout.get(); - - // 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.anchor.point.x + cos * a.anchor.point.y; - const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; - return aRotated != bRotated ? - aRotated < bRotated : - a.index > b.index; - }); - } - - for (SymbolInstance &symbolInstance : symbolInstances) { + for (SymbolInstance &symbolInstance : bucket->symbolInstances) { const bool hasText = symbolInstance.hasText; const bool hasIcon = symbolInstance.hasIcon; - const bool iconWithoutText = layout.get() || !hasText; - const bool textWithoutIcon = layout.get() || !hasIcon; - - // Calculate the scales at which the text and icon can be placed without collision. - - float glyphScale = hasText ? - collisionTile.placeFeature(symbolInstance.textCollisionFeature, - layout.get(), layout.get()) : - collisionTile.minScale; - float iconScale = hasIcon ? - collisionTile.placeFeature(symbolInstance.iconCollisionFeature, - layout.get(), layout.get()) : - 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); - } - const auto& feature = features.at(symbolInstance.featureIndex); // Insert final placement into collision tree and add glyphs/icons to buffers if (hasText) { - const float placementZoom = util::max(util::log2(glyphScale) + zoom, 0.0f); - collisionTile.insertFeature(symbolInstance.textCollisionFeature, glyphScale, layout.get()); - if (glyphScale < collisionTile.maxScale) { - - const float labelAngle = std::fmod((symbolInstance.anchor.angle + collisionTile.config.angle) + 2 * M_PI, 2 * M_PI); - const bool inVerticalRange = ( - (labelAngle > M_PI * 1.0 / 4.0 && labelAngle <= M_PI * 3.0 / 4) || - (labelAngle > M_PI * 5.0 / 4.0 && labelAngle <= M_PI * 7.0 / 4)); - const bool useVerticalMode = symbolInstance.writingModes & WritingModeType::Vertical && inVerticalRange; - - const Range sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + const Range sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, + symbolInstance.textOffset, symbolInstance.writingModes, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); + symbolInstance.placedTextIndex = bucket->text.placedSymbols.size() - 1; + PlacedSymbol& horizontalSymbol = bucket->text.placedSymbols.back(); + + bool firstHorizontal = true; + for (const auto& symbol : symbolInstance.horizontalGlyphQuads) { + size_t index = addSymbol( + bucket->text, sizeData, symbol, + symbolInstance.anchor, horizontalSymbol); + if (firstHorizontal) { + horizontalSymbol.vertexStartIndex = index; + firstHorizontal = false; + } + } + + if (symbolInstance.writingModes & WritingModeType::Vertical) { bucket->text.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, - symbolInstance.textOffset, placementZoom, useVerticalMode, symbolInstance.line); - - for (const auto& symbol : symbolInstance.glyphQuads) { - addSymbol( - bucket->text, sizeData, symbol, placementZoom, - keepUpright, textPlacement, symbolInstance.anchor, bucket->text.placedSymbols.back()); + symbolInstance.textOffset, WritingModeType::Vertical, symbolInstance.line, CalculateTileDistances(symbolInstance.line, symbolInstance.anchor)); + symbolInstance.placedVerticalTextIndex = bucket->text.placedSymbols.size() - 1; + + PlacedSymbol& verticalSymbol = bucket->text.placedSymbols.back(); + bool firstVertical = true; + + for (const auto& symbol : symbolInstance.verticalGlyphQuads) { + size_t index = addSymbol( + bucket->text, sizeData, symbol, + symbolInstance.anchor, verticalSymbol); + + if (firstVertical) { + verticalSymbol.vertexStartIndex = index; + firstVertical = false; + } } } } if (hasIcon) { - const float placementZoom = util::max(util::log2(iconScale) + zoom, 0.0f); - collisionTile.insertFeature(symbolInstance.iconCollisionFeature, iconScale, layout.get()); - if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + if (symbolInstance.iconQuad) { const Range sizeData = bucket->iconSizeBinder->getVertexSizeData(feature); bucket->icon.placedSymbols.emplace_back(symbolInstance.anchor.point, symbolInstance.anchor.segment, sizeData.min, sizeData.max, - symbolInstance.iconOffset, placementZoom, false, symbolInstance.line); - addSymbol( - bucket->icon, sizeData, *symbolInstance.iconQuad, placementZoom, - keepUpright, iconPlacement, symbolInstance.anchor, bucket->icon.placedSymbols.back()); + symbolInstance.iconOffset, WritingModeType::None, symbolInstance.line, std::vector()); + symbolInstance.placedIconIndex = bucket->icon.placedSymbols.size() - 1; + PlacedSymbol& iconSymbol = bucket->icon.placedSymbols.back(); + iconSymbol.vertexStartIndex = addSymbol( + bucket->icon, sizeData, *symbolInstance.iconQuad, + symbolInstance.anchor, iconSymbol); } } @@ -503,20 +490,17 @@ std::unique_ptr SymbolLayout::place(CollisionTile& collisionTile) } } - if (collisionTile.config.debug) { - addToDebugBuffers(collisionTile, *bucket); + if (showCollisionBoxes) { + addToDebugBuffers(*bucket); } return bucket; } template -void SymbolLayout::addSymbol(Buffer& buffer, +size_t SymbolLayout::addSymbol(Buffer& buffer, const Range sizeData, const SymbolQuad& symbol, - const float placementZoom, - const bool keepUpright, - const style::SymbolPlacementType placement, const Anchor& labelAnchor, PlacedSymbol& placedSymbol) { constexpr const uint16_t vertexLength = 4; @@ -527,11 +511,6 @@ void SymbolLayout::addSymbol(Buffer& buffer, const auto &br = symbol.br; const auto &tex = symbol.tex; - if (placement == style::SymbolPlacementType::Line && keepUpright) { - // drop incorrectly oriented glyphs - if ((symbol.writingMode == WritingModeType::Vertical) != placedSymbol.useVerticalMode) return; - } - if (buffer.segments.empty() || buffer.segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { buffer.segments.emplace_back(buffer.vertices.vertexSize(), buffer.triangles.indexSize()); } @@ -548,11 +527,19 @@ void SymbolLayout::addSymbol(Buffer& buffer, buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, bl, symbol.glyphOffset.y, tex.x, tex.y + tex.h, sizeData)); buffer.vertices.emplace_back(SymbolLayoutAttributes::vertex(labelAnchor.point, br, symbol.glyphOffset.y, tex.x + tex.w, tex.y + tex.h, sizeData)); - auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0, placementZoom); + // Dynamic/Opacity vertices are initialized so that the vertex count always agrees with + // the layout vertex buffer, but they will always be updated before rendering happens + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(labelAnchor.point, 0); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); buffer.dynamicVertices.emplace_back(dynamicVertex); + + auto opacityVertex = SymbolOpacityAttributes::vertex(1.0, 1.0); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); + buffer.opacityVertices.emplace_back(opacityVertex); // add the two triangles, referencing the four coordinates we just inserted. buffer.triangles.emplace_back(index + 0, index + 1, index + 2); @@ -562,54 +549,62 @@ void SymbolLayout::addSymbol(Buffer& buffer, segment.indexLength += 6; placedSymbol.glyphOffsets.push_back(symbol.glyphOffset.x); + + return index; } -void SymbolLayout::addToDebugBuffers(CollisionTile& collisionTile, SymbolBucket& bucket) { +void SymbolLayout::addToDebugBuffers(SymbolBucket& bucket) { if (!hasSymbolInstances()) { return; } - const float yStretch = collisionTile.yStretch; - - auto& collisionBox = bucket.collisionBox; - for (const SymbolInstance &symbolInstance : symbolInstances) { auto populateCollisionBox = [&](const auto& feature) { + SymbolBucket::CollisionBuffer& collisionBuffer = feature.alongLine ? + static_cast(bucket.collisionCircle) : + static_cast(bucket.collisionBox); for (const CollisionBox &box : feature.boxes) { auto& anchor = box.anchor; - Point tl{box.x1, box.y1 * yStretch}; - Point tr{box.x2, box.y1 * yStretch}; - Point bl{box.x1, box.y2 * yStretch}; - Point br{box.x2, box.y2 * yStretch}; - tl = util::matrixMultiply(collisionTile.reverseRotationMatrix, tl); - tr = util::matrixMultiply(collisionTile.reverseRotationMatrix, tr); - bl = util::matrixMultiply(collisionTile.reverseRotationMatrix, bl); - br = util::matrixMultiply(collisionTile.reverseRotationMatrix, br); - - const float maxZoom = util::clamp(zoom + util::log2(box.maxScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F); - const float placementZoom = util::clamp(zoom + util::log2(box.placementScale), util::MIN_ZOOM_F, util::MAX_ZOOM_F); + Point tl{box.x1, box.y1}; + Point tr{box.x2, box.y1}; + Point bl{box.x1, box.y2}; + Point br{box.x2, box.y2}; static constexpr std::size_t vertexLength = 4; - static constexpr std::size_t indexLength = 8; + const std::size_t indexLength = feature.alongLine ? 6 : 8; - if (collisionBox.segments.empty() || collisionBox.segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { - collisionBox.segments.emplace_back(collisionBox.vertices.vertexSize(), collisionBox.lines.indexSize()); + if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits::max()) { + collisionBuffer.segments.emplace_back(collisionBuffer.vertices.vertexSize(), + feature.alongLine? bucket.collisionCircle.triangles.indexSize() : bucket.collisionBox.lines.indexSize()); } - auto& segment = collisionBox.segments.back(); + auto& segment = collisionBuffer.segments.back(); uint16_t index = segment.vertexLength; - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br, maxZoom, placementZoom)); - collisionBox.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl, maxZoom, placementZoom)); - - collisionBox.lines.emplace_back(index + 0, index + 1); - collisionBox.lines.emplace_back(index + 1, index + 2); - collisionBox.lines.emplace_back(index + 2, index + 3); - collisionBox.lines.emplace_back(index + 3, index + 0); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tl)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, tr)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, br)); + collisionBuffer.vertices.emplace_back(CollisionBoxProgram::vertex(anchor, symbolInstance.anchor.point, bl)); + + // Dynamic vertices are initialized so that the vertex count always agrees with + // the layout vertex buffer, but they will always be updated before rendering happens + auto dynamicVertex = CollisionBoxDynamicAttributes::vertex(false, false); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + collisionBuffer.dynamicVertices.emplace_back(dynamicVertex); + + if (feature.alongLine) { + bucket.collisionCircle.triangles.emplace_back(index, index + 1, index + 2); + bucket.collisionCircle.triangles.emplace_back(index, index + 2, index + 3); + } else { + bucket.collisionBox.lines.emplace_back(index + 0, index + 1); + bucket.collisionBox.lines.emplace_back(index + 1, index + 2); + bucket.collisionBox.lines.emplace_back(index + 2, index + 3); + bucket.collisionBox.lines.emplace_back(index + 3, index + 0); + } segment.vertexLength += vertexLength; segment.indexLength += indexLength; diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index 90f5b3c91d..6951c29ada 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -16,7 +16,6 @@ namespace mbgl { class BucketParameters; -class CollisionTile; class SymbolBucket; class Anchor; class RenderLayer; @@ -35,42 +34,44 @@ public: GlyphDependencies&); void prepare(const GlyphMap&, const GlyphPositions&, - const ImageMap&, const ImagePositions&); + const ImageMap&, const ImagePositions&, + const OverscaledTileID&, const std::string&); - std::unique_ptr place(CollisionTile&); + std::unique_ptr place(const bool showCollisionBoxes); bool hasSymbolInstances() const; std::map> layerPaintProperties; + const std::string bucketName; + std::vector symbolInstances; + private: void addFeature(const size_t, const SymbolFeature&, const std::pair& shapedTextOrientations, optional shapedIcon, - const GlyphPositionMap&); + const GlyphPositionMap&, + const OverscaledTileID&, + const std::string&); bool anchorIsTooClose(const std::u16string& text, const float repeatDistance, const Anchor&); std::map> compareText; - void addToDebugBuffers(CollisionTile&, SymbolBucket&); + void addToDebugBuffers(SymbolBucket&); // Adds placed items to the buffer. template - void addSymbol(Buffer&, + size_t addSymbol(Buffer&, const Range sizeData, const SymbolQuad&, - float scale, - const bool keepUpright, - const style::SymbolPlacementType, const Anchor& labelAnchor, PlacedSymbol& placedSymbol); // Stores the layer so that we can hold on to GeometryTileFeature instances in SymbolFeature, // which may reference data from this object. const std::unique_ptr sourceLayer; - const std::string bucketName; const float overscaling; const float zoom; const MapMode mode; @@ -87,7 +88,6 @@ private: style::TextSize::UnevaluatedType textSize; style::IconSize::UnevaluatedType iconSize; - std::vector symbolInstances; std::vector features; BiDi bidi; // Consider moving this up to geometry tile worker to reduce reinstantiation costs; use of BiDi/ubiditransform object must be constrained to one thread diff --git a/src/mbgl/layout/symbol_projection.cpp b/src/mbgl/layout/symbol_projection.cpp index 279d251f8f..b8c399d857 100644 --- a/src/mbgl/layout/symbol_projection.cpp +++ b/src/mbgl/layout/symbol_projection.cpp @@ -3,7 +3,6 @@ #include #include #include -#include #include #include @@ -93,9 +92,6 @@ namespace mbgl { return m; } - - typedef std::pair,float> PointAndCameraDistance; - PointAndCameraDistance project(const Point& point, const mat4& matrix) { vec4 pos = {{ point.x, point.y, 0, 1 }}; matrix::transformMat4(pos, pos, matrix); @@ -114,7 +110,7 @@ namespace mbgl { } } - bool isVisible(const vec4& anchorPos, const float placementZoom, const std::array& clippingBuffer, const FrameHistory& frameHistory) { + bool isVisible(const vec4& anchorPos, const std::array& clippingBuffer) { const float x = anchorPos[0] / anchorPos[3]; const float y = anchorPos[1] / anchorPos[3]; const bool inPaddedViewport = ( @@ -122,12 +118,12 @@ namespace mbgl { x <= clippingBuffer[0] && y >= -clippingBuffer[1] && y <= clippingBuffer[1]); - return inPaddedViewport && frameHistory.isVisible(placementZoom); + return inPaddedViewport; } - void addDynamicAttributes(const Point& anchorPoint, const float angle, const float placementZoom, + void addDynamicAttributes(const Point& anchorPoint, const float angle, gl::VertexVector& dynamicVertexArray) { - auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle, placementZoom); + auto dynamicVertex = SymbolDynamicLayoutAttributes::vertex(anchorPoint, angle); dynamicVertexArray.emplace_back(dynamicVertex); dynamicVertexArray.emplace_back(dynamicVertex); dynamicVertexArray.emplace_back(dynamicVertex); @@ -137,20 +133,15 @@ namespace mbgl { void hideGlyphs(size_t numGlyphs, gl::VertexVector& dynamicVertexArray) { const Point offscreenPoint = { -INFINITY, -INFINITY }; for (size_t i = 0; i < numGlyphs; i++) { - addDynamicAttributes(offscreenPoint, 0, 25, dynamicVertexArray); + addDynamicAttributes(offscreenPoint, 0, dynamicVertexArray); } } - struct PlacedGlyph { - PlacedGlyph(Point point_, float angle_) : point(point_), angle(angle_) {} - Point point; - float angle; - }; - enum PlacementResult { OK, NotEnoughRoom, - NeedsFlipping + NeedsFlipping, + UseVertical }; Point projectTruncatedLineSegment(const Point& previousTilePoint, const Point& currentTilePoint, const Point& previousProjectedPoint, const float minimumLength, const mat4& projectionMatrix) { @@ -165,7 +156,7 @@ namespace mbgl { } optional placeGlyphAlongLine(const float offsetX, const float lineOffsetX, const float lineOffsetY, const bool flip, - const Point& projectedAnchorPoint, const Point& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const mat4& labelPlaneMatrix) { + const Point& projectedAnchorPoint, const Point& tileAnchorPoint, const uint16_t anchorSegment, const GeometryCoordinates& line, const std::vector& tileDistances, const mat4& labelPlaneMatrix, const bool returnTileDistance) { const float combinedOffsetX = flip ? offsetX - lineOffsetX : @@ -185,6 +176,7 @@ namespace mbgl { int32_t currentIndex = dir > 0 ? anchorSegment : anchorSegment + 1; + const int32_t initialIndex = currentIndex; Point current = projectedAnchorPoint; Point prev = projectedAnchorPoint; float distanceToPrev = 0.0; @@ -195,7 +187,9 @@ namespace mbgl { currentIndex += dir; // offset does not fit on the projected line - if (currentIndex < 0 || currentIndex >= static_cast(line.size())) return {}; + if (currentIndex < 0 || currentIndex >= static_cast(line.size())) { + return {}; + } prev = current; PointAndCameraDistance projection = project(convertPoint(line.at(currentIndex)), labelPlaneMatrix); @@ -225,7 +219,62 @@ namespace mbgl { const float segmentAngle = angle + std::atan2(current.y - prev.y, current.x - prev.x); - return {{ p, segmentAngle }}; + return {{ + p, + segmentAngle, + returnTileDistance ? + TileDistance( + (currentIndex - dir) == initialIndex ? 0 : tileDistances[currentIndex - dir], + absOffsetX - distanceToPrev + ) : + optional() + }}; + } + + optional> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point& anchorPoint, + const Point& tileAnchorPoint, + const PlacedSymbol& symbol, + const mat4& labelPlaneMatrix, + const bool returnTileDistance) { + + const float firstGlyphOffset = symbol.glyphOffsets.front(); + const float lastGlyphOffset = symbol.glyphOffsets.back();; + + optional firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!firstPlacedGlyph) + return optional>(); + + optional lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, anchorPoint, tileAnchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, returnTileDistance); + if (!lastPlacedGlyph) + return optional>(); + + return std::make_pair(*firstPlacedGlyph, *lastPlacedGlyph); + } + + optional requiresOrientationChange(const WritingModeType writingModes, const Point& firstPoint, const Point& lastPoint, const float aspectRatio) { + if (writingModes == (WritingModeType::Horizontal | WritingModeType::Vertical)) { + // On top of choosing whether to flip, choose whether to render this version of the glyphs or the alternate + // vertical glyphs. We can't just filter out vertical glyphs in the horizontal range because the horizontal + // and vertical versions can have slightly different projections which could lead to angles where both or + // neither showed. + auto rise = std::abs(lastPoint.y - firstPoint.y); + auto run = std::abs(lastPoint.x - firstPoint.x) * aspectRatio; + if (rise > run) { + return PlacementResult::UseVertical; + } + } + + if ((writingModes == WritingModeType::Vertical) ? + (firstPoint.y < lastPoint.y) : + (firstPoint.x > lastPoint.x)) { + // Includes "horizontalOnly" case for labels without vertical glyphs + return PlacementResult::NeedsFlipping; + } + return {}; } PlacementResult placeGlyphsAlongLine(const PlacedSymbol& symbol, @@ -236,7 +285,8 @@ namespace mbgl { const mat4& labelPlaneMatrix, const mat4& glCoordMatrix, gl::VertexVector& dynamicVertexArray, - const Point& projectedAnchorPoint) { + const Point& projectedAnchorPoint, + const float aspectRatio) { const float fontScale = fontSize / 24.0; const float lineOffsetX = symbol.lineOffset[0] * fontSize; const float lineOffsetY = symbol.lineOffset[1] * fontSize; @@ -244,33 +294,30 @@ namespace mbgl { std::vector placedGlyphs; if (symbol.glyphOffsets.size() > 1) { - const float firstGlyphOffset = symbol.glyphOffsets.front(); - const float lastGlyphOffset = symbol.glyphOffsets.back(); - - optional firstPlacedGlyph = placeGlyphAlongLine(fontScale * firstGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!firstPlacedGlyph) - return PlacementResult::NotEnoughRoom; - - optional lastPlacedGlyph = placeGlyphAlongLine(fontScale * lastGlyphOffset, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); - if (!lastPlacedGlyph) + const optional> firstAndLastGlyph = + placeFirstAndLastGlyph(fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol, labelPlaneMatrix, false); + if (!firstAndLastGlyph) { return PlacementResult::NotEnoughRoom; + } - const Point firstPoint = project(firstPlacedGlyph->point, glCoordMatrix).first; - const Point lastPoint = project(lastPlacedGlyph->point, glCoordMatrix).first; + const Point firstPoint = project(firstAndLastGlyph->first.point, glCoordMatrix).first; + const Point lastPoint = project(firstAndLastGlyph->second.point, glCoordMatrix).first; - if (keepUpright && !flip && - (symbol.useVerticalMode ? firstPoint.y < lastPoint.y : firstPoint.x > lastPoint.x)) { - return PlacementResult::NeedsFlipping; + if (keepUpright && !flip) { + auto orientationChange = requiresOrientationChange(symbol.writingModes, firstPoint, lastPoint, aspectRatio); + if (orientationChange) { + return *orientationChange; + } } - placedGlyphs.push_back(*firstPlacedGlyph); + placedGlyphs.push_back(firstAndLastGlyph->first); for (size_t glyphIndex = 1; glyphIndex < symbol.glyphOffsets.size() - 1; glyphIndex++) { const float glyphOffsetX = symbol.glyphOffsets[glyphIndex]; // Since first and last glyph fit on the line, we're sure that the rest of the glyphs can be placed - auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, labelPlaneMatrix); + auto placedGlyph = placeGlyphAlongLine(glyphOffsetX * fontScale, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, symbol.line, symbol.tileDistances, labelPlaneMatrix, false); placedGlyphs.push_back(*placedGlyph); } - placedGlyphs.push_back(*lastPlacedGlyph); + placedGlyphs.push_back(firstAndLastGlyph->second); } else { // Only a single glyph to place // So, determine whether to flip based on projected angle of the line segment it's on @@ -285,14 +332,15 @@ namespace mbgl { projectedVertex.first : projectTruncatedLineSegment(symbol.anchorPoint,tileSegmentEnd, a, 1, posMatrix); - if (symbol.useVerticalMode ? b.y > a.y : b.x < a.x) { - return PlacementResult::NeedsFlipping; + auto orientationChange = requiresOrientationChange(symbol.writingModes, a, b, aspectRatio); + if (orientationChange) { + return *orientationChange; } } assert(symbol.glyphOffsets.size() == 1); // We are relying on SymbolInstance.hasText filtering out symbols without any glyphs at all const float glyphOffsetX = symbol.glyphOffsets.front(); optional singleGlyph = placeGlyphAlongLine(fontScale * glyphOffsetX, lineOffsetX, lineOffsetY, flip, projectedAnchorPoint, symbol.anchorPoint, symbol.segment, - symbol.line, labelPlaneMatrix); + symbol.line, symbol.tileDistances, labelPlaneMatrix, false); if (!singleGlyph) return PlacementResult::NotEnoughRoom; @@ -300,7 +348,7 @@ namespace mbgl { } for (auto& placedGlyph : placedGlyphs) { - addDynamicAttributes(placedGlyph.point, placedGlyph.angle, symbol.placementZoom, dynamicVertexArray); + addDynamicAttributes(placedGlyph.point, placedGlyph.angle, dynamicVertexArray); } return PlacementResult::OK; @@ -309,7 +357,7 @@ namespace mbgl { void reprojectLineLabels(gl::VertexVector& dynamicVertexArray, const std::vector& placedSymbols, const mat4& posMatrix, const style::SymbolPropertyValues& values, - const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state, const FrameHistory& frameHistory) { + const RenderTile& tile, const SymbolSizeBinder& sizeBinder, const TransformState& state) { const ZoomEvaluatedSize partiallyEvaluatedSize = sizeBinder.evaluateForZoom(state.getZoom()); @@ -325,19 +373,31 @@ namespace mbgl { const mat4 glCoordMatrix = getGlCoordMatrix(posMatrix, pitchWithMap, rotateWithMap, state, pixelsToTileUnits); dynamicVertexArray.clear(); + + bool useVertical = false; for (auto& placedSymbol : placedSymbols) { + // Don't do calculations for vertical glyphs unless the previous symbol was horizontal + // and we determined that vertical glyphs were necessary. + // Also don't do calculations for symbols that are collided and fully faded out + if (placedSymbol.hidden || (placedSymbol.writingModes == WritingModeType::Vertical && !useVertical)) { + hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); + continue; + } + // Awkward... but we're counting on the paired "vertical" symbol coming immediately after its horizontal counterpart + useVertical = false; + vec4 anchorPos = {{ placedSymbol.anchorPoint.x, placedSymbol.anchorPoint.y, 0, 1 }}; matrix::transformMat4(anchorPos, anchorPos, posMatrix); // Don't bother calculating the correct point for invisible labels. - if (!isVisible(anchorPos, placedSymbol.placementZoom, clippingBuffer, frameHistory)) { + if (!isVisible(anchorPos, clippingBuffer)) { hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); continue; } const float cameraToAnchorDistance = anchorPos[3]; - const float perspectiveRatio = 1 + 0.5 * ((cameraToAnchorDistance / state.getCameraToCenterDistance()) - 1.0); + const float perspectiveRatio = 0.5 + 0.5 * (cameraToAnchorDistance / state.getCameraToCenterDistance()); const float fontSize = evaluateSizeForFeature(partiallyEvaluatedSize, placedSymbol); const float pitchScaledFontSize = values.pitchAlignment == style::AlignmentType::Map ? @@ -346,11 +406,13 @@ namespace mbgl { const Point anchorPoint = project(placedSymbol.anchorPoint, labelPlaneMatrix).first; - PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint); + PlacementResult placeUnflipped = placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, false /*unflipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()); + + useVertical = placeUnflipped == PlacementResult::UseVertical; - if (placeUnflipped == PlacementResult::NotEnoughRoom || + if (placeUnflipped == PlacementResult::NotEnoughRoom || useVertical || (placeUnflipped == PlacementResult::NeedsFlipping && - placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint) == PlacementResult::NotEnoughRoom)) { + placeGlyphsAlongLine(placedSymbol, pitchScaledFontSize, true /*flipped*/, values.keepUpright, posMatrix, labelPlaneMatrix, glCoordMatrix, dynamicVertexArray, anchorPoint, state.getSize().aspectRatio()) == PlacementResult::NotEnoughRoom)) { hideGlyphs(placedSymbol.glyphOffsets.size(), dynamicVertexArray); } } diff --git a/src/mbgl/layout/symbol_projection.hpp b/src/mbgl/layout/symbol_projection.hpp index 2652fe7ace..8535014f22 100644 --- a/src/mbgl/layout/symbol_projection.hpp +++ b/src/mbgl/layout/symbol_projection.hpp @@ -8,18 +8,48 @@ namespace mbgl { class TransformState; class RenderTile; - class FrameHistory; class SymbolSizeBinder; class PlacedSymbol; namespace style { class SymbolPropertyValues; } // end namespace style + + struct TileDistance { + TileDistance(float prevTileDistance_, float lastSegmentViewportDistance_) + : prevTileDistance(prevTileDistance_), lastSegmentViewportDistance(lastSegmentViewportDistance_) + {} + float prevTileDistance; + float lastSegmentViewportDistance; + }; + + struct PlacedGlyph { + PlacedGlyph(Point point_, float angle_, optional tileDistance_) + : point(point_), angle(angle_), tileDistance(std::move(tileDistance_)) + {} + Point point; + float angle; + optional tileDistance; + }; + float evaluateSizeForFeature(const ZoomEvaluatedSize& zoomEvaluatedSize, const PlacedSymbol& placedSymbol); mat4 getLabelPlaneMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); mat4 getGlCoordMatrix(const mat4& posMatrix, const bool pitchWithMap, const bool rotateWithMap, const TransformState& state, const float pixelsToTileUnits); + + using PointAndCameraDistance = std::pair,float>; + PointAndCameraDistance project(const Point& point, const mat4& matrix); void reprojectLineLabels(gl::VertexVector&, const std::vector&, const mat4& posMatrix, const style::SymbolPropertyValues&, - const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&, const FrameHistory& frameHistory); + const RenderTile&, const SymbolSizeBinder& sizeBinder, const TransformState&); + + optional> placeFirstAndLastGlyph(const float fontScale, + const float lineOffsetX, + const float lineOffsetY, + const bool flip, + const Point& anchorPoint, + const Point& tileAnchorPoint, + const PlacedSymbol& symbol, + const mat4& labelPlaneMatrix, + const bool returnTileDistance); } // end namespace mbgl diff --git a/src/mbgl/renderer/buckets/symbol_bucket.cpp b/src/mbgl/renderer/buckets/symbol_bucket.cpp index a3f71f1f6e..ebcf93f2b1 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.cpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.cpp @@ -16,10 +16,14 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo const style::DataDrivenPropertyValue& iconSize, float zoom, bool sdfIcons_, - bool iconsNeedLinear_) + bool iconsNeedLinear_, + bool sortFeaturesByY_, + const std::vector&& symbolInstances_) : layout(std::move(layout_)), sdfIcons(sdfIcons_), iconsNeedLinear(iconsNeedLinear_ || iconSize.isDataDriven() || !iconSize.isZoomConstant()), + sortFeaturesByY(sortFeaturesByY_), + symbolInstances(std::move(symbolInstances_)), textSizeBinder(SymbolSizeBinder::create(zoom, textSize, TextSize::defaultValue())), iconSizeBinder(SymbolSizeBinder::create(zoom, iconSize, IconSize::defaultValue())) { @@ -36,28 +40,84 @@ SymbolBucket::SymbolBucket(style::SymbolLayoutProperties::PossiblyEvaluated layo void SymbolBucket::upload(gl::Context& context) { if (hasTextData()) { - text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices)); - text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw); - text.indexBuffer = context.createIndexBuffer(std::move(text.triangles)); + if (!staticUploaded) { + text.indexBuffer = context.createIndexBuffer(std::move(text.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw); + text.vertexBuffer = context.createVertexBuffer(std::move(text.vertices)); + } else if (!sortUploaded) { + context.updateIndexBuffer(*text.indexBuffer, std::move(text.triangles)); + } + + if (!dynamicUploaded) { + text.dynamicVertexBuffer = context.createVertexBuffer(std::move(text.dynamicVertices), gl::BufferUsage::StreamDraw); + } + if (!placementChangesUploaded) { + if (!text.opacityVertexBuffer) { + text.opacityVertexBuffer = context.createVertexBuffer(std::move(text.opacityVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*text.opacityVertexBuffer, std::move(text.opacityVertices)); + } + } } if (hasIconData()) { - icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); - icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw); - icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles)); + if (!staticUploaded) { + icon.indexBuffer = context.createIndexBuffer(std::move(icon.triangles), sortFeaturesByY ? gl::BufferUsage::StreamDraw : gl::BufferUsage::StaticDraw); + icon.vertexBuffer = context.createVertexBuffer(std::move(icon.vertices)); + } else if (!sortUploaded) { + context.updateIndexBuffer(*icon.indexBuffer, std::move(icon.triangles)); + } + if (!dynamicUploaded) { + icon.dynamicVertexBuffer = context.createVertexBuffer(std::move(icon.dynamicVertices), gl::BufferUsage::StreamDraw); + } + if (!placementChangesUploaded) { + if (!icon.opacityVertexBuffer) { + icon.opacityVertexBuffer = context.createVertexBuffer(std::move(icon.opacityVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*icon.opacityVertexBuffer, std::move(icon.opacityVertices)); + } + } } - if (!collisionBox.vertices.empty()) { - collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices)); - collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines)); + if (hasCollisionBoxData()) { + if (!staticUploaded) { + collisionBox.indexBuffer = context.createIndexBuffer(std::move(collisionBox.lines)); + collisionBox.vertexBuffer = context.createVertexBuffer(std::move(collisionBox.vertices)); + } + if (!placementChangesUploaded) { + if (!collisionBox.dynamicVertexBuffer) { + collisionBox.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionBox.dynamicVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*collisionBox.dynamicVertexBuffer, std::move(collisionBox.dynamicVertices)); + } + } } - - for (auto& pair : paintPropertyBinders) { - pair.second.first.upload(context); - pair.second.second.upload(context); + + if (hasCollisionCircleData()) { + if (!staticUploaded) { + collisionCircle.indexBuffer = context.createIndexBuffer(std::move(collisionCircle.triangles)); + collisionCircle.vertexBuffer = context.createVertexBuffer(std::move(collisionCircle.vertices)); + } + if (!placementChangesUploaded) { + if (!collisionCircle.dynamicVertexBuffer) { + collisionCircle.dynamicVertexBuffer = context.createVertexBuffer(std::move(collisionCircle.dynamicVertices), gl::BufferUsage::StreamDraw); + } else { + context.updateVertexBuffer(*collisionCircle.dynamicVertexBuffer, std::move(collisionCircle.dynamicVertices)); + } + } } + if (!staticUploaded) { + for (auto& pair : paintPropertyBinders) { + pair.second.first.upload(context); + pair.second.second.upload(context); + } + } + uploaded = true; + staticUploaded = true; + placementChangesUploaded = true; + dynamicUploaded = true; + sortUploaded = true; } bool SymbolBucket::hasData() const { @@ -76,4 +136,82 @@ bool SymbolBucket::hasCollisionBoxData() const { return !collisionBox.segments.empty(); } +bool SymbolBucket::hasCollisionCircleData() const { + return !collisionCircle.segments.empty(); +} + +void SymbolBucket::updateOpacity() { + placementChangesUploaded = false; + uploaded = false; +} + +void addPlacedSymbol(gl::IndexVector& triangles, const PlacedSymbol& placedSymbol) { + auto endIndex = placedSymbol.vertexStartIndex + placedSymbol.glyphOffsets.size() * 4; + for (auto vertexIndex = placedSymbol.vertexStartIndex; vertexIndex < endIndex; vertexIndex += 4) { + triangles.emplace_back(vertexIndex + 0, vertexIndex + 1, vertexIndex + 2); + triangles.emplace_back(vertexIndex + 1, vertexIndex + 2, vertexIndex + 3); + } +} + +void SymbolBucket::sortFeatures(const float angle) { + if (!sortFeaturesByY) { + return; + } + + if (sortedAngle && *sortedAngle == angle) { + return; + } + + sortedAngle = angle; + + // The current approach to sorting doesn't sort across segments so don't try. + // Sorting within segments separately seemed not to be worth the complexity. + if (text.segments.size() > 1 || icon.segments.size() > 1) { + return; + } + + sortUploaded = false; + uploaded = false; + + // If the symbols are allowed to overlap sort them by their vertical screen position. + // The index array buffer is rewritten to reference the (unchanged) vertices in the + // sorted order. + + // To avoid sorting the actual symbolInstance array we sort an array of indexes. + std::vector symbolInstanceIndexes; + for (size_t i = 0; i < symbolInstances.size(); i++) { + symbolInstanceIndexes.push_back(i); + } + + const float sin = std::sin(angle); + const float cos = std::cos(angle); + + std::sort(symbolInstanceIndexes.begin(), symbolInstanceIndexes.end(), [sin, cos, this](size_t &aIndex, size_t &bIndex) { + const SymbolInstance& a = symbolInstances[aIndex]; + const SymbolInstance& b = symbolInstances[bIndex]; + const int32_t aRotated = sin * a.anchor.point.x + cos * a.anchor.point.y; + const int32_t bRotated = sin * b.anchor.point.x + cos * b.anchor.point.y; + return aRotated != bRotated ? + aRotated < bRotated : + a.index > b.index; + }); + + text.triangles.clear(); + icon.triangles.clear(); + + for (auto i : symbolInstanceIndexes) { + const SymbolInstance& symbolInstance = symbolInstances[i]; + + if (symbolInstance.placedTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedTextIndex]); + } + if (symbolInstance.placedVerticalTextIndex) { + addPlacedSymbol(text.triangles, text.placedSymbols[*symbolInstance.placedVerticalTextIndex]); + } + if (symbolInstance.placedIconIndex) { + addPlacedSymbol(icon.triangles, icon.placedSymbols[*symbolInstance.placedIconIndex]); + } + } +} + } // namespace mbgl diff --git a/src/mbgl/renderer/buckets/symbol_bucket.hpp b/src/mbgl/renderer/buckets/symbol_bucket.hpp index 32f976bcb2..4abea90508 100644 --- a/src/mbgl/renderer/buckets/symbol_bucket.hpp +++ b/src/mbgl/renderer/buckets/symbol_bucket.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include @@ -18,18 +19,22 @@ namespace mbgl { class PlacedSymbol { public: PlacedSymbol(Point anchorPoint_, uint16_t segment_, float lowerSize_, float upperSize_, - std::array lineOffset_, float placementZoom_, bool useVerticalMode_, GeometryCoordinates line_) : + std::array lineOffset_, WritingModeType writingModes_, GeometryCoordinates line_, std::vector tileDistances_) : anchorPoint(anchorPoint_), segment(segment_), lowerSize(lowerSize_), upperSize(upperSize_), - lineOffset(lineOffset_), placementZoom(placementZoom_), useVerticalMode(useVerticalMode_), line(std::move(line_)) {} + lineOffset(lineOffset_), writingModes(writingModes_), line(std::move(line_)), tileDistances(std::move(tileDistances_)), hidden(false), vertexStartIndex(0) + { + } Point anchorPoint; uint16_t segment; float lowerSize; float upperSize; std::array lineOffset; - float placementZoom; - bool useVerticalMode; + WritingModeType writingModes; GeometryCoordinates line; + std::vector tileDistances; std::vector glyphOffsets; + bool hidden; + size_t vertexStartIndex; }; class SymbolBucket : public Bucket { @@ -40,17 +45,33 @@ public: const style::DataDrivenPropertyValue& iconSize, float zoom, bool sdfIcons, - bool iconsNeedLinear); + bool iconsNeedLinear, + bool sortFeaturesByY, + const std::vector&&); void upload(gl::Context&) override; bool hasData() const override; bool hasTextData() const; bool hasIconData() const; bool hasCollisionBoxData() const; + bool hasCollisionCircleData() const; + + void updateOpacity(); + void sortFeatures(const float angle); const style::SymbolLayoutProperties::PossiblyEvaluated layout; const bool sdfIcons; const bool iconsNeedLinear; + const bool sortFeaturesByY; + + optional sortedAngle; + + bool staticUploaded = false; + bool placementChangesUploaded = false; + bool dynamicUploaded = false; + bool sortUploaded = false; + + std::vector symbolInstances; std::map vertices; gl::VertexVector dynamicVertices; + gl::VertexVector opacityVertices; gl::IndexVector triangles; SegmentVector segments; std::vector placedSymbols; optional> vertexBuffer; optional> dynamicVertexBuffer; + optional> opacityVertexBuffer; optional> indexBuffer; } text; @@ -75,6 +98,7 @@ public: struct IconBuffer { gl::VertexVector vertices; gl::VertexVector dynamicVertices; + gl::VertexVector opacityVertices; gl::IndexVector triangles; SegmentVector segments; std::vector placedSymbols; @@ -82,18 +106,30 @@ public: optional> vertexBuffer; optional> dynamicVertexBuffer; + optional> opacityVertexBuffer; optional> indexBuffer; } icon; - struct CollisionBoxBuffer { - gl::VertexVector vertices; - gl::IndexVector lines; - SegmentVector segments; + struct CollisionBuffer { + gl::VertexVector vertices; + gl::VertexVector dynamicVertices; + SegmentVector segments; - optional> vertexBuffer; - optional> dynamicVertexBuffer; + optional> vertexBuffer; + optional> dynamicVertexBuffer; + }; + + struct CollisionBoxBuffer : public CollisionBuffer { + gl::IndexVector lines; optional> indexBuffer; } collisionBox; + + struct CollisionCircleBuffer : public CollisionBuffer { + gl::IndexVector triangles; + optional> indexBuffer; + } collisionCircle; + + uint32_t bucketInstanceId = 0; }; } // namespace mbgl diff --git a/src/mbgl/renderer/paint_parameters.hpp b/src/mbgl/renderer/paint_parameters.hpp index 60f5af4e9a..8d9a57955f 100644 --- a/src/mbgl/renderer/paint_parameters.hpp +++ b/src/mbgl/renderer/paint_parameters.hpp @@ -74,6 +74,8 @@ public: uint32_t currentLayer; float depthRangeSize; const float depthEpsilon = 1.0f / (1 << 16); + + float symbolFadeChange; }; } // namespace mbgl diff --git a/src/mbgl/renderer/render_layer.hpp b/src/mbgl/renderer/render_layer.hpp index dfc6bcf2fd..aa5777a186 100644 --- a/src/mbgl/renderer/render_layer.hpp +++ b/src/mbgl/renderer/render_layer.hpp @@ -81,14 +81,13 @@ public: friend std::string layoutKey(const RenderLayer&); + // Stores current set of tiles to be rendered for this layer. + std::vector> renderTiles; + protected: // Stores what render passes this layer is currently enabled for. This depends on the // evaluated StyleProperties object and is updated accordingly. RenderPass passes = RenderPass::None; - - //Stores current set of tiles to be rendered for this layer. - std::vector> renderTiles; - }; } // namespace mbgl diff --git a/src/mbgl/renderer/renderer_impl.cpp b/src/mbgl/renderer/renderer_impl.cpp index 5987e69374..7cc92f6d7d 100644 --- a/src/mbgl/renderer/renderer_impl.cpp +++ b/src/mbgl/renderer/renderer_impl.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -56,7 +57,9 @@ Renderer::Impl::Impl(RendererBackend& backend_, , imageImpls(makeMutable>>()) , sourceImpls(makeMutable>>()) , layerImpls(makeMutable>>()) - , renderLight(makeMutable()) { + , renderLight(makeMutable()) + , crossTileSymbolIndex(std::make_unique()) + , placement(std::make_unique(TransformState{}, MapMode::Still)) { glyphManager->setObserver(this); } @@ -348,26 +351,6 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { continue; } - // We're not clipping symbol layers, so when we have both parents and children of symbol - // layers, we drop all children in favor of their parent to avoid duplicate labels. - // See https://github.com/mapbox/mapbox-gl-native/issues/2482 - if (symbolLayer) { - bool skip = false; - // Look back through the buckets we decided to render to find out whether there is - // already a bucket from this layer that is a parent of this tile. Tiles are ordered - // by zoom level when we obtain them from getTiles(). - for (auto it = sortedTilesForInsertion.rbegin(); - it != sortedTilesForInsertion.rend(); ++it) { - if (tile.tile.id.isChildOf(it->get().tile.id)) { - skip = true; - break; - } - } - if (skip) { - continue; - } - } - auto bucket = tile.tile.getBucket(*layer->baseImpl); if (bucket) { sortedTilesForInsertion.emplace_back(tile); @@ -388,6 +371,45 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { frameHistory.record(parameters.timePoint, parameters.state.getZoom(), parameters.mapMode == MapMode::Continuous ? util::DEFAULT_TRANSITION_DURATION : Milliseconds(0)); + bool symbolBucketsChanged = false; + if (parameters.mapMode == MapMode::Still) { + // TODO: Think about right way for symbol index to handle still rendering + crossTileSymbolIndex->reset(); + } + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is()) { + if (crossTileSymbolIndex->addLayer(*it->layer.as())) symbolBucketsChanged = true; + } + } + + bool placementChanged = false; + if (!placement->stillRecent(parameters.timePoint)) { + auto newPlacement = std::make_unique(parameters.state, parameters.mapMode); + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is()) { + newPlacement->placeLayer(*it->layer.as(), parameters.projMatrix, parameters.debugOptions & MapDebugOptions::Collision); + } + } + + placementChanged = newPlacement->commit(*placement, parameters.timePoint); + if (placementChanged || symbolBucketsChanged) { + placement = std::move(newPlacement); + } + + placement->setRecent(parameters.timePoint); + } else { + placement->setStale(); + } + + parameters.symbolFadeChange = placement->symbolFadeChange(parameters.timePoint); + + if (placementChanged || symbolBucketsChanged) { + for (auto it = order.rbegin(); it != order.rend(); ++it) { + if (it->layer.is()) { + placement->updateLayerOpacities(*it->layer.as()); + } + } + } // - UPLOAD PASS ------------------------------------------------------------------------------- // Uploads all required buffers and images before we do any actual rendering. @@ -607,7 +629,7 @@ void Renderer::Impl::render(const UpdateParameters& updateParameters) { observer->onDidFinishRenderingFrame( loaded ? RendererObserver::RenderMode::Full : RendererObserver::RenderMode::Partial, - updateParameters.mode == MapMode::Continuous && (hasTransitions() || frameHistory.needsAnimation(util::DEFAULT_TRANSITION_DURATION)) + updateParameters.mode == MapMode::Continuous && (hasTransitions(parameters.timePoint)) ); if (!loaded) { @@ -647,7 +669,7 @@ std::vector Renderer::Impl::queryRenderedFeatures(const ScreenLineStrin std::unordered_map> resultsByLayer; for (const auto& sourceID : sourceIDs) { if (RenderSource* renderSource = getRenderSource(sourceID)) { - auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options); + auto sourceResults = renderSource->queryRenderedFeatures(geometry, transformState, layers, options, placement->getCollisionIndex()); std::move(sourceResults.begin(), sourceResults.end(), std::inserter(resultsByLayer, resultsByLayer.begin())); } } @@ -727,7 +749,7 @@ RenderSource* Renderer::Impl::getRenderSource(const std::string& id) const { return it != renderSources.end() ? it->second.get() : nullptr; } -bool Renderer::Impl::hasTransitions() const { +bool Renderer::Impl::hasTransitions(TimePoint timePoint) const { if (renderLight.hasTransition()) { return true; } @@ -738,6 +760,10 @@ bool Renderer::Impl::hasTransitions() const { } } + if (placement->hasTransitions(timePoint)) { + return true; + } + return false; } diff --git a/src/mbgl/renderer/renderer_impl.hpp b/src/mbgl/renderer/renderer_impl.hpp index 720e01ed53..e4cc893d07 100644 --- a/src/mbgl/renderer/renderer_impl.hpp +++ b/src/mbgl/renderer/renderer_impl.hpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -31,6 +32,7 @@ class Scheduler; class GlyphManager; class ImageManager; class LineAtlas; +class CrossTileSymbolIndex; class Renderer::Impl : public GlyphManagerObserver, public RenderSourceObserver{ @@ -56,7 +58,7 @@ public: private: bool isLoaded() const; - bool hasTransitions() const; + bool hasTransitions(TimePoint) const; RenderSource* getRenderSource(const std::string& id) const; @@ -108,6 +110,9 @@ private: std::unordered_map> renderLayers; RenderLight renderLight; + std::unique_ptr crossTileSymbolIndex; + std::unique_ptr placement; + bool contextLost = false; }; diff --git a/src/mbgl/renderer/tile_pyramid.cpp b/src/mbgl/renderer/tile_pyramid.cpp index 3e2311089d..f3ee787442 100644 --- a/src/mbgl/renderer/tile_pyramid.cpp +++ b/src/mbgl/renderer/tile_pyramid.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -120,6 +119,7 @@ void TilePyramid::update(const std::vector>& layer // use because they're still loading. In addition to that, we also need to retain all tiles that // we're actively using, e.g. as a replacement for tile that aren't loaded yet. std::set retain; + std::set rendered; auto retainTileFn = [&](Tile& tile, TileNecessity necessity) -> void { if (retain.emplace(tile.id).second) { @@ -150,6 +150,7 @@ void TilePyramid::update(const std::vector>& layer }; auto renderTileFn = [&](const UnwrappedTileID& tileID, Tile& tile) { renderTiles.emplace_back(tileID, tile); + rendered.emplace(tileID); }; renderTiles.clear(); @@ -177,6 +178,13 @@ void TilePyramid::update(const std::vector>& layer auto tilesIt = tiles.begin(); auto retainIt = retain.begin(); while (tilesIt != tiles.end()) { + auto renderedIt = rendered.find(tilesIt->first.toUnwrapped()); + if (renderedIt == rendered.end()) { + // Since this tile isn't in the render set, crossTileIDs won't be kept + // updated by CrossTileSymbolIndex. We need to reset the stored crossTileIDs + // so they're not reused if/when this tile is re-added to the render set + tilesIt->second->resetCrossTileIDs(); + } if (retainIt == retain.end() || tilesIt->first < *retainIt) { if (!needsRelayout) { tilesIt->second->setNecessity(TileNecessity::Optional); @@ -193,13 +201,7 @@ void TilePyramid::update(const std::vector>& layer } for (auto& pair : tiles) { - const PlacementConfig config { parameters.transformState.getAngle(), - parameters.transformState.getPitch(), - parameters.transformState.getCameraToCenterDistance(), - parameters.transformState.getCameraToTileDistance(pair.first.toUnwrapped()), - parameters.debugOptions & MapDebugOptions::Collision }; - - pair.second->setPlacementConfig(config); + pair.second->setShowCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision); } } diff --git a/src/mbgl/text/collision_feature.cpp b/src/mbgl/text/collision_feature.cpp index 3eb08da8d1..6d6f2aabc7 100644 --- a/src/mbgl/text/collision_feature.cpp +++ b/src/mbgl/text/collision_feature.cpp @@ -13,8 +13,9 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, const float padding, const style::SymbolPlacementType placement, IndexedSubfeature indexedFeature_, - const AlignmentType alignment) - : indexedFeature(std::move(indexedFeature_)) { + const float overscaling) + : indexedFeature(std::move(indexedFeature_)) + , alongLine(placement == style::SymbolPlacementType::Line) { if (top == 0 && bottom == 0 && left == 0 && right == 0) return; const float y1 = top * boxScale - padding; @@ -22,7 +23,7 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, const float x1 = left * boxScale - padding; const float x2 = right * boxScale + padding; - if (placement == style::SymbolPlacementType::Line) { + if (alongLine) { float height = y2 - y1; const double length = x2 - x1; @@ -31,29 +32,26 @@ CollisionFeature::CollisionFeature(const GeometryCoordinates& line, height = std::max(10.0f * boxScale, height); GeometryCoordinate anchorPoint = convertPoint(anchor.point); - - if (alignment == AlignmentType::Straight) { - // used for icon labels that are aligned with the line, but don't curve along it - const GeometryCoordinate vector = convertPoint(util::unit(convertPoint(line[anchor.segment + 1] - line[anchor.segment])) * length); - const GeometryCoordinates newLine({ anchorPoint - vector, anchorPoint + vector }); - bboxifyLabel(newLine, anchorPoint, 0, length, height); - } else { - // used for text labels that curve along a line - bboxifyLabel(line, anchorPoint, anchor.segment, length, height); - } + bboxifyLabel(line, anchorPoint, anchor.segment, length, height, overscaling); } else { - boxes.emplace_back(anchor.point, Point{ 0, 0 }, x1, y1, x2, y2, std::numeric_limits::infinity()); + boxes.emplace_back(anchor.point, Point{ 0, 0 }, x1, y1, x2, y2); } } void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint, - const int segment, const float labelLength, const float boxSize) { + const int segment, const float labelLength, const float boxSize, const float overscaling) { const float step = boxSize / 2; const int nBoxes = std::floor(labelLength / step); - // We calculate line collision boxes out to 300% of what would normally be our + // We calculate line collision circles out to 300% of what would normally be our // max size, to allow collision detection to work on labels that expand as // they move into the distance - const int nPitchPaddingBoxes = std::floor(nBoxes / 2); + // Vertically oriented labels in the distant field can extend past this padding + // This is a noticeable problem in overscaled tiles where the pitch 0-based + // symbol spacing will put labels very close together in a pitched map. + // To reduce the cost of adding extra collision circles, we slowly increase + // them for overscaled tiles. + const float overscalingPaddingFactor = 1 + .4 * std::log(overscaling) / std::log(2); + const int nPitchPaddingBoxes = std::floor(nBoxes * overscalingPaddingFactor / 2); // offset the center of the first box by half a box so that the edge of the // box is at the edge of the label. @@ -124,47 +122,18 @@ void CollisionFeature::bboxifyLabel(const GeometryCoordinates& line, GeometryCoo p0.x + segmentBoxDistance / segmentLength * (p1.x - p0.x), p0.y + segmentBoxDistance / segmentLength * (p1.y - p0.y) }; - - // Distance from label anchor point to inner (towards center) edge of this box - // The tricky thing here is that box positioning doesn't change with scale, - // but box size does change with scale. - // Technically, distanceToInnerEdge should be: - // Math.max(Math.abs(boxDistanceToAnchor - firstBoxOffset) - (step / scale), 0); - // But using that formula would make solving for maxScale more difficult, so we - // approximate with scale=2. - // This makes our calculation spot-on at scale=2, and on the conservative side for - // lower scales - const float distanceToInnerEdge = std::max(std::fabs(boxDistanceToAnchor - firstBoxOffset) - step / 2, 0.0f); - float maxScale = util::division(labelLength / 2, distanceToInnerEdge, std::numeric_limits::infinity()); - - // The box maxScale calculations are designed to be conservative on collisions in the scale range - // [1,2]. At scale=1, each box has 50% overlap, and at scale=2, the boxes are lined up edge - // to edge (beyond scale 2, gaps start to appear, which could potentially allow missed collisions). - // We add "pitch padding" boxes to the left and right to handle effective underzooming - // (scale < 1) when labels are in the distance. The overlap approximation could cause us to use - // these boxes when the scale is greater than 1, but we prevent that because we know - // they're only necessary for scales less than one. - // This preserves the pre-pitch-padding behavior for unpitched maps. - if (i < 0 || i >= nBoxes) { - maxScale = std::min(maxScale, 0.99f); - } - - boxes.emplace_back(boxAnchor, boxAnchor - convertPoint(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, maxScale); + + // If the box is within boxSize of the anchor, force the box to be used + // (so even 0-width labels use at least one box) + // Otherwise, the .8 multiplication gives us a little bit of conservative + // padding in choosing which boxes to use (see CollisionIndex#placedCollisionCircles) + const float paddedAnchorDistance = std::abs(boxDistanceToAnchor - firstBoxOffset) < step ? + 0 : + (boxDistanceToAnchor - firstBoxOffset) * 0.8; + + boxes.emplace_back(boxAnchor, boxAnchor - convertPoint(anchorPoint), -boxSize / 2, -boxSize / 2, boxSize / 2, boxSize / 2, paddedAnchorDistance, boxSize / 2); } } -float CollisionBox::adjustedMaxScale(const std::array& rotationMatrix, const float yStretch) const { - // When the map is pitched the distance covered by a line changes. - // Adjust the max scale by (approximatePitchedLength / approximateRegularLength) - // to compensate for this. - const Point rotatedOffset = util::matrixMultiply(rotationMatrix, offset); - const float xSqr = rotatedOffset.x * rotatedOffset.x; - const float ySqr = rotatedOffset.y * rotatedOffset.y; - const float yStretchSqr = ySqr * yStretch * yStretch; - const float adjustmentFactor = xSqr + ySqr != 0 ? - std::sqrt((xSqr + yStretchSqr) / (xSqr + ySqr)) : - 1.0f; - return maxScale * adjustmentFactor; -} } // namespace mbgl diff --git a/src/mbgl/text/collision_feature.hpp b/src/mbgl/text/collision_feature.hpp index 3b6e461a26..4ecd25c717 100644 --- a/src/mbgl/text/collision_feature.hpp +++ b/src/mbgl/text/collision_feature.hpp @@ -11,10 +11,8 @@ namespace mbgl { class CollisionBox { public: - CollisionBox(Point _anchor, Point _offset, float _x1, float _y1, float _x2, float _y2, float _maxScale) : - anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), maxScale(_maxScale) {} - - float adjustedMaxScale(const std::array& rotationMatrix, const float yStretch) const; + CollisionBox(Point _anchor, Point _offset, float _x1, float _y1, float _x2, float _y2, float _signedDistanceFromAnchor = 0, float _radius = 0) : + anchor(std::move(_anchor)), offset(_offset), x1(_x1), y1(_y1), x2(_x2), y2(_y2), used(true), signedDistanceFromAnchor(_signedDistanceFromAnchor), radius(_radius) {} // the box is centered around the anchor point Point anchor; @@ -28,20 +26,23 @@ public: float x2; float y2; - // the box is only valid for scales < maxScale. - // The box does not block other boxes at scales >= maxScale; - float maxScale; + // Projected box geometry: generated/updated at placement time + float px1; + float py1; + float px2; + float py2; + + // Projected circle geometry: generated/updated at placement time + float px; + float py; + bool used; - // the scale at which the label can first be shown - float placementScale = 0.0f; + float signedDistanceFromAnchor; + float radius; }; class CollisionFeature { public: - enum class AlignmentType : bool { - Straight = false, - Curved - }; // for text CollisionFeature(const GeometryCoordinates& line, @@ -50,8 +51,9 @@ public: const float boxScale, const float padding, const style::SymbolPlacementType placement, - const IndexedSubfeature& indexedFeature_) - : CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, AlignmentType::Curved) {} + const IndexedSubfeature& indexedFeature_, + const float overscaling) + : CollisionFeature(line, anchor, shapedText.top, shapedText.bottom, shapedText.left, shapedText.right, boxScale, padding, placement, indexedFeature_, overscaling) {} // for icons CollisionFeature(const GeometryCoordinates& line, @@ -66,7 +68,7 @@ public: (shapedIcon ? shapedIcon->bottom() : 0), (shapedIcon ? shapedIcon->left() : 0), (shapedIcon ? shapedIcon->right() : 0), - boxScale, padding, placement, indexedFeature_, AlignmentType::Straight) {} + boxScale, padding, placement, indexedFeature_, 1) {} CollisionFeature(const GeometryCoordinates& line, const Anchor&, @@ -78,14 +80,15 @@ public: const float padding, const style::SymbolPlacementType, IndexedSubfeature, - const AlignmentType); + const float overscaling); std::vector boxes; IndexedSubfeature indexedFeature; + bool alongLine; private: void bboxifyLabel(const GeometryCoordinates& line, GeometryCoordinate& anchorPoint, - const int segment, const float length, const float height); + const int segment, const float length, const float height, const float overscaling); }; } // namespace mbgl diff --git a/src/mbgl/text/placement.cpp b/src/mbgl/text/placement.cpp index ac8c15aae9..b9f270d52e 100644 --- a/src/mbgl/text/placement.cpp +++ b/src/mbgl/text/placement.cpp @@ -62,13 +62,13 @@ void Placement::placeLayer(RenderSymbolLayer& symbolLayer, const mat4& projMatri state.matrixFor(posMatrix, renderTile.id); matrix::multiply(posMatrix, projMatrix, posMatrix); - mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(renderTile.matrix, + mat4 textLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, layout.get() == style::AlignmentType::Map, layout.get() == style::AlignmentType::Map, state, pixelsToTileUnits); - mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(renderTile.matrix, + mat4 iconLabelPlaneMatrix = getLabelPlaneMatrix(posMatrix, layout.get() == style::AlignmentType::Map, layout.get() == style::AlignmentType::Map, state, diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 8c018ce3aa..576c23b682 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -33,7 +32,7 @@ using namespace style; GeometryTile's 'correlationID' is used for ensuring the tile will be flagged as non-pending only when the placement coming from the last operation (as in - 'setData', 'setLayers', 'setPlacementConfig') occurs. This is important for + 'setData', 'setLayers', 'setShowCollisionBoxes') occurs. This is important for still mode rendering as we want to render only when all layout and placement operations are completed. @@ -52,13 +51,15 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, worker(parameters.workerScheduler, ActorRef(*this, mailbox), id_, + sourceID, obsolete, parameters.mode, - parameters.pixelRatio), + parameters.pixelRatio, + parameters.debugOptions & MapDebugOptions::Collision), glyphManager(parameters.glyphManager), imageManager(parameters.imageManager), - lastYStretch(1.0f), - mode(parameters.mode) { + mode(parameters.mode), + showCollisionBoxes(parameters.debugOptions & MapDebugOptions::Collision) { } GeometryTile::~GeometryTile() { @@ -89,25 +90,6 @@ void GeometryTile::setData(std::unique_ptr data_) { worker.invoke(&GeometryTileWorker::setData, std::move(data_), correlationID); } -void GeometryTile::setPlacementConfig(const PlacementConfig& desiredConfig) { - if (requestedConfig == desiredConfig) { - return; - } - - // Mark the tile as pending again if it was complete before to prevent signaling a complete - // state despite pending parse operations. - pending = true; - - ++correlationID; - requestedConfig = desiredConfig; - invokePlacement(); -} - -void GeometryTile::invokePlacement() { - if (requestedConfig) { - worker.invoke(&GeometryTileWorker::setPlacementConfig, *requestedConfig, correlationID); - } -} void GeometryTile::setLayers(const std::vector>& layers) { // Mark the tile as pending again if it was complete before to prevent signaling a complete @@ -134,14 +116,22 @@ void GeometryTile::setLayers(const std::vector>& layers) worker.invoke(&GeometryTileWorker::setLayers, std::move(impls), correlationID); } +void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) { + if (showCollisionBoxes != showCollisionBoxes_) { + showCollisionBoxes = showCollisionBoxes_; + ++correlationID; + worker.invoke(&GeometryTileWorker::setShowCollisionBoxes, showCollisionBoxes, correlationID); + } +} + void GeometryTile::onLayout(LayoutResult result, const uint64_t resultCorrelationID) { - loaded = true; - renderable = true; + // Don't mark ourselves loaded or renderable until the first successful placement + // TODO: Ideally we'd render this tile without symbols as long as this tile wasn't + // replacing a tile at a different zoom that _did_ have symbols. (void)resultCorrelationID; nonSymbolBuckets = std::move(result.nonSymbolBuckets); featureIndex = std::move(result.featureIndex); data = std::move(result.tileData); - collisionTile.reset(); observer->onTileChanged(*this); } @@ -152,16 +142,13 @@ void GeometryTile::onPlacement(PlacementResult result, const uint64_t resultCorr pending = false; } symbolBuckets = std::move(result.symbolBuckets); - collisionTile = std::move(result.collisionTile); if (result.glyphAtlasImage) { glyphAtlasImage = std::move(*result.glyphAtlasImage); } if (result.iconAtlasImage) { iconAtlasImage = std::move(*result.iconAtlasImage); } - if (collisionTile.get()) { - lastYStretch = collisionTile->yStretch; - } + observer->onTileChanged(*this); } @@ -231,7 +218,8 @@ void GeometryTile::queryRenderedFeatures( const GeometryCoordinates& queryGeometry, const TransformState& transformState, const std::vector& layers, - const RenderedQueryOptions& options) { + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) { if (!featureIndex || !data) return; @@ -251,9 +239,10 @@ void GeometryTile::queryRenderedFeatures( std::pow(2, transformState.getZoom() - id.overscaledZ), options, *data, - id.canonical, + id.toUnwrapped(), + sourceID, layers, - collisionTile.get(), + collisionIndex, additionalRadius); } @@ -293,11 +282,16 @@ void GeometryTile::querySourceFeatures( } } -float GeometryTile::yStretch() const { - // collisionTile gets reset in onLayout but we don't clear the symbolBuckets - // until a new placement result comes along, so keep the yStretch value in - // case we need to render them. - return lastYStretch; +void GeometryTile::resetCrossTileIDs() { + for (auto& bucket : symbolBuckets) { + auto symbolBucket = dynamic_cast(bucket.second.get()); + if (symbolBucket && symbolBucket->bucketInstanceId) { + symbolBucket->bucketInstanceId = 0; + for (auto& symbolInstance : symbolBucket->symbolInstances) { + symbolInstance.crossTileID = 0; + } + } + } } } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index a478aad504..3f4b36984b 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -4,12 +4,11 @@ #include #include #include -#include -#include #include #include #include #include +#include #include #include @@ -36,9 +35,9 @@ public: void setError(std::exception_ptr); void setData(std::unique_ptr); - void setPlacementConfig(const PlacementConfig&) override; void setLayers(const std::vector>&) override; - + void setShowCollisionBoxes(const bool showCollisionBoxes) override; + void onGlyphsAvailable(GlyphMap) override; void onImagesAvailable(ImageMap, uint64_t imageCorrelationID) override; @@ -56,7 +55,8 @@ public: const GeometryCoordinates& queryGeometry, const TransformState&, const std::vector& layers, - const RenderedQueryOptions& options) override; + const RenderedQueryOptions& options, + const CollisionIndex& collisionIndex) override; void querySourceFeatures( std::vector& result, @@ -82,16 +82,13 @@ public: class PlacementResult { public: std::unordered_map> symbolBuckets; - std::unique_ptr collisionTile; optional glyphAtlasImage; optional iconAtlasImage; PlacementResult(std::unordered_map> symbolBuckets_, - std::unique_ptr collisionTile_, optional glyphAtlasImage_, optional iconAtlasImage_) : symbolBuckets(std::move(symbolBuckets_)), - collisionTile(std::move(collisionTile_)), glyphAtlasImage(std::move(glyphAtlasImage_)), iconAtlasImage(std::move(iconAtlasImage_)) {} }; @@ -99,7 +96,7 @@ public: void onError(std::exception_ptr, uint64_t correlationID); - float yStretch() const override; + void resetCrossTileIDs() override; protected: const GeometryTileData* getData() { @@ -108,7 +105,6 @@ protected: private: void markObsolete(); - void invokePlacement(); const std::string sourceID; @@ -122,7 +118,6 @@ private: ImageManager& imageManager; uint64_t correlationID = 0; - optional requestedConfig; std::unordered_map> nonSymbolBuckets; std::unique_ptr featureIndex; @@ -132,10 +127,11 @@ private: optional iconAtlasImage; std::unordered_map> symbolBuckets; - std::unique_ptr collisionTile; + std::unordered_map> symbolLayouts; - float lastYStretch; const MapMode mode; + + bool showCollisionBoxes; public: optional glyphAtlasTexture; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 50429420c3..969b137c1f 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -1,7 +1,6 @@ #include #include #include -#include #include #include #include @@ -24,15 +23,19 @@ using namespace style; GeometryTileWorker::GeometryTileWorker(ActorRef self_, ActorRef parent_, OverscaledTileID id_, + const std::string& sourceID_, const std::atomic& obsolete_, const MapMode mode_, - const float pixelRatio_) + const float pixelRatio_, + const bool showCollisionBoxes_) : self(std::move(self_)), parent(std::move(parent_)), id(std::move(id_)), + sourceID(sourceID_), obsolete(obsolete_), mode(mode_), - pixelRatio(pixelRatio_) { + pixelRatio(pixelRatio_), + showCollisionBoxes(showCollisionBoxes_) { } GeometryTileWorker::~GeometryTileWorker() = default; @@ -116,9 +119,9 @@ void GeometryTileWorker::setLayers(std::vector> layers_, } } -void GeometryTileWorker::setPlacementConfig(PlacementConfig placementConfig_, uint64_t correlationID_) { +void GeometryTileWorker::setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_) { try { - placementConfig = std::move(placementConfig_); + showCollisionBoxes = showCollisionBoxes_; correlationID = correlationID_; switch (state) { @@ -372,7 +375,7 @@ bool GeometryTileWorker::hasPendingSymbolDependencies() const { } void GeometryTileWorker::attemptPlacement() { - if (!data || !layers || !placementConfig || hasPendingSymbolDependencies()) { + if (!data || !layers || hasPendingSymbolDependencies()) { return; } @@ -392,13 +395,13 @@ void GeometryTileWorker::attemptPlacement() { } symbolLayout->prepare(glyphMap, glyphAtlas.positions, - imageMap, imageAtlas.positions); + imageMap, imageAtlas.positions, + id, sourceID); } symbolLayoutsNeedPreparation = false; } - auto collisionTile = std::make_unique(*placementConfig); std::unordered_map> buckets; for (auto& symbolLayout : symbolLayouts) { @@ -410,7 +413,7 @@ void GeometryTileWorker::attemptPlacement() { continue; } - std::shared_ptr bucket = symbolLayout->place(*collisionTile); + std::shared_ptr bucket = symbolLayout->place(showCollisionBoxes); for (const auto& pair : symbolLayout->layerPaintProperties) { buckets.emplace(pair.first, bucket); } @@ -418,7 +421,6 @@ void GeometryTileWorker::attemptPlacement() { parent.invoke(&GeometryTile::onPlacement, GeometryTile::PlacementResult { std::move(buckets), - std::move(collisionTile), std::move(glyphAtlasImage), std::move(iconAtlasImage), }, correlationID); diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 1425daa7a1..cc86248cec 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -28,14 +27,16 @@ public: GeometryTileWorker(ActorRef self, ActorRef parent, OverscaledTileID, + const std::string&, const std::atomic&, const MapMode, - const float pixelRatio); + const float pixelRatio, + const bool showCollisionBoxes_); ~GeometryTileWorker(); void setLayers(std::vector>, uint64_t correlationID); void setData(std::unique_ptr, uint64_t correlationID); - void setPlacementConfig(PlacementConfig, uint64_t correlationID); + void setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_); void onGlyphsAvailable(GlyphMap glyphs); void onImagesAvailable(ImageMap images, uint64_t imageCorrelationID); @@ -57,6 +58,7 @@ private: ActorRef parent; const OverscaledTileID id; + const std::string sourceID; const std::atomic& obsolete; const MapMode mode; const float pixelRatio; @@ -75,7 +77,6 @@ private: // Outer optional indicates whether we've received it or not. optional>> layers; optional> data; - optional placementConfig; bool symbolLayoutsNeedPreparation = false; std::vector> symbolLayouts; @@ -83,6 +84,8 @@ private: ImageDependencies pendingImageDependencies; GlyphMap glyphMap; ImageMap imageMap; + + bool showCollisionBoxes; }; } // namespace mbgl diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 8be7c4d862..65a497aaae 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -23,7 +23,6 @@ namespace mbgl { class DebugBucket; class TransformState; class TileObserver; -class PlacementConfig; class RenderLayer; class RenderedQueryOptions; class SourceQueryOptions; @@ -47,7 +46,7 @@ public: virtual void upload(gl::Context&) = 0; virtual Bucket* getBucket(const style::Layer::Impl&) const = 0; - virtual void setPlacementConfig(const PlacementConfig&) {} + virtual void setShowCollisionBoxes(const bool) {} virtual void setLayers(const std::vector>&) {} virtual void setMask(TileMask&&) {} @@ -92,6 +91,8 @@ public: bool isComplete() const { return loaded && !pending; } + + virtual void resetCrossTileIDs() {}; void dumpDebugLogs() const; @@ -101,8 +102,6 @@ public: // Contains the tile ID string for painting debug information. std::unique_ptr debugBucket; - - virtual float yStretch() const { return 1.0f; } protected: bool triedOptional = false; -- cgit v1.2.1