Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/engine/Source/Core/VectorPipeline.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ const scratchSegmentEnd = new Cartesian2();
* @typedef {object} VectorTileData
*
* @property {boolean} show Whether this vector data should be rendered.
* @property {number} [changeCount] Provider change count this data was last
* validated against, managed by VectorProvider.
*
* Stage 1: Collect vector segments intersecting tile.
* @property {number[][]} [segments]
Expand Down
42 changes: 37 additions & 5 deletions packages/engine/Source/Core/VectorProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,21 @@ class VectorProvider {
* @private
*/
this._dirtyRectangles = [];

/**
* Total number of dirty regions ever recorded. Tile data is stamped with
* this value when validated, so a tile that missed regions cleared while
* it was not rendered can be detected and re-baked.
* @private
*/
this._changeCount = 0;

/**
* Value of {@link VectorProvider#_changeCount} at the last
* {@link VectorProvider#makeClean}.
* @private
*/
this._changeCountAtClean = 0;
}

/** @type {TilingScheme} */
Expand Down Expand Up @@ -160,6 +175,7 @@ class VectorProvider {
new Rectangle(),
);
this._dirtyRectangles.push(collectionRectangle);
this._changeCount++;
}

/**
Expand All @@ -175,7 +191,7 @@ class VectorProvider {
const width = Rectangle.computeWidth(tileRectangle);

/** @type {VectorTileData} */
const result = { show: true };
const result = { show: true, changeCount: this._changeCount };

for (const collection of this._collections) {
const collectionRectangle = Rectangle.fromBoundingSphere(
Expand Down Expand Up @@ -228,7 +244,9 @@ class VectorProvider {
* Re-bakes a tile's vector data if the tile overlaps a region changed since
* the last {@link VectorProvider#makeClean}, releasing the previous data.
* Returns the current data unchanged when the tile is outside every changed
* region.
* region. A tile whose data predates regions already cleared — because the
* tile was not rendered while they were consumed — is re-baked
* conservatively.
*
* @param {number} x
* @param {number} y
Expand All @@ -238,9 +256,22 @@ class VectorProvider {
* @returns {VectorTileData|undefined}
*/
updateTileData(x, y, level, context, currentData) {
const dirtyRectangles = this._dirtyRectangles;
const tilingScheme = this._tilingScheme;
if (!intersectRectangles(x, y, level, dirtyRectangles, tilingScheme)) {
const validated =
defined(currentData) &&
currentData.changeCount >= this._changeCountAtClean;

if (
validated &&
!intersectRectangles(
x,
y,
level,
this._dirtyRectangles,
this._tilingScheme,
)
) {
// Now validated against every recorded change.
currentData.changeCount = this._changeCount;
return currentData;
}

Expand All @@ -257,6 +288,7 @@ class VectorProvider {
*/
makeClean() {
this._dirtyRectangles.length = 0;
this._changeCountAtClean = this._changeCount;
}

/**
Expand Down
39 changes: 30 additions & 9 deletions packages/engine/Specs/Core/VectorProviderSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,9 @@ describe("Core/VectorProvider", function () {
it("returns hidden vector data with no collections", function () {
const provider = new VectorProvider({ tilingScheme });
const xy = tilingScheme.positionToTileXY(lineMidpoint, level);
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual({
show: false,
});
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual(
jasmine.objectContaining({ show: false }),
);
});

it("returns packed lookup data for a tile overlapping a polyline", function () {
Expand Down Expand Up @@ -103,9 +103,9 @@ describe("Core/VectorProvider", function () {
provider.add(createPolylineCollection());

const xy = tilingScheme.positionToTileXY(farPoint, level);
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual({
show: false,
});
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual(
jasmine.objectContaining({ show: false }),
);
});

it("stops returning data after a collection is removed", function () {
Expand All @@ -115,9 +115,9 @@ describe("Core/VectorProvider", function () {
provider.remove(collection);

const xy = tilingScheme.positionToTileXY(lineMidpoint, level);
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual({
show: false,
});
expect(provider.requestTileData(xy.x, xy.y, level, context)).toEqual(
jasmine.objectContaining({ show: false }),
);
});

it("keeps existing tile data when no dirty regions are recorded", function () {
Expand Down Expand Up @@ -156,6 +156,27 @@ describe("Core/VectorProvider", function () {
expect(updated.show).toBe(true);
});

it("re-bakes a tile that missed dirty regions consumed while it was not rendered", function () {
const provider = new VectorProvider({ tilingScheme });
const collection = createPolylineCollection();
provider.add(collection);

const xy = tilingScheme.positionToTileXY(lineMidpoint, level);
const data = provider.requestTileData(xy.x, xy.y, level, context);
provider.makeClean();

// The collection is removed while the tile is outside the rendered set;
// another frame's pass consumes and clears the dirty region.
provider.remove(collection);
provider.update();
provider.makeClean();

// When the tile re-enters the rendered set it must not keep stale data.
const updated = provider.updateTileData(xy.x, xy.y, level, context, data);
expect(updated).not.toBe(data);
expect(updated.show).toBe(false);
});

it("records and clears a dirty rectangle for a collection with a local region", function () {
const provider = new VectorProvider({ tilingScheme });
const collection = new BufferPolylineCollection({
Expand Down