From 6775cdeed5c36933ea34a1e6891e8b9cfac3938a Mon Sep 17 00:00:00 2001 From: "yanjin.li" Date: Thu, 23 Apr 2026 11:22:54 +0800 Subject: [PATCH 1/2] feat(tile): implement progressive tile loading to reduce white-screen time MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Emit geometry (fill/line/circle) buckets immediately after parse() completes, before glyph/image dependencies for symbol layers are resolved. This lets tiles appear on screen with road/polygon/water data right away; text and icons follow in a second full onLayout call once all deps are ready. - GeometryTileWorker: add comingFromParse flag; finalizeLayout() emits a partial LayoutResult (isPartialResult=true) on first load when deps are still pending and renderData is non-empty (geometry-only buckets available) - GeometryTile: onLayout() accepts isPartialResult — suppresses pending=false and EndParse event for partial results so the tile stays pending until symbols arrive Co-Authored-By: Claude Sonnet 4.6 --- src/mbgl/tile/geometry_tile.cpp | 13 +++++++-- src/mbgl/tile/geometry_tile.hpp | 4 ++- src/mbgl/tile/geometry_tile_worker.cpp | 40 ++++++++++++++++++++++++-- src/mbgl/tile/geometry_tile_worker.hpp | 5 ++++ 4 files changed, 56 insertions(+), 6 deletions(-) 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..0fba3b7faba7 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,35 @@ 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 +588,7 @@ void GeometryTileWorker::finalizeLayout() { if (obsolete) { dynamicTextureAtlas->removeTextures(glyphAtlas.textureHandles, glyphAtlas.dynamicTexture); dynamicTextureAtlas->removeTextures(imageAtlas.textureHandles, imageAtlas.dynamicTexture); + comingFromParse = false; return; } @@ -566,7 +598,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 +607,7 @@ void GeometryTileWorker::finalizeLayout() { layouts.clear(); firstLoad = false; + comingFromParse = false; MBGL_TIMING_FINISH(watch, " Action: " << "SymbolLayout," @@ -588,7 +621,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; From 2e32d85c1ac94f058e3c8f6a4042b13c9704b996 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 23 Apr 2026 03:54:40 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/mbgl/tile/geometry_tile_worker.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/mbgl/tile/geometry_tile_worker.cpp b/src/mbgl/tile/geometry_tile_worker.cpp index 0fba3b7faba7..645fd12d46ed 100644 --- a/src/mbgl/tile/geometry_tile_worker.cpp +++ b/src/mbgl/tile/geometry_tile_worker.cpp @@ -561,11 +561,8 @@ void GeometryTileWorker::finalizeLayout() { // 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), + std::make_shared( + std::move(partialRenderData), nullptr, gfx::GlyphAtlas{}, gfx::ImageAtlas{}, nullptr), correlationID, /*isPartialResult=*/true); }