1 #include <mbgl/tile/geometry_tile_worker.hpp>
2 #include <mbgl/tile/geometry_tile_data.hpp>
3 #include <mbgl/tile/geometry_tile.hpp>
4 #include <mbgl/layout/symbol_layout.hpp>
5 #include <mbgl/renderer/bucket_parameters.hpp>
6 #include <mbgl/renderer/group_by_layout.hpp>
7 #include <mbgl/style/filter.hpp>
8 #include <mbgl/style/layers/symbol_layer_impl.hpp>
9 #include <mbgl/renderer/layers/render_symbol_layer.hpp>
10 #include <mbgl/renderer/buckets/symbol_bucket.hpp>
11 #include <mbgl/util/logging.hpp>
12 #include <mbgl/util/constants.hpp>
13 #include <mbgl/util/string.hpp>
14 #include <mbgl/util/exception.hpp>
15 #include <mbgl/util/stopwatch.hpp>
16 
17 #include <unordered_set>
18 
19 namespace mbgl {
20 
21 using namespace style;
22 
GeometryTileWorker(ActorRef<GeometryTileWorker> self_,ActorRef<GeometryTile> parent_,OverscaledTileID id_,const std::string & sourceID_,const std::atomic<bool> & obsolete_,const MapMode mode_,const float pixelRatio_,const bool showCollisionBoxes_)23 GeometryTileWorker::GeometryTileWorker(ActorRef<GeometryTileWorker> self_,
24                                        ActorRef<GeometryTile> parent_,
25                                        OverscaledTileID id_,
26                                        const std::string& sourceID_,
27                                        const std::atomic<bool>& obsolete_,
28                                        const MapMode mode_,
29                                        const float pixelRatio_,
30                                        const bool showCollisionBoxes_)
31     : self(std::move(self_)),
32       parent(std::move(parent_)),
33       id(std::move(id_)),
34       sourceID(sourceID_),
35       obsolete(obsolete_),
36       mode(mode_),
37       pixelRatio(pixelRatio_),
38       showCollisionBoxes(showCollisionBoxes_) {
39 }
40 
41 GeometryTileWorker::~GeometryTileWorker() = default;
42 
43 /*
44    GeometryTileWorker is a state machine. This is its transition diagram.
45    States are indicated by [state], lines are transitions triggered by
46    messages, (parentheses) are actions taken on transition.
47 
48                          [Idle] <-------------------------.
49                             |                             |
50         set{Data,Layers}, symbolDependenciesChanged,      |
51                     setShowCollisionBoxes                 |
52                             |                             |
53    (do parse and/or symbol layout; self-send "coalesced") |
54                             v                             |
55                       [Coalescing] --- coalesced ---------.
56                          |   |
57              .-----------.   .---------------------.
58              |                                     |
59    .--- set{Data,Layers}                setShowCollisionBoxes,
60    |         |                         symbolDependenciesChanged --.
61    |         |                                     |               |
62    |         v                                     v               |
63    .-- [NeedsParse] <-- set{Data,Layers} -- [NeedsSymbolLayout] ---.
64              |                                     |
65          coalesced                             coalesced
66              |                                     |
67              v                                     v
68    (do parse or symbol layout; self-send "coalesced"; goto [coalescing])
69 
70    The idea is that in the [idle] state, parsing happens immediately in response to
71    a "set" message, and symbol layout happens once all symbol dependencies are met.
72    During this processing, multiple "set" messages might get queued in the mailbox.
73    At the end of processing, we self-send "coalesced", read all the queued messages
74    until we get to "coalesced", and then re-parse if there were one or more "set"s or
75    return to the [idle] state if not.
76 
77    One important goal of the design is to prevent starvation. Under heavy load new
78    requests for tiles should not prevent in progress request from completing.
79    It is nevertheless possible to restart an in-progress request:
80 
81     - [Idle] setData -> parse()
82         sends getGlyphs, hasPendingSymbolDependencies() is true
83         enters [Coalescing], sends coalesced
84     - [Coalescing] coalesced -> [Idle]
85     - [Idle] setData -> new parse(), interrupts old parse()
86         sends getGlyphs, hasPendingSymbolDependencies() is true
87         enters [Coalescing], sends coalesced
88     - [Coalescing] onGlyphsAvailable -> [NeedsSymbolLayout]
89            hasPendingSymbolDependencies() may or may not be true
90     - [NeedsSymbolLayout] coalesced -> performSymbolLayout()
91            Generates result depending on whether dependencies are met
92            -> [Idle]
93 
94    In this situation, we are counting on the idea that even with rapid changes to
95    the tile's data, the set of glyphs/images it requires will not keep growing without
96    limit.
97 
98    Although parsing (which populates all non-symbol buckets and requests dependencies
99    for symbol buckets) is internally separate from symbol layout, we only return
100    results to the foreground when we have completed both steps. Because we _move_
101    the result buckets to the foreground, it is necessary to re-generate all buckets from
102    scratch for `setShowCollisionBoxes`, even though it only affects symbol layers.
103 
104    The GL JS equivalent (in worker_tile.js and vector_tile_worker_source.js)
105    is somewhat simpler because it relies on getGlyphs/getImages calls that transfer
106    an entire set of glyphs/images on every tile load, while the native logic
107    maintains a local state that can be incrementally updated. Because each tile load
108    call becomes self-contained, the equivalent of the coalescing logic is handled by
109    'reloadTile' queueing a single extra 'reloadTile' callback to run after the next
110    completed parse.
111 */
112 
setData(std::unique_ptr<const GeometryTileData> data_,uint64_t correlationID_)113 void GeometryTileWorker::setData(std::unique_ptr<const GeometryTileData> data_, uint64_t correlationID_) {
114     try {
115         data = std::move(data_);
116         correlationID = correlationID_;
117 
118         switch (state) {
119         case Idle:
120             parse();
121             coalesce();
122             break;
123 
124         case Coalescing:
125         case NeedsParse:
126         case NeedsSymbolLayout:
127             state = NeedsParse;
128             break;
129         }
130     } catch (...) {
131         parent.invoke(&GeometryTile::onError, std::current_exception(), correlationID);
132     }
133 }
134 
setLayers(std::vector<Immutable<Layer::Impl>> layers_,uint64_t correlationID_)135 void GeometryTileWorker::setLayers(std::vector<Immutable<Layer::Impl>> layers_, uint64_t correlationID_) {
136     try {
137         layers = std::move(layers_);
138         correlationID = correlationID_;
139 
140         switch (state) {
141         case Idle:
142             parse();
143             coalesce();
144             break;
145 
146         case Coalescing:
147         case NeedsSymbolLayout:
148             state = NeedsParse;
149             break;
150 
151         case NeedsParse:
152             break;
153         }
154     } catch (...) {
155         parent.invoke(&GeometryTile::onError, std::current_exception(), correlationID);
156     }
157 }
158 
setShowCollisionBoxes(bool showCollisionBoxes_,uint64_t correlationID_)159 void GeometryTileWorker::setShowCollisionBoxes(bool showCollisionBoxes_, uint64_t correlationID_) {
160     try {
161         showCollisionBoxes = showCollisionBoxes_;
162         correlationID = correlationID_;
163 
164         switch (state) {
165         case Idle:
166             if (!hasPendingParseResult()) {
167                 // Trigger parse if nothing is in flight, otherwise symbol layout will automatically
168                 // pick up the change
169                 parse();
170                 coalesce();
171             }
172             break;
173 
174         case Coalescing:
175             state = NeedsSymbolLayout;
176             break;
177 
178         case NeedsSymbolLayout:
179         case NeedsParse:
180             break;
181         }
182     } catch (...) {
183         parent.invoke(&GeometryTile::onError, std::current_exception(), correlationID);
184     }
185 }
186 
symbolDependenciesChanged()187 void GeometryTileWorker::symbolDependenciesChanged() {
188     try {
189         switch (state) {
190         case Idle:
191             if (symbolLayoutsNeedPreparation) {
192                 // symbolLayoutsNeedPreparation can only be set true by parsing
193                 // and the parse result can only be cleared by performSymbolLayout
194                 // which also clears symbolLayoutsNeedPreparation
195                 assert(hasPendingParseResult());
196                 performSymbolLayout();
197                 coalesce();
198             }
199             break;
200 
201         case Coalescing:
202             if (symbolLayoutsNeedPreparation) {
203                 state = NeedsSymbolLayout;
204             }
205             break;
206 
207         case NeedsSymbolLayout:
208         case NeedsParse:
209             break;
210         }
211     } catch (...) {
212         parent.invoke(&GeometryTile::onError, std::current_exception(), correlationID);
213     }
214 }
215 
coalesced()216 void GeometryTileWorker::coalesced() {
217     try {
218         switch (state) {
219         case Idle:
220             assert(false);
221             break;
222 
223         case Coalescing:
224             state = Idle;
225             break;
226 
227         case NeedsParse:
228             parse();
229             coalesce();
230             break;
231 
232         case NeedsSymbolLayout:
233             // We may have entered NeedsSymbolLayout while coalescing
234             // after a performSymbolLayout. In that case, we need to
235             // start over with parsing in order to do another layout.
236             hasPendingParseResult() ? performSymbolLayout() : parse();
237             coalesce();
238             break;
239         }
240     } catch (...) {
241         parent.invoke(&GeometryTile::onError, std::current_exception(), correlationID);
242     }
243 }
244 
coalesce()245 void GeometryTileWorker::coalesce() {
246     state = Coalescing;
247     self.invoke(&GeometryTileWorker::coalesced);
248 }
249 
onGlyphsAvailable(GlyphMap newGlyphMap)250 void GeometryTileWorker::onGlyphsAvailable(GlyphMap newGlyphMap) {
251     for (auto& newFontGlyphs : newGlyphMap) {
252         const FontStack& fontStack = newFontGlyphs.first;
253         Glyphs& newGlyphs = newFontGlyphs.second;
254 
255         Glyphs& glyphs = glyphMap[fontStack];
256         GlyphIDs& pendingGlyphIDs = pendingGlyphDependencies[fontStack];
257 
258         for (auto& newGlyph : newGlyphs) {
259             const GlyphID& glyphID = newGlyph.first;
260             optional<Immutable<Glyph>>& glyph = newGlyph.second;
261 
262             if (pendingGlyphIDs.erase(glyphID)) {
263                 glyphs.emplace(glyphID, std::move(glyph));
264             }
265         }
266     }
267     symbolDependenciesChanged();
268 }
269 
onImagesAvailable(ImageMap newImageMap,uint64_t imageCorrelationID_)270 void GeometryTileWorker::onImagesAvailable(ImageMap newImageMap, uint64_t imageCorrelationID_) {
271     if (imageCorrelationID != imageCorrelationID_) {
272         return; // Ignore outdated image request replies.
273     }
274     imageMap = std::move(newImageMap);
275     pendingImageDependencies.clear();
276     symbolDependenciesChanged();
277 }
278 
requestNewGlyphs(const GlyphDependencies & glyphDependencies)279 void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependencies) {
280     for (auto& fontDependencies : glyphDependencies) {
281         auto fontGlyphs = glyphMap.find(fontDependencies.first);
282         for (auto glyphID : fontDependencies.second) {
283             if (fontGlyphs == glyphMap.end() || fontGlyphs->second.find(glyphID) == fontGlyphs->second.end()) {
284                 pendingGlyphDependencies[fontDependencies.first].insert(glyphID);
285             }
286         }
287     }
288     if (!pendingGlyphDependencies.empty()) {
289         parent.invoke(&GeometryTile::getGlyphs, pendingGlyphDependencies);
290     }
291 }
292 
requestNewImages(const ImageDependencies & imageDependencies)293 void GeometryTileWorker::requestNewImages(const ImageDependencies& imageDependencies) {
294     pendingImageDependencies = imageDependencies;
295     if (!pendingImageDependencies.empty()) {
296         parent.invoke(&GeometryTile::getImages, std::make_pair(pendingImageDependencies, ++imageCorrelationID));
297     }
298 }
299 
toRenderLayers(const std::vector<Immutable<style::Layer::Impl>> & layers,float zoom)300 static std::vector<std::unique_ptr<RenderLayer>> toRenderLayers(const std::vector<Immutable<style::Layer::Impl>>& layers, float zoom) {
301     std::vector<std::unique_ptr<RenderLayer>> renderLayers;
302     renderLayers.reserve(layers.size());
303     for (auto& layer : layers) {
304         renderLayers.push_back(RenderLayer::create(layer));
305 
306         renderLayers.back()->transition(TransitionParameters {
307             Clock::time_point::max(),
308             TransitionOptions()
309         });
310 
311         renderLayers.back()->evaluate(PropertyEvaluationParameters {
312             zoom
313         });
314     }
315     return renderLayers;
316 }
317 
parse()318 void GeometryTileWorker::parse() {
319     if (!data || !layers) {
320         return;
321     }
322 
323     MBGL_TIMING_START(watch)
324     std::vector<std::string> symbolOrder;
325     for (auto it = layers->rbegin(); it != layers->rend(); it++) {
326         if ((*it)->type == LayerType::Symbol) {
327             symbolOrder.push_back((*it)->id);
328         }
329     }
330 
331     std::unordered_map<std::string, std::unique_ptr<SymbolLayout>> symbolLayoutMap;
332     buckets.clear();
333     featureIndex = std::make_unique<FeatureIndex>(*data ? (*data)->clone() : nullptr);
334     BucketParameters parameters { id, mode, pixelRatio };
335 
336     GlyphDependencies glyphDependencies;
337     ImageDependencies imageDependencies;
338 
339     // Create render layers and group by layout
340     std::vector<std::unique_ptr<RenderLayer>> renderLayers = toRenderLayers(*layers, id.overscaledZ);
341     std::vector<std::vector<const RenderLayer*>> groups = groupByLayout(renderLayers);
342 
343     for (auto& group : groups) {
344         if (obsolete) {
345             return;
346         }
347 
348         if (!*data) {
349             continue; // Tile has no data.
350         }
351 
352         const RenderLayer& leader = *group.at(0);
353 
354         auto geometryLayer = (*data)->getLayer(leader.baseImpl->sourceLayer);
355         if (!geometryLayer) {
356             continue;
357         }
358 
359         std::vector<std::string> layerIDs;
360         for (const auto& layer : group) {
361             layerIDs.push_back(layer->getID());
362         }
363 
364         featureIndex->setBucketLayerIDs(leader.getID(), layerIDs);
365 
366         if (leader.is<RenderSymbolLayer>()) {
367             auto layout = leader.as<RenderSymbolLayer>()->createLayout(
368                 parameters, group, std::move(geometryLayer), glyphDependencies, imageDependencies);
369             symbolLayoutMap.emplace(leader.getID(), std::move(layout));
370             symbolLayoutsNeedPreparation = true;
371         } else {
372             const Filter& filter = leader.baseImpl->filter;
373             const std::string& sourceLayerID = leader.baseImpl->sourceLayer;
374             std::shared_ptr<Bucket> bucket = leader.createBucket(parameters, group);
375 
376             for (std::size_t i = 0; !obsolete && i < geometryLayer->featureCount(); i++) {
377                 std::unique_ptr<GeometryTileFeature> feature = geometryLayer->getFeature(i);
378 
379                 if (!filter(expression::EvaluationContext { static_cast<float>(this->id.overscaledZ), feature.get() }))
380                     continue;
381 
382                 GeometryCollection geometries = feature->getGeometries();
383                 bucket->addFeature(*feature, geometries);
384                 featureIndex->insert(geometries, i, sourceLayerID, leader.getID());
385             }
386 
387             if (!bucket->hasData()) {
388                 continue;
389             }
390 
391             for (const auto& layer : group) {
392                 buckets.emplace(layer->getID(), bucket);
393             }
394         }
395     }
396 
397     symbolLayouts.clear();
398     for (const auto& symbolLayerID : symbolOrder) {
399         auto it = symbolLayoutMap.find(symbolLayerID);
400         if (it != symbolLayoutMap.end()) {
401             symbolLayouts.push_back(std::move(it->second));
402         }
403     }
404 
405     requestNewGlyphs(glyphDependencies);
406     requestNewImages(imageDependencies);
407 
408     MBGL_TIMING_FINISH(watch,
409                        " Action: " << "Parsing," <<
410                        " SourceID: " << sourceID.c_str() <<
411                        " Canonical: " << static_cast<int>(id.canonical.z) << "/" << id.canonical.x << "/" << id.canonical.y <<
412                        " Time");
413     performSymbolLayout();
414 }
415 
hasPendingSymbolDependencies() const416 bool GeometryTileWorker::hasPendingSymbolDependencies() const {
417     for (auto& glyphDependency : pendingGlyphDependencies) {
418         if (!glyphDependency.second.empty()) {
419             return true;
420         }
421     }
422     return !pendingImageDependencies.empty();
423 }
424 
hasPendingParseResult() const425 bool GeometryTileWorker::hasPendingParseResult() const {
426     return bool(featureIndex);
427 }
428 
performSymbolLayout()429 void GeometryTileWorker::performSymbolLayout() {
430     if (!data || !layers || !hasPendingParseResult() || hasPendingSymbolDependencies()) {
431         return;
432     }
433 
434     MBGL_TIMING_START(watch)
435     optional<AlphaImage> glyphAtlasImage;
436     optional<PremultipliedImage> iconAtlasImage;
437 
438     if (symbolLayoutsNeedPreparation) {
439         GlyphAtlas glyphAtlas = makeGlyphAtlas(glyphMap);
440         ImageAtlas imageAtlas = makeImageAtlas(imageMap);
441 
442         glyphAtlasImage = std::move(glyphAtlas.image);
443         iconAtlasImage = std::move(imageAtlas.image);
444 
445         for (auto& symbolLayout : symbolLayouts) {
446             if (obsolete) {
447                 return;
448             }
449 
450             symbolLayout->prepare(glyphMap, glyphAtlas.positions,
451                                   imageMap, imageAtlas.positions);
452         }
453 
454         symbolLayoutsNeedPreparation = false;
455     }
456 
457     for (auto& symbolLayout : symbolLayouts) {
458         if (obsolete) {
459             return;
460         }
461 
462         if (!symbolLayout->hasSymbolInstances()) {
463             continue;
464         }
465 
466         std::shared_ptr<SymbolBucket> bucket = symbolLayout->place(showCollisionBoxes);
467         for (const auto& pair : symbolLayout->layerPaintProperties) {
468             if (!firstLoad) {
469                 bucket->justReloaded = true;
470             }
471             buckets.emplace(pair.first, bucket);
472         }
473     }
474 
475     firstLoad = false;
476 
477     MBGL_TIMING_FINISH(watch,
478                        " Action: " << "SymbolLayout," <<
479                        " SourceID: " << sourceID.c_str() <<
480                        " Canonical: " << static_cast<int>(id.canonical.z) << "/" << id.canonical.x << "/" << id.canonical.y <<
481                        " Time");
482     parent.invoke(&GeometryTile::onLayout, GeometryTile::LayoutResult {
483         std::move(buckets),
484         std::move(featureIndex),
485         std::move(glyphAtlasImage),
486         std::move(iconAtlasImage)
487     }, correlationID);
488 }
489 
490 } // namespace mbgl
491