Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce symbol cross fading when crossing integer zoom levels. #6951

Merged
merged 2 commits into from
Jul 16, 2018
Merged
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
12 changes: 10 additions & 2 deletions src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,7 @@ class Painter {
{
let sourceCache;
let coords = [];
let symbolCoords = [];

this.currentLayer = 0;

Expand All @@ -382,19 +383,26 @@ class Painter {
if (layer.source !== (sourceCache && sourceCache.id)) {
sourceCache = this.style.sourceCaches[layer.source];
coords = [];
symbolCoords = [];

if (sourceCache) {
this.clearStencil();
coords = sourceCache.getVisibleCoordinates();
coords = sourceCache.getVisibleCoordinates(false);
// For symbol layers in the translucent pass, we add extra tiles to
// the renderable set for cross-tile symbol fading.
// Symbol layers don't use tile clipping, so no need to render
// separate clipping masks
symbolCoords = sourceCache.getVisibleCoordinates(true);
if (sourceCache.getSource().isTileClipped) {
this._renderTileClippingMasks(coords);
}
}

coords.reverse();
symbolCoords.reverse();
}

this.renderLayer(this, (sourceCache: any), layer, coords);
this.renderLayer(this, (sourceCache: any), layer, layer.type === 'symbol' ? symbolCoords : coords);
}
}

Expand Down
42 changes: 36 additions & 6 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class SourceCache extends Evented {
_coveredTiles: {[any]: boolean};
transform: Transform;
_isIdRenderable: (id: number) => boolean;
_isIdRenderableForSymbols: (id: number) => boolean;
used: boolean;
_state: SourceFeatureState

Expand Down Expand Up @@ -94,6 +95,7 @@ class SourceCache extends Evented {
this._maxTileCacheSize = null;

this._isIdRenderable = this._isIdRenderable.bind(this);
this._isIdRenderableForSymbols = this._isIdRenderableForSymbols.bind(this);

this._coveredTiles = {};
this._state = new SourceFeatureState();
Expand Down Expand Up @@ -190,8 +192,10 @@ class SourceCache extends Evented {
return Object.keys(this._tiles).map(Number).sort(compareKeyZoom);
}

getRenderableIds() {
return this.getIds().filter(this._isIdRenderable);
getRenderableIds(symbolLayer?: boolean) {
return symbolLayer ?
this.getIds().filter(this._isIdRenderableForSymbols) :
this.getIds().filter(this._isIdRenderable);
}

hasRenderableParent(tileID: OverscaledTileID) {
Expand All @@ -203,6 +207,10 @@ class SourceCache extends Evented {
}

_isIdRenderable(id: number) {
return this._tiles[id] && this._tiles[id].hasData() && !this._coveredTiles[id] && !this._tiles[id].holdingForFade();
}

_isIdRenderableForSymbols(id: number) {
return this._tiles[id] && this._tiles[id].hasData() && !this._coveredTiles[id];
}

Expand Down Expand Up @@ -524,10 +532,29 @@ class SourceCache extends Evented {
for (fadedParent in parentsForFading) {
retain[fadedParent] = parentsForFading[fadedParent];
}
for (const retainedId in retain) {
// Make sure retained tiles always clear any existing fade holds
// so that if they're removed again their fade timer starts fresh.
this._tiles[retainedId].clearFadeHold();
}
// Remove the tiles we don't need anymore.
const remove = keysDifference(this._tiles, retain);
for (let i = 0; i < remove.length; i++) {
this._removeTile(remove[i]);
const tileID = remove[i];
const tile = this._tiles[tileID];
if (tile.hasSymbolBuckets && !tile.holdingForFade()) {
tile.setHoldDuration(this.map._fadeDuration);
} else if (!tile.hasSymbolBuckets || tile.symbolFadeFinished()) {
this._removeTile(tileID);
}
}
}

releaseSymbolFadeTiles() {
for (const id in this._tiles) {
if (this._tiles[id].holdingForFade()) {
this._removeTile(id);
}
}
}

Expand Down Expand Up @@ -734,6 +761,10 @@ class SourceCache extends Evented {

for (let i = 0; i < ids.length; i++) {
const tile = this._tiles[ids[i]];
if (tile.holdingForFade()) {
// Tiles held for fading are covered by tiles that are closer to ideal
continue;
}
const tileID = tile.tileID;
const scale = Math.pow(2, this.transform.zoom - tile.tileID.overscaledZ);
const queryPadding = maxPitchScaleFactor * tile.queryPadding * EXTENT / tile.tileSize / scale;
Expand Down Expand Up @@ -763,8 +794,8 @@ class SourceCache extends Evented {
return tileResults;
}

getVisibleCoordinates() {
const coords = this.getRenderableIds().map((id) => this._tiles[id].tileID);
getVisibleCoordinates(symbolLayer?: boolean) {
const coords = this.getRenderableIds(symbolLayer).map((id) => this._tiles[id].tileID);
for (const coord of coords) {
coord.posMatrix = this.transform.calculatePosMatrix(coord.toUnwrapped());
}
Expand Down Expand Up @@ -827,4 +858,3 @@ function isRasterType(type) {
}

export default SourceCache;

32 changes: 28 additions & 4 deletions src/source/tile.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@ class Tile {
resourceTiming: ?Array<PerformanceResourceTiming>;
queryPadding: number;

symbolFadeHoldUntil: ?number;
hasSymbolBuckets: boolean;

/**
* @param {OverscaledTileID} tileID
* @param size
Expand All @@ -103,6 +106,7 @@ class Tile {
this.buckets = {};
this.expirationTime = null;
this.queryPadding = 0;
this.hasSymbolBuckets = false;

// Counts the number of times a response was already expired when
// received. We're using this to add a delay when making a new request
Expand Down Expand Up @@ -164,11 +168,15 @@ class Tile {
this.collisionBoxArray = data.collisionBoxArray;
this.buckets = deserializeBucket(data.buckets, painter.style);

if (justReloaded) {
for (const id in this.buckets) {
const bucket = this.buckets[id];
if (bucket instanceof SymbolBucket) {
this.hasSymbolBuckets = false;
for (const id in this.buckets) {
const bucket = this.buckets[id];
if (bucket instanceof SymbolBucket) {
this.hasSymbolBuckets = true;
if (justReloaded) {
bucket.justReloaded = true;
} else {
break;
}
}
}
Expand Down Expand Up @@ -438,6 +446,22 @@ class Tile {
}
}
}

holdingForFade(): boolean {
return this.symbolFadeHoldUntil !== undefined;
}

symbolFadeFinished(): boolean {
return !this.symbolFadeHoldUntil || this.symbolFadeHoldUntil < browser.now();
}

clearFadeHold() {
this.symbolFadeHoldUntil = undefined;
}

setHoldDuration(duration: number) {
this.symbolFadeHoldUntil = browser.now() + duration;
}
}

export default Tile;
8 changes: 7 additions & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ class Style extends Evented {

if (!layerTiles[styleLayer.source]) {
const sourceCache = this.sourceCaches[styleLayer.source];
layerTiles[styleLayer.source] = sourceCache.getRenderableIds()
layerTiles[styleLayer.source] = sourceCache.getRenderableIds(true)
.map((id) => sourceCache.getTileByID(id))
.sort((a, b) => (b.tileID.overscaledZ - a.tileID.overscaledZ) || (a.tileID.isLessThan(b.tileID) ? -1 : 1));
}
Expand Down Expand Up @@ -1086,6 +1086,12 @@ class Style extends Evented {
return needsRerender;
}

_releaseSymbolFadeTiles() {
for (const id in this.sourceCaches) {
this.sourceCaches[id].releaseSymbolFadeTiles();
}
}

// Callbacks from web workers

getImages(mapId: string, params: {icons: Array<string>}, callback: Callback<{[string]: StyleImage}>) {
Expand Down
10 changes: 8 additions & 2 deletions src/symbol/placement.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,11 +176,11 @@ export class Placement {
);

this.placeLayerBucket(symbolBucket, posMatrix, textLabelPlaneMatrix, iconLabelPlaneMatrix, scale, textPixelRatio,
showCollisionBoxes, seenCrossTileIDs, collisionBoxArray);
showCollisionBoxes, tile.holdingForFade(), seenCrossTileIDs, collisionBoxArray);
}

placeLayerBucket(bucket: SymbolBucket, posMatrix: mat4, textLabelPlaneMatrix: mat4, iconLabelPlaneMatrix: mat4,
scale: number, textPixelRatio: number, showCollisionBoxes: boolean, seenCrossTileIDs: { [string | number]: boolean },
scale: number, textPixelRatio: number, showCollisionBoxes: boolean, holdingForFade: boolean, seenCrossTileIDs: { [string | number]: boolean },
collisionBoxArray: ?CollisionBoxArray) {
const layout = bucket.layers[0].layout;

Expand All @@ -193,6 +193,12 @@ export class Placement {

for (const symbolInstance of bucket.symbolInstances) {
if (!seenCrossTileIDs[symbolInstance.crossTileID]) {
if (holdingForFade) {
// Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
// know yet if we have a duplicate in a parent tile that _should_ be placed.
this.placements[symbolInstance.crossTileID] = new JointPlacement(false, false, false);
continue;
}

let placeText = symbolInstance.feature.text !== undefined;
let placeIcon = symbolInstance.feature.icon !== undefined;
Expand Down
7 changes: 7 additions & 0 deletions src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -1681,6 +1681,13 @@ class Map extends Camera {
this._styleDirty = true;
}

if (this.style && !this._placementDirty) {
// Since no fade operations are in progress, we can release
// all tiles held for fading. If we didn't do this, the tiles
// would just sit in the SourceCaches until the next render
this.style._releaseSymbolFadeTiles();
}

// Schedule another render frame if it's needed.
//
// Even though `_styleDirty` and `_sourcesDirty` are reset in this
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading