diff options
Diffstat (limited to 'src')
26 files changed, 804 insertions, 629 deletions
diff --git a/src/mbgl/layout/symbol_layout.cpp b/src/mbgl/layout/symbol_layout.cpp index ad58f06311..978caabd70 100644 --- a/src/mbgl/layout/symbol_layout.cpp +++ b/src/mbgl/layout/symbol_layout.cpp @@ -7,10 +7,9 @@ #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/symbol_layer_impl.hpp> #include <mbgl/sprite/sprite_atlas.hpp> -#include <mbgl/text/glyph_atlas.hpp> #include <mbgl/text/get_anchors.hpp> -#include <mbgl/text/glyph_atlas.hpp> #include <mbgl/text/collision_tile.hpp> +#include <mbgl/text/shaping.hpp> #include <mbgl/util/constants.hpp> #include <mbgl/util/utf.hpp> #include <mbgl/util/token.hpp> @@ -41,13 +40,15 @@ static bool has(const style::SymbolLayoutProperties::PossiblyEvaluated& layout) SymbolLayout::SymbolLayout(const BucketParameters& parameters, const std::vector<const Layer*>& layers, const GeometryTileLayer& sourceLayer, - SpriteAtlas& spriteAtlas_) + IconDependencies& iconDependencies, + uintptr_t _spriteAtlasMapIndex, + GlyphDependencies& glyphDependencies) : sourceLayerName(sourceLayer.getName()), bucketName(layers.at(0)->getID()), overscaling(parameters.tileID.overscaleFactor()), zoom(parameters.tileID.overscaledZ), mode(parameters.mode), - spriteAtlas(spriteAtlas_), + spriteAtlasMapIndex(_spriteAtlasMapIndex), tileSize(util::tileSize * overscaling), tilePixelRatio(float(util::EXTENT) / tileSize) { @@ -95,7 +96,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, )); } - // Determine and load glyph ranges + // Determine glyph dependencies const size_t featureCount = sourceLayer.featureCount(); for (size_t i = 0; i < featureCount; ++i) { auto feature = sourceLayer.getFeature(i); @@ -141,9 +142,9 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, // Loop through all characters of this text and collect unique codepoints. for (char16_t chr : *ft.text) { - ranges.insert(getGlyphRange(chr)); + glyphDependencies[layout.get<TextFont>()].insert(chr); if (char16_t verticalChr = util::i18n::verticalizePunctuation(chr)) { - ranges.insert(getGlyphRange(verticalChr)); + glyphDependencies[layout.get<TextFont>()].insert(verticalChr); } } } @@ -154,6 +155,7 @@ SymbolLayout::SymbolLayout(const BucketParameters& parameters, icon = util::replaceTokens(icon, getValue); } ft.icon = icon; + iconDependencies.insert(*ft.icon); } if (ft.text || ft.icon) { @@ -170,20 +172,7 @@ bool SymbolLayout::hasSymbolInstances() const { return !symbolInstances.empty(); } -bool SymbolLayout::canPrepare(GlyphAtlas& glyphAtlas) { - if (has<TextField>(layout) && !layout.get<TextFont>().empty() && !glyphAtlas.hasGlyphRanges(layout.get<TextFont>(), ranges)) { - return false; - } - - if (has<IconImage>(layout) && !spriteAtlas.isLoaded()) { - return false; - } - - return true; -} - -void SymbolLayout::prepare(uintptr_t tileUID, - GlyphAtlas& glyphAtlas) { +void SymbolLayout::prepare(const GlyphPositionMap& glyphs, const IconAtlasMap& iconMap) { float horizontalAlign = 0.5; float verticalAlign = 0.5; @@ -225,7 +214,6 @@ void SymbolLayout::prepare(uintptr_t tileUID, layout.get<TextJustify>() == TextJustifyType::Left ? 0 : 0.5; - auto glyphSet = glyphAtlas.getGlyphSet(layout.get<TextFont>()); const bool textAlongLine = layout.get<TextRotationAlignment>() == AlignmentType::Map && layout.get<SymbolPlacement>() == SymbolPlacementType::Line; @@ -240,59 +228,61 @@ void SymbolLayout::prepare(uintptr_t tileUID, // if feature has text, shape the text if (feature.text) { - auto getShaping = [&] (const std::u16string& text, WritingModeType writingMode) { - const float oneEm = 24.0f; - const Shaping result = glyphSet->getShaping( - /* string */ text, - /* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ? - layout.get<TextMaxWidth>() * oneEm : 0, - /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm, - /* horizontalAlign */ horizontalAlign, - /* verticalAlign */ verticalAlign, - /* justify */ justify, - /* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm, - /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm), - /* verticalHeight */ oneEm, - /* writingMode */ writingMode, - /* bidirectional algorithm object */ bidi); - - // Add the glyphs we need for this label to the glyph atlas. - if (result) { - glyphAtlas.addGlyphs(tileUID, text, layout.get<TextFont>(), glyphSet, face); + auto glyphPositions = glyphs.find(layout.get<TextFont>()); + if (glyphPositions != glyphs.end()) { // If there are no glyphs available for this feature, skip shaping + auto applyShaping = [&] (const std::u16string& text, WritingModeType writingMode) { + const float oneEm = 24.0f; + const Shaping result = getShaping( + /* string */ text, + /* maxWidth: ems */ layout.get<SymbolPlacement>() != SymbolPlacementType::Line ? + layout.get<TextMaxWidth>() * oneEm : 0, + /* lineHeight: ems */ layout.get<TextLineHeight>() * oneEm, + /* horizontalAlign */ horizontalAlign, + /* verticalAlign */ verticalAlign, + /* justify */ justify, + /* spacing: ems */ layout.get<TextLetterSpacing>() * oneEm, + /* translate */ Point<float>(layout.evaluate<TextOffset>(zoom, feature)[0] * oneEm, layout.evaluate<TextOffset>(zoom, feature)[1] * oneEm), + /* verticalHeight */ oneEm, + /* writingMode */ writingMode, + /* bidirectional algorithm object */ bidi, + /* glyphs */ glyphPositions->second); + + return result; + }; + + shapedTextOrientations.first = applyShaping(*feature.text, WritingModeType::Horizontal); + + if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) { + shapedTextOrientations.second = applyShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical); } - - return result; - }; - - shapedTextOrientations.first = getShaping(*feature.text, WritingModeType::Horizontal); - - if (util::i18n::allowsVerticalWritingMode(*feature.text) && textAlongLine) { - shapedTextOrientations.second = getShaping(util::i18n::verticalizePunctuation(*feature.text), WritingModeType::Vertical); } } // if feature has icon, get sprite atlas position if (feature.icon) { - auto image = spriteAtlas.getIcon(*feature.icon); - if (image) { - shapedIcon = shapeIcon(*image, - layout.evaluate<IconOffset>(zoom, feature), - layout.evaluate<IconRotate>(zoom, feature) * util::DEG2RAD); - assert((*image).spriteImage); - if ((*image).spriteImage->sdf) { - sdfIcons = true; - } - if ((*image).relativePixelRatio != 1.0f) { - iconsNeedLinear = true; - } else if (layout.get<IconRotate>().constantOr(1) != 0) { - iconsNeedLinear = true; + auto icons = iconMap.find(spriteAtlasMapIndex); + if (icons != iconMap.end()) { + auto image = icons->second.find(*feature.icon); + if (image != icons->second.end()) { + shapedIcon = shapeIcon(image->second, + layout.evaluate<IconOffset>(zoom, feature), + layout.evaluate<IconRotate>(zoom, feature) * util::DEG2RAD); + if (image->second.sdf) { + sdfIcons = true; + } + if (image->second.relativePixelRatio != 1.0f) { + iconsNeedLinear = true; + } else if (layout.get<IconRotate>().constantOr(1) != 0) { + iconsNeedLinear = true; + } } } } // if either shapedText or icon position is present, add the feature if (shapedTextOrientations.first || shapedIcon) { - addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, face); + auto glyphPositionsIt = glyphs.find(layout.get<TextFont>()); + addFeature(std::distance(features.begin(), it), feature, shapedTextOrientations, shapedIcon, glyphPositionsIt == glyphs.end() ? GlyphPositions() : glyphPositionsIt->second); } feature.geometry.clear(); @@ -305,7 +295,7 @@ void SymbolLayout::addFeature(const std::size_t index, const SymbolFeature& feature, const std::pair<Shaping, Shaping>& shapedTextOrientations, const PositionedIcon& shapedIcon, - const GlyphPositions& face) { + const GlyphPositions& glyphs) { const float minScale = 0.5f; const float glyphSize = 24.0f; @@ -351,7 +341,7 @@ void SymbolLayout::addFeature(const std::size_t index, symbolInstances.emplace_back(anchor, line, shapedTextOrientations, shapedIcon, layout.evaluate(zoom, feature), addToBuffers, symbolInstances.size(), textBoxScale, textPadding, textPlacement, iconBoxScale, iconPadding, iconPlacement, - face, indexedFeature, index); + glyphs, indexedFeature, index); }; const auto& type = feature.getType(); diff --git a/src/mbgl/layout/symbol_layout.hpp b/src/mbgl/layout/symbol_layout.hpp index bd72d3cd3f..d79ccb3273 100644 --- a/src/mbgl/layout/symbol_layout.hpp +++ b/src/mbgl/layout/symbol_layout.hpp @@ -16,8 +16,6 @@ namespace mbgl { class GeometryTileLayer; class CollisionTile; -class SpriteAtlas; -class GlyphAtlas; class SymbolBucket; class Anchor; @@ -32,12 +30,11 @@ public: SymbolLayout(const style::BucketParameters&, const std::vector<const style::Layer*>&, const GeometryTileLayer&, - SpriteAtlas&); + IconDependencies&, + uintptr_t, + GlyphDependencies&); - bool canPrepare(GlyphAtlas&); - - void prepare(uintptr_t tileUID, - GlyphAtlas&); + void prepare(const GlyphPositionMap& glyphs, const IconAtlasMap& icons); std::unique_ptr<SymbolBucket> place(CollisionTile&); @@ -45,7 +42,6 @@ public: enum State { Pending, // Waiting for the necessary glyphs or icons to be available. - Prepared, // The potential positions of text and icons have been determined. Placed // The final positions have been determined, taking into account prior layers. }; @@ -80,8 +76,8 @@ private: style::SymbolLayoutProperties::PossiblyEvaluated layout; float textMaxSize; - - SpriteAtlas& spriteAtlas; + + uintptr_t spriteAtlasMapIndex; // Actually a pointer to the SpriteAtlas for this symbol's layer, but don't use it from worker threads! const uint32_t tileSize; const float tilePixelRatio; @@ -89,7 +85,6 @@ private: bool sdfIcons = false; bool iconsNeedLinear = false; - GlyphRangeSet ranges; std::vector<SymbolInstance> symbolInstances; std::vector<SymbolFeature> features; diff --git a/src/mbgl/sprite/sprite_atlas.cpp b/src/mbgl/sprite/sprite_atlas.cpp index 2bf8a3095a..bdccf289df 100644 --- a/src/mbgl/sprite/sprite_atlas.cpp +++ b/src/mbgl/sprite/sprite_atlas.cpp @@ -28,11 +28,13 @@ struct SpriteAtlas::Loader { }; SpriteAtlasElement::SpriteAtlasElement(Rect<uint16_t> rect_, - std::shared_ptr<const SpriteImage> image_, + std::shared_ptr<const SpriteImage> spriteImage, Size size_, float pixelRatio) : pos(std::move(rect_)), - spriteImage(std::move(image_)), - relativePixelRatio(spriteImage->pixelRatio / pixelRatio) { + sdf(spriteImage->sdf), + relativePixelRatio(spriteImage->pixelRatio / pixelRatio), + width(spriteImage->getWidth()), + height(spriteImage->getHeight()) { const float padding = 1; @@ -105,6 +107,10 @@ void SpriteAtlas::emitSpriteLoadedIfComplete() { loaded = true; setSprites(result.get<Sprites>()); observer->onSpriteLoaded(); + for (auto requestor : requestors) { + requestor->onIconsAvailable(this, buildIconMap()); + } + requestors.clear(); } else { observer->onSpriteError(result.get<std::exception_ptr>()); } @@ -119,20 +125,17 @@ void SpriteAtlas::dumpDebugLogs() const { } void SpriteAtlas::setSprites(const Sprites& newSprites) { - std::lock_guard<std::mutex> lock(mutex); for (const auto& pair : newSprites) { _setSprite(pair.first, pair.second); } } void SpriteAtlas::setSprite(const std::string& name, std::shared_ptr<const SpriteImage> sprite) { - std::lock_guard<std::mutex> lock(mutex); _setSprite(name, sprite); } void SpriteAtlas::removeSprite(const std::string& name) { - std::lock_guard<std::mutex> lock(mutex); - + icons.clear(); auto it = entries.find(name); if (it == entries.end()) { return; @@ -153,6 +156,7 @@ void SpriteAtlas::removeSprite(const std::string& name) { void SpriteAtlas::_setSprite(const std::string& name, const std::shared_ptr<const SpriteImage>& sprite) { + icons.clear(); if (!sprite->image.valid()) { Log::Warning(Event::Sprite, "invalid sprite image '%s'", name.c_str()); return; @@ -184,7 +188,6 @@ void SpriteAtlas::_setSprite(const std::string& name, } std::shared_ptr<const SpriteImage> SpriteAtlas::getSprite(const std::string& name) { - std::lock_guard<std::mutex> lock(mutex); const auto it = entries.find(name); if (it != entries.end()) { return it->second.spriteImage; @@ -196,6 +199,18 @@ std::shared_ptr<const SpriteImage> SpriteAtlas::getSprite(const std::string& nam } } +void SpriteAtlas::getIcons(IconRequestor& requestor) { + if (isLoaded()) { + requestor.onIconsAvailable(this, buildIconMap()); + } else { + requestors.insert(&requestor); + } +} + +void SpriteAtlas::removeRequestor(IconRequestor& requestor) { + requestors.erase(&requestor); +} + optional<SpriteAtlasElement> SpriteAtlas::getIcon(const std::string& name) { return getImage(name, &Entry::iconRect); } @@ -206,7 +221,6 @@ optional<SpriteAtlasElement> SpriteAtlas::getPattern(const std::string& name) { optional<SpriteAtlasElement> SpriteAtlas::getImage(const std::string& name, optional<Rect<uint16_t>> Entry::*entryRect) { - std::lock_guard<std::mutex> lock(mutex); auto it = entries.find(name); if (it == entries.end()) { @@ -219,6 +233,7 @@ optional<SpriteAtlasElement> SpriteAtlas::getImage(const std::string& name, Entry& entry = it->second; if (entry.*entryRect) { + assert(entry.spriteImage.get()); return SpriteAtlasElement { *(entry.*entryRect), entry.spriteImage, @@ -285,6 +300,18 @@ void SpriteAtlas::copy(const Entry& entry, optional<Rect<uint16_t>> Entry::*entr dirty = true; } +IconMap SpriteAtlas::buildIconMap() { + if (icons.empty()) { + for (auto entry : entries) { + icons.emplace(std::piecewise_construct, + std::forward_as_tuple(entry.first), + std::forward_as_tuple(*getIcon(entry.first))); + + } + } + return icons; +} + void SpriteAtlas::upload(gl::Context& context, gl::TextureUnit unit) { if (!texture) { texture = context.createTexture(image, unit); diff --git a/src/mbgl/sprite/sprite_atlas.hpp b/src/mbgl/sprite/sprite_atlas.hpp index c7b266376b..3c37f57708 100644 --- a/src/mbgl/sprite/sprite_atlas.hpp +++ b/src/mbgl/sprite/sprite_atlas.hpp @@ -6,10 +6,9 @@ #include <mbgl/util/optional.hpp> #include <mbgl/sprite/sprite_image.hpp> -#include <atomic> #include <string> #include <map> -#include <mutex> +#include <set> #include <unordered_map> #include <array> #include <memory> @@ -28,12 +27,26 @@ public: SpriteAtlasElement(Rect<uint16_t>, std::shared_ptr<const SpriteImage>, Size size, float pixelRatio); Rect<uint16_t> pos; - std::shared_ptr<const SpriteImage> spriteImage; + bool sdf; float relativePixelRatio; std::array<float, 2> size; std::array<float, 2> tl; std::array<float, 2> br; + float width; + float height; +}; + +class SpriteAtlas; + +typedef std::map<std::string,SpriteAtlasElement> IconMap; +typedef std::set<std::string> IconDependencies; +typedef std::map<uintptr_t,IconMap> IconAtlasMap; +typedef std::map<SpriteAtlas*,IconDependencies> IconDependencyMap; + +class IconRequestor { +public: + virtual void onIconsAvailable(SpriteAtlas*, IconMap) = 0; }; class SpriteAtlas : public util::noncopyable { @@ -55,8 +68,11 @@ public: void setSprite(const std::string&, std::shared_ptr<const SpriteImage>); void removeSprite(const std::string&); + + std::shared_ptr<const SpriteImage> getSprite(const std::string& name); - std::shared_ptr<const SpriteImage> getSprite(const std::string&); + void getIcons(IconRequestor& requestor); + void removeRequestor(IconRequestor& requestor); optional<SpriteAtlasElement> getIcon(const std::string& name); optional<SpriteAtlasElement> getPattern(const std::string& name); @@ -105,13 +121,17 @@ private: optional<SpriteAtlasElement> getImage(const std::string& name, optional<Rect<uint16_t>> Entry::*rect); void copy(const Entry&, optional<Rect<uint16_t>> Entry::*rect); + + IconMap buildIconMap(); - std::mutex mutex; std::unordered_map<std::string, Entry> entries; BinPack<uint16_t> bin; PremultipliedImage image; mbgl::optional<gl::Texture> texture; - std::atomic<bool> dirty; + bool dirty; + + std::set<IconRequestor*> requestors; + IconMap icons; }; } // namespace mbgl diff --git a/src/mbgl/style/layers/symbol_layer_impl.cpp b/src/mbgl/style/layers/symbol_layer_impl.cpp index e4557c953f..c637770c04 100644 --- a/src/mbgl/style/layers/symbol_layer_impl.cpp +++ b/src/mbgl/style/layers/symbol_layer_impl.cpp @@ -36,11 +36,15 @@ std::unique_ptr<Bucket> SymbolLayer::Impl::createBucket(const BucketParameters&, std::unique_ptr<SymbolLayout> SymbolLayer::Impl::createLayout(const BucketParameters& parameters, const std::vector<const Layer*>& group, - const GeometryTileLayer& layer) const { + const GeometryTileLayer& layer, + GlyphDependencies& glyphDependencies, + IconDependencyMap& iconDependencyMap) const { return std::make_unique<SymbolLayout>(parameters, group, layer, - *spriteAtlas); + iconDependencyMap[spriteAtlas], + (uintptr_t)spriteAtlas, + glyphDependencies); } IconPaintProperties::Evaluated SymbolLayer::Impl::iconPaintProperties() const { diff --git a/src/mbgl/style/layers/symbol_layer_impl.hpp b/src/mbgl/style/layers/symbol_layer_impl.hpp index 0c4b74e833..db20989f01 100644 --- a/src/mbgl/style/layers/symbol_layer_impl.hpp +++ b/src/mbgl/style/layers/symbol_layer_impl.hpp @@ -1,13 +1,14 @@ #pragma once +#include <mbgl/text/glyph.hpp> #include <mbgl/util/variant.hpp> +#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/style/layer_impl.hpp> #include <mbgl/style/layers/symbol_layer.hpp> #include <mbgl/style/layers/symbol_layer_properties.hpp> namespace mbgl { -class SpriteAtlas; class SymbolLayout; namespace style { @@ -67,7 +68,7 @@ public: std::unique_ptr<Bucket> createBucket(const BucketParameters&, const std::vector<const Layer*>&) const override; std::unique_ptr<SymbolLayout> createLayout(const BucketParameters&, const std::vector<const Layer*>&, - const GeometryTileLayer&) const; + const GeometryTileLayer&, GlyphDependencies&, IconDependencyMap&) const; IconPaintProperties::Evaluated iconPaintProperties() const; TextPaintProperties::Evaluated textPaintProperties() const; diff --git a/src/mbgl/style/source_impl.cpp b/src/mbgl/style/source_impl.cpp index 356d420e19..9fabc54f7d 100644 --- a/src/mbgl/style/source_impl.cpp +++ b/src/mbgl/style/source_impl.cpp @@ -191,12 +191,6 @@ void Source::Impl::removeTiles() { } } -void Source::Impl::updateSymbolDependentTiles() { - for (auto& pair : tiles) { - pair.second->symbolDependenciesChanged(); - } -} - void Source::Impl::reloadTiles() { cache.clear(); diff --git a/src/mbgl/style/source_impl.hpp b/src/mbgl/style/source_impl.hpp index b4941dc638..e8ba54b2b6 100644 --- a/src/mbgl/style/source_impl.hpp +++ b/src/mbgl/style/source_impl.hpp @@ -48,10 +48,6 @@ public: // trigger re-placement of existing complete tiles. void updateTiles(const UpdateParameters&); - // Called when icons or glyphs are loaded. Triggers further processing of tiles which - // were waiting on such dependencies. - void updateSymbolDependentTiles(); - // Removes all tiles (by putting them into the cache). void removeTiles(); diff --git a/src/mbgl/style/style.cpp b/src/mbgl/style/style.cpp index 5f9983ae94..9640988402 100644 --- a/src/mbgl/style/style.cpp +++ b/src/mbgl/style/style.cpp @@ -274,12 +274,6 @@ void Style::updateTiles(const UpdateParameters& parameters) { } } -void Style::updateSymbolDependentTiles() { - for (const auto& source : sources) { - source->baseImpl->updateSymbolDependentTiles(); - } -} - void Style::relayout() { for (const auto& sourceID : updateBatch.sourceIDs) { Source* source = getSource(sourceID); @@ -575,7 +569,6 @@ void Style::setObserver(style::Observer* observer_) { void Style::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& glyphRange) { observer->onGlyphsLoaded(fontStack, glyphRange); - updateSymbolDependentTiles(); } void Style::onGlyphsError(const FontStack& fontStack, const GlyphRange& glyphRange, std::exception_ptr error) { @@ -626,7 +619,6 @@ void Style::onTileError(Source& source, const OverscaledTileID& tileID, std::exc void Style::onSpriteLoaded() { observer->onSpriteLoaded(); observer->onUpdate(Update::Repaint); // For *-pattern properties. - updateSymbolDependentTiles(); } void Style::onSpriteError(std::exception_ptr error) { diff --git a/src/mbgl/style/style.hpp b/src/mbgl/style/style.hpp index 0c65129422..817cab5fd5 100644 --- a/src/mbgl/style/style.hpp +++ b/src/mbgl/style/style.hpp @@ -128,7 +128,6 @@ private: std::vector<std::unique_ptr<Layer>>::const_iterator findLayer(const std::string& layerID) const; void reloadLayerSource(Layer&); - void updateSymbolDependentTiles(); // GlyphStoreObserver implementation. void onGlyphsLoaded(const FontStack&, const GlyphRange&) override; diff --git a/src/mbgl/text/glyph.cpp b/src/mbgl/text/glyph.cpp index 29929b73e6..74863d7435 100644 --- a/src/mbgl/text/glyph.cpp +++ b/src/mbgl/text/glyph.cpp @@ -3,7 +3,7 @@ namespace mbgl { // Note: this only works for the BMP -GlyphRange getGlyphRange(char16_t glyph) { +GlyphRange getGlyphRange(GlyphID glyph) { unsigned start = (glyph/256) * 256; unsigned end = (start + 255); if (start > 65280) start = 65280; diff --git a/src/mbgl/text/glyph.hpp b/src/mbgl/text/glyph.hpp index 175a36f0fa..6aa4e11b1c 100644 --- a/src/mbgl/text/glyph.hpp +++ b/src/mbgl/text/glyph.hpp @@ -1,6 +1,7 @@ #pragma once #include <mbgl/text/glyph_range.hpp> +#include <mbgl/util/font_stack.hpp> #include <mbgl/util/rect.hpp> #include <mbgl/util/traits.hpp> #include <mbgl/util/image.hpp> @@ -12,8 +13,11 @@ namespace mbgl { +typedef char16_t GlyphID; +typedef std::set<GlyphID> GlyphIDs; + // Note: this only works for the BMP -GlyphRange getGlyphRange(char16_t glyph); +GlyphRange getGlyphRange(GlyphID glyph); struct GlyphMetrics { explicit operator bool() const { @@ -50,14 +54,14 @@ struct Glyph { const GlyphMetrics metrics; }; -typedef std::map<uint32_t, Glyph> GlyphPositions; +typedef std::map<GlyphID, Glyph> GlyphPositions; class PositionedGlyph { public: - explicit PositionedGlyph(uint32_t glyph_, float x_, float y_, float angle_) + explicit PositionedGlyph(GlyphID glyph_, float x_, float y_, float angle_) : glyph(glyph_), x(x_), y(y_), angle(angle_) {} - uint32_t glyph = 0; + GlyphID glyph = 0; float x = 0; float y = 0; float angle = 0; @@ -86,7 +90,7 @@ public: // also need to be reencoded. static constexpr const uint8_t borderSize = 3; - uint32_t id = 0; + GlyphID id = 0; // A signed distance field of the glyph with a border (see above). AlphaImage bitmap; @@ -121,4 +125,9 @@ constexpr WritingModeType operator~(WritingModeType value) { return WritingModeType(~mbgl::underlying_type(value)); } +typedef std::map<FontStack,GlyphIDs> GlyphDependencies; +typedef std::map<FontStack,GlyphRangeSet> GlyphRangeDependencies; +typedef std::map<FontStack,GlyphPositions> GlyphPositionMap; + + } // end namespace mbgl diff --git a/src/mbgl/text/glyph_atlas.cpp b/src/mbgl/text/glyph_atlas.cpp index 1b3f7518b5..ade2734ca9 100644 --- a/src/mbgl/text/glyph_atlas.cpp +++ b/src/mbgl/text/glyph_atlas.cpp @@ -22,81 +22,140 @@ GlyphAtlas::GlyphAtlas(const Size size, FileSource& fileSource_) GlyphAtlas::~GlyphAtlas() = default; -void GlyphAtlas::requestGlyphRange(const FontStack& fontStack, const GlyphRange& range) { - std::lock_guard<std::mutex> lock(mutex); - auto& rangeSets = entries[fontStack].ranges; - - const auto& rangeSetsIt = rangeSets.find(range); - if (rangeSetsIt != rangeSets.end()) { - return; +GlyphSet& GlyphAtlas::getGlyphSet(const FontStack& fontStack) { + return entries[fontStack].glyphSet; +} + +bool GlyphAtlas::hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& ranges) const { + for (const auto& range : ranges) { + if (!hasGlyphRange(fontStack,range)) { + return false; + } } - - rangeSets.emplace(std::piecewise_construct, - std::forward_as_tuple(range), - std::forward_as_tuple(this, fontStack, range, observer, fileSource)); + return true; +} + +bool rangeIsParsed(const std::map<GlyphRange, GlyphPBF>& ranges, const GlyphRange& range) { + auto rangeIt = ranges.find(range); + if (rangeIt == ranges.end()) + return false; + + return rangeIt->second.isParsed(); } + +bool GlyphAtlas::hasGlyphRange(const FontStack& fontStack, const GlyphRange& range) const { + auto entry = entries.find(fontStack); + if (entry == entries.end()) + return false; + + return rangeIsParsed(entry->second.ranges, range); +} + +void GlyphAtlas::getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphDependencies) { + auto dependencies = std::make_shared<GlyphDependencies>(std::move(glyphDependencies)); + + // Figure out which glyph ranges need to be fetched. For each range that does need to + // be fetched, record an entry mapping the requestor to a shared pointer containing the + // dependencies. When the shared pointer becomes unique, we know that all the dependencies + // for that requestor have been fetched, and can notify it of completion. + for (const auto& dependency : *dependencies) { + const FontStack& fontStack = dependency.first; + const GlyphIDs& glyphIDs = dependency.second; + + Entry& entry = entries[fontStack]; + + GlyphRangeSet processedRanges; + for (const auto& glyphID : glyphIDs) { + GlyphRange range = getGlyphRange(glyphID); + if (processedRanges.find(range) == processedRanges.end() && !rangeIsParsed(entry.ranges, range)) { + if (entry.ranges.find(range) == entry.ranges.end()) { + entry.ranges.emplace(std::piecewise_construct, + std::forward_as_tuple(range), + std::forward_as_tuple(this, fontStack, range, this, fileSource)); + } -bool GlyphAtlas::hasGlyphRanges(const FontStack& fontStack, const GlyphRangeSet& glyphRanges) { - if (glyphRanges.empty()) { - return true; + entry.ranges.find(range)->second.requestors[&requestor] = dependencies; + } + processedRanges.insert(range); + } } - std::lock_guard<std::mutex> lock(mutex); - const auto& rangeSets = entries[fontStack].ranges; + // If the shared dependencies pointer is already unique, then all dependent glyph ranges + // have already been loaded. Send a notification immediately. + if (dependencies.unique()) { + addGlyphs(requestor, *dependencies); + } +} - bool hasRanges = true; - for (const auto& range : glyphRanges) { - const auto& rangeSetsIt = rangeSets.find(range); - if (rangeSetsIt == rangeSets.end()) { - // Push the request to the MapThread, so we can easly cancel - // if it is still pending when we destroy this object. - workQueue.push(std::bind(&GlyphAtlas::requestGlyphRange, this, fontStack, range)); +void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) { + observer = observer_; +} + +void GlyphAtlas::onGlyphsError(const FontStack& fontStack, const GlyphRange& range, std::exception_ptr err) { + if (observer) { + observer->onGlyphsError(fontStack, range, err); + } +} - hasRanges = false; - continue; - } +void GlyphAtlas::onGlyphsLoaded(const FontStack& fontStack, const GlyphRange& range) { + Entry& entry = entries[fontStack]; - if (!rangeSetsIt->second.isParsed()) { - hasRanges = false; + auto it = entry.ranges.find(range); + if (it != entry.ranges.end()) { + for (auto& pair : it->second.requestors) { + GlyphRequestor& requestor = *pair.first; + const std::shared_ptr<GlyphDependencies>& dependencies = pair.second; + if (dependencies.unique()) { + addGlyphs(requestor, *dependencies); + } } + it->second.requestors.clear(); } - return hasRanges; + if (observer) { + observer->onGlyphsLoaded(fontStack, range); + } } -util::exclusive<GlyphSet> GlyphAtlas::getGlyphSet(const FontStack& fontStack) { - auto lock = std::make_unique<std::lock_guard<std::mutex>>(mutex); - return { &entries[fontStack].glyphSet, std::move(lock) }; -} +void GlyphAtlas::addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies) { + GlyphPositionMap glyphPositions; + GlyphRangeSet loadedRanges; -void GlyphAtlas::setObserver(GlyphAtlasObserver* observer_) { - observer = observer_; -} + for (const auto& dependency : glyphDependencies) { + const FontStack& fontStack = dependency.first; + const GlyphIDs& glyphIDs = dependency.second; -void GlyphAtlas::addGlyphs(uintptr_t tileUID, - const std::u16string& text, - const FontStack& fontStack, - const util::exclusive<GlyphSet>& glyphSet, - GlyphPositions& face) -{ - const std::map<uint32_t, SDFGlyph>& sdfs = glyphSet->getSDFs(); + GlyphPositions& positions = glyphPositions[fontStack]; + Entry& entry = entries[fontStack]; + const auto& sdfs = entry.glyphSet.getSDFs(); - for (char16_t chr : text) - { - auto sdf_it = sdfs.find(chr); - if (sdf_it == sdfs.end()) { - continue; - } + for (const auto& glyphID : glyphIDs) { + loadedRanges.insert(getGlyphRange(glyphID)); + auto it = sdfs.find(glyphID); + if (it == sdfs.end()) + continue; + + addGlyph(requestor, fontStack, it->second); - const SDFGlyph& sdf = sdf_it->second; - Rect<uint16_t> rect = addGlyph(tileUID, fontStack, sdf); - face.emplace(chr, Glyph{rect, sdf.metrics}); + // It's possible to have an SDF without a valid position (if the SDF was malformed). + // We indicate this case with Rect<uint16_t>(0,0,0,0). + auto glyphRect = entry.glyphValues.find(glyphID); + const Rect<uint16_t> rect = glyphRect == entry.glyphValues.end() + ? Rect<uint16_t>(0,0,0,0) + : glyphRect->second.rect; + + positions.emplace(std::piecewise_construct, + std::forward_as_tuple(glyphID), + std::forward_as_tuple(rect, it->second.metrics)); + } } + + requestor.onGlyphsAvailable(glyphPositions, loadedRanges); } -Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID, - const FontStack& fontStack, - const SDFGlyph& glyph) +void GlyphAtlas::addGlyph(GlyphRequestor& requestor, + const FontStack& fontStack, + const SDFGlyph& glyph) { std::map<uint32_t, GlyphValue>& face = entries[fontStack].glyphValues; auto it = face.find(glyph.id); @@ -104,15 +163,15 @@ Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID, // The glyph is already in this texture. if (it != face.end()) { GlyphValue& value = it->second; - value.ids.insert(tileUID); - return value.rect; + value.ids.insert(&requestor); + return; } // Guard against glyphs that are too large, or that we don't need to place into the atlas since // they don't have any pixels. if (glyph.metrics.width == 0 || glyph.metrics.width >= 256 || glyph.metrics.height == 0 || glyph.metrics.height >= 256) { - return Rect<uint16_t>{ 0, 0, 0, 0 }; + return; } // Add a 1px border around every image. @@ -129,53 +188,59 @@ Rect<uint16_t> GlyphAtlas::addGlyph(uintptr_t tileUID, Rect<uint16_t> rect = bin.allocate(width, height); if (rect.w == 0) { Log::Error(Event::OpenGL, "glyph bitmap overflow"); - return rect; + return; } - face.emplace(glyph.id, GlyphValue { rect, tileUID }); + face.emplace(glyph.id, GlyphValue { rect, &requestor }); AlphaImage::copy(glyph.bitmap, image, { 0, 0 }, { rect.x + padding, rect.y + padding }, glyph.bitmap.size); dirty = true; - - return rect; } -void GlyphAtlas::removeGlyphs(uintptr_t tileUID) { - std::lock_guard<std::mutex> lock(mutex); +void GlyphAtlas::removeGlyphValues(GlyphRequestor& requestor, std::map<uint32_t, GlyphValue>& face) { + for (auto it = face.begin(); it != face.end(); /* we advance in the body */) { + GlyphValue& value = it->second; + value.ids.erase(&requestor); - for (auto& entry : entries) { - std::map<uint32_t, GlyphValue>& face = entry.second.glyphValues; - for (auto it = face.begin(); it != face.end(); /* we advance in the body */) { - GlyphValue& value = it->second; - value.ids.erase(tileUID); - - if (value.ids.empty()) { - const Rect<uint16_t>& rect = value.rect; - - // Clear out the bitmap. - uint8_t *target = image.data.get(); - for (uint32_t y = 0; y < rect.h; y++) { - uint32_t y1 = image.size.width * (rect.y + y) + rect.x; - for (uint32_t x = 0; x < rect.w; x++) { - target[y1 + x] = 0; - } + if (value.ids.empty()) { + const Rect<uint16_t>& rect = value.rect; + + // Clear out the bitmap. + uint8_t *target = image.data.get(); + for (uint32_t y = 0; y < rect.h; y++) { + uint32_t y1 = image.size.width * (rect.y + y) + rect.x; + for (uint32_t x = 0; x < rect.w; x++) { + target[y1 + x] = 0; } + } - bin.release(rect); + bin.release(rect); - // Make sure to post-increment the iterator: This will return the - // current iterator, but will go to the next position before we - // erase the element from the map. That way, the iterator stays - // valid. - face.erase(it++); - } else { - ++it; - } + // Make sure to post-increment the iterator: This will return the + // current iterator, but will go to the next position before we + // erase the element from the map. That way, the iterator stays + // valid. + face.erase(it++); + } else { + ++it; } } } +void GlyphAtlas::removePendingRanges(mbgl::GlyphRequestor &requestor, std::map<GlyphRange, GlyphPBF> &ranges) { + for (auto it = ranges.begin(); it != ranges.end(); it++) { + it->second.requestors.erase(&requestor); + } +} + +void GlyphAtlas::removeGlyphs(GlyphRequestor& requestor) { + for (auto& entry : entries) { + removeGlyphValues(requestor, entry.second.glyphValues); + removePendingRanges(requestor, entry.second.ranges); + } +} + Size GlyphAtlas::getSize() const { return image.size; } @@ -186,7 +251,7 @@ void GlyphAtlas::upload(gl::Context& context, gl::TextureUnit unit) { } else if (dirty) { context.updateTexture(*texture, image, unit); } - + dirty = false; } diff --git a/src/mbgl/text/glyph_atlas.hpp b/src/mbgl/text/glyph_atlas.hpp index 8267630096..120f19accc 100644 --- a/src/mbgl/text/glyph_atlas.hpp +++ b/src/mbgl/text/glyph_atlas.hpp @@ -1,46 +1,55 @@ #pragma once #include <mbgl/text/glyph.hpp> +#include <mbgl/text/glyph_atlas_observer.hpp> +#include <mbgl/text/glyph_range.hpp> #include <mbgl/text/glyph_set.hpp> #include <mbgl/geometry/binpack.hpp> #include <mbgl/util/noncopyable.hpp> #include <mbgl/util/optional.hpp> #include <mbgl/util/font_stack.hpp> -#include <mbgl/util/exclusive.hpp> #include <mbgl/util/work_queue.hpp> #include <mbgl/util/image.hpp> #include <mbgl/gl/texture.hpp> #include <mbgl/gl/object.hpp> -#include <atomic> #include <string> #include <unordered_set> #include <unordered_map> -#include <mutex> + +class GlyphAtlasTest; namespace mbgl { class FileSource; class GlyphPBF; -class GlyphAtlasObserver; namespace gl { class Context; } // namespace gl -class GlyphAtlas : public util::noncopyable { +class GlyphRequestor { +public: + virtual void onGlyphsAvailable(GlyphPositionMap, GlyphRangeSet) = 0; +}; + +class GlyphAtlas : public util::noncopyable, public GlyphAtlasObserver { public: GlyphAtlas(Size, FileSource&); ~GlyphAtlas(); - util::exclusive<GlyphSet> getGlyphSet(const FontStack&); - - // Returns true if the set of GlyphRanges are available and parsed or false - // if they are not. For the missing ranges, a request on the FileSource is - // made and when the glyph if finally parsed, it gets added to the respective - // GlyphSet and a signal is emitted to notify the observers. This method - // can be called from any thread. - bool hasGlyphRanges(const FontStack&, const GlyphRangeSet&); + GlyphSet& getGlyphSet(const FontStack&); + + // Workers send a `getGlyphs` message to the main thread once they have determined + // which glyphs they will need. Invoking this method will increment reference + // counts for all the glyphs in `GlyphDependencies`. If all glyphs are already + // locally available, the observer will be notified that the glyphs are available + // immediately. Otherwise, a request on the FileSource is made, and when all glyphs + // are parsed and added to the atlas, the observer will be notified. + // Workers are given a copied 'GlyphPositions' map to use for placing their glyphs. + // The positions specified in this object are guaranteed to be + // valid for the lifetime of the tile. + void getGlyphs(GlyphRequestor& requestor, GlyphDependencies glyphs); void setURL(const std::string &url) { glyphURL = url; @@ -52,12 +61,7 @@ public: void setObserver(GlyphAtlasObserver* observer); - void addGlyphs(uintptr_t tileUID, - const std::u16string& text, - const FontStack&, - const util::exclusive<GlyphSet>&, - GlyphPositions&); - void removeGlyphs(uintptr_t tileUID); + void removeGlyphs(GlyphRequestor&); // Binds the atlas texture to the GPU, and uploads data if it is out of date. void bind(gl::Context&, gl::TextureUnit unit); @@ -67,22 +71,29 @@ public: void upload(gl::Context&, gl::TextureUnit unit); Size getSize() const; + + virtual void onGlyphsLoaded(const FontStack&, const GlyphRange&); + virtual void onGlyphsError(const FontStack&, const GlyphRange&, std::exception_ptr); + + friend class ::GlyphAtlasTest; private: - void requestGlyphRange(const FontStack&, const GlyphRange&); + void addGlyphs(GlyphRequestor& requestor, const GlyphDependencies& glyphDependencies); - Rect<uint16_t> addGlyph(uintptr_t tileID, - const FontStack&, - const SDFGlyph&); + // Only used by GlyphAtlasTest + bool hasGlyphRanges(const FontStack&, const GlyphRangeSet& ranges) const; + bool hasGlyphRange(const FontStack&, const GlyphRange& range) const; + void addGlyph(GlyphRequestor& requestor, const FontStack&, const SDFGlyph&); + FileSource& fileSource; std::string glyphURL; struct GlyphValue { - GlyphValue(Rect<uint16_t> rect_, uintptr_t id) + GlyphValue(Rect<uint16_t> rect_, GlyphRequestor* id) : rect(std::move(rect_)), ids({ id }) {} Rect<uint16_t> rect; - std::unordered_set<uintptr_t> ids; + std::unordered_set<GlyphRequestor*> ids; }; struct Entry { @@ -92,14 +103,15 @@ private: }; std::unordered_map<FontStack, Entry, FontStackHash> entries; - std::mutex mutex; + + void removeGlyphValues(GlyphRequestor& requestor, std::map<uint32_t, GlyphValue>& face); + void removePendingRanges(GlyphRequestor& requestor, std::map<GlyphRange, GlyphPBF>& ranges); - util::WorkQueue workQueue; GlyphAtlasObserver* observer = nullptr; BinPack<uint16_t> bin; AlphaImage image; - std::atomic<bool> dirty; + bool dirty; mbgl::optional<gl::Texture> texture; }; diff --git a/src/mbgl/text/glyph_pbf.cpp b/src/mbgl/text/glyph_pbf.cpp index 5c57d278db..26eff812b7 100644 --- a/src/mbgl/text/glyph_pbf.cpp +++ b/src/mbgl/text/glyph_pbf.cpp @@ -16,6 +16,7 @@ namespace mbgl { namespace { +// Parses a Glyph Protobuf and inserts it into the GlyphAtlas. Must be called from main thread. void parseGlyphPBF(GlyphSet& glyphSet, const GlyphRange& glyphRange, const std::string& data) { protozero::pbf_reader glyphs_pbf(data); @@ -117,7 +118,7 @@ GlyphPBF::GlyphPBF(GlyphAtlas* atlas, observer->onGlyphsLoaded(fontStack, glyphRange); } else { try { - parseGlyphPBF(**atlas->getGlyphSet(fontStack), glyphRange, *res.data); + parseGlyphPBF(atlas->getGlyphSet(fontStack), glyphRange, *res.data); } catch (...) { observer->onGlyphsError(fontStack, glyphRange, std::current_exception()); return; diff --git a/src/mbgl/text/glyph_pbf.hpp b/src/mbgl/text/glyph_pbf.hpp index 7412ebe411..d5b89cd107 100644 --- a/src/mbgl/text/glyph_pbf.hpp +++ b/src/mbgl/text/glyph_pbf.hpp @@ -8,10 +8,12 @@ #include <functional> #include <string> #include <memory> +#include <unordered_map> namespace mbgl { class GlyphAtlas; +class GlyphRequestor; class GlyphAtlasObserver; class AsyncRequest; class FileSource; @@ -29,10 +31,10 @@ public: return parsed; } -private: - std::atomic<bool> parsed; + bool parsed; std::unique_ptr<AsyncRequest> req; GlyphAtlasObserver* observer = nullptr; + std::unordered_map<GlyphRequestor*, std::shared_ptr<GlyphDependencies>> requestors; }; } // namespace mbgl diff --git a/src/mbgl/text/glyph_set.cpp b/src/mbgl/text/glyph_set.cpp index ea0dd123db..b8e155502e 100644 --- a/src/mbgl/text/glyph_set.cpp +++ b/src/mbgl/text/glyph_set.cpp @@ -1,13 +1,6 @@ -#include <mbgl/math/minmax.hpp> #include <mbgl/text/glyph_set.hpp> -#include <mbgl/util/i18n.hpp> #include <mbgl/util/logging.hpp> -#include <boost/algorithm/string.hpp> - -#include <algorithm> -#include <cassert> - namespace mbgl { void GlyphSet::insert(uint32_t id, SDFGlyph&& glyph) { @@ -34,272 +27,4 @@ const std::map<uint32_t, SDFGlyph>& GlyphSet::getSDFs() const { return sdfs; } -const Shaping GlyphSet::getShaping(const std::u16string& logicalInput, - const float maxWidth, - const float lineHeight, - const float horizontalAlign, - const float verticalAlign, - const float justify, - const float spacing, - const Point<float>& translate, - const float verticalHeight, - const WritingModeType writingMode, - BiDi& bidi) const { - Shaping shaping(translate.x, translate.y, writingMode); - - std::vector<std::u16string> reorderedLines = - bidi.processText(logicalInput, - determineLineBreaks(logicalInput, spacing, maxWidth, writingMode)); - - shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign, - justify, translate, verticalHeight, writingMode); - - return shaping; -} - -void align(Shaping& shaping, - const float justify, - const float horizontalAlign, - const float verticalAlign, - const float maxLineLength, - const float lineHeight, - const std::size_t lineCount, - const Point<float>& translate) { - const float shiftX = - (justify - horizontalAlign) * maxLineLength + ::round(translate.x); - const float shiftY = - (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y); - - for (auto& glyph : shaping.positionedGlyphs) { - glyph.x += shiftX; - glyph.y += shiftY; - } -} - -// justify left = 0, right = 1, center = .5 -void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, - const std::map<uint32_t, SDFGlyph>& sdfs, - std::size_t start, - std::size_t end, - float justify) { - if (!justify) { - return; - } - - PositionedGlyph& glyph = positionedGlyphs[end]; - auto it = sdfs.find(glyph.glyph); - if (it != sdfs.end()) { - const uint32_t lastAdvance = it->second.metrics.advance; - const float lineIndent = float(glyph.x + lastAdvance) * justify; - - for (std::size_t j = start; j <= end; j++) { - positionedGlyphs[j].x -= lineIndent; - } - } -} - -float GlyphSet::determineAverageLineWidth(const std::u16string& logicalInput, - const float spacing, - float maxWidth) const { - float totalWidth = 0; - - for (char16_t chr : logicalInput) { - auto it = sdfs.find(chr); - if (it != sdfs.end()) { - totalWidth += it->second.metrics.advance + spacing; - } - } - - int32_t targetLineCount = std::fmax(1, std::ceil(totalWidth / maxWidth)); - return totalWidth / targetLineCount; -} - -float calculateBadness(const float lineWidth, const float targetWidth, const float penalty, const bool isLastBreak) { - const float raggedness = std::pow(lineWidth - targetWidth, 2); - if (isLastBreak) { - // Favor finals lines shorter than average over longer than average - if (lineWidth < targetWidth) { - return raggedness / 2; - } else { - return raggedness * 2; - } - } - if (penalty < 0) { - return raggedness - std::pow(penalty, 2); - } - return raggedness + std::pow(penalty, 2); -} - -float calculatePenalty(char16_t codePoint, char16_t nextCodePoint) { - float penalty = 0; - // Force break on newline - if (codePoint == 0x0a) { - penalty -= 10000; - } - // Penalize open parenthesis at end of line - if (codePoint == 0x28 || codePoint == 0xff08) { - penalty += 50; - } - - // Penalize close parenthesis at beginning of line - if (nextCodePoint == 0x29 || nextCodePoint == 0xff09) { - penalty += 50; - } - - return penalty; -} - -struct PotentialBreak { - PotentialBreak(const std::size_t p_index, const float p_x, const PotentialBreak* p_priorBreak, const float p_badness) - : index(p_index), x(p_x), priorBreak(p_priorBreak), badness(p_badness) - {} - - const std::size_t index; - const float x; - const PotentialBreak* priorBreak; - const float badness; -}; - - -PotentialBreak evaluateBreak(const std::size_t breakIndex, const float breakX, const float targetWidth, const std::list<PotentialBreak>& potentialBreaks, const float penalty, const bool isLastBreak) { - // We could skip evaluating breaks where the line length (breakX - priorBreak.x) > maxWidth - // ...but in fact we allow lines longer than maxWidth (if there's no break points) - // ...and when targetWidth and maxWidth are close, strictly enforcing maxWidth can give - // more lopsided results. - - const PotentialBreak* bestPriorBreak = nullptr; - float bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak); - for (const auto& potentialBreak : potentialBreaks) { - const float lineWidth = breakX - potentialBreak.x; - float breakBadness = - calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) + potentialBreak.badness; - if (breakBadness <= bestBreakBadness) { - bestPriorBreak = &potentialBreak; - bestBreakBadness = breakBadness; - } - } - - return PotentialBreak(breakIndex, breakX, bestPriorBreak, bestBreakBadness); -} - -std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) { - std::set<std::size_t> leastBadBreaks = { lastLineBreak.index }; - const PotentialBreak* priorBreak = lastLineBreak.priorBreak; - while (priorBreak) { - leastBadBreaks.insert(priorBreak->index); - priorBreak = priorBreak->priorBreak; - } - return leastBadBreaks; -} - - -// We determine line breaks based on shaped text in logical order. Working in visual order would be -// more intuitive, but we can't do that because the visual order may be changed by line breaks! -std::set<std::size_t> GlyphSet::determineLineBreaks(const std::u16string& logicalInput, - const float spacing, - float maxWidth, - const WritingModeType writingMode) const { - if (!maxWidth || writingMode != WritingModeType::Horizontal) { - return {}; - } - - if (logicalInput.empty()) { - return {}; - } - - const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth); - - std::list<PotentialBreak> potentialBreaks; - float currentX = 0; - - for (std::size_t i = 0; i < logicalInput.size(); i++) { - const char16_t codePoint = logicalInput[i]; - auto it = sdfs.find(codePoint); - if (it != sdfs.end() && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) { - currentX += it->second.metrics.advance + spacing; - } - - // Ideographic characters, spaces, and word-breaking punctuation that often appear without - // surrounding spaces. - if ((i < logicalInput.size() - 1) && - (util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) { - potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks, - calculatePenalty(codePoint, logicalInput[i+1]), - false)); - } - } - - return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true)); -} - -void GlyphSet::shapeLines(Shaping& shaping, - const std::vector<std::u16string>& lines, - const float spacing, - const float lineHeight, - const float horizontalAlign, - const float verticalAlign, - const float justify, - const Point<float>& translate, - const float verticalHeight, - const WritingModeType writingMode) const { - - // the y offset *should* be part of the font metadata - const int32_t yOffset = -17; - - float x = 0; - float y = yOffset; - - float maxLineLength = 0; - - for (std::u16string line : lines) { - // Collapse whitespace so it doesn't throw off justification - boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r")); - - if (line.empty()) { - y += lineHeight; // Still need a line feed after empty line - continue; - } - - std::size_t lineStartIndex = shaping.positionedGlyphs.size(); - for (char16_t chr : line) { - auto it = sdfs.find(chr); - if (it == sdfs.end()) { - continue; - } - - const SDFGlyph& glyph = it->second; - - if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) { - shaping.positionedGlyphs.emplace_back(chr, x, y, 0); - x += glyph.metrics.advance + spacing; - } else { - shaping.positionedGlyphs.emplace_back(chr, x, 0, -M_PI_2); - x += verticalHeight + spacing; - } - } - - // Only justify if we placed at least one glyph - if (shaping.positionedGlyphs.size() != lineStartIndex) { - float lineLength = x - spacing; // Don't count trailing spacing - maxLineLength = util::max(lineLength, maxLineLength); - - justifyLine(shaping.positionedGlyphs, sdfs, lineStartIndex, - shaping.positionedGlyphs.size() - 1, justify); - } - - x = 0; - y += lineHeight; - } - - align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, - lines.size(), translate); - const uint32_t height = lines.size() * lineHeight; - - // Calculate the bounding box - shaping.top += -verticalAlign * height; - shaping.bottom = shaping.top + height; - shaping.left += -horizontalAlign * maxLineLength; - shaping.right = shaping.left + maxLineLength; -} - } // end namespace mbgl diff --git a/src/mbgl/text/glyph_set.hpp b/src/mbgl/text/glyph_set.hpp index 0342c82eb5..9f4bef94d2 100644 --- a/src/mbgl/text/glyph_set.hpp +++ b/src/mbgl/text/glyph_set.hpp @@ -1,6 +1,5 @@ #pragma once -#include <mbgl/text/bidi.hpp> #include <mbgl/text/glyph.hpp> #include <mbgl/util/geometry.hpp> @@ -10,38 +9,8 @@ class GlyphSet { public: void insert(uint32_t id, SDFGlyph&&); const std::map<uint32_t, SDFGlyph>& getSDFs() const; - const Shaping getShaping(const std::u16string& string, - float maxWidth, - float lineHeight, - float horizontalAlign, - float verticalAlign, - float justify, - float spacing, - const Point<float>& translate, - float verticalHeight, - const WritingModeType, - BiDi& bidi) const; private: - float determineAverageLineWidth(const std::u16string& logicalInput, - const float spacing, - float maxWidth) const; - std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput, - const float spacing, - float maxWidth, - const WritingModeType) const; - - void shapeLines(Shaping& shaping, - const std::vector<std::u16string>& lines, - const float spacing, - float lineHeight, - float horizontalAlign, - float verticalAlign, - float justify, - const Point<float>& translate, - float verticalHeight, - const WritingModeType) const; - std::map<uint32_t, SDFGlyph> sdfs; }; diff --git a/src/mbgl/text/shaping.cpp b/src/mbgl/text/shaping.cpp index e68566d419..aa760ea4fe 100644 --- a/src/mbgl/text/shaping.cpp +++ b/src/mbgl/text/shaping.cpp @@ -1,17 +1,297 @@ #include <mbgl/text/shaping.hpp> +#include <mbgl/util/i18n.hpp> #include <mbgl/layout/symbol_feature.hpp> +#include <mbgl/math/minmax.hpp> +#include <mbgl/text/bidi.hpp> + +#include <boost/algorithm/string.hpp> + +#include <algorithm> namespace mbgl { PositionedIcon shapeIcon(const SpriteAtlasElement& image, const std::array<float, 2>& iconOffset, const float iconRotation) { float dx = iconOffset[0]; float dy = iconOffset[1]; - float x1 = dx - image.spriteImage->getWidth() / 2.0f; - float x2 = x1 + image.spriteImage->getWidth(); - float y1 = dy - image.spriteImage->getHeight() / 2.0f; - float y2 = y1 + image.spriteImage->getHeight(); + float x1 = dx - image.width/ 2.0f; + float x2 = x1 + image.width; + float y1 = dy - image.height / 2.0f; + float y2 = y1 + image.height; return PositionedIcon(image, y1, y2, x1, x2, iconRotation); } +void align(Shaping& shaping, + const float justify, + const float horizontalAlign, + const float verticalAlign, + const float maxLineLength, + const float lineHeight, + const std::size_t lineCount, + const Point<float>& translate) { + const float shiftX = + (justify - horizontalAlign) * maxLineLength + ::round(translate.x); + const float shiftY = + (-verticalAlign * lineCount + 0.5) * lineHeight + ::round(translate.y); + + for (auto& glyph : shaping.positionedGlyphs) { + glyph.x += shiftX; + glyph.y += shiftY; + } +} + +// justify left = 0, right = 1, center = .5 +void justifyLine(std::vector<PositionedGlyph>& positionedGlyphs, + const GlyphPositions& glyphs, + std::size_t start, + std::size_t end, + float justify) { + if (!justify) { + return; + } + + PositionedGlyph& glyph = positionedGlyphs[end]; + auto it = glyphs.find(glyph.glyph); + if (it != glyphs.end()) { + const uint32_t lastAdvance = it->second.metrics.advance; + const float lineIndent = float(glyph.x + lastAdvance) * justify; + + for (std::size_t j = start; j <= end; j++) { + positionedGlyphs[j].x -= lineIndent; + } + } +} + +float determineAverageLineWidth(const std::u16string& logicalInput, + const float spacing, + float maxWidth, + const GlyphPositions& glyphs) { + float totalWidth = 0; + + for (char16_t chr : logicalInput) { + auto it = glyphs.find(chr); + if (it != glyphs.end()) { + totalWidth += it->second.metrics.advance + spacing; + } + } + + int32_t targetLineCount = std::fmax(1, std::ceil(totalWidth / maxWidth)); + return totalWidth / targetLineCount; +} + +float calculateBadness(const float lineWidth, const float targetWidth, const float penalty, const bool isLastBreak) { + const float raggedness = std::pow(lineWidth - targetWidth, 2); + if (isLastBreak) { + // Favor finals lines shorter than average over longer than average + if (lineWidth < targetWidth) { + return raggedness / 2; + } else { + return raggedness * 2; + } + } + if (penalty < 0) { + return raggedness - std::pow(penalty, 2); + } + return raggedness + std::pow(penalty, 2); +} + +float calculatePenalty(char16_t codePoint, char16_t nextCodePoint) { + float penalty = 0; + // Force break on newline + if (codePoint == 0x0a) { + penalty -= 10000; + } + // Penalize open parenthesis at end of line + if (codePoint == 0x28 || codePoint == 0xff08) { + penalty += 50; + } + + // Penalize close parenthesis at beginning of line + if (nextCodePoint == 0x29 || nextCodePoint == 0xff09) { + penalty += 50; + } + + return penalty; +} + +struct PotentialBreak { + PotentialBreak(const std::size_t p_index, const float p_x, const PotentialBreak* p_priorBreak, const float p_badness) + : index(p_index), x(p_x), priorBreak(p_priorBreak), badness(p_badness) + {} + + const std::size_t index; + const float x; + const PotentialBreak* priorBreak; + const float badness; +}; + + +PotentialBreak evaluateBreak(const std::size_t breakIndex, const float breakX, const float targetWidth, const std::list<PotentialBreak>& potentialBreaks, const float penalty, const bool isLastBreak) { + // We could skip evaluating breaks where the line length (breakX - priorBreak.x) > maxWidth + // ...but in fact we allow lines longer than maxWidth (if there's no break points) + // ...and when targetWidth and maxWidth are close, strictly enforcing maxWidth can give + // more lopsided results. + + const PotentialBreak* bestPriorBreak = nullptr; + float bestBreakBadness = calculateBadness(breakX, targetWidth, penalty, isLastBreak); + for (const auto& potentialBreak : potentialBreaks) { + const float lineWidth = breakX - potentialBreak.x; + float breakBadness = + calculateBadness(lineWidth, targetWidth, penalty, isLastBreak) + potentialBreak.badness; + if (breakBadness <= bestBreakBadness) { + bestPriorBreak = &potentialBreak; + bestBreakBadness = breakBadness; + } + } + + return PotentialBreak(breakIndex, breakX, bestPriorBreak, bestBreakBadness); +} + +std::set<std::size_t> leastBadBreaks(const PotentialBreak& lastLineBreak) { + std::set<std::size_t> leastBadBreaks = { lastLineBreak.index }; + const PotentialBreak* priorBreak = lastLineBreak.priorBreak; + while (priorBreak) { + leastBadBreaks.insert(priorBreak->index); + priorBreak = priorBreak->priorBreak; + } + return leastBadBreaks; +} + + +// We determine line breaks based on shaped text in logical order. Working in visual order would be +// more intuitive, but we can't do that because the visual order may be changed by line breaks! +std::set<std::size_t> determineLineBreaks(const std::u16string& logicalInput, + const float spacing, + float maxWidth, + const WritingModeType writingMode, + const GlyphPositions& glyphs) { + if (!maxWidth || writingMode != WritingModeType::Horizontal) { + return {}; + } + + if (logicalInput.empty()) { + return {}; + } + + const float targetWidth = determineAverageLineWidth(logicalInput, spacing, maxWidth, glyphs); + + std::list<PotentialBreak> potentialBreaks; + float currentX = 0; + + for (std::size_t i = 0; i < logicalInput.size(); i++) { + const char16_t codePoint = logicalInput[i]; + auto it = glyphs.find(codePoint); + if (it != glyphs.end() && !boost::algorithm::is_any_of(u" \t\n\v\f\r")(codePoint)) { + currentX += it->second.metrics.advance + spacing; + } + + // Ideographic characters, spaces, and word-breaking punctuation that often appear without + // surrounding spaces. + if ((i < logicalInput.size() - 1) && + (util::i18n::allowsWordBreaking(codePoint) || util::i18n::allowsIdeographicBreaking(codePoint))) { + potentialBreaks.push_back(evaluateBreak(i+1, currentX, targetWidth, potentialBreaks, + calculatePenalty(codePoint, logicalInput[i+1]), + false)); + } + } + + return leastBadBreaks(evaluateBreak(logicalInput.size(), currentX, targetWidth, potentialBreaks, 0, true)); +} + +void shapeLines(Shaping& shaping, + const std::vector<std::u16string>& lines, + const float spacing, + const float lineHeight, + const float horizontalAlign, + const float verticalAlign, + const float justify, + const Point<float>& translate, + const float verticalHeight, + const WritingModeType writingMode, + const GlyphPositions& glyphs) { + + // the y offset *should* be part of the font metadata + const int32_t yOffset = -17; + + float x = 0; + float y = yOffset; + + float maxLineLength = 0; + + for (std::u16string line : lines) { + // Collapse whitespace so it doesn't throw off justification + boost::algorithm::trim_if(line, boost::algorithm::is_any_of(u" \t\n\v\f\r")); + + if (line.empty()) { + y += lineHeight; // Still need a line feed after empty line + continue; + } + + std::size_t lineStartIndex = shaping.positionedGlyphs.size(); + for (char16_t chr : line) { + auto it = glyphs.find(chr); + if (it == glyphs.end()) { + continue; + } + + const Glyph& glyph = it->second; + + if (writingMode == WritingModeType::Horizontal || !util::i18n::hasUprightVerticalOrientation(chr)) { + shaping.positionedGlyphs.emplace_back(chr, x, y, 0); + x += glyph.metrics.advance + spacing; + } else { + shaping.positionedGlyphs.emplace_back(chr, x, 0, -M_PI_2); + x += verticalHeight + spacing; + } + } + + // Only justify if we placed at least one glyph + if (shaping.positionedGlyphs.size() != lineStartIndex) { + float lineLength = x - spacing; // Don't count trailing spacing + maxLineLength = util::max(lineLength, maxLineLength); + + justifyLine(shaping.positionedGlyphs, glyphs, lineStartIndex, + shaping.positionedGlyphs.size() - 1, justify); + } + + x = 0; + y += lineHeight; + } + + align(shaping, justify, horizontalAlign, verticalAlign, maxLineLength, lineHeight, + lines.size(), translate); + const uint32_t height = lines.size() * lineHeight; + + // Calculate the bounding box + shaping.top += -verticalAlign * height; + shaping.bottom = shaping.top + height; + shaping.left += -horizontalAlign * maxLineLength; + shaping.right = shaping.left + maxLineLength; +} + +const Shaping getShaping(const std::u16string& logicalInput, + const float maxWidth, + const float lineHeight, + const float horizontalAlign, + const float verticalAlign, + const float justify, + const float spacing, + const Point<float>& translate, + const float verticalHeight, + const WritingModeType writingMode, + BiDi& bidi, + const GlyphPositions& glyphs) { + Shaping shaping(translate.x, translate.y, writingMode); + + std::vector<std::u16string> reorderedLines = + bidi.processText(logicalInput, + determineLineBreaks(logicalInput, spacing, maxWidth, writingMode, glyphs)); + + shapeLines(shaping, reorderedLines, spacing, lineHeight, horizontalAlign, verticalAlign, + justify, translate, verticalHeight, writingMode, glyphs); + + return shaping; +} + + } // namespace mbgl diff --git a/src/mbgl/text/shaping.hpp b/src/mbgl/text/shaping.hpp index 1b7b8b2733..fc404e3a97 100644 --- a/src/mbgl/text/shaping.hpp +++ b/src/mbgl/text/shaping.hpp @@ -9,6 +9,7 @@ namespace mbgl { class SpriteAtlasElement; class SymbolFeature; +class BiDi; class PositionedIcon { public: @@ -37,5 +38,18 @@ public: }; PositionedIcon shapeIcon(const SpriteAtlasElement&, const std::array<float, 2>& iconOffset, const float iconRotation); + +const Shaping getShaping(const std::u16string& string, + float maxWidth, + float lineHeight, + float horizontalAlign, + float verticalAlign, + float justify, + float spacing, + const Point<float>& translate, + float verticalHeight, + const WritingModeType, + BiDi& bidi, + const GlyphPositions& glyphs); } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index 5ccc037ce0..4e67144082 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -18,6 +18,8 @@ #include <mbgl/style/query.hpp> #include <mbgl/util/logging.hpp> +#include <iostream> + namespace mbgl { using namespace style; @@ -32,12 +34,16 @@ GeometryTile::GeometryTile(const OverscaledTileID& id_, worker(parameters.workerScheduler, ActorRef<GeometryTile>(*this, mailbox), id_, - *parameters.style.glyphAtlas, obsolete, - parameters.mode) { + parameters.mode), + glyphAtlas(*parameters.style.glyphAtlas) { } GeometryTile::~GeometryTile() { + glyphAtlas.removeGlyphs(*this); + for (auto spriteAtlas : pendingSpriteAtlases) { + spriteAtlas->removeRequestor(*this); + } cancel(); } @@ -77,10 +83,6 @@ void GeometryTile::setPlacementConfig(const PlacementConfig& desiredConfig) { worker.invoke(&GeometryTileWorker::setPlacementConfig, desiredConfig, correlationID); } -void GeometryTile::symbolDependenciesChanged() { - worker.invoke(&GeometryTileWorker::symbolDependenciesChanged); -} - void GeometryTile::redoLayout() { // Mark the tile as pending again if it was complete before to prevent signaling a complete // state despite pending parse operations. @@ -130,6 +132,31 @@ void GeometryTile::onError(std::exception_ptr err) { availableData = DataAvailability::All; observer->onTileError(*this, err); } + +void GeometryTile::onGlyphsAvailable(GlyphPositionMap glyphPositions, GlyphRangeSet loadedRanges) { + worker.invoke(&GeometryTileWorker::onGlyphsAvailable, std::move(glyphPositions), std::move(loadedRanges)); +} + +void GeometryTile::getGlyphs(GlyphDependencies glyphDependencies) { + glyphAtlas.getGlyphs(*this, std::move(glyphDependencies)); +} + +void GeometryTile::onIconsAvailable(SpriteAtlas* spriteAtlas, IconMap icons) { + iconAtlasMap[(uintptr_t)spriteAtlas] = icons; + pendingSpriteAtlases.erase(spriteAtlas); + if (pendingSpriteAtlases.empty()) { + worker.invoke(&GeometryTileWorker::onIconsAvailable, std::move(iconAtlasMap)); + } +} + +// TODO: If there's any value to be gained by it, we can narrow our request to just the sprites +// we need, but SpriteAtlases are just "loaded" or "not loaded" +void GeometryTile::getIcons(IconDependencyMap iconDependencyMap) { + for (auto dependency : iconDependencyMap) { + pendingSpriteAtlases.insert(dependency.first); + dependency.first->getIcons(*this); + } +} Bucket* GeometryTile::getBucket(const Layer& layer) { const auto& buckets = layer.is<SymbolLayer>() ? symbolBuckets : nonSymbolBuckets; diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index cabe193467..f057e21507 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -1,7 +1,9 @@ #pragma once +#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/tile/tile.hpp> #include <mbgl/tile/geometry_tile_worker.hpp> +#include <mbgl/text/glyph_atlas.hpp> #include <mbgl/text/placement_config.hpp> #include <mbgl/util/feature.hpp> #include <mbgl/actor/actor.hpp> @@ -24,7 +26,7 @@ class UpdateParameters; class SourceQueryOptions; } // namespace style -class GeometryTile : public Tile { +class GeometryTile : public Tile, public GlyphRequestor, IconRequestor { public: GeometryTile(const OverscaledTileID&, std::string sourceID, @@ -36,8 +38,13 @@ public: void setData(std::unique_ptr<const GeometryTileData>); void setPlacementConfig(const PlacementConfig&) override; - void symbolDependenciesChanged() override; void redoLayout() override; + + void onGlyphsAvailable(GlyphPositionMap, GlyphRangeSet) override; + void onIconsAvailable(SpriteAtlas*, IconMap) override; + + void getGlyphs(GlyphDependencies); + void getIcons(IconDependencyMap); Bucket* getBucket(const style::Layer&) override; @@ -87,6 +94,10 @@ private: std::shared_ptr<Mailbox> mailbox; Actor<GeometryTileWorker> worker; + GlyphAtlas& glyphAtlas; + std::set<SpriteAtlas*> pendingSpriteAtlases; + IconAtlasMap iconAtlasMap; + uint64_t correlationID = 0; optional<PlacementConfig> requestedConfig; diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index b1fd7a852e..d1d4c9e9b8 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -2,8 +2,8 @@ #include <mbgl/tile/geometry_tile_data.hpp> #include <mbgl/tile/geometry_tile.hpp> #include <mbgl/text/collision_tile.hpp> -#include <mbgl/text/glyph_atlas.hpp> #include <mbgl/layout/symbol_layout.hpp> +#include <mbgl/sprite/sprite_atlas.hpp> #include <mbgl/style/bucket_parameters.hpp> #include <mbgl/style/group_by_layout.hpp> #include <mbgl/style/filter.hpp> @@ -25,19 +25,16 @@ using namespace style; GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_, ActorRef<GeometryTile> parent_, OverscaledTileID id_, - GlyphAtlas& glyphAtlas_, const std::atomic<bool>& obsolete_, const MapMode mode_) : self(std::move(self_)), parent(std::move(parent_)), id(std::move(id_)), - glyphAtlas(glyphAtlas_), obsolete(obsolete_), mode(mode_) { } GeometryTileWorker::~GeometryTileWorker() { - glyphAtlas.removeGlyphs(reinterpret_cast<uintptr_t>(this)); } /* @@ -147,14 +144,14 @@ void GeometryTileWorker::symbolDependenciesChanged() { try { switch (state) { case Idle: - if (hasPendingSymbolDependencies()) { + if (hasPendingSymbolLayouts()) { attemptPlacement(); coalesce(); } break; case Coalescing: - if (hasPendingSymbolDependencies()) { + if (hasPendingSymbolLayouts()) { state = NeedPlacement; } break; @@ -199,6 +196,71 @@ void GeometryTileWorker::coalesce() { self.invoke(&GeometryTileWorker::coalesced); } + +void GeometryTileWorker::onGlyphsAvailable(GlyphPositionMap newGlyphPositions, GlyphRangeSet loadedRanges) { + GlyphDependencies loadedGlyphs; + for (auto& pendingFontGlyphs : pendingGlyphDependencies) { + auto newFontGlyphs = newGlyphPositions.find(pendingFontGlyphs.first); + for (auto glyphID : pendingFontGlyphs.second) { + if (newFontGlyphs != newGlyphPositions.end()) { + auto newFontGlyph = newFontGlyphs->second.find(glyphID); + if (newFontGlyph != newFontGlyphs->second.end()) { + glyphPositions[pendingFontGlyphs.first].emplace(glyphID, newFontGlyph->second); + } + } + if (loadedRanges.find(getGlyphRange(glyphID)) != loadedRanges.end()) { + // Erase the glyph from our pending font set as long as its range is loaded + // If the glyph itself is missing, that means we can't get a glyph for + // this fontstack, and we go ahead and render with missing glyphs + loadedGlyphs[pendingFontGlyphs.first].insert(glyphID); + } + } + } + + for (auto& loadedFont : loadedGlyphs) { + for (auto loadedGlyph : loadedFont.second) { + pendingGlyphDependencies[loadedFont.first].erase(loadedGlyph); + } + } + symbolDependenciesChanged(); +} + +void GeometryTileWorker::onIconsAvailable(IconAtlasMap newIcons) { + for (auto& atlasIcons : newIcons) { + auto pendingAtlasIcons = pendingIconDependencies.find((SpriteAtlas*)atlasIcons.first); + if (pendingAtlasIcons != pendingIconDependencies.end()) { + icons[atlasIcons.first] = std::move(newIcons[atlasIcons.first]); + pendingIconDependencies.erase((SpriteAtlas*)atlasIcons.first); + } + } + symbolDependenciesChanged(); +} + +void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependencies) { + for (auto& fontDependencies : glyphDependencies) { + auto fontGlyphs = glyphPositions.find(fontDependencies.first); + for (auto glyphID : fontDependencies.second) { + if (fontGlyphs == glyphPositions.end() || fontGlyphs->second.find(glyphID) == fontGlyphs->second.end()) { + pendingGlyphDependencies[fontDependencies.first].insert(glyphID); + } + } + } + if (!pendingGlyphDependencies.empty()) { + parent.invoke(&GeometryTile::getGlyphs, pendingGlyphDependencies); + } +} + +void GeometryTileWorker::requestNewIcons(const IconDependencyMap &iconDependencies) { + for (auto& atlasDependency : iconDependencies) { + if (icons.find((uintptr_t)atlasDependency.first) == icons.end()) { + pendingIconDependencies[atlasDependency.first] = IconDependencies(); + } + } + if (!pendingIconDependencies.empty()) { + parent.invoke(&GeometryTile::getIcons, pendingIconDependencies); + } +} + void GeometryTileWorker::redoLayout() { if (!data || !layers) { return; @@ -215,6 +277,9 @@ void GeometryTileWorker::redoLayout() { std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets; auto featureIndex = std::make_unique<FeatureIndex>(); BucketParameters parameters { id, mode }; + + GlyphDependencies glyphDependencies; + IconDependencyMap iconDependencyMap; std::vector<std::vector<const Layer*>> groups = groupByLayout(*layers); for (auto& group : groups) { @@ -242,7 +307,7 @@ void GeometryTileWorker::redoLayout() { if (leader.is<SymbolLayer>()) { symbolLayoutMap.emplace(leader.getID(), - leader.as<SymbolLayer>()->impl->createLayout(parameters, group, *geometryLayer)); + leader.as<SymbolLayer>()->impl->createLayout(parameters, group, *geometryLayer, glyphDependencies, iconDependencyMap)); } else { const Filter& filter = leader.baseImpl->filter; const std::string& sourceLayerID = leader.baseImpl->sourceLayer; @@ -276,6 +341,9 @@ void GeometryTileWorker::redoLayout() { symbolLayouts.push_back(std::move(it->second)); } } + + requestNewGlyphs(glyphDependencies); + requestNewIcons(iconDependencyMap); parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult { std::move(buckets), @@ -287,46 +355,31 @@ void GeometryTileWorker::redoLayout() { attemptPlacement(); } -bool GeometryTileWorker::hasPendingSymbolDependencies() const { - bool result = false; - +bool GeometryTileWorker::hasPendingSymbolLayouts() const { for (const auto& symbolLayout : symbolLayouts) { if (symbolLayout->state == SymbolLayout::Pending) { - result = true; + return true; } } - return result; + return false; } -void GeometryTileWorker::attemptPlacement() { - if (!data || !layers || !placementConfig) { - return; - } - - bool canPlace = true; - - // Prepare as many SymbolLayouts as possible. - for (auto& symbolLayout : symbolLayouts) { - if (obsolete) { - return; - } - - if (symbolLayout->state == SymbolLayout::Pending) { - if (symbolLayout->canPrepare(glyphAtlas)) { - symbolLayout->state = SymbolLayout::Prepared; - symbolLayout->prepare(reinterpret_cast<uintptr_t>(this), - glyphAtlas); - } else { - canPlace = false; - } +bool GeometryTileWorker::hasPendingSymbolDependencies() const { + for (auto& glyphDependency : pendingGlyphDependencies) { + if (!glyphDependency.second.empty()) { + return true; } } + return !pendingIconDependencies.empty(); +} - if (!canPlace) { - return; // We'll be notified (via `setPlacementConfig`) when it's time to try again. - } +void GeometryTileWorker::attemptPlacement() { + if (!data || !layers || !placementConfig || hasPendingSymbolDependencies()) { + return; + } + auto collisionTile = std::make_unique<CollisionTile>(*placementConfig); std::unordered_map<std::string, std::shared_ptr<Bucket>> buckets; @@ -334,8 +387,12 @@ void GeometryTileWorker::attemptPlacement() { if (obsolete) { return; } - - symbolLayout->state = SymbolLayout::Placed; + + if (symbolLayout->state == SymbolLayout::Pending) { + symbolLayout->prepare(glyphPositions,icons); + symbolLayout->state = SymbolLayout::Placed; + } + if (!symbolLayout->hasSymbolInstances()) { continue; } diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 91bf81a697..39f4411e23 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -2,6 +2,8 @@ #include <mbgl/map/mode.hpp> #include <mbgl/tile/tile_id.hpp> +#include <mbgl/sprite/sprite_atlas.hpp> +#include <mbgl/text/glyph.hpp> #include <mbgl/text/placement_config.hpp> #include <mbgl/actor/actor_ref.hpp> #include <mbgl/util/optional.hpp> @@ -25,7 +27,6 @@ public: GeometryTileWorker(ActorRef<GeometryTileWorker> self, ActorRef<GeometryTile> parent, OverscaledTileID, - GlyphAtlas&, const std::atomic<bool>&, const MapMode); ~GeometryTileWorker(); @@ -33,20 +34,28 @@ public: void setLayers(std::vector<std::unique_ptr<style::Layer>>, uint64_t correlationID); void setData(std::unique_ptr<const GeometryTileData>, uint64_t correlationID); void setPlacementConfig(PlacementConfig, uint64_t correlationID); - void symbolDependenciesChanged(); + + void onGlyphsAvailable(GlyphPositionMap glyphs, GlyphRangeSet loadedRanges); + void onIconsAvailable(IconAtlasMap icons); private: - void coalesce(); void coalesced(); void redoLayout(); void attemptPlacement(); + + void coalesce(); + + void requestNewGlyphs(const GlyphDependencies&); + void requestNewIcons(const IconDependencyMap&); + + void symbolDependenciesChanged(); bool hasPendingSymbolDependencies() const; + bool hasPendingSymbolLayouts() const; ActorRef<GeometryTileWorker> self; ActorRef<GeometryTile> parent; const OverscaledTileID id; - GlyphAtlas& glyphAtlas; const std::atomic<bool>& obsolete; const MapMode mode; @@ -66,6 +75,10 @@ private: optional<PlacementConfig> placementConfig; std::vector<std::unique_ptr<SymbolLayout>> symbolLayouts; + GlyphDependencies pendingGlyphDependencies; + IconDependencyMap pendingIconDependencies; + GlyphPositionMap glyphPositions; + IconAtlasMap icons; }; } // namespace mbgl diff --git a/src/mbgl/tile/tile.hpp b/src/mbgl/tile/tile.hpp index 613b15f36c..a5f574b567 100644 --- a/src/mbgl/tile/tile.hpp +++ b/src/mbgl/tile/tile.hpp @@ -50,7 +50,6 @@ public: virtual Bucket* getBucket(const style::Layer&) = 0; virtual void setPlacementConfig(const PlacementConfig&) {} - virtual void symbolDependenciesChanged() {}; virtual void redoLayout() {} virtual void queryRenderedFeatures( diff --git a/src/mbgl/util/exclusive.hpp b/src/mbgl/util/exclusive.hpp deleted file mode 100644 index 844588dc90..0000000000 --- a/src/mbgl/util/exclusive.hpp +++ /dev/null @@ -1,27 +0,0 @@ -#pragma once - -#include <memory> -#include <mutex> - - -namespace mbgl { -namespace util { - -template <class T> -class exclusive { -public: - exclusive(T* val, std::unique_ptr<std::lock_guard<std::mutex>> mtx) : ptr(val), lock(std::move(mtx)) {} - - T* operator->() { return ptr; } - const T* operator->() const { return ptr; } - T* operator*() { return ptr; } - const T* operator*() const { return ptr; } - -private: - T *ptr; - std::unique_ptr<std::lock_guard<std::mutex>> lock; -}; - - -} // end namespace util -} // end namespace mbgl |