From a55505b36acf7a58811fc0f0c017555705511ab1 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 17 May 2019 22:25:13 +0200 Subject: [PATCH] Reuse containers for tile, vector and vector tile layers --- src/ol/renderer/Composite.js | 10 ++- src/ol/renderer/canvas/ImageLayer.js | 4 +- src/ol/renderer/canvas/Layer.js | 73 ++++++++++++---------- src/ol/renderer/canvas/TileLayer.js | 71 +++++++++++---------- src/ol/renderer/canvas/VectorTileLayer.js | 75 ++++++++++++----------- 5 files changed, 125 insertions(+), 108 deletions(-) diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index d58d19e72f..bdd3923d4b 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -9,6 +9,7 @@ import MapRenderer from './Map.js'; import SourceState from '../source/State.js'; import {replaceChildren} from '../dom.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; this.children_.length = 0; - let previousElement = null; + const previousElement = null; for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { const layerState = layerStatesArray[i]; if (!visibleAtResolution(layerState, viewResolution) || @@ -97,11 +98,8 @@ class CompositeMapRenderer extends MapRenderer { const layer = layerState.layer; const element = layer.render(frameState, previousElement); - if (element) { - previousElement = element; - if (element !== this.children_[this.children_.length - 1]) { - this.children_.push(element); - } + if (element !== previousElement) { + this.children_.push(element); } } super.renderFrame(frameState); diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 26793e7a1d..6b07c5013c 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -101,13 +101,15 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { ); makeInverse(this.inversePixelTransform_, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_); + const context = this.context; const canvas = context.canvas; if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - } else { + } else if (!this.containerReused) { context.clearRect(0, 0, width, height); } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 260c62bfd6..833c463417 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -22,6 +22,12 @@ class CanvasLayerRenderer extends LayerRenderer { super(layer); + /** + * @protected + * @type {HTMLElement} + */ + this.container = null; + /** * @protected * @type {number} @@ -61,44 +67,47 @@ class CanvasLayerRenderer extends LayerRenderer { } /** - * Reuse the passed canvas's context, or create a new one. - * @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; - } - - /** + * Get a rendering container from an existing target, if compatible. * @param {HTMLElement} target Potential render target. * @param {import("../../transform").Transform} transform Transform. - * @return {HTMLCanvasElement} Canvas. + * @return {boolean} The target is reused for this layer. */ - getCanvas(target, transform) { - let canvas = null; + useContainer(target, transform) { + let reused = false; + let container, context; if (target) { - canvas = target.firstElementChild || target; - if (canvas && canvas instanceof HTMLCanvasElement && canvas.style.transform === transformToString(transform)) { - return canvas; + const canvas = target.firstElementChild; + if (canvas instanceof HTMLCanvasElement) { + 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; } /** diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 58ba273fb3..80ce382e73 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -7,6 +7,7 @@ import TileState from '../../TileState.js'; import {createEmpty, equals, getIntersection, getTopLeft} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; import {apply as applyTransform, compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; +import {numberSafeCompareFunction} from '../../array.js'; /** * @classdesc @@ -233,7 +234,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { -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; makeInverse(this.inversePixelTransform_, this.pixelTransform_); @@ -249,6 +251,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; + } else if (!reused) { + context.clearRect(0, 0, width, height); } if (layerState.extent) { @@ -259,7 +263,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { this.renderedTiles.length = 0; /** @type {Array} */ - const zs = Object.keys(tilesToDrawByZ).map(Number); + let zs = Object.keys(tilesToDrawByZ).map(Number); zs.sort(function(a, b) { if (a === z) { return 1; @@ -270,9 +274,13 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } }); - const clips = []; - const clipZs = []; - let currentClip; + let clips, clipZs, currentClip; + if (tileSource.getOpaque(frameState.viewState.projection)) { + zs = zs.reverse(); + } else { + clips = []; + clipZs = []; + } for (let i = zs.length - 1; i >= 0; --i) { const currentZ = zs[i]; const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); @@ -302,30 +310,34 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const w = nextX - x; const h = nextY - y; - // Clip mask for regions in this tile that already filled by a lower z tile - context.save(); - currentClip = [x, y, x + w, y, x + w, y + h, x, y + h]; - for (let i = 0, ii = clips.length; i < ii; ++i) { - if (currentZ < clipZs[i]) { - const clip = clips[i]; - context.beginPath(); - // counter-clockwise (inner ring) for current tile - context.moveTo(currentClip[0], currentClip[1]); - context.lineTo(currentClip[2], currentClip[3]); - context.lineTo(currentClip[4], currentClip[5]); - context.lineTo(currentClip[6], currentClip[7]); - // clockwise (outer ring) for lower z tile - context.moveTo(clip[6], clip[7]); - context.lineTo(clip[4], clip[5]); - context.lineTo(clip[2], clip[3]); - context.lineTo(clip[0], clip[1]); - context.clip(); + if (clips) { + // Clip mask for regions in this tile that already filled by a higher z tile + context.save(); + currentClip = [x, y, x + w, y, x + w, y + h, x, y + h]; + for (let i = 0, ii = clips.length; i < ii; ++i) { + if (z !== currentZ && currentZ < clipZs[i]) { + const clip = clips[i]; + context.beginPath(); + // counter-clockwise (outer ring) for current tile + context.moveTo(currentClip[0], currentClip[1]); + context.lineTo(currentClip[2], currentClip[3]); + context.lineTo(currentClip[4], currentClip[5]); + context.lineTo(currentClip[6], currentClip[7]); + // clockwise (inner ring) for higher z tile + context.moveTo(clip[6], clip[7]); + context.lineTo(clip[4], clip[5]); + context.lineTo(clip[2], clip[3]); + 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); - context.restore(); + if (clips) { + context.restore(); + } this.renderedTiles.push(tile); this.updateUsedTiles(frameState.usedTiles, tileSource, tile); } @@ -358,7 +370,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { canvas.style.transform = canvasTransform; } - return canvas; + return this.container; } /** @@ -378,11 +390,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } const uid = getUid(this); 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; if (alphaChanged) { this.context.save(); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 70f9ecff39..8349307174 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -24,7 +24,6 @@ import { makeInverse } from '../../transform.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) { super(layer); - const baseCanvas = this.context.canvas; - /** * @private * @type {CanvasRenderingContext2D} */ - this.overlayContext_ = createCanvasContext2D(); - - 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; + this.overlayContext_ = null; /** * The transform for rendered pixels to viewport CSS pixels for the overlay canvas. @@ -141,6 +119,33 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { 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 {number} pixelRatio Pixel ratio. @@ -379,21 +384,24 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { /** * @inheritDoc */ - renderFrame(frameState, layerState) { - super.renderFrame(frameState, layerState); - - const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); + renderFrame(frameState, layerState, target) { const viewHints = frameState.viewHints; 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(); + if (renderMode === VectorTileRenderType.IMAGE) { this.renderTileImages_(hifi, frameState); - return this.container_; + return this.container; } if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) { this.renderTileImages_(hifi, frameState); - return this.container_; + return this.container; } const context = this.overlayContext_; @@ -492,7 +500,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { // for the next frame 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. */ 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_) { - if (!hifi && Date.now() - frameState.time > 8) { - break; - } const tile = this.renderTileImageQueue_[uid]; - frameState.animate = true; delete this.renderTileImageQueue_[uid]; const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const viewState = frameState.viewState; @@ -516,7 +518,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution; this.renderTileImage_(tile, frameState.pixelRatio, renderPixelRatio, viewState.projection); } - clear(this.renderTileImageQueue_); } /**