Allow canvas reuse for WebGL layers
This commit is contained in:
@@ -34,6 +34,7 @@ import {
|
|||||||
isEmpty,
|
isEmpty,
|
||||||
} from './extent.js';
|
} from './extent.js';
|
||||||
import {fromUserCoordinate, toUserCoordinate} from './proj.js';
|
import {fromUserCoordinate, toUserCoordinate} from './proj.js';
|
||||||
|
import {getUid} from './util.js';
|
||||||
import {hasArea} from './size.js';
|
import {hasArea} from './size.js';
|
||||||
import {listen, unlistenByKey} from './events.js';
|
import {listen, unlistenByKey} from './events.js';
|
||||||
import {removeNode} from './dom.js';
|
import {removeNode} from './dom.js';
|
||||||
@@ -60,6 +61,7 @@ import {removeNode} from './dom.js';
|
|||||||
* @property {!Object<string, Object<string, boolean>>} usedTiles UsedTiles.
|
* @property {!Object<string, Object<string, boolean>>} usedTiles UsedTiles.
|
||||||
* @property {Array<number>} viewHints ViewHints.
|
* @property {Array<number>} viewHints ViewHints.
|
||||||
* @property {!Object<string, Object<string, boolean>>} wantedTiles WantedTiles.
|
* @property {!Object<string, Object<string, boolean>>} wantedTiles WantedTiles.
|
||||||
|
* @property {string} mapId The id of the map.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1469,6 +1471,7 @@ class PluggableMap extends BaseObject {
|
|||||||
viewState: viewState,
|
viewState: viewState,
|
||||||
viewHints: viewHints,
|
viewHints: viewHints,
|
||||||
wantedTiles: {},
|
wantedTiles: {},
|
||||||
|
mapId: getUid(this),
|
||||||
};
|
};
|
||||||
if (viewState.nextCenter && viewState.nextResolution) {
|
if (viewState.nextCenter && viewState.nextResolution) {
|
||||||
const rotation = isNaN(viewState.nextRotation)
|
const rotation = isNaN(viewState.nextRotation)
|
||||||
|
|||||||
@@ -255,9 +255,6 @@ function parseStyle(style, bandCount) {
|
|||||||
* property on the layer object; for example, setting `title: 'My Title'` in the
|
* property on the layer object; for example, setting `title: 'My Title'` in the
|
||||||
* options means that `title` is observable, and has get/set accessors.
|
* options means that `title` is observable, and has get/set accessors.
|
||||||
*
|
*
|
||||||
* **Important**: after removing a `WebGLTile` layer from your map, call `layer.dispose()`
|
|
||||||
* to clean up underlying resources.
|
|
||||||
*
|
|
||||||
* @extends BaseTileLayer<import("../source/DataTile.js").default|import("../source/TileImage.js").default>
|
* @extends BaseTileLayer<import("../source/DataTile.js").default|import("../source/TileImage.js").default>
|
||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* @module ol/renderer/webgl/Layer
|
* @module ol/renderer/webgl/Layer
|
||||||
*/
|
*/
|
||||||
|
import LayerProperty from '../../layer/Property.js';
|
||||||
import LayerRenderer from '../Layer.js';
|
import LayerRenderer from '../Layer.js';
|
||||||
import RenderEvent from '../../render/Event.js';
|
import RenderEvent from '../../render/Event.js';
|
||||||
import RenderEventType from '../../render/EventType.js';
|
import RenderEventType from '../../render/EventType.js';
|
||||||
@@ -84,6 +85,8 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
* @protected
|
* @protected
|
||||||
*/
|
*/
|
||||||
this.helper;
|
this.helper;
|
||||||
|
|
||||||
|
layer.addChangeListener(LayerProperty.MAP, this.removeHelper_.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
removeHelper_() {
|
removeHelper_() {
|
||||||
@@ -99,18 +102,48 @@ class WebGLLayerRenderer extends LayerRenderer {
|
|||||||
* @return {boolean} Layer is ready to be rendered.
|
* @return {boolean} Layer is ready to be rendered.
|
||||||
*/
|
*/
|
||||||
prepareFrame(frameState) {
|
prepareFrame(frameState) {
|
||||||
if (!this.helper && !!this.getLayer().getSource()) {
|
if (this.getLayer().getSource()) {
|
||||||
this.helper = new WebGLHelper({
|
let incrementGroup = true;
|
||||||
postProcesses: this.postProcesses_,
|
let groupNumber = -1;
|
||||||
uniforms: this.uniforms_,
|
let className;
|
||||||
});
|
for (let i = 0, ii = frameState.layerStatesArray.length; i < ii; i++) {
|
||||||
|
const layer = frameState.layerStatesArray[i].layer;
|
||||||
const className = this.getLayer().getClassName();
|
const renderer = layer.getRenderer();
|
||||||
if (className) {
|
if (!(renderer instanceof WebGLLayerRenderer)) {
|
||||||
this.helper.getCanvas().className = className;
|
incrementGroup = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const layerClassName = layer.getClassName();
|
||||||
|
if (incrementGroup || layerClassName !== className) {
|
||||||
|
groupNumber += 1;
|
||||||
|
incrementGroup = false;
|
||||||
|
}
|
||||||
|
className = layerClassName;
|
||||||
|
if (renderer === this) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.afterHelperCreated();
|
const canvasCacheKey =
|
||||||
|
'map/' + frameState.mapId + '/group/' + groupNumber;
|
||||||
|
|
||||||
|
if (!this.helper || !this.helper.canvasCacheKeyMatches(canvasCacheKey)) {
|
||||||
|
if (this.helper) {
|
||||||
|
this.helper.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.helper = new WebGLHelper({
|
||||||
|
postProcesses: this.postProcesses_,
|
||||||
|
uniforms: this.uniforms_,
|
||||||
|
canvasCacheKey: canvasCacheKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (className) {
|
||||||
|
this.helper.getCanvas().className = className;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.afterHelperCreated();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.prepareFrameInternal(frameState);
|
return this.prepareFrameInternal(frameState);
|
||||||
|
|||||||
@@ -453,12 +453,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.helper.finalizeDraw(frameState);
|
this.helper.finalizeDraw(frameState);
|
||||||
const canvas = this.helper.getCanvas();
|
const canvas = this.helper.getCanvas();
|
||||||
|
|
||||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
|
||||||
const opacity = layerState.opacity;
|
|
||||||
if (opacity !== parseFloat(canvas.style.opacity)) {
|
|
||||||
canvas.style.opacity = String(opacity);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.hitDetectionEnabled_) {
|
if (this.hitDetectionEnabled_) {
|
||||||
this.renderHitDetection(frameState);
|
this.renderHitDetection(frameState);
|
||||||
this.hitRenderTarget_.clearCachedData();
|
this.hitRenderTarget_.clearCachedData();
|
||||||
|
|||||||
@@ -195,12 +195,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
this.tileTextureCache_ = new LRUCache(cacheSize);
|
this.tileTextureCache_ = new LRUCache(cacheSize);
|
||||||
|
|
||||||
/**
|
|
||||||
* @type {number}
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
this.renderedOpacity_ = NaN;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
afterHelperCreated() {
|
afterHelperCreated() {
|
||||||
@@ -318,7 +312,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
this.preRender(gl, frameState);
|
this.preRender(gl, frameState);
|
||||||
|
|
||||||
const viewState = frameState.viewState;
|
const viewState = frameState.viewState;
|
||||||
const layerState = frameState.layerStatesArray[frameState.layerIndex];
|
|
||||||
const extent = getRenderExtent(frameState, frameState.extent);
|
const extent = getRenderExtent(frameState, frameState.extent);
|
||||||
const tileLayer = this.getLayer();
|
const tileLayer = this.getLayer();
|
||||||
const tileSource = tileLayer.getSource();
|
const tileSource = tileLayer.getSource();
|
||||||
@@ -505,12 +498,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
|
|||||||
|
|
||||||
const canvas = this.helper.getCanvas();
|
const canvas = this.helper.getCanvas();
|
||||||
|
|
||||||
const opacity = layerState.opacity;
|
|
||||||
if (this.renderedOpacity_ !== opacity) {
|
|
||||||
canvas.style.opacity = String(opacity);
|
|
||||||
this.renderedOpacity_ = opacity;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tileTextureCache = this.tileTextureCache_;
|
const tileTextureCache = this.tileTextureCache_;
|
||||||
while (tileTextureCache.canExpireCache()) {
|
while (tileTextureCache.canExpireCache()) {
|
||||||
const tileTexture = tileTextureCache.pop();
|
const tileTexture = tileTextureCache.pop();
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import {assign} from '../obj.js';
|
|||||||
import {createCanvasContext2D} from '../dom.js';
|
import {createCanvasContext2D} from '../dom.js';
|
||||||
import {create as createTransform} from '../transform.js';
|
import {create as createTransform} from '../transform.js';
|
||||||
import {equals, getCenter, getHeight, getWidth} from '../extent.js';
|
import {equals, getCenter, getHeight, getWidth} from '../extent.js';
|
||||||
|
import {getUid} from '../util.js';
|
||||||
|
|
||||||
let hasImageData = true;
|
let hasImageData = true;
|
||||||
try {
|
try {
|
||||||
@@ -646,6 +647,7 @@ class RasterSource extends ImageSource {
|
|||||||
}),
|
}),
|
||||||
viewHints: [],
|
viewHints: [],
|
||||||
wantedTiles: {},
|
wantedTiles: {},
|
||||||
|
mapId: getUid(this),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.setAttributions(function (frameState) {
|
this.setAttributions(function (frameState) {
|
||||||
|
|||||||
@@ -97,6 +97,7 @@ export const AttributeType = {
|
|||||||
* @property {Object<string,UniformValue>} [uniforms] Uniform definitions; property names must match the uniform
|
* @property {Object<string,UniformValue>} [uniforms] Uniform definitions; property names must match the uniform
|
||||||
* names in the provided or default shaders.
|
* names in the provided or default shaders.
|
||||||
* @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions
|
* @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions
|
||||||
|
* @property {string} [canvasCacheKey] The cache key for the canvas.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +108,78 @@ export const AttributeType = {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CanvasCacheItem
|
||||||
|
* @property {HTMLCanvasElement} canvas Canvas element.
|
||||||
|
* @property {number} users The count of users of this canvas.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Object<string,CanvasCacheItem>}
|
||||||
|
*/
|
||||||
|
const canvasCache = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key The cache key for the canvas.
|
||||||
|
* @return {string} The shared cache key.
|
||||||
|
*/
|
||||||
|
function getSharedCanvasCacheKey(key) {
|
||||||
|
return 'shared/' + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
let uniqueCanvasCacheKeyCount = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {string} The unique cache key.
|
||||||
|
*/
|
||||||
|
function getUniqueCanvasCacheKey() {
|
||||||
|
const key = 'unique/' + uniqueCanvasCacheKeyCount;
|
||||||
|
uniqueCanvasCacheKeyCount += 1;
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key The cache key for the canvas.
|
||||||
|
* @return {HTMLCanvasElement} The canvas.
|
||||||
|
*/
|
||||||
|
function getCanvas(key) {
|
||||||
|
let cacheItem = canvasCache[key];
|
||||||
|
if (!cacheItem) {
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
canvas.style.position = 'absolute';
|
||||||
|
canvas.style.left = '0';
|
||||||
|
cacheItem = {users: 0, canvas};
|
||||||
|
canvasCache[key] = cacheItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheItem.users += 1;
|
||||||
|
return cacheItem.canvas;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key The cache key for the canvas.
|
||||||
|
*/
|
||||||
|
function releaseCanvas(key) {
|
||||||
|
const cacheItem = canvasCache[key];
|
||||||
|
if (!cacheItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheItem.users -= 1;
|
||||||
|
if (cacheItem.users > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = cacheItem.canvas;
|
||||||
|
const gl = getContext(canvas);
|
||||||
|
const extension = gl.getExtension('WEBGL_lose_context');
|
||||||
|
if (extension) {
|
||||||
|
extension.loseContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
delete canvasCache[key];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @classdesc
|
* @classdesc
|
||||||
* This class is intended to provide low-level functions related to WebGL rendering, so that accessing
|
* This class is intended to provide low-level functions related to WebGL rendering, so that accessing
|
||||||
@@ -248,13 +321,19 @@ class WebGLHelper extends Disposable {
|
|||||||
this.boundHandleWebGLContextRestored_ =
|
this.boundHandleWebGLContextRestored_ =
|
||||||
this.handleWebGLContextRestored.bind(this);
|
this.handleWebGLContextRestored.bind(this);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this.canvasCacheKey_ = options.canvasCacheKey
|
||||||
|
? getSharedCanvasCacheKey(options.canvasCacheKey)
|
||||||
|
: getUniqueCanvasCacheKey();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {HTMLCanvasElement}
|
* @type {HTMLCanvasElement}
|
||||||
*/
|
*/
|
||||||
this.canvas_ = document.createElement('canvas');
|
this.canvas_ = getCanvas(this.canvasCacheKey_);
|
||||||
this.canvas_.style.position = 'absolute';
|
|
||||||
this.canvas_.style.left = '0';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
@@ -368,6 +447,14 @@ class WebGLHelper extends Disposable {
|
|||||||
this.startTime_ = Date.now();
|
this.startTime_ = Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} canvasCacheKey The canvas cache key.
|
||||||
|
* @return {boolean} The provided key matches the one this helper was constructed with.
|
||||||
|
*/
|
||||||
|
canvasCacheKeyMatches(canvasCacheKey) {
|
||||||
|
return this.canvasCacheKey_ === getSharedCanvasCacheKey(canvasCacheKey);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a WebGL extension. If the extension is not supported, null is returned.
|
* Get a WebGL extension. If the extension is not supported, null is returned.
|
||||||
* Extensions are cached after they are enabled for the first time.
|
* Extensions are cached after they are enabled for the first time.
|
||||||
@@ -443,10 +530,8 @@ class WebGLHelper extends Disposable {
|
|||||||
this.boundHandleWebGLContextRestored_
|
this.boundHandleWebGLContextRestored_
|
||||||
);
|
);
|
||||||
|
|
||||||
const extension = this.gl_.getExtension('WEBGL_lose_context');
|
releaseCanvas(this.canvasCacheKey_);
|
||||||
if (extension) {
|
|
||||||
extension.loseContext();
|
|
||||||
}
|
|
||||||
delete this.gl_;
|
delete this.gl_;
|
||||||
delete this.canvas_;
|
delete this.canvas_;
|
||||||
}
|
}
|
||||||
@@ -481,6 +566,7 @@ class WebGLHelper extends Disposable {
|
|||||||
|
|
||||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
gl.clear(gl.COLOR_BUFFER_BIT);
|
||||||
|
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(
|
gl.blendFunc(
|
||||||
gl.ONE,
|
gl.ONE,
|
||||||
|
|||||||
@@ -22,11 +22,12 @@ const DEFAULT_FRAGMENT_SHADER = `
|
|||||||
precision mediump float;
|
precision mediump float;
|
||||||
|
|
||||||
uniform sampler2D u_image;
|
uniform sampler2D u_image;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_FragColor = texture2D(u_image, v_texCoord);
|
gl_FragColor = texture2D(u_image, v_texCoord) * u_opacity;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -86,11 +87,12 @@ const DEFAULT_FRAGMENT_SHADER = `
|
|||||||
* precision mediump float;
|
* precision mediump float;
|
||||||
*
|
*
|
||||||
* uniform sampler2D u_image;
|
* uniform sampler2D u_image;
|
||||||
|
* uniform float u_opacity;
|
||||||
*
|
*
|
||||||
* varying vec2 v_texCoord;
|
* varying vec2 v_texCoord;
|
||||||
*
|
*
|
||||||
* void main() {
|
* void main() {
|
||||||
* gl_FragColor = texture2D(u_image, v_texCoord);
|
* gl_FragColor = texture2D(u_image, v_texCoord) * u_opacity;
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
@@ -148,6 +150,10 @@ class WebGLPostProcessingPass {
|
|||||||
this.renderTargetProgram_,
|
this.renderTargetProgram_,
|
||||||
'u_screenSize'
|
'u_screenSize'
|
||||||
);
|
);
|
||||||
|
this.renderTargetOpacityLocation_ = gl.getUniformLocation(
|
||||||
|
this.renderTargetProgram_,
|
||||||
|
'u_opacity'
|
||||||
|
);
|
||||||
this.renderTargetTextureLocation_ = gl.getUniformLocation(
|
this.renderTargetTextureLocation_ = gl.getUniformLocation(
|
||||||
this.renderTargetProgram_,
|
this.renderTargetProgram_,
|
||||||
'u_image'
|
'u_image'
|
||||||
@@ -258,8 +264,6 @@ class WebGLPostProcessingPass {
|
|||||||
gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
|
gl.bindTexture(gl.TEXTURE_2D, this.renderTargetTexture_);
|
||||||
|
|
||||||
// render the frame buffer to the canvas
|
// render the frame buffer to the canvas
|
||||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
|
||||||
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
||||||
gl.enable(gl.BLEND);
|
gl.enable(gl.BLEND);
|
||||||
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight);
|
||||||
@@ -279,6 +283,9 @@ class WebGLPostProcessingPass {
|
|||||||
gl.uniform2f(this.renderTargetUniformLocation_, size[0], size[1]);
|
gl.uniform2f(this.renderTargetUniformLocation_, size[0], size[1]);
|
||||||
gl.uniform1i(this.renderTargetTextureLocation_, 0);
|
gl.uniform1i(this.renderTargetTextureLocation_, 0);
|
||||||
|
|
||||||
|
const opacity = frameState.layerStatesArray[frameState.layerIndex].opacity;
|
||||||
|
gl.uniform1f(this.renderTargetOpacityLocation_, opacity);
|
||||||
|
|
||||||
this.applyUniforms(frameState);
|
this.applyUniforms(frameState);
|
||||||
|
|
||||||
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
||||||
|
|||||||
@@ -1,12 +1,19 @@
|
|||||||
|
import DataTileSource from '../../../../../../src/ol/source/DataTile.js';
|
||||||
import Layer from '../../../../../../src/ol/layer/Layer.js';
|
import Layer from '../../../../../../src/ol/layer/Layer.js';
|
||||||
|
import Map from '../../../../../../src/ol/Map.js';
|
||||||
|
import TileLayer from '../../../../../../src/ol/layer/WebGLTile.js';
|
||||||
|
import VectorLayer from '../../../../../../src/ol/layer/Vector.js';
|
||||||
|
import VectorSource from '../../../../../../src/ol/source/Vector.js';
|
||||||
|
import View from '../../../../../../src/ol/View.js';
|
||||||
import WebGLLayerRenderer, {
|
import WebGLLayerRenderer, {
|
||||||
colorDecodeId,
|
colorDecodeId,
|
||||||
colorEncodeId,
|
colorEncodeId,
|
||||||
getBlankImageData,
|
getBlankImageData,
|
||||||
writePointFeatureToBuffers,
|
writePointFeatureToBuffers,
|
||||||
} from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
} from '../../../../../../src/ol/renderer/webgl/Layer.js';
|
||||||
|
import {getUid} from '../../../../../../src/ol/util.js';
|
||||||
|
|
||||||
describe('ol.renderer.webgl.Layer', function () {
|
describe('ol/renderer/webgl/Layer', function () {
|
||||||
describe('constructor', function () {
|
describe('constructor', function () {
|
||||||
let target;
|
let target;
|
||||||
|
|
||||||
@@ -226,4 +233,205 @@ describe('ol.renderer.webgl.Layer', function () {
|
|||||||
expect(decoded).to.eql(91612);
|
expect(decoded).to.eql(91612);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('context sharing', () => {
|
||||||
|
let target;
|
||||||
|
beforeEach(() => {
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '256px';
|
||||||
|
target.style.height = '256px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
|
||||||
|
function getWebGLLayer(className) {
|
||||||
|
return new TileLayer({
|
||||||
|
className: className,
|
||||||
|
source: new DataTileSource({
|
||||||
|
loader(z, x, y) {
|
||||||
|
return Promise.resolve(new ImageData(256, 256));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCanvasLayer(className) {
|
||||||
|
return new VectorLayer({
|
||||||
|
className: className,
|
||||||
|
source: new VectorSource(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectCacheKeyMatches(layer, key) {
|
||||||
|
expect(layer.getRenderer().helper.canvasCacheKeyMatches(key)).to.be(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function dispose(map) {
|
||||||
|
map.setLayers([]);
|
||||||
|
map.setTarget(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
it('allows sequences of WebGL layers to share a canvas', () => {
|
||||||
|
const layer1 = getWebGLLayer();
|
||||||
|
const layer2 = getWebGLLayer();
|
||||||
|
const layer3 = getWebGLLayer();
|
||||||
|
const layer4 = getCanvasLayer();
|
||||||
|
const layer5 = getCanvasLayer();
|
||||||
|
const layer6 = getWebGLLayer();
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer1, layer2, layer3, layer4, layer5, layer6],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
const mapId = getUid(map);
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/0`);
|
||||||
|
// layer4 and layer5 cannot be grouped
|
||||||
|
expectCacheKeyMatches(layer6, `map/${mapId}/group/1`);
|
||||||
|
|
||||||
|
dispose(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not group layers with different className', () => {
|
||||||
|
const layer1 = getWebGLLayer();
|
||||||
|
const layer2 = getWebGLLayer();
|
||||||
|
const layer3 = getWebGLLayer('foo');
|
||||||
|
const layer4 = getWebGLLayer('foo');
|
||||||
|
const layer5 = getWebGLLayer();
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer1, layer2, layer3, layer4, layer5],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
const mapId = getUid(map);
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer4, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer5, `map/${mapId}/group/2`);
|
||||||
|
|
||||||
|
dispose(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('collapses groups when a layer is removed', () => {
|
||||||
|
const layer1 = getWebGLLayer();
|
||||||
|
const layer2 = getWebGLLayer('foo');
|
||||||
|
const layer3 = getWebGLLayer();
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer1, layer2, layer3],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
const mapId = getUid(map);
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/2`);
|
||||||
|
|
||||||
|
map.removeLayer(layer2);
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expect(layer2.getRenderer().helper).to.be(undefined);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/0`);
|
||||||
|
|
||||||
|
dispose(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('regroups when layer order changes', () => {
|
||||||
|
const layer1 = getWebGLLayer();
|
||||||
|
const layer2 = getWebGLLayer();
|
||||||
|
const layer3 = getCanvasLayer();
|
||||||
|
const layer4 = getWebGLLayer();
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer1, layer2, layer3, layer4],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
const mapId = getUid(map);
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/0`);
|
||||||
|
// layer3 cannot be grouped
|
||||||
|
expectCacheKeyMatches(layer4, `map/${mapId}/group/1`);
|
||||||
|
|
||||||
|
map.removeLayer(layer2);
|
||||||
|
map.addLayer(layer2);
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
// layer3 cannot be grouped
|
||||||
|
expectCacheKeyMatches(layer4, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/1`);
|
||||||
|
|
||||||
|
dispose(map);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('changes groups based on z-index', () => {
|
||||||
|
const layer1 = getWebGLLayer();
|
||||||
|
const layer2 = getWebGLLayer('foo');
|
||||||
|
const layer3 = getWebGLLayer();
|
||||||
|
|
||||||
|
const map = new Map({
|
||||||
|
target: target,
|
||||||
|
layers: [layer1, layer2, layer3],
|
||||||
|
view: new View({
|
||||||
|
center: [0, 0],
|
||||||
|
zoom: 0,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
const mapId = getUid(map);
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/2`);
|
||||||
|
|
||||||
|
layer1.setZIndex(1);
|
||||||
|
map.renderSync();
|
||||||
|
|
||||||
|
expectCacheKeyMatches(layer2, `map/${mapId}/group/0`);
|
||||||
|
expectCacheKeyMatches(layer3, `map/${mapId}/group/1`);
|
||||||
|
expectCacheKeyMatches(layer1, `map/${mapId}/group/1`);
|
||||||
|
|
||||||
|
dispose(map);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
BIN
test/rendering/cases/webgl-mixed-layers/expected.png
Normal file
BIN
test/rendering/cases/webgl-mixed-layers/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 104 KiB |
84
test/rendering/cases/webgl-mixed-layers/main.js
Normal file
84
test/rendering/cases/webgl-mixed-layers/main.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import DataTile from '../../../../src/ol/source/DataTile.js';
|
||||||
|
import KML from '../../../../src/ol/format/KML.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
|
||||||
|
import VectorLayer from '../../../../src/ol/layer/Vector.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
import XYZ from '../../../../src/ol/source/XYZ.js';
|
||||||
|
import {Circle as CircleStyle, Fill, Style} from '../../../../src/ol/style.js';
|
||||||
|
|
||||||
|
const labelCanvasSize = 256;
|
||||||
|
|
||||||
|
const labelCanvas = document.createElement('canvas');
|
||||||
|
labelCanvas.width = labelCanvasSize;
|
||||||
|
labelCanvas.height = labelCanvasSize;
|
||||||
|
|
||||||
|
const labelContext = labelCanvas.getContext('2d');
|
||||||
|
labelContext.textAlign = 'center';
|
||||||
|
labelContext.font = '16px sans-serif';
|
||||||
|
const labelLineHeight = 16;
|
||||||
|
|
||||||
|
new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
|
||||||
|
transition: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new VectorLayer({
|
||||||
|
opacity: 0.5,
|
||||||
|
source: new VectorSource({
|
||||||
|
url: '/data/2012_Earthquakes_Mag5.kml',
|
||||||
|
format: new KML({
|
||||||
|
extractStyles: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
style: new Style({
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 3,
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'orange',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new TileLayer({
|
||||||
|
source: new DataTile({
|
||||||
|
loader: function (z, x, y) {
|
||||||
|
const half = labelCanvasSize / 2;
|
||||||
|
|
||||||
|
labelContext.clearRect(0, 0, labelCanvasSize, labelCanvasSize);
|
||||||
|
|
||||||
|
labelContext.fillStyle = 'white';
|
||||||
|
labelContext.fillText(`z: ${z}`, half, half - labelLineHeight);
|
||||||
|
labelContext.fillText(`x: ${x}`, half, half);
|
||||||
|
labelContext.fillText(`y: ${y}`, half, half + labelLineHeight);
|
||||||
|
|
||||||
|
labelContext.strokeStyle = 'white';
|
||||||
|
labelContext.lineWidth = 2;
|
||||||
|
labelContext.strokeRect(0, 0, labelCanvasSize, labelCanvasSize);
|
||||||
|
|
||||||
|
const data = labelContext.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
labelCanvasSize,
|
||||||
|
labelCanvasSize
|
||||||
|
).data;
|
||||||
|
return Promise.resolve(new Uint8Array(data.buffer));
|
||||||
|
},
|
||||||
|
transition: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [15180597.9736, 2700366.3807],
|
||||||
|
zoom: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
render({
|
||||||
|
message: 'a mix of WebGL and Canvas layers are rendered',
|
||||||
|
});
|
||||||
BIN
test/rendering/cases/webgl-multiple-layers/expected.png
Normal file
BIN
test/rendering/cases/webgl-multiple-layers/expected.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 106 KiB |
82
test/rendering/cases/webgl-multiple-layers/main.js
Normal file
82
test/rendering/cases/webgl-multiple-layers/main.js
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
import DataTile from '../../../../src/ol/source/DataTile.js';
|
||||||
|
import KML from '../../../../src/ol/format/KML.js';
|
||||||
|
import Map from '../../../../src/ol/Map.js';
|
||||||
|
import PointsLayer from '../../../../src/ol/layer/WebGLPoints.js';
|
||||||
|
import TileLayer from '../../../../src/ol/layer/WebGLTile.js';
|
||||||
|
import VectorSource from '../../../../src/ol/source/Vector.js';
|
||||||
|
import View from '../../../../src/ol/View.js';
|
||||||
|
import XYZ from '../../../../src/ol/source/XYZ.js';
|
||||||
|
|
||||||
|
const labelCanvasSize = 256;
|
||||||
|
|
||||||
|
const labelCanvas = document.createElement('canvas');
|
||||||
|
labelCanvas.width = labelCanvasSize;
|
||||||
|
labelCanvas.height = labelCanvasSize;
|
||||||
|
|
||||||
|
const labelContext = labelCanvas.getContext('2d');
|
||||||
|
labelContext.textAlign = 'center';
|
||||||
|
labelContext.font = '16px sans-serif';
|
||||||
|
const labelLineHeight = 16;
|
||||||
|
|
||||||
|
new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new XYZ({
|
||||||
|
url: '/data/tiles/satellite/{z}/{x}/{y}.jpg',
|
||||||
|
transition: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new PointsLayer({
|
||||||
|
opacity: 0.5,
|
||||||
|
source: new VectorSource({
|
||||||
|
url: '/data/2012_Earthquakes_Mag5.kml',
|
||||||
|
format: new KML({
|
||||||
|
extractStyles: false,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
style: {
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: 6,
|
||||||
|
color: 'orange',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
new TileLayer({
|
||||||
|
source: new DataTile({
|
||||||
|
loader: function (z, x, y) {
|
||||||
|
const half = labelCanvasSize / 2;
|
||||||
|
|
||||||
|
labelContext.clearRect(0, 0, labelCanvasSize, labelCanvasSize);
|
||||||
|
|
||||||
|
labelContext.fillStyle = 'white';
|
||||||
|
labelContext.fillText(`z: ${z}`, half, half - labelLineHeight);
|
||||||
|
labelContext.fillText(`x: ${x}`, half, half);
|
||||||
|
labelContext.fillText(`y: ${y}`, half, half + labelLineHeight);
|
||||||
|
|
||||||
|
labelContext.strokeStyle = 'white';
|
||||||
|
labelContext.lineWidth = 2;
|
||||||
|
labelContext.strokeRect(0, 0, labelCanvasSize, labelCanvasSize);
|
||||||
|
|
||||||
|
const data = labelContext.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
labelCanvasSize,
|
||||||
|
labelCanvasSize
|
||||||
|
).data;
|
||||||
|
return Promise.resolve(new Uint8Array(data.buffer));
|
||||||
|
},
|
||||||
|
transition: 0,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: new View({
|
||||||
|
center: [15180597.9736, 2700366.3807],
|
||||||
|
zoom: 2,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
render({
|
||||||
|
message: 'multiple WebGL layers are rendered',
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user