From b0fae46aa654f994c3ac023744f4f9f7fd5d20fd Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 16 May 2019 09:57:47 +0200 Subject: [PATCH 01/14] Do not use css z-index --- rendering/cases/multiple-layers/main.js | 12 ++++++------ src/ol/renderer/Composite.js | 11 +++++------ 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/rendering/cases/multiple-layers/main.js b/rendering/cases/multiple-layers/main.js index c5dd78ae69..0d2df97582 100644 --- a/rendering/cases/multiple-layers/main.js +++ b/rendering/cases/multiple-layers/main.js @@ -9,12 +9,6 @@ import Point from '../../../src/ol/geom/Point.js'; const map = new Map({ layers: [ - new TileLayer({ - source: new XYZ({ - url: '/data/tiles/satellite/{z}/{x}/{y}.jpg', - maxZoom: 3 - }) - }), new VectorLayer({ zIndex: 1, style: new Style({ @@ -27,6 +21,12 @@ const map = new Map({ url: '/data/countries.json', format: new GeoJSON() }) + }), + new TileLayer({ + source: new XYZ({ + url: '/data/tiles/satellite/{z}/{x}/{y}.jpg', + maxZoom: 3 + }) }) ], target: 'map', diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index e1e68f70cd..d0b5566d0b 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -81,10 +81,13 @@ class CompositeMapRenderer extends MapRenderer { this.calculateMatrices2D(frameState); this.dispatchRenderEvent(RenderEventType.PRECOMPOSE, frameState); - const layerStatesArray = frameState.layerStatesArray; + const layerStatesArray = frameState.layerStatesArray.sort(function(a, b) { + return a.zIndex - b.zIndex; + }); const viewResolution = frameState.viewState.resolution; this.children_.length = 0; + let element = null; for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { const layerState = layerStatesArray[i]; if (!visibleAtResolution(layerState, viewResolution) || @@ -93,12 +96,8 @@ class CompositeMapRenderer extends MapRenderer { } const layer = layerState.layer; - const element = layer.render(frameState); + element = layer.render(frameState); if (element) { - const zIndex = layerState.zIndex; - if (zIndex !== element.style.zIndex) { - element.style.zIndex = zIndex === Infinity ? Number.MAX_SAFE_INTEGER : zIndex; - } this.children_.push(element); } } From 606443bc6dd32fa1aff69a32a279696ca4324739 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Thu, 16 May 2019 20:41:54 +0200 Subject: [PATCH 02/14] Pass render target of previous layer to next layer --- src/ol/layer/Layer.js | 6 ++-- src/ol/renderer/Composite.js | 9 ++++-- src/ol/renderer/Layer.js | 3 +- src/ol/renderer/canvas/Layer.js | 50 +++++++++++++++++++++++++---- src/ol/renderer/canvas/TileLayer.js | 15 +++++---- 5 files changed, 65 insertions(+), 18 deletions(-) diff --git a/src/ol/layer/Layer.js b/src/ol/layer/Layer.js index 23a4324a63..424429cc26 100644 --- a/src/ol/layer/Layer.js +++ b/src/ol/layer/Layer.js @@ -189,13 +189,15 @@ class Layer extends BaseLayer { * In charge to manage the rendering of the layer. One layer type is * bounded with one layer renderer. * @param {?import("../PluggableMap.js").FrameState} frameState Frame state. + * @param {HTMLElement} target Target which the renderer may (but need not) use + * for rendering its content. * @return {HTMLElement} The rendered element. */ - render(frameState) { + render(frameState, target) { const layerRenderer = this.getRenderer(); const layerState = this.getLayerState(); if (layerRenderer.prepareFrame(frameState, layerState)) { - return layerRenderer.renderFrame(frameState, layerState); + return layerRenderer.renderFrame(frameState, layerState, target); } } diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index d0b5566d0b..d58d19e72f 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -87,7 +87,7 @@ class CompositeMapRenderer extends MapRenderer { const viewResolution = frameState.viewState.resolution; this.children_.length = 0; - let element = null; + let previousElement = null; for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { const layerState = layerStatesArray[i]; if (!visibleAtResolution(layerState, viewResolution) || @@ -96,9 +96,12 @@ class CompositeMapRenderer extends MapRenderer { } const layer = layerState.layer; - element = layer.render(frameState); + const element = layer.render(frameState, previousElement); if (element) { - this.children_.push(element); + previousElement = element; + if (element !== this.children_[this.children_.length - 1]) { + this.children_.push(element); + } } } super.renderFrame(frameState); diff --git a/src/ol/renderer/Layer.js b/src/ol/renderer/Layer.js index f266a28c57..a42276a62b 100644 --- a/src/ol/renderer/Layer.js +++ b/src/ol/renderer/Layer.js @@ -41,9 +41,10 @@ class LayerRenderer extends Observable { * @abstract * @param {import("../PluggableMap.js").FrameState} frameState Frame state. * @param {import("../layer/Layer.js").State} layerState Layer state. + * @param {HTMLElement} target Target that may be used to render content to. * @return {HTMLElement} The rendered element. */ - renderFrame(frameState, layerState) { + renderFrame(frameState, layerState, target) { return abstract(); } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index aa7531c0f0..260c62bfd6 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -7,7 +7,8 @@ import RenderEvent from '../../render/Event.js'; import RenderEventType from '../../render/EventType.js'; import {rotateAtOffset} from '../../render/canvas.js'; import LayerRenderer from '../Layer.js'; -import {create as createTransform, apply as applyTransform, compose as composeTransform} from '../../transform.js'; +import {create as createTransform, apply as applyTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; +import ContextEventType from '../../webgl/ContextEventType.js'; /** * @abstract @@ -55,12 +56,49 @@ class CanvasLayerRenderer extends LayerRenderer { * @protected * @type {CanvasRenderingContext2D} */ - this.context = createCanvasContext2D(); + this.context = null; - const canvas = this.context.canvas; - canvas.style.position = 'absolute'; - canvas.style.transformOrigin = 'top left'; - canvas.className = this.getLayer().getClassName(); + } + + /** + * 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; + } + + /** + * @param {HTMLElement} target Potential render target. + * @param {import("../../transform").Transform} transform Transform. + * @return {HTMLCanvasElement} Canvas. + */ + getCanvas(target, transform) { + let canvas = null; + if (target) { + canvas = target.firstElementChild || target; + if (canvas && canvas instanceof HTMLCanvasElement && canvas.style.transform === transformToString(transform)) { + return canvas; + } + } else if (this.context) { + canvas = this.context.canvas; + this.context.clearRect(0, 0, canvas.width, canvas.height); + } + return canvas; } /** diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 512fb58a4b..b27feb1cdf 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -137,8 +137,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { * @inheritDoc * @returns {HTMLElement} The rendered element. */ - renderFrame(frameState, layerState) { - const context = this.context; + renderFrame(frameState, layerState, target) { const viewState = frameState.viewState; const projection = viewState.projection; const viewResolution = viewState.resolution; @@ -224,7 +223,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } - const canvas = context.canvas; const canvasScale = tileResolution / viewResolution; // set forward and inverse pixel transforms @@ -234,6 +232,10 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { rotation, -width / 2, -height / 2 ); + + const context = this.useContext(this.getCanvas(target, this.pixelTransform_)); + const canvas = context.canvas; + makeInverse(this.inversePixelTransform_, this.pixelTransform_); // set scale transform for calculating tile positions on the canvas @@ -247,8 +249,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - } else { - context.clearRect(0, 0, width, height); } if (layerState.extent) { @@ -270,6 +270,8 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } }); + const clips = []; + let currentClip; for (let i = 0, ii = zs.length; i < ii; ++i) { const currentZ = zs[i]; const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); @@ -299,6 +301,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const w = nextX - x; const h = nextY - y; + currentClip = [x, y, w, h]; this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, z === currentZ); this.renderedTiles.push(tile); this.updateUsedTiles(frameState.usedTiles, tileSource, tile); @@ -355,7 +358,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { 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); + //this.context.clearRect(x, y, w, h); } const alphaChanged = alpha !== this.context.globalAlpha; if (alphaChanged) { From 5fe9e06535e67a0fe0bbfe83d3b26ada915b5bdd Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 17 May 2019 13:28:13 +0200 Subject: [PATCH 03/14] Use clipping to not render high res tiles on top of low res --- src/ol/renderer/canvas/TileLayer.js | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index b27feb1cdf..58ba273fb3 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -271,8 +271,9 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { }); const clips = []; + const clipZs = []; let currentClip; - for (let i = 0, ii = zs.length; i < ii; ++i) { + for (let i = zs.length - 1; i >= 0; --i) { const currentZ = zs[i]; const currentTilePixelSize = tileSource.getTilePixelSize(currentZ, pixelRatio, projection); const currentResolution = tileGrid.getResolution(currentZ); @@ -301,8 +302,30 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const w = nextX - x; const h = nextY - y; - currentClip = [x, y, w, h]; + // 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(); + } + } + clips.push(currentClip); + clipZs.push(currentZ); this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, z === currentZ); + context.restore(); this.renderedTiles.push(tile); this.updateUsedTiles(frameState.usedTiles, tileSource, tile); } From 930318ab7af07baf4e56006be6e0e70d43c7a5ab Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 17 May 2019 10:08:59 +0200 Subject: [PATCH 04/14] Clip high res tiles out of low res tiles only once --- src/ol/renderer/canvas/VectorTileLayer.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 66f3645452..70f9ecff39 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -426,6 +426,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const tiles = this.renderedTiles; const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); const clips = []; + const clipZs = []; for (let i = tiles.length - 1; i >= 0; --i) { const tile = /** @type {import("../../VectorRenderTile.js").default} */ (tiles[i]); if (tile.getState() == TileState.ABORT) { @@ -436,6 +437,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tileExtent[0]; const transform = this.getRenderTransform(frameState, width, height, worldOffset); const executorGroups = tile.executorGroups[getUid(layer)]; + let clipped = false; for (let t = 0, tt = executorGroups.length; t < tt; ++t) { const executorGroup = executorGroups[t]; if (!executorGroup.hasExecutors(replayTypes)) { @@ -443,9 +445,8 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { continue; } const currentZ = tile.tileCoord[0]; - let zs, currentClip; - if (!declutterReplays) { - zs = []; + let currentClip; + if (!declutterReplays && !clipped) { currentClip = executorGroup.getClipCoords(transform); context.save(); @@ -453,7 +454,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { // already filled by a higher resolution tile for (let j = 0, jj = clips.length; j < jj; ++j) { const clip = clips[j]; - if (currentZ < zs[j]) { + if (currentZ < clipZs[j]) { context.beginPath(); // counter-clockwise (outer ring) for current tile context.moveTo(currentClip[0], currentClip[1]); @@ -470,10 +471,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } executorGroup.execute(context, transform, rotation, {}, hifi, replayTypes, declutterReplays); - if (!declutterReplays) { + if (!declutterReplays && !clipped) { context.restore(); clips.push(currentClip); - zs.push(currentZ); + clipZs.push(currentZ); + clipped = true; } } } From a55505b36acf7a58811fc0f0c017555705511ab1 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 17 May 2019 22:25:13 +0200 Subject: [PATCH 05/14] 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_); } /** From d1f1b468b109ff3a88b23dde4fdba9c154c2bb92 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sun, 19 May 2019 22:31:49 +0200 Subject: [PATCH 06/14] Reuse target for image layers --- src/ol/renderer/Composite.js | 3 ++- src/ol/renderer/canvas/ImageLayer.js | 6 +++--- src/ol/renderer/canvas/Layer.js | 1 + src/ol/renderer/canvas/TileLayer.js | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index bdd3923d4b..752210889d 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -88,7 +88,7 @@ class CompositeMapRenderer extends MapRenderer { const viewResolution = frameState.viewState.resolution; this.children_.length = 0; - const previousElement = null; + let previousElement = null; for (let i = 0, ii = layerStatesArray.length; i < ii; ++i) { const layerState = layerStatesArray[i]; if (!visibleAtResolution(layerState, viewResolution) || @@ -100,6 +100,7 @@ class CompositeMapRenderer extends MapRenderer { const element = layer.render(frameState, previousElement); if (element !== previousElement) { this.children_.push(element); + previousElement = element; } } super.renderFrame(frameState); diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 6b07c5013c..e97a8ec956 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; +import {compose as composeTransform, makeInverse, toString as transformToString, apply as applyTransform} from '../../transform.js'; /** * @classdesc @@ -72,7 +72,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { /** * @inheritDoc */ - renderFrame(frameState, layerState) { + renderFrame(frameState, layerState, target) { const image = this.image_; const imageExtent = image.getExtent(); const imageResolution = image.getResolution(); @@ -159,7 +159,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { canvas.style.transform = canvasTransform; } - return canvas; + return this.container; } diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 833c463417..4c349df0e2 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -85,6 +85,7 @@ class CanvasLayerRenderer extends LayerRenderer { container = target; reused = true; } else { + context = null; container = this.container; if (!container) { container = document.createElement('div'); diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 80ce382e73..635ffb3417 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -309,8 +309,9 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { const y = Math.round(floatY); const w = nextX - x; const h = nextY - y; + const transition = z === currentZ; - if (clips) { + if (clips && (!transition || tile.getAlpha(getUid(this), frameState.time) === 1)) { // 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]; @@ -334,7 +335,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { 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, transition); if (clips) { context.restore(); } From a45e704be2b5e859a58d5fd99d0fbffd3eea07b8 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 21 May 2019 18:24:02 +0200 Subject: [PATCH 07/14] Smarter reuse detection --- src/ol/render/canvas/Executor.js | 8 ++-- src/ol/renderer/Composite.js | 1 - src/ol/renderer/canvas/ImageLayer.js | 2 +- src/ol/renderer/canvas/Layer.js | 55 ++++++++++++----------- src/ol/renderer/canvas/TileLayer.js | 5 +-- src/ol/renderer/canvas/VectorLayer.js | 28 ++++++------ src/ol/renderer/canvas/VectorTileLayer.js | 41 +++++++++-------- 7 files changed, 72 insertions(+), 68 deletions(-) diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 0f2b0a75f1..0ae1c224a5 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -362,10 +362,12 @@ class Executor extends Disposable { const declutterArgs = intersects ? [context, transform ? transform.slice(0) : null, opacity, image, originX, originY, w, h, x, y, scale] : null; - if (declutterArgs && fillStroke) { - declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4); + if (declutterArgs) { + if (fillStroke) { + declutterArgs.push(fillInstruction, strokeInstruction, p1, p2, p3, p4); + } + declutterGroup.push(declutterArgs); } - declutterGroup.push(declutterArgs); } else if (intersects) { if (fillStroke) { this.replayTextBackground_(context, p1, p2, p3, p4, diff --git a/src/ol/renderer/Composite.js b/src/ol/renderer/Composite.js index 752210889d..3934b5b14d 100644 --- a/src/ol/renderer/Composite.js +++ b/src/ol/renderer/Composite.js @@ -9,7 +9,6 @@ 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'; /** diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index e97a8ec956..e830cbf645 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -6,7 +6,7 @@ import ViewHint from '../../ViewHint.js'; import {containsExtent, intersects} from '../../extent.js'; import {getIntersection, isEmpty} from '../../extent.js'; import CanvasLayerRenderer from './Layer.js'; -import {compose as composeTransform, makeInverse, toString as transformToString, apply as applyTransform} from '../../transform.js'; +import {compose as composeTransform, makeInverse, toString as transformToString} from '../../transform.js'; /** * @classdesc diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 4c349df0e2..5fdd6b0ecc 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -8,7 +8,6 @@ import RenderEventType from '../../render/EventType.js'; import {rotateAtOffset} from '../../render/canvas.js'; import LayerRenderer from '../Layer.js'; import {create as createTransform, apply as applyTransform, compose as composeTransform, toString as transformToString} from '../../transform.js'; -import ContextEventType from '../../webgl/ContextEventType.js'; /** * @abstract @@ -64,16 +63,20 @@ class CanvasLayerRenderer extends LayerRenderer { */ this.context = null; + /** + * @type {boolean} + */ + this.containerReused = false; + } /** * Get a rendering container from an existing target, if compatible. * @param {HTMLElement} target Potential render target. * @param {import("../../transform").Transform} transform Transform. - * @return {boolean} The target is reused for this layer. */ useContainer(target, transform) { - let reused = false; + const layerClassName = this.getLayer().getClassName(); let container, context; if (target) { const canvas = target.firstElementChild; @@ -82,33 +85,33 @@ class CanvasLayerRenderer extends LayerRenderer { } } if (context && context.canvas.style.transform === transformToString(transform)) { - container = target; - reused = true; - } else { - context = null; - container = this.container; - if (!container) { - container = document.createElement('div'); - const style = container.style; - style.position = 'absolute'; - style.width = '100%'; - style.height = '100%'; - } + // Container of the previous layer renderer can be used. + target.classList.add(layerClassName); + this.container = target; + this.context = context; + this.containerReused = true; + } else if (this.containerReused) { + // Previously reused container cannot be used any more. + this.container = null; + this.context = null; + this.containerReused = false; } - if (container !== this.container) { - container.classList.add(this.getLayer().getClassName()); + if (!this.container) { + container = document.createElement('div'); + container.className = layerClassName; + let style = container.style; + style.position = 'absolute'; + style.width = '100%'; + style.height = '100%'; + context = createCanvasContext2D(); + const canvas = context.canvas; + container.appendChild(canvas); + style = canvas.style; + style.position = 'absolute'; + style.transformOrigin = 'top left'; 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 635ffb3417..0f1077e820 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -7,7 +7,6 @@ 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 @@ -234,7 +233,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { -width / 2, -height / 2 ); - const reused = this.useContainer(target, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_); const context = this.context; const canvas = context.canvas; @@ -251,7 +250,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { if (canvas.width != width || canvas.height != height) { canvas.width = width; canvas.height = height; - } else if (!reused) { + } else if (!this.containerReused) { context.clearRect(0, 0, width, height); } diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 3c481c9b62..62741a36d7 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -70,17 +70,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { /** * @inheritDoc */ - renderFrame(frameState, layerState) { - const context = this.context; - const canvas = context.canvas; - - const replayGroup = this.replayGroup_; - if (!replayGroup || replayGroup.isEmpty()) { - if (canvas.width > 0) { - canvas.width = 0; - } - return canvas; - } + renderFrame(frameState, layerState, target) { const pixelRatio = frameState.pixelRatio; @@ -88,6 +78,18 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio); makeInverse(this.inversePixelTransform_, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_); + const context = this.context; + const canvas = context.canvas; + + const replayGroup = this.replayGroup_; + if (!replayGroup || replayGroup.isEmpty()) { + if (!this.containerReused && canvas.width > 0) { + canvas.width = 0; + } + return this.container; + } + // resize and clear const width = Math.round(frameState.size[0] * pixelRatio); const height = Math.round(frameState.size[1] * pixelRatio); @@ -98,7 +100,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { if (canvas.style.transform !== canvasTransform) { canvas.style.transform = canvasTransform; } - } else { + } else if (!this.containerReused) { context.clearRect(0, 0, width, height); } @@ -166,7 +168,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { canvas.style.opacity = opacity; } - return canvas; + return this.container; } /** diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 8349307174..5f80684fa3 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -123,27 +123,31 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @inheritDoc */ useContainer(target, transform) { - let context; + let overlayContext; if (target && target.childElementCount === 2) { - context = target.lastElementChild.getContext('2d'); - if (!context) { + overlayContext = target.lastElementChild.getContext('2d'); + if (!overlayContext) { 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); + const containerReused = this.containerReused; + super.useContainer(target, transform); + if (containerReused && !this.containerReused && !overlayContext) { + this.overlayContext_ = null; + } + if (this.containerReused && overlayContext) { + this.overlayContext_ = overlayContext; + } + if (!this.overlayContext_) { + const overlayContext = createCanvasContext2D(); + const style = overlayContext.canvas.style; + style.position = 'absolute'; + style.transformOrigin = 'top left'; + this.overlayContext_ = overlayContext; + } if (this.container.childElementCount === 1) { - this.container.appendChild(context.canvas); + this.container.appendChild(this.overlayContext_.canvas); } - this.overlayContext_ = context; - return reused; } /** @@ -401,7 +405,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) { this.renderTileImages_(hifi, frameState); - return this.container; } const context = this.overlayContext_; @@ -427,7 +430,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (canvas.style.transform !== canvasTransform) { canvas.style.transform = canvasTransform; } - } else { + } else if (!this.containerReused) { context.clearRect(0, 0, width, height); } @@ -496,10 +499,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { canvas.style.opacity = opacity; } - // Now that we have rendered the tiles we have already, let's prepare new tile images - // for the next frame - this.renderTileImages_(hifi, frameState); - return this.container; } From 7895b160431324b164f1d2c46d445e4723b2236d Mon Sep 17 00:00:00 2001 From: ahocevar Date: Tue, 21 May 2019 23:11:06 +0200 Subject: [PATCH 08/14] Reuse container for raster and vector image layers --- src/ol/renderer/canvas/Layer.js | 8 -------- src/ol/renderer/canvas/VectorImageLayer.js | 3 ++- src/ol/renderer/canvas/VectorTileLayer.js | 13 ------------- src/ol/source/Raster.js | 6 +++++- test/spec/ol/renderer/canvas/vectorlayer.test.js | 2 +- .../spec/ol/renderer/canvas/vectortilelayer.test.js | 4 ++-- 6 files changed, 10 insertions(+), 26 deletions(-) diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 5fdd6b0ecc..af036f3c60 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -114,14 +114,6 @@ class CanvasLayerRenderer extends LayerRenderer { } } - /** - * @inheritDoc - */ - disposeInternal() { - this.context.canvas.width = this.context.canvas.height = 0; - super.disposeInternal(); - } - /** * @param {CanvasRenderingContext2D} context Context. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index 13a3e14c14..f3bc9a5428 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -78,6 +78,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) { let skippedFeatures = this.skippedFeatures_; + vectorRenderer.useContainer(null, null); const context = vectorRenderer.context; const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, { declutterItems: [], @@ -94,7 +95,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { if (vectorRenderer.prepareFrame(imageFrameState, layerState) && (vectorRenderer.replayGroupChanged || !equals(skippedFeatures, newSkippedFeatures))) { - vectorRenderer.renderFrame(imageFrameState, layerState); + vectorRenderer.renderFrame(imageFrameState, layerState, null); renderDeclutterItems(imageFrameState, null); skippedFeatures = newSkippedFeatures; callback(); diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 5f80684fa3..03ff61092e 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -111,14 +111,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { this.zDirection = 1; } - /** - * @inheritDoc - */ - disposeInternal() { - this.overlayContext_.canvas.width = this.overlayContext_.canvas.height = 0; - super.disposeInternal(); - } - /** * @inheritDoc */ @@ -399,14 +391,9 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { const renderMode = layer.getRenderMode(); if (renderMode === VectorTileRenderType.IMAGE) { - this.renderTileImages_(hifi, frameState); return this.container; } - if (!isEmpty(this.renderTileImageQueue_) && !this.extentChanged) { - this.renderTileImages_(hifi, frameState); - } - const context = this.overlayContext_; const declutterReplays = layer.getDeclutter() ? {} : null; const source = layer.getSource(); diff --git a/src/ol/source/Raster.js b/src/ol/source/Raster.js index ed44849ff8..9759b66dbb 100644 --- a/src/ol/source/Raster.js +++ b/src/ol/source/Raster.js @@ -444,7 +444,11 @@ function getImageData(layer, frameState, layerState) { } const width = frameState.size[0]; const height = frameState.size[1]; - const element = renderer.renderFrame(frameState, layerState); + const container = renderer.renderFrame(frameState, layerState, null); + let element; + if (container) { + element = container.firstElementChild; + } if (!(element instanceof HTMLCanvasElement)) { throw new Error('Unsupported rendered element: ' + element); } diff --git a/test/spec/ol/renderer/canvas/vectorlayer.test.js b/test/spec/ol/renderer/canvas/vectorlayer.test.js index ea591ddc9c..b2ea559139 100644 --- a/test/spec/ol/renderer/canvas/vectorlayer.test.js +++ b/test/spec/ol/renderer/canvas/vectorlayer.test.js @@ -307,7 +307,7 @@ describe('ol.renderer.canvas.VectorLayer', function() { let rendered = false; if (renderer.prepareFrame(frameState, {})) { rendered = true; - renderer.renderFrame(frameState, layer.getLayerState()); + renderer.renderFrame(frameState, layer.getLayerState(), null); } expect(rendered).to.be(true); }); diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index af8d06bc17..9efe8fe0d1 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -250,10 +250,10 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { renderer.renderFrame(frameState, {}); const replayState = renderer.renderedTiles[0].getReplayState(layer); const revision = replayState.renderedTileRevision; - renderer.renderFrame(frameState, {}); + renderer.renderFrame(frameState, {}, null); expect(replayState.renderedTileRevision).to.be(revision); layer.changed(); - renderer.renderFrame(frameState, {}); + renderer.renderFrame(frameState, {}, null); expect(replayState.renderedTileRevision).to.be(revision + 1); expect(Object.keys(renderer.tileListenerKeys_).length).to.be(0); }); From c56ad4363dad2067cd548310fd466f967583046c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 22 May 2019 11:53:38 +0200 Subject: [PATCH 09/14] Canvas opacity instead of css --- src/ol/render/canvas.js | 2 +- src/ol/renderer/canvas/ImageLayer.js | 14 +++++++++----- src/ol/renderer/canvas/TileLayer.js | 9 ++------- src/ol/renderer/canvas/VectorLayer.js | 10 ++++++++++ src/ol/renderer/canvas/VectorTileLayer.js | 5 ----- 5 files changed, 22 insertions(+), 18 deletions(-) diff --git a/src/ol/render/canvas.js b/src/ol/render/canvas.js index 3e55f4216f..0dc981a605 100644 --- a/src/ol/render/canvas.js +++ b/src/ol/render/canvas.js @@ -403,7 +403,7 @@ export function drawImage(context, context.drawImage(image, originX, originY, w, h, x, y, w * scale, h * scale); - if (alpha) { + if (opacity != 1) { context.globalAlpha = alpha; } if (transform) { diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index e830cbf645..0b7e9a4fa3 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -140,8 +140,17 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { this.preRender(context, frameState); if (dw >= 0.5 && dh >= 0.5) { + const opacity = this.getLayer().getOpacity(); + let previousAlpha; + if (opacity !== 1) { + previousAlpha = this.context.globalAlpha; + this.context.globalAlpha = opacity; + } this.context.drawImage(img, 0, 0, +img.width, +img.height, Math.round(dx), Math.round(dy), Math.round(dw), Math.round(dh)); + if (opacity !== 1) { + this.context.globalAlpha = previousAlpha; + } } this.postRender(context, frameState); @@ -149,11 +158,6 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { context.restore(); } - const opacity = layerState.opacity; - if (opacity !== parseFloat(canvas.style.opacity)) { - canvas.style.opacity = opacity; - } - const canvasTransform = transformToString(this.pixelTransform_); if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 0f1077e820..9dda91bf95 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -274,7 +274,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { }); let clips, clipZs, currentClip; - if (tileSource.getOpaque(frameState.viewState.projection)) { + if (!this.containerReused || tileSource.getOpaque(frameState.viewState.projection)) { zs = zs.reverse(); } else { clips = []; @@ -360,11 +360,6 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { context.restore(); } - const opacity = layerState.opacity; - if (opacity !== parseFloat(canvas.style.opacity)) { - canvas.style.opacity = opacity; - } - const canvasTransform = transformToString(this.pixelTransform_); if (canvasTransform !== canvas.style.transform) { canvas.style.transform = canvasTransform; @@ -389,7 +384,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { return; } const uid = getUid(this); - const alpha = transition ? tile.getAlpha(uid, frameState.time) : 1; + const alpha = this.getLayer().getOpacity() * (transition ? tile.getAlpha(uid, frameState.time) : 1); const alphaChanged = alpha !== this.context.globalAlpha; if (alphaChanged) { this.context.save(); diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 62741a36d7..ff73140764 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -67,6 +67,16 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.replayGroupChanged = true; } + /** + * @inheritDoc + */ + useContainer(target, transform) { + if (this.getLayer().getOpacity() < 1) { + target = null; + } + super.useContainer(target, transform); + } + /** * @inheritDoc */ diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index 03ff61092e..d3b16c63a3 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -481,11 +481,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems); } - const opacity = layerState.opacity; - if (opacity !== parseFloat(canvas.style.opacity)) { - canvas.style.opacity = opacity; - } - return this.container; } From 4c8effe6faaf98e02c9a149a833cbae1e6b84849 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 22 May 2019 12:02:31 +0200 Subject: [PATCH 10/14] No tile transition when layer opacity is set --- src/ol/renderer/canvas/TileLayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 9dda91bf95..a8eaea7205 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -274,7 +274,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { }); let clips, clipZs, currentClip; - if (!this.containerReused || tileSource.getOpaque(frameState.viewState.projection)) { + if (layerState.opacity === 1 && (!this.containerReused || tileSource.getOpaque(frameState.viewState.projection))) { zs = zs.reverse(); } else { clips = []; From ae47d3df589666223704c806240cad01eec675af Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 22 May 2019 12:20:34 +0200 Subject: [PATCH 11/14] Use opacity from layer state --- src/ol/renderer/canvas/ImageLayer.js | 4 ++-- src/ol/renderer/canvas/Layer.js | 3 ++- src/ol/renderer/canvas/TileLayer.js | 9 +++++---- src/ol/renderer/canvas/VectorImageLayer.js | 2 +- src/ol/renderer/canvas/VectorLayer.js | 8 ++++---- src/ol/renderer/canvas/VectorTileLayer.js | 4 ++-- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/src/ol/renderer/canvas/ImageLayer.js b/src/ol/renderer/canvas/ImageLayer.js index 0b7e9a4fa3..bd40b16878 100644 --- a/src/ol/renderer/canvas/ImageLayer.js +++ b/src/ol/renderer/canvas/ImageLayer.js @@ -101,7 +101,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { ); makeInverse(this.inversePixelTransform_, this.pixelTransform_); - this.useContainer(target, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_, layerState.opacity); const context = this.context; const canvas = context.canvas; @@ -140,7 +140,7 @@ class CanvasImageLayerRenderer extends CanvasLayerRenderer { this.preRender(context, frameState); if (dw >= 0.5 && dh >= 0.5) { - const opacity = this.getLayer().getOpacity(); + const opacity = layerState.opacity; let previousAlpha; if (opacity !== 1) { previousAlpha = this.context.globalAlpha; diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index af036f3c60..568ba6edd8 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -74,8 +74,9 @@ class CanvasLayerRenderer extends LayerRenderer { * Get a rendering container from an existing target, if compatible. * @param {HTMLElement} target Potential render target. * @param {import("../../transform").Transform} transform Transform. + * @param {number} opacity Opacity. */ - useContainer(target, transform) { + useContainer(target, transform, opacity) { const layerClassName = this.getLayer().getClassName(); let container, context; if (target) { diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index a8eaea7205..dd65150e9a 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -233,7 +233,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { -width / 2, -height / 2 ); - this.useContainer(target, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_, layerState.opacity); const context = this.context; const canvas = context.canvas; @@ -334,7 +334,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { clips.push(currentClip); clipZs.push(currentZ); } - this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, transition); + this.drawTileImage(tile, frameState, x, y, w, h, tileGutter, transition, layerState.opacity); if (clips) { context.restore(); } @@ -377,14 +377,15 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { * @param {number} h Height of the tile. * @param {number} gutter Tile gutter. * @param {boolean} transition Apply an alpha transition. + * @param {number} opacity Opacity. */ - drawTileImage(tile, frameState, x, y, w, h, gutter, transition) { + drawTileImage(tile, frameState, x, y, w, h, gutter, transition, opacity) { const image = this.getTileImage(tile); if (!image) { return; } const uid = getUid(this); - const alpha = this.getLayer().getOpacity() * (transition ? tile.getAlpha(uid, frameState.time) : 1); + const alpha = opacity * (transition ? tile.getAlpha(uid, frameState.time) : 1); const alphaChanged = alpha !== this.context.globalAlpha; if (alphaChanged) { this.context.save(); diff --git a/src/ol/renderer/canvas/VectorImageLayer.js b/src/ol/renderer/canvas/VectorImageLayer.js index f3bc9a5428..c4b6d98c27 100644 --- a/src/ol/renderer/canvas/VectorImageLayer.js +++ b/src/ol/renderer/canvas/VectorImageLayer.js @@ -78,7 +78,7 @@ class CanvasVectorImageLayerRenderer extends CanvasImageLayerRenderer { if (!hints[ViewHint.ANIMATING] && !hints[ViewHint.INTERACTING] && !isEmpty(renderedExtent)) { let skippedFeatures = this.skippedFeatures_; - vectorRenderer.useContainer(null, null); + vectorRenderer.useContainer(null, null, 1); const context = vectorRenderer.context; const imageFrameState = /** @type {import("../../PluggableMap.js").FrameState} */ (assign({}, frameState, { declutterItems: [], diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index ff73140764..5e872c63cc 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -70,11 +70,11 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { /** * @inheritDoc */ - useContainer(target, transform) { - if (this.getLayer().getOpacity() < 1) { + useContainer(target, transform, opacity) { + if (opacity < 1) { target = null; } - super.useContainer(target, transform); + super.useContainer(target, transform, opacity); } /** @@ -88,7 +88,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { makeScale(this.pixelTransform_, 1 / pixelRatio, 1 / pixelRatio); makeInverse(this.inversePixelTransform_, this.pixelTransform_); - this.useContainer(target, this.pixelTransform_); + this.useContainer(target, this.pixelTransform_, layerState.opacity); const context = this.context; const canvas = context.canvas; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index d3b16c63a3..e463b2aff7 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -114,7 +114,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { /** * @inheritDoc */ - useContainer(target, transform) { + useContainer(target, transform, opacity) { let overlayContext; if (target && target.childElementCount === 2) { overlayContext = target.lastElementChild.getContext('2d'); @@ -123,7 +123,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } const containerReused = this.containerReused; - super.useContainer(target, transform); + super.useContainer(target, transform, opacity); if (containerReused && !this.containerReused && !overlayContext) { this.overlayContext_ = null; } From ace5c65ee573649dfb953502d34c4b113c46301a Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 22 May 2019 15:39:21 +0200 Subject: [PATCH 12/14] Smarter opacity handling --- src/ol/PluggableMap.js | 9 ++++++++- src/ol/render.js | 5 +++-- src/ol/render/canvas/Executor.js | 23 +++++++++++++++-------- src/ol/render/canvas/ExecutorGroup.js | 10 +++++++--- src/ol/renderer/canvas/Layer.js | 2 +- src/ol/renderer/canvas/VectorLayer.js | 7 ++++--- src/ol/renderer/canvas/VectorTileLayer.js | 2 +- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index b73e6489ba..46e3233525 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -39,7 +39,7 @@ import {create as createTransform, apply as applyTransform} from './transform.js * @property {boolean} animate * @property {import("./transform.js").Transform} coordinateToPixelTransform * @property {null|import("./extent.js").Extent} extent - * @property {Array<*>} declutterItems + * @property {Array} declutterItems * @property {import("./coordinate.js").Coordinate} focus * @property {number} index * @property {Array} layerStatesArray @@ -54,6 +54,13 @@ import {create as createTransform, apply as applyTransform} from './transform.js */ +/** + * @typedef {Object} DeclutterItems + * @property {Array<*>} items Declutter items of an executor. + * @property {number} opacity Layer opacity. + */ + + /** * @typedef {function(PluggableMap, ?FrameState): any} PostRenderFunction */ diff --git a/src/ol/render.js b/src/ol/render.js index c413506596..fb48e303bd 100644 --- a/src/ol/render.js +++ b/src/ol/render.js @@ -122,9 +122,10 @@ export function renderDeclutterItems(frameState, declutterTree) { } const items = frameState.declutterItems; for (let z = items.length - 1; z >= 0; --z) { - const zIndexItems = items[z]; + const item = items[z]; + const zIndexItems = item.items; for (let i = 0, ii = zIndexItems.length; i < ii; i += 3) { - declutterTree = zIndexItems[i].renderDeclutter(zIndexItems[i + 1], zIndexItems[i + 2], declutterTree); + declutterTree = zIndexItems[i].renderDeclutter(zIndexItems[i + 1], zIndexItems[i + 2], item.opacity, declutterTree); } } items.length = 0; diff --git a/src/ol/render/canvas/Executor.js b/src/ol/render/canvas/Executor.js index 0ae1c224a5..afb0198a8c 100644 --- a/src/ol/render/canvas/Executor.js +++ b/src/ol/render/canvas/Executor.js @@ -416,10 +416,11 @@ class Executor extends Disposable { /** * @param {import("../canvas.js").DeclutterGroup} declutterGroup Declutter group. * @param {import("../../Feature.js").FeatureLike} feature Feature. + * @param {number} opacity Layer opacity. * @param {?} declutterTree Declutter tree. * @return {?} Declutter tree. */ - renderDeclutter(declutterGroup, feature, declutterTree) { + renderDeclutter(declutterGroup, feature, opacity, declutterTree) { if (declutterGroup && declutterGroup.length > 5) { const groupCount = declutterGroup[4]; if (groupCount == 1 || groupCount == declutterGroup.length - 5) { @@ -438,13 +439,19 @@ class Executor extends Disposable { declutterTree.insert(box); for (let j = 5, jj = declutterGroup.length; j < jj; ++j) { const declutterData = /** @type {Array} */ (declutterGroup[j]); - if (declutterData) { - if (declutterData.length > 11) { - this.replayTextBackground_(declutterData[0], - declutterData[13], declutterData[14], declutterData[15], declutterData[16], - declutterData[11], declutterData[12]); - } - drawImage.apply(undefined, declutterData); + const context = declutterData[0]; + const currentAlpha = context.globalAlpha; + if (currentAlpha !== opacity) { + context.globalAlpha = opacity; + } + if (declutterData.length > 11) { + this.replayTextBackground_(declutterData[0], + declutterData[13], declutterData[14], declutterData[15], declutterData[16], + declutterData[11], declutterData[12]); + } + drawImage.apply(undefined, declutterData); + if (currentAlpha !== opacity) { + context.globalAlpha = currentAlpha; } } } diff --git a/src/ol/render/canvas/ExecutorGroup.js b/src/ol/render/canvas/ExecutorGroup.js index ea28923ee6..1d74dd0eb1 100644 --- a/src/ol/render/canvas/ExecutorGroup.js +++ b/src/ol/render/canvas/ExecutorGroup.js @@ -430,10 +430,11 @@ export function getCircleArray(radius) { * @param {!Object>} declutterReplays Declutter replays. * @param {CanvasRenderingContext2D} context Context. * @param {number} rotation Rotation. + * @param {number} opacity Opacity. * @param {boolean} snapToPixel Snap point symbols and text to integer pixels. - * @param {Array>} declutterItems Declutter items. + * @param {Array} declutterItems Declutter items. */ -export function replayDeclutter(declutterReplays, context, rotation, snapToPixel, declutterItems) { +export function replayDeclutter(declutterReplays, context, rotation, opacity, snapToPixel, declutterItems) { const zs = Object.keys(declutterReplays).map(Number).sort(numberSafeCompareFunction); const skippedFeatureUids = {}; for (let z = 0, zz = zs.length; z < zz; ++z) { @@ -443,7 +444,10 @@ export function replayDeclutter(declutterReplays, context, rotation, snapToPixel const executor = executorData[i++]; if (executor !== currentExecutor) { currentExecutor = executor; - declutterItems.push(executor.declutterItems); + declutterItems.push({ + items: executor.declutterItems, + opacity: opacity + }); } const transform = executorData[i++]; executor.execute(context, transform, rotation, skippedFeatureUids, snapToPixel); diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index 568ba6edd8..d47c1908fb 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -79,7 +79,7 @@ class CanvasLayerRenderer extends LayerRenderer { useContainer(target, transform, opacity) { const layerClassName = this.getLayer().getClassName(); let container, context; - if (target) { + if (target && target.style.opacity === '') { const canvas = target.firstElementChild; if (canvas instanceof HTMLCanvasElement) { context = canvas.getContext('2d'); diff --git a/src/ol/renderer/canvas/VectorLayer.js b/src/ol/renderer/canvas/VectorLayer.js index 5e872c63cc..9246bf3c6c 100644 --- a/src/ol/renderer/canvas/VectorLayer.js +++ b/src/ol/renderer/canvas/VectorLayer.js @@ -164,7 +164,7 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { if (declutterReplays) { const viewHints = frameState.viewHints; const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems); + replayDeclutter(declutterReplays, context, rotation, 1, hifi, frameState.declutterItems); } if (clipped) { @@ -174,8 +174,9 @@ class CanvasVectorLayerRenderer extends CanvasLayerRenderer { this.postRender(context, frameState); const opacity = layerState.opacity; - if (opacity !== parseFloat(canvas.style.opacity)) { - canvas.style.opacity = opacity; + const container = this.container; + if (opacity !== parseFloat(container.style.opacity)) { + container.style.opacity = opacity === 1 ? '' : opacity; } return this.container; diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index e463b2aff7..a0263b0315 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -478,7 +478,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { } } if (declutterReplays) { - replayDeclutter(declutterReplays, context, rotation, hifi, frameState.declutterItems); + replayDeclutter(declutterReplays, context, rotation, layerState.opacity, hifi, frameState.declutterItems); } return this.container; From bdb87f06f9932842b5fbae8aeaae7a21919936ff Mon Sep 17 00:00:00 2001 From: ahocevar Date: Wed, 22 May 2019 18:04:27 +0200 Subject: [PATCH 13/14] Rework tile image render queue --- src/ol/renderer/canvas/VectorTileLayer.js | 59 ++++++++++++++++------- 1 file changed, 41 insertions(+), 18 deletions(-) diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index a0263b0315..e59bcbafa5 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -146,8 +146,11 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {import("../../VectorRenderTile.js").default} tile Tile. * @param {number} pixelRatio Pixel ratio. * @param {import("../../proj/Projection").default} projection Projection. + * @param {boolean} queue Queue tile for rendering. + * @return {boolean} Tile needs to be rendered. */ - prepareTile(tile, pixelRatio, projection) { + prepareTile(tile, pixelRatio, projection, queue) { + let render = false; const tileUid = getUid(tile); const state = tile.getState(); if (((state === TileState.LOADED && tile.hifi) || @@ -159,9 +162,13 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (state === TileState.LOADED || state === TileState.ERROR) { this.updateExecutorGroup_(tile, pixelRatio, projection); if (this.tileImageNeedsRender_(tile, pixelRatio, projection)) { - this.renderTileImageQueue_[tileUid] = tile; + render = true; + if (queue) { + this.renderTileImageQueue_[tileUid] = tile; + } } } + return render; } /** @@ -177,7 +184,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { tile.wantedResolution = resolution; const tileUid = getUid(tile); if (!(tileUid in this.tileListenerKeys_)) { - const listenerKey = listen(tile, EventType.CHANGE, this.prepareTile.bind(this, tile, pixelRatio, projection)); + const listenerKey = listen(tile, EventType.CHANGE, this.prepareTile.bind(this, tile, pixelRatio, projection, true)); this.tileListenerKeys_[tileUid] = listenerKey; } } else { @@ -186,7 +193,10 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { if (hifi || !tile.wantedResolution) { tile.wantedResolution = resolution; } - this.prepareTile(tile, pixelRatio, projection); + const render = this.prepareTile(tile, pixelRatio, projection, false); + if (render) { + this.renderTileImage_(tile, frameState); + } } return tile; } @@ -383,20 +393,27 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { renderFrame(frameState, layerState, target) { const viewHints = frameState.viewHints; const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]); - this.renderTileImages_(hifi, frameState); + this.renderQueuedTileImages_(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) { return this.container; } + const source = layer.getSource(); + // Unqueue tiles from the image queue when we don't need any more + const usedTiles = frameState.usedTiles[getUid(source)]; + for (const tileUid in this.renderTileImageQueue_) { + if (!(tileUid in usedTiles)) { + delete this.renderTileImageQueue_[tileUid]; + } + } + const context = this.overlayContext_; const declutterReplays = layer.getDeclutter() ? {} : null; - const source = layer.getSource(); const replayTypes = VECTOR_REPLAYS[renderMode]; const pixelRatio = frameState.pixelRatio; const rotation = frameState.viewState.rotation; @@ -488,16 +505,17 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @param {boolean} hifi We have time to render a high fidelity map image. * @param {import('../../PluggableMap.js').FrameState} frameState Frame state. */ - renderTileImages_(hifi, frameState) { + renderQueuedTileImages_(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) { + frameState.animate = true; + break; + } const tile = this.renderTileImageQueue_[uid]; delete this.renderTileImageQueue_[uid]; - const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); - const viewState = frameState.viewState; - const tileGrid = layer.getSource().getTileGridForProjection(viewState.projection); - const tileResolution = tileGrid.getResolution(tile.tileCoord[0]); - const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution; - this.renderTileImage_(tile, frameState.pixelRatio, renderPixelRatio, viewState.projection); + this.renderTileImage_(tile, frameState); } } @@ -545,24 +563,29 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { /** * @param {import("../../VectorRenderTile.js").default} tile Tile. - * @param {number} pixelRatio Pixel ratio. - * @param {number} renderPixelRatio Render pixel ratio. - * @param {import("../../proj/Projection.js").default} projection Projection. + * @param {import("../../PluggableMap").FrameState} frameState Frame state. * @private */ - renderTileImage_(tile, pixelRatio, renderPixelRatio, projection) { + renderTileImage_(tile, frameState) { const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer()); const replayState = tile.getReplayState(layer); const revision = layer.getRevision(); const executorGroups = tile.executorGroups[getUid(layer)]; replayState.renderedTileRevision = revision; replayState.renderedTileZ = tile.sourceZ; + const tileCoord = tile.wrappedTileCoord; const z = tileCoord[0]; const source = layer.getSource(); + let pixelRatio = frameState.pixelRatio; + const viewState = frameState.viewState; + const projection = viewState.projection; const tileGrid = source.getTileGridForProjection(projection); + const tileResolution = tileGrid.getResolution(tile.tileCoord[0]); + const renderPixelRatio = frameState.pixelRatio / tile.wantedResolution * tileResolution; const resolution = tileGrid.getResolution(z); const context = tile.getContext(layer); + // Increase tile size when overzooming for low pixel ratio, to avoid blurry tiles pixelRatio = Math.max(pixelRatio, renderPixelRatio / pixelRatio); const size = source.getTilePixelSize(z, pixelRatio, projection); From d8f41a9d7386e70e8c5430cf49d71e3159033a9c Mon Sep 17 00:00:00 2001 From: ahocevar Date: Fri, 24 May 2019 10:16:20 +0200 Subject: [PATCH 14/14] Only reuse target when className is the same --- src/ol/renderer/canvas/Layer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ol/renderer/canvas/Layer.js b/src/ol/renderer/canvas/Layer.js index d47c1908fb..8fd413a508 100644 --- a/src/ol/renderer/canvas/Layer.js +++ b/src/ol/renderer/canvas/Layer.js @@ -79,7 +79,7 @@ class CanvasLayerRenderer extends LayerRenderer { useContainer(target, transform, opacity) { const layerClassName = this.getLayer().getClassName(); let container, context; - if (target && target.style.opacity === '') { + if (target && target.style.opacity === '' && target.className === layerClassName) { const canvas = target.firstElementChild; if (canvas instanceof HTMLCanvasElement) { context = canvas.getContext('2d'); @@ -87,7 +87,6 @@ class CanvasLayerRenderer extends LayerRenderer { } if (context && context.canvas.style.transform === transformToString(transform)) { // Container of the previous layer renderer can be used. - target.classList.add(layerClassName); this.container = target; this.context = context; this.containerReused = true;