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 | |
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 | |
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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID); |
132 | } |
133 | } |
134 | |
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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID); |
156 | } |
157 | } |
158 | |
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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID); |
184 | } |
185 | } |
186 | |
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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID); |
213 | } |
214 | } |
215 | |
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(fn: &GeometryTile::onError, args: std::current_exception(), args&: correlationID); |
242 | } |
243 | } |
244 | |
245 | void GeometryTileWorker::coalesce() { |
246 | state = Coalescing; |
247 | self.invoke(fn: &GeometryTileWorker::coalesced); |
248 | } |
249 | |
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(x: glyphID)) { |
263 | glyphs.emplace(args: glyphID, args: std::move(glyph)); |
264 | } |
265 | } |
266 | } |
267 | symbolDependenciesChanged(); |
268 | } |
269 | |
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 | |
279 | void 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 | |
293 | void 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 | |
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(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 | |
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(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 | |
416 | 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 | |
425 | bool GeometryTileWorker::hasPendingParseResult() const { |
426 | return bool(featureIndex); |
427 | } |
428 | |
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(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 | |