diff options
author | Chris Loer <chris.loer@gmail.com> | 2017-11-09 13:24:43 -0800 |
---|---|---|
committer | Chris Loer <chris.loer@mapbox.com> | 2017-11-17 10:05:15 -0800 |
commit | c0cb210ddca1901a956cd68e9142b7fb04183248 (patch) | |
tree | 7a6d5dc3d79720b930d5669d16d5912239fcc344 /src/mbgl/layout/symbol_layout.cpp | |
parent | 9a5b2fdfc362e7041a10d5066161b51aedbb0a31 (diff) | |
download | qtlocation-mapboxgl-c0cb210ddca1901a956cd68e9142b7fb04183248.tar.gz |
[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.
Diffstat (limited to 'src/mbgl/layout/symbol_layout.cpp')
-rw-r--r-- | src/mbgl/layout/symbol_layout.cpp | 277 |
1 files changed, 136 insertions, 141 deletions
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 <mbgl/renderer/image_atlas.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/text/get_anchors.hpp> -#include <mbgl/text/collision_tile.hpp> #include <mbgl/text/shaping.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/utf.hpp> @@ -43,8 +42,8 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, std::unique_ptr<GeometryTileLayer> 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<TextRotationAlignment>() == AlignmentType::Map && layout.get<SymbolPlacement>() == 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<Shaping, Shaping>& shapedTextOrientations, optional<PositionedIcon> 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<TextRotationAlignment>() != AlignmentType::Map ? SymbolPlacementType::Point : layout.get<SymbolPlacement>(); - const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); + 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<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) { - auto bucket = std::make_unique<SymbolBucket>(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<TextRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); - const SymbolPlacementType iconPlacement = layout.get<IconRotationAlignment>() != AlignmentType::Map - ? SymbolPlacementType::Point - : layout.get<SymbolPlacement>(); +// 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<float> CalculateTileDistances(const GeometryCoordinates& line, const Anchor& anchor) { + std::vector<float> tileDistances(line.size()); + if (anchor.segment != -1) { + auto sumForwardLength = util::dist<float>(anchor.point, line[anchor.segment + 1]); + auto sumBackwardLength = util::dist<float>(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<float>(line[i + 1], line[i]); + } + } + for (auto i = anchor.segment; i >= 0; i--) { + tileDistances[i] = sumBackwardLength; + if (i > 0) { + sumBackwardLength += util::dist<float>(line[i - 1], line[i]); + } + } + } + return tileDistances; +} +std::unique_ptr<SymbolBucket> SymbolLayout::place(const bool showCollisionBoxes) { const bool mayOverlap = layout.get<TextAllowOverlap>() || layout.get<IconAllowOverlap>() || layout.get<TextIgnorePlacement>() || layout.get<IconIgnorePlacement>(); + + auto bucket = std::make_unique<SymbolBucket>(layout, layerPaintProperties, textSize, iconSize, zoom, sdfIcons, iconsNeedLinear, mayOverlap, std::move(symbolInstances)); - const bool keepUpright = layout.get<TextKeepUpright>(); - - // 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<TextOptional>() || !hasText; - const bool textWithoutIcon = layout.get<IconOptional>() || !hasIcon; - - // Calculate the scales at which the text and icon can be placed without collision. - - float glyphScale = hasText ? - collisionTile.placeFeature(symbolInstance.textCollisionFeature, - layout.get<TextAllowOverlap>(), layout.get<SymbolAvoidEdges>()) : - collisionTile.minScale; - float iconScale = hasIcon ? - collisionTile.placeFeature(symbolInstance.iconCollisionFeature, - layout.get<IconAllowOverlap>(), layout.get<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); - } - 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<TextIgnorePlacement>()); - 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<float> sizeData = bucket->textSizeBinder->getVertexSizeData(feature); + const Range<float> 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<IconIgnorePlacement>()); - if (iconScale < collisionTile.maxScale && symbolInstance.iconQuad) { + if (symbolInstance.iconQuad) { const Range<float> 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<float>()); + 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<SymbolBucket> SymbolLayout::place(CollisionTile& collisionTile) } } - if (collisionTile.config.debug) { - addToDebugBuffers(collisionTile, *bucket); + if (showCollisionBoxes) { + addToDebugBuffers(*bucket); } return bucket; } template <typename Buffer> -void SymbolLayout::addSymbol(Buffer& buffer, +size_t SymbolLayout::addSymbol(Buffer& buffer, const Range<float> 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<uint16_t>::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<SymbolBucket::CollisionBuffer&>(bucket.collisionCircle) : + static_cast<SymbolBucket::CollisionBuffer&>(bucket.collisionBox); 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(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<float> tl{box.x1, box.y1}; + Point<float> tr{box.x2, box.y1}; + Point<float> bl{box.x1, box.y2}; + Point<float> 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<uint16_t>::max()) { - collisionBox.segments.emplace_back(collisionBox.vertices.vertexSize(), collisionBox.lines.indexSize()); + if (collisionBuffer.segments.empty() || collisionBuffer.segments.back().vertexLength + vertexLength > std::numeric_limits<uint16_t>::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; |