diff --git a/src/mbgl/tile/geometry_tile.cpp b/src/mbgl/tile/geometry_tile.cpp index d51724fcbd29..9381c850998a 100644 --- a/src/mbgl/tile/geometry_tile.cpp +++ b/src/mbgl/tile/geometry_tile.cpp @@ -332,12 +332,21 @@ void GeometryTile::setShowCollisionBoxes(const bool showCollisionBoxes_) { } } -void GeometryTile::onLayout(std::shared_ptr result, const uint64_t resultCorrelationID) { +void GeometryTile::onLayout(std::shared_ptr result, + const uint64_t resultCorrelationID, + bool isPartialResult) { MLN_TRACE_FUNC(); + // Partial progressive updates are optional visual improvements. Ignore stale + // partial callbacks to avoid applying outdated buckets and triggering + // redundant tile updates while a newer parse cycle is already in progress. + if (isPartialResult && resultCorrelationID != correlationID) { + return; + } + loaded = true; renderable = true; - if (resultCorrelationID == correlationID) { + if (!isPartialResult && resultCorrelationID == correlationID) { pending = false; observer->onTileAction(id, sourceID, TileOperation::EndParse); } diff --git a/src/mbgl/tile/geometry_tile.hpp b/src/mbgl/tile/geometry_tile.hpp index 3b7a2e0035f5..f76dcf025930 100644 --- a/src/mbgl/tile/geometry_tile.hpp +++ b/src/mbgl/tile/geometry_tile.hpp @@ -89,7 +89,9 @@ class GeometryTile : public Tile, public GlyphRequestor, public ImageRequestor { ~LayoutResult(); }; - void onLayout(std::shared_ptr, uint64_t correlationID); + // isPartialResult=true means only geometry (fill/line/circle) buckets are present; + // symbol buckets are still pending and will arrive in a subsequent full onLayout call. + void onLayout(std::shared_ptr, uint64_t correlationID, bool isPartialResult = false); void onError(std::exception_ptr, uint64_t correlationID); diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 24fb226611f6..645fd12d46ed 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -519,6 +519,9 @@ void GeometryTileWorker::parse() { << " SourceID: " << sourceID.c_str() << " Canonical: " << static_cast(id.canonical.z) << "/" << id.canonical.x << "/" << id.canonical.y << " Time"); + // Mark that the upcoming finalizeLayout() call is triggered directly by parse so + // it can emit a partial (geometry-only) result for progressive rendering. + comingFromParse = true; finalizeLayout(); } @@ -538,7 +541,32 @@ bool GeometryTileWorker::hasPendingParseResult() const { void GeometryTileWorker::finalizeLayout() { MLN_TRACE_FUNC(); - if (!data || !layers || !hasPendingParseResult() || hasPendingDependencies()) { + if (!data || !layers || !hasPendingParseResult()) { + comingFromParse = false; + return; + } + + if (hasPendingDependencies()) { + // Progressive early render: on the first load of a tile, if parse just completed + // (comingFromParse=true) but symbol glyph/image dependencies are still pending, + // emit a partial LayoutResult containing only the geometry (fill/line/circle) + // buckets that are already ready. This lets the tile appear on screen immediately + // without waiting for text/icon resources, reducing white-screen time. + // + // The symbol buckets will arrive in a subsequent full onLayout call once all + // dependencies have been resolved. + if (firstLoad && comingFromParse && !renderData.empty()) { + // renderData holds only non-symbol buckets at this point: symbol layouts + // with dependencies were deferred into `layouts` rather than being added to + // renderData directly. Copy the map cheaply — buckets are shared_ptr. + auto partialRenderData = renderData; + parent.invoke(&GeometryTile::onLayout, + std::make_shared( + std::move(partialRenderData), nullptr, gfx::GlyphAtlas{}, gfx::ImageAtlas{}, nullptr), + correlationID, + /*isPartialResult=*/true); + } + comingFromParse = false; return; } @@ -557,6 +585,7 @@ void GeometryTileWorker::finalizeLayout() { if (obsolete) { dynamicTextureAtlas->removeTextures(glyphAtlas.textureHandles, glyphAtlas.dynamicTexture); dynamicTextureAtlas->removeTextures(imageAtlas.textureHandles, imageAtlas.dynamicTexture); + comingFromParse = false; return; } @@ -566,7 +595,7 @@ void GeometryTileWorker::finalizeLayout() { continue; } - // layout adds the bucket to buckets + // layout adds the bucket to renderData layout->createBucket( imageAtlas.patternPositions, featureIndex, renderData, firstLoad, showCollisionBoxes, id.canonical); } @@ -575,6 +604,7 @@ void GeometryTileWorker::finalizeLayout() { layouts.clear(); firstLoad = false; + comingFromParse = false; MBGL_TIMING_FINISH(watch, " Action: " << "SymbolLayout," @@ -588,7 +618,8 @@ void GeometryTileWorker::finalizeLayout() { std::move(glyphAtlas), std::move(imageAtlas), dynamicTextureAtlas), - correlationID); + correlationID, + /*isPartialResult=*/false); } } // namespace mbgl diff --git a/src/mbgl/tile/geometry_tile_worker.hpp b/src/mbgl/tile/geometry_tile_worker.hpp index 53378aca1f87..40ecfebc4124 100644 --- a/src/mbgl/tile/geometry_tile_worker.hpp +++ b/src/mbgl/tile/geometry_tile_worker.hpp @@ -119,6 +119,11 @@ class GeometryTileWorker { bool showCollisionBoxes; bool firstLoad = true; + // Set to true at the end of parse() and reset after finalizeLayout() returns. + // When true, finalizeLayout() will emit a partial (geometry-only) LayoutResult + // immediately without waiting for symbol glyph/image dependencies to resolve, + // reducing the white-screen time on first tile load. + bool comingFromParse = false; gfx::DynamicTextureAtlasPtr dynamicTextureAtlas;