Reuse containers for tile, vector and vector tile layers
This commit is contained in:
@@ -9,6 +9,7 @@ import MapRenderer from './Map.js';
|
|||||||
import SourceState from '../source/State.js';
|
import SourceState from '../source/State.js';
|
||||||
import {replaceChildren} from '../dom.js';
|
import {replaceChildren} from '../dom.js';
|
||||||
import {labelCache} from '../render/canvas.js';
|
import {labelCache} from '../render/canvas.js';
|
||||||
|
import {altShiftKeysOnly} from '../events/condition.js';
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,7 +88,7 @@ class CompositeMapRenderer extends MapRenderer {
|
|||||||
const viewResolution = frameState.viewState.resolution;
|
const viewResolution = frameState.viewState.resolution;
|
||||||
|
|
||||||
this.children_.length = 0;
|
this.children_.length = 0;
|
||||||
let previousElement = null;
|
const previousElement = null;
|
||||||
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
|
for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) {
|
||||||
const layerState = layerStatesArray[i];
|
const layerState = layerStatesArray[i];
|
||||||
if (!visibleAtResolution(layerState, viewResolution) ||
|
if (!visibleAtResolution(layerState, viewResolution) ||
|
||||||
@@ -97,11 +98,8 @@ class CompositeMapRenderer extends MapRenderer {
|
|||||||
|
|
||||||
const layer = layerState.layer;
|
const layer = layerState.layer;
|
||||||
const element = layer.render(frameState, previousElement);
|
const element = layer.render(frameState, previousElement);
|
||||||
if (element) {
|
if (element !== previousElement) {
|
||||||
previousElement = element;
|
this.children_.push(element);
|
||||||
if (element !== this.children_[this.children_.length - 1]) {
|
|
||||||
this.children_.push(element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
super.renderFrame(frameState);
|
super.renderFrame(frameState);
|
||||||
|
|||||||
@@ -101,13 +101,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer {
|
|||||||
);
|
);
|
||||||
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
||||||
|
|
||||||
|
this.useContainer(target, this.pixelTransform_);
|
||||||
|
|
||||||
const context = this.context;
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
|
|
||||||
if (canvas.width != width || canvas.height != height) {
|
if (canvas.width != width || canvas.height != height) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
} else {
|
} else if (!this.containerReused) {
|
||||||
context.clearRect(0, 0, width, height);
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,12 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
|
|
||||||
super(layer);
|
super(layer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {HTMLElement}
|
||||||
|
*/
|
||||||
|
this.container = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @protected
|
* @protected
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -61,44 +67,47 @@ class CanvasLayerRenderer extends LayerRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reuse the passed canvas's context, or create a new one.
|
* Get a rendering container from an existing target, if compatible.
|
||||||
* @protected
|
|
||||||
* @param {HTMLCanvasElement} canvas Canvas.
|
|
||||||
* @return {CanvasRenderingContext2D} context Context.
|
|
||||||
*/
|
|
||||||
useContext(canvas) {
|
|
||||||
let context;
|
|
||||||
if (canvas) {
|
|
||||||
context = canvas.getContext('2d');
|
|
||||||
}
|
|
||||||
if (!context) {
|
|
||||||
context = createCanvasContext2D();
|
|
||||||
canvas = context.canvas;
|
|
||||||
canvas.style.position = 'absolute';
|
|
||||||
canvas.style.transformOrigin = 'top left';
|
|
||||||
}
|
|
||||||
canvas.classList.add(this.getLayer().getClassName());
|
|
||||||
this.context = context;
|
|
||||||
return context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {HTMLElement} target Potential render target.
|
* @param {HTMLElement} target Potential render target.
|
||||||
* @param {import("../../transform").Transform} transform Transform.
|
* @param {import("../../transform").Transform} transform Transform.
|
||||||
* @return {HTMLCanvasElement} Canvas.
|
* @return {boolean} The target is reused for this layer.
|
||||||
*/
|
*/
|
||||||
getCanvas(target, transform) {
|
useContainer(target, transform) {
|
||||||
let canvas = null;
|
let reused = false;
|
||||||
|
let container, context;
|
||||||
if (target) {
|
if (target) {
|
||||||
canvas = target.firstElementChild || target;
|
const canvas = target.firstElementChild;
|
||||||
if (canvas && canvas instanceof HTMLCanvasElement && canvas.style.transform === transformToString(transform)) {
|
if (canvas instanceof HTMLCanvasElement) {
|
||||||
return canvas;
|
context = canvas.getContext('2d');
|
||||||
}
|
}
|
||||||
} else if (this.context) {
|
|
||||||
canvas = this.context.canvas;
|
|
||||||
this.context.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
}
|
}
|
||||||
return canvas;
|
if (context && context.canvas.style.transform === transformToString(transform)) {
|
||||||
|
container = target;
|
||||||
|
reused = true;
|
||||||
|
} else {
|
||||||
|
container = this.container;
|
||||||
|
if (!container) {
|
||||||
|
container = document.createElement('div');
|
||||||
|
const style = container.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.width = '100%';
|
||||||
|
style.height = '100%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (container !== this.container) {
|
||||||
|
container.classList.add(this.getLayer().getClassName());
|
||||||
|
this.container = container;
|
||||||
|
if (!context) {
|
||||||
|
context = createCanvasContext2D();
|
||||||
|
const canvas = context.canvas;
|
||||||
|
container.appendChild(canvas);
|
||||||
|
const style = canvas.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.transformOrigin = 'top left';
|
||||||
|
}
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
return reused;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import TileState from '../../TileState.js';
|
|||||||
import {createEmpty, equals, getIntersection, getTopLeft} from '../../extent.js';
|
import {createEmpty, equals, getIntersection, getTopLeft} from '../../extent.js';
|
||||||
import CanvasLayerRenderer from './Layer.js';
|
import CanvasLayerRenderer from './Layer.js';
|
||||||
import {apply as applyTransform, compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js';
|
import {apply as applyTransform, compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js';
|
||||||
|
import {numberSafeCompareFunction} from '../../array.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
@@ -233,7 +234,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
-width / 2, -height / 2
|
-width / 2, -height / 2
|
||||||
);
|
);
|
||||||
|
|
||||||
const context = this.useContext(this.getCanvas(target, this.pixelTransform_));
|
const reused = this.useContainer(target, this.pixelTransform_);
|
||||||
|
const context = this.context;
|
||||||
const canvas = context.canvas;
|
const canvas = context.canvas;
|
||||||
|
|
||||||
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
makeInverse(this.inversePixelTransform_, this.pixelTransform_);
|
||||||
@@ -249,6 +251,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
if (canvas.width != width || canvas.height != height) {
|
if (canvas.width != width || canvas.height != height) {
|
||||||
canvas.width = width;
|
canvas.width = width;
|
||||||
canvas.height = height;
|
canvas.height = height;
|
||||||
|
} else if (!reused) {
|
||||||
|
context.clearRect(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (layerState.extent) {
|
if (layerState.extent) {
|
||||||
@@ -259,7 +263,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
|
|
||||||
this.renderedTiles.length = 0;
|
this.renderedTiles.length = 0;
|
||||||
/** @type {Array<number>} */
|
/** @type {Array<number>} */
|
||||||
const zs = Object.keys(tilesToDrawByZ).map(Number);
|
let zs = Object.keys(tilesToDrawByZ).map(Number);
|
||||||
zs.sort(function(a, b) {
|
zs.sort(function(a, b) {
|
||||||
if (a === z) {
|
if (a === z) {
|
||||||
return 1;
|
return 1;
|
||||||
@@ -270,9 +274,13 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const clips = [];
|
let clips, clipZs, currentClip;
|
||||||
const clipZs = [];
|
if (tileSource.getOpaque(frameState.viewState.projection)) {
|
||||||
let currentClip;
|
zs = zs.reverse();
|
||||||
|
} else {
|
||||||
|
clips = [];
|
||||||
|
clipZs = [];
|
||||||
|
}
|
||||||
for (let i = zs.length - 1; i >= 0; --i) {
|
for (let i = zs.length - 1; i >= 0; --i) {
|
||||||
const currentZ = zs[i];
|
const currentZ = zs[i];
|
||||||
const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
|
const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection);
|
||||||
@@ -302,30 +310,34 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
const w = nextX - x;
|
const w = nextX - x;
|
||||||
const h = nextY - y;
|
const h = nextY - y;
|
||||||
|
|
||||||
// Clip mask for regions in this tile that already filled by a lower z tile
|
if (clips) {
|
||||||
context.save();
|
// Clip mask for regions in this tile that already filled by a higher z tile
|
||||||
currentClip = [x, y, x + w, y, x + w, y + h, x, y + h];
|
context.save();
|
||||||
for (let i = 0, ii = clips.length; i < ii; ++i) {
|
currentClip = [x, y, x + w, y, x + w, y + h, x, y + h];
|
||||||
if (currentZ < clipZs[i]) {
|
for (let i = 0, ii = clips.length; i < ii; ++i) {
|
||||||
const clip = clips[i];
|
if (z !== currentZ && currentZ < clipZs[i]) {
|
||||||
context.beginPath();
|
const clip = clips[i];
|
||||||
// counter-clockwise (inner ring) for current tile
|
context.beginPath();
|
||||||
context.moveTo(currentClip[0], currentClip[1]);
|
// counter-clockwise (outer ring) for current tile
|
||||||
context.lineTo(currentClip[2], currentClip[3]);
|
context.moveTo(currentClip[0], currentClip[1]);
|
||||||
context.lineTo(currentClip[4], currentClip[5]);
|
context.lineTo(currentClip[2], currentClip[3]);
|
||||||
context.lineTo(currentClip[6], currentClip[7]);
|
context.lineTo(currentClip[4], currentClip[5]);
|
||||||
// clockwise (outer ring) for lower z tile
|
context.lineTo(currentClip[6], currentClip[7]);
|
||||||
context.moveTo(clip[6], clip[7]);
|
// clockwise (inner ring) for higher z tile
|
||||||
context.lineTo(clip[4], clip[5]);
|
context.moveTo(clip[6], clip[7]);
|
||||||
context.lineTo(clip[2], clip[3]);
|
context.lineTo(clip[4], clip[5]);
|
||||||
context.lineTo(clip[0], clip[1]);
|
context.lineTo(clip[2], clip[3]);
|
||||||
context.clip();
|
context.lineTo(clip[0], clip[1]);
|
||||||
|
context.clip();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
clips.push(currentClip);
|
||||||
|
clipZs.push(currentZ);
|
||||||
}
|
}
|
||||||
clips.push(currentClip);
|
|
||||||
clipZs.push(currentZ);
|
|
||||||
this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, z === currentZ);
|
this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, z === currentZ);
|
||||||
context.restore();
|
if (clips) {
|
||||||
|
context.restore();
|
||||||
|
}
|
||||||
this.renderedTiles.push(tile);
|
this.renderedTiles.push(tile);
|
||||||
this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
|
this.updateUsedTiles(frameState.usedTiles, tileSource, tile);
|
||||||
}
|
}
|
||||||
@@ -358,7 +370,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
canvas.style.transform = canvasTransform;
|
canvas.style.transform = canvasTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
return canvas;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -378,11 +390,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
|||||||
}
|
}
|
||||||
const uid = getUid(this);
|
const uid = getUid(this);
|
||||||
const alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
|
const alpha = transition ? tile.getAlpha(uid, frameState.time) : 1;
|
||||||
const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer());
|
|
||||||
const tileSource = tileLayer.getSource();
|
|
||||||
if (alpha === 1 && !tileSource.getOpaque(frameState.viewState.projection)) {
|
|
||||||
//this.context.clearRect(x, y, w, h);
|
|
||||||
}
|
|
||||||
const alphaChanged = alpha !== this.context.globalAlpha;
|
const alphaChanged = alpha !== this.context.globalAlpha;
|
||||||
if (alphaChanged) {
|
if (alphaChanged) {
|
||||||
this.context.save();
|
this.context.save();
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import {
|
|||||||
makeInverse
|
makeInverse
|
||||||
} from '../../transform.js';
|
} from '../../transform.js';
|
||||||
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
import CanvasExecutorGroup, {replayDeclutter} from '../../render/canvas/ExecutorGroup.js';
|
||||||
import {clear, isEmpty} from '../../obj.js';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,32 +58,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
constructor(layer) {
|
constructor(layer) {
|
||||||
super(layer);
|
super(layer);
|
||||||
|
|
||||||
const baseCanvas = this.context.canvas;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {CanvasRenderingContext2D}
|
* @type {CanvasRenderingContext2D}
|
||||||
*/
|
*/
|
||||||
this.overlayContext_ = createCanvasContext2D();
|
this.overlayContext_ = null;
|
||||||
|
|
||||||
const overlayCanvas = this.overlayContext_.canvas;
|
|
||||||
overlayCanvas.style.position = 'absolute';
|
|
||||||
overlayCanvas.style.transformOrigin = 'top left';
|
|
||||||
|
|
||||||
const container = document.createElement('div');
|
|
||||||
const style = container.style;
|
|
||||||
style.position = 'absolute';
|
|
||||||
style.width = '100%';
|
|
||||||
style.height = '100%';
|
|
||||||
|
|
||||||
container.appendChild(baseCanvas);
|
|
||||||
container.appendChild(overlayCanvas);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @private
|
|
||||||
* @type {HTMLElement}
|
|
||||||
*/
|
|
||||||
this.container_ = container;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The transform for rendered pixels to viewport CSS pixels for the overlay canvas.
|
* The transform for rendered pixels to viewport CSS pixels for the overlay canvas.
|
||||||
@@ -141,6 +119,33 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
super.disposeInternal();
|
super.disposeInternal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritDoc
|
||||||
|
*/
|
||||||
|
useContainer(target, transform) {
|
||||||
|
let context;
|
||||||
|
if (target && target.childElementCount === 2) {
|
||||||
|
context = target.lastElementChild.getContext('2d');
|
||||||
|
if (!context) {
|
||||||
|
target = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
context = this.overlayContext_;
|
||||||
|
if (!this.overlayContext_) {
|
||||||
|
context = createCanvasContext2D();
|
||||||
|
const style = context.canvas.style;
|
||||||
|
style.position = 'absolute';
|
||||||
|
style.transformOrigin = 'top left';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const reused = super.useContainer(target, transform);
|
||||||
|
if (this.container.childElementCount === 1) {
|
||||||
|
this.container.appendChild(context.canvas);
|
||||||
|
}
|
||||||
|
this.overlayContext_ = context;
|
||||||
|
return reused;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
* @param {import("../../VectorRenderTile.js").default} tile Tile.
|
||||||
* @param {number} pixelRatio Pixel ratio.
|
* @param {number} pixelRatio Pixel ratio.
|
||||||
@@ -379,21 +384,24 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
renderFrame(frameState, layerState) {
|
renderFrame(frameState, layerState, target) {
|
||||||
super.renderFrame(frameState, layerState);
|
|
||||||
|
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
|
||||||
const viewHints = frameState.viewHints;
|
const viewHints = frameState.viewHints;
|
||||||
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||||
|
this.renderTileImages_(hifi, frameState);
|
||||||
|
|
||||||
|
super.renderFrame(frameState, layerState, target);
|
||||||
|
|
||||||
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
const renderMode = layer.getRenderMode();
|
const renderMode = layer.getRenderMode();
|
||||||
|
|
||||||
if (renderMode === VectorTileRenderType.IMAGE) {
|
if (renderMode === VectorTileRenderType.IMAGE) {
|
||||||
this.renderTileImages_(hifi, frameState);
|
this.renderTileImages_(hifi, frameState);
|
||||||
return this.container_;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) {
|
if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) {
|
||||||
this.renderTileImages_(hifi, frameState);
|
this.renderTileImages_(hifi, frameState);
|
||||||
return this.container_;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = this.overlayContext_;
|
const context = this.overlayContext_;
|
||||||
@@ -492,7 +500,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
// for the next frame
|
// for the next frame
|
||||||
this.renderTileImages_(hifi, frameState);
|
this.renderTileImages_(hifi, frameState);
|
||||||
|
|
||||||
return this.container_;
|
return this.container;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -500,14 +508,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
||||||
*/
|
*/
|
||||||
renderTileImages_(hifi, frameState) {
|
renderTileImages_(hifi, frameState) {
|
||||||
// When we don't have time to render hifi, only render tiles until we have used up
|
|
||||||
// half of the frame budget of 16 ms
|
|
||||||
for (const uid in this.renderTileImageQueue_) {
|
for (const uid in this.renderTileImageQueue_) {
|
||||||
if (!hifi && Date.now() - frameState.time > 8) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const tile = this.renderTileImageQueue_[uid];
|
const tile = this.renderTileImageQueue_[uid];
|
||||||
frameState.animate = true;
|
|
||||||
delete this.renderTileImageQueue_[uid];
|
delete this.renderTileImageQueue_[uid];
|
||||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
@@ -516,7 +518,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
|||||||
const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution;
|
const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution;
|
||||||
this.renderTileImage_(tile, frameState.pixelRatio, renderPixelRatio, viewState.projection);
|
this.renderTileImage_(tile, frameState.pixelRatio, renderPixelRatio, viewState.projection);
|
||||||
}
|
}
|
||||||
clear(this.renderTileImageQueue_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user