From 982d1971115ee0a3db418e5fd7550b8879ca018d Mon Sep 17 00:00:00 2001 From: German Khokhlov Date: Wed, 11 Nov 2026 00:25:31 +0400 Subject: [PATCH] Check the surface pass's time budget right before each tile sample The 4 ms budget was checked at the top of each point, but projecting the point and reading the terrain came before its sample, so on Cesium a pick render could start after the deadline. The time is checked again just before the sample once the pass has taken one. --- src/globe/surfacePlacement.js | 9 ++++++--- src/globe/surfacePlacement.test.mjs | 30 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/globe/surfacePlacement.js b/src/globe/surfacePlacement.js index eceafaa34..b90d150cc 100644 --- a/src/globe/surfacePlacement.js +++ b/src/globe/surfacePlacement.js @@ -219,9 +219,12 @@ export function createSurfacePoints( ) : point.position; if (!onScreen(position, size)) continue; - // The camera estimate above counts too: leave this point for the - // next pass rather than go over the budget. - if (samples >= SAMPLES_PER_PASS) { + // The camera estimate and the work since the check above count too: + // leave this point for the next pass rather than go over the budget. + if ( + samples >= SAMPLES_PER_PASS || + (samples > 0 && performance.now() - started >= SAMPLE_BUDGET_MS) + ) { point.surfaceRevision = previousRevision; yieldPass(); return; diff --git a/src/globe/surfacePlacement.test.mjs b/src/globe/surfacePlacement.test.mjs index 5bcd5c23d..d44adb5fb 100644 --- a/src/globe/surfacePlacement.test.mjs +++ b/src/globe/surfacePlacement.test.mjs @@ -263,3 +263,33 @@ test('placement makes progress when preparing a group exhausts the time budget', surface.place(); assert.equal(point.surfaceHeight, 50); }); + +test('no tile sample starts once the pass has used its time', async (t) => { + let now = 0; + const { engine, surface } = setup(t, 4); + t.mock.method(performance, 'now', () => now); + await engine.maps.setPhotorealTiles({}); + engine.fake.script('terrainHeight', 10); + // Each sample takes 1.5 ms; projecting the next point another 0.4 ms. + engine.fake.script('sampleHeight', () => { + now += 1.5; + return 50; + }); + const project = engine.projector.worldToWindow; + engine.projector.worldToWindow = (position) => { + now += 0.4; + return project(position); + }; + const starts = []; + const sample = engine.surface.sampleHeight; + engine.surface.sampleHeight = (...args) => { + starts.push(now); + return sample(...args); + }; + surface.place(); + assert.ok(starts.length >= 1); + assert.ok( + starts.slice(1).every((ms) => ms < 4), + `samples started at ${starts.join(', ')} ms`, + ); +});