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
19namespace mbgl {
20
21using namespace style;
22
23GeometryTileWorker::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
41GeometryTileWorker::~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
113void 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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID);
132 }
133}
134
135void 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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID);
156 }
157}
158
159void 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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID);
184 }
185}
186
187void 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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID);
213 }
214}
215
216void 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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID);
242 }
243}
244
245void GeometryTileWorker::coalesce() {
246 state = Coalescing;
247 self.invoke(fn: &GeometryTileWorker::coalesced);
248}
249
250void 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(x: glyphID)) {
263 glyphs.emplace(args: glyphID, args: std::move(glyph));
264 }
265 }
266 }
267 symbolDependenciesChanged();
268}
269
270void 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
279void GeometryTileWorker::requestNewGlyphs(const GlyphDependencies& glyphDependencies) {
280 for (auto& fontDependencies : glyphDependencies) {
281 auto fontGlyphs = glyphMap.find(x: fontDependencies.first);
282 for (auto glyphID : fontDependencies.second) {
283 if (fontGlyphs == glyphMap.end() || fontGlyphs->second.find(x: glyphID) == fontGlyphs->second.end()) {
284 pendingGlyphDependencies[fontDependencies.first].insert(x: glyphID);
285 }
286 }
287 }
288 if (!pendingGlyphDependencies.empty()) {
289 parent.invoke(fn: &GeometryTile::getGlyphs, args&: pendingGlyphDependencies);
290 }
291}
292
293void GeometryTileWorker::requestNewImages(const ImageDependencies& imageDependencies) {
294 pendingImageDependencies = imageDependencies;
295 if (!pendingImageDependencies.empty()) {
296 parent.invoke(fn: &GeometryTile::getImages, args: std::make_pair(x&: pendingImageDependencies, y&: ++imageCorrelationID));
297 }
298}
299
300static 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(n: layers.size());
303 for (auto& layer : layers) {
304 renderLayers.push_back(x: RenderLayer::create(layer));
305
306 renderLayers.back()->transition(TransitionParameters {
307 .now: Clock::time_point::max(),
308 .transition: TransitionOptions()
309 });
310
311 renderLayers.back()->evaluate(PropertyEvaluationParameters {
312 zoom
313 });
314 }
315 return renderLayers;
316}
317
318void 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(x: (*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>(args: *data ? (*data)->clone() : nullptr);
334 BucketParameters parameters { .tileID: id, .mode: mode, .pixelRatio: 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: *layers, zoom: 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(n: 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(x: layer->getID());
362 }
363
364 featureIndex->setBucketLayerIDs(bucketLeaderID: 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(args: leader.getID(), args: 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, index: i, sourceLayerName: sourceLayerID, bucketLeaderID: leader.getID());
385 }
386
387 if (!bucket->hasData()) {
388 continue;
389 }
390
391 for (const auto& layer : group) {
392 buckets.emplace(args: layer->getID(), args&: bucket);
393 }
394 }
395 }
396
397 symbolLayouts.clear();
398 for (const auto& symbolLayerID : symbolOrder) {
399 auto it = symbolLayoutMap.find(x: symbolLayerID);
400 if (it != symbolLayoutMap.end()) {
401 symbolLayouts.push_back(x: 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
416bool GeometryTileWorker::hasPendingSymbolDependencies() const {
417 for (auto& glyphDependency : pendingGlyphDependencies) {
418 if (!glyphDependency.second.empty()) {
419 return true;
420 }
421 }
422 return !pendingImageDependencies.empty();
423}
424
425bool GeometryTileWorker::hasPendingParseResult() const {
426 return bool(featureIndex);
427}
428
429void 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(args: pair.first, args&: 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(fn: &GeometryTile::onLayout, args: GeometryTile::LayoutResult {
483 std::move(buckets),
484 std::move(featureIndex),
485 std::move(glyphAtlasImage),
486 std::move(iconAtlasImage)
487 }, args&: correlationID);
488}
489
490} // namespace mbgl
491

source code of qtlocation/src/3rdparty/mapbox-gl-native/src/mbgl/tile/geometry_tile_worker.cpp