diff --git a/src/ol/layer/WebGLPoints.js b/src/ol/layer/WebGLPoints.js index de9a7360e1..e9460666f6 100644 --- a/src/ol/layer/WebGLPoints.js +++ b/src/ol/layer/WebGLPoints.js @@ -100,7 +100,6 @@ class WebGLPointsLayer extends Layer { */ createRenderer() { return new WebGLPointsLayerRenderer(this, { - className: this.getClassName(), vertexShader: this.parseResult_.builder.getSymbolVertexShader(), fragmentShader: this.parseResult_.builder.getSymbolFragmentShader(), hitVertexShader: diff --git a/src/ol/layer/WebGLTile.js b/src/ol/layer/WebGLTile.js index f92bcfd2a8..a0eb3b170a 100644 --- a/src/ol/layer/WebGLTile.js +++ b/src/ol/layer/WebGLTile.js @@ -311,7 +311,6 @@ class WebGLTileLayer extends BaseTileLayer { vertexShader: parsedStyle.vertexShader, fragmentShader: parsedStyle.fragmentShader, uniforms: parsedStyle.uniforms, - className: this.getClassName(), cacheSize: this.cacheSize_, }); } diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 4b5780efb9..d3de5d205f 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -41,7 +41,6 @@ export const WebGLWorkerMessageType = { /** * @typedef {Object} Options - * @property {string} [className='ol-layer'] A CSS class name to set to the canvas element. * @property {Object} [uniforms] Uniform definitions for the post process steps * @property {Array} [postProcesses] Post-processes definitions */ @@ -70,27 +69,73 @@ class WebGLLayerRenderer extends LayerRenderer { */ this.inversePixelTransform_ = createTransform(); + /** + * @private + */ + this.postProcesses_ = options.postProcesses; + + /** + * @private + */ + this.uniforms_ = options.uniforms; + /** * @type {WebGLHelper} * @protected */ - this.helper = new WebGLHelper({ - postProcesses: options.postProcesses, - uniforms: options.uniforms, - }); + this.helper; + } - if (options.className !== undefined) { - this.helper.getCanvas().className = options.className; + removeHelper_() { + if (this.helper) { + this.helper.dispose(); + delete this.helper; } } + /** + * Determine whether renderFrame should be called. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @return {boolean} Layer is ready to be rendered. + */ + prepareFrame(frameState) { + if (!this.helper && !!this.getLayer().getSource()) { + this.helper = new WebGLHelper({ + postProcesses: this.postProcesses_, + uniforms: this.uniforms_, + }); + + const className = this.getLayer().getClassName(); + if (className) { + this.helper.getCanvas().className = className; + } + + this.afterHelperCreated(); + } + + return this.prepareFrameInternal(frameState); + } + + /** + * @protected + */ + afterHelperCreated() {} + + /** + * Determine whether renderFrame should be called. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @return {boolean} Layer is ready to be rendered. + * @protected + */ + prepareFrameInternal(frameState) { + return true; + } + /** * Clean up. */ disposeInternal() { - this.helper.dispose(); - delete this.helper; - + this.removeHelper_(); super.disposeInternal(); } diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 2397e586cd..5c40dc0eb3 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -132,7 +132,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform; super(layer, { - className: options.className, uniforms: uniforms, postProcesses: options.postProcesses, }); @@ -146,10 +145,21 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { DYNAMIC_DRAW ); - this.program_ = this.helper.getProgram( - options.fragmentShader, - options.vertexShader - ); + /** + * @private + */ + this.vertexShader_ = options.vertexShader; + + /** + * @private + */ + this.fragmentShader_ = options.fragmentShader; + + /** + * @type {WebGLProgram} + * @private + */ + this.program_; /** * @type {boolean} @@ -158,12 +168,21 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.hitDetectionEnabled_ = options.hitFragmentShader && options.hitVertexShader ? true : false; - this.hitProgram_ = - this.hitDetectionEnabled_ && - this.helper.getProgram( - options.hitFragmentShader, - options.hitVertexShader - ); + /** + * @private + */ + this.hitVertexShader_ = options.hitVertexShader; + + /** + * @private + */ + this.hitFragmentShader_ = options.hitFragmentShader; + + /** + * @type {WebGLProgram} + * @private + */ + this.hitProgram_; const customAttributes = options.attributes ? options.attributes.map(function (attribute) { @@ -263,8 +282,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { * @type {WebGLRenderTarget} * @private */ - this.hitRenderTarget_ = - this.hitDetectionEnabled_ && new WebGLRenderTarget(this.helper); + this.hitRenderTarget_; this.worker_ = createWebGLWorker(); this.worker_.addEventListener( @@ -360,6 +378,22 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { ); } + afterHelperCreated() { + this.program_ = this.helper.getProgram( + this.fragmentShader_, + this.vertexShader_ + ); + + if (this.hitDetectionEnabled_) { + this.hitProgram_ = this.helper.getProgram( + this.hitFragmentShader_, + this.hitVertexShader_ + ); + + this.hitRenderTarget_ = new WebGLRenderTarget(this.helper); + } + } + /** * @param {import("../../source/Vector.js").VectorSourceEvent} event Event. * @private @@ -436,11 +470,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { } /** - * Determine whether render should be called. + * Determine whether renderFrame should be called. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @return {boolean} Layer is ready to be rendered. */ - prepareFrame(frameState) { + prepareFrameInternal(frameState) { const layer = this.getLayer(); const vectorSource = layer.getSource(); const viewState = frameState.viewState; diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js index cc6f9c3f88..00a2a03c56 100644 --- a/src/ol/renderer/webgl/TileLayer.js +++ b/src/ol/renderer/webgl/TileLayer.js @@ -103,7 +103,6 @@ function getRenderExtent(frameState, extent) { * @property {string} fragmentShader Fragment shader source. * @property {Object} [uniforms] Additional uniforms * made available to shaders. - * @property {string} [className='ol-layer'] A CSS class name to set to the canvas element. * @property {number} [cacheSize=512] The texture cache size. */ @@ -120,7 +119,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { constructor(tileLayer, options) { super(tileLayer, { uniforms: options.uniforms, - className: options.className, }); /** @@ -154,10 +152,21 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { */ this.tempSize_ = [0, 0]; - this.program_ = this.helper.getProgram( - options.fragmentShader, - options.vertexShader - ); + /** + * @type {WebGLProgram} + * @private + */ + this.program_; + + /** + * @private + */ + this.vertexShader_ = options.vertexShader; + + /** + * @private + */ + this.fragmentShader_ = options.fragmentShader; /** * Tiles are rendered as a quad with the following structure: @@ -173,11 +182,11 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { * * Triangle A: P0, P1, P3 * Triangle B: P1, P2, P3 + * + * @private */ - const indices = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW); - indices.fromArray([0, 1, 3, 1, 2, 3]); - this.helper.flushBufferData(indices); - this.indices_ = indices; + this.indices_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW); + this.indices_.fromArray([0, 1, 3, 1, 2, 3]); const cacheSize = options.cacheSize !== undefined ? options.cacheSize : 512; @@ -187,9 +196,22 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { */ this.tileTextureCache_ = new LRUCache(cacheSize); + /** + * @type {number} + * @private + */ this.renderedOpacity_ = NaN; } + afterHelperCreated() { + this.program_ = this.helper.getProgram( + this.fragmentShader_, + this.vertexShader_ + ); + + this.helper.flushBufferData(this.indices_); + } + /** * @protected * @param {import("../../Tile.js").default} tile Tile. @@ -207,11 +229,11 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { } /** - * Determine whether render should be called. + * Determine whether renderFrame should be called. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. * @return {boolean} Layer is ready to be rendered. */ - prepareFrame(frameState) { + prepareFrameInternal(frameState) { if (isEmpty(getRenderExtent(frameState, frameState.extent))) { return false; } diff --git a/test/browser/spec/ol/layer/heatmap.test.js b/test/browser/spec/ol/layer/Heatmap.test.js similarity index 82% rename from test/browser/spec/ol/layer/heatmap.test.js rename to test/browser/spec/ol/layer/Heatmap.test.js index d67f0a896c..ad8b01f4c3 100644 --- a/test/browser/spec/ol/layer/heatmap.test.js +++ b/test/browser/spec/ol/layer/Heatmap.test.js @@ -5,24 +5,53 @@ import Point from '../../../../../src/ol/geom/Point.js'; import VectorSource from '../../../../../src/ol/source/Vector.js'; import View from '../../../../../src/ol/View.js'; -describe('ol.layer.Heatmap', function () { +describe('ol/layer/Heatmap', function () { describe('constructor', function () { + let target, map; + beforeEach(() => { + target = document.createElement('div'); + target.style.width = '300px'; + target.style.height = '300px'; + document.body.appendChild(target); + + map = new Map({ + view: new View({ + center: [0, 0], + resolution: 0.1, + }), + target: target, + }); + }); + + afterEach(() => { + map.dispose(); + document.body.removeChild(target); + }); + it('can be constructed without arguments', function () { const instance = new HeatmapLayer(); expect(instance).to.be.an(HeatmapLayer); }); + it('has a default className', function () { const layer = new HeatmapLayer({ source: new VectorSource(), }); + map.addLayer(layer); + map.renderSync(); + const canvas = layer.getRenderer().helper.getCanvas(); expect(canvas.className).to.eql('ol-layer'); }); + it('accepts a custom className', function () { const layer = new HeatmapLayer({ source: new VectorSource(), className: 'a-class-name', }); + map.addLayer(layer); + map.renderSync(); + const canvas = layer.getRenderer().helper.getCanvas(); expect(canvas.className).to.eql('a-class-name'); }); diff --git a/test/browser/spec/ol/layer/WebGLTile.test.js b/test/browser/spec/ol/layer/WebGLTile.test.js index 0675807bb8..04662ab1ab 100644 --- a/test/browser/spec/ol/layer/WebGLTile.test.js +++ b/test/browser/spec/ol/layer/WebGLTile.test.js @@ -4,6 +4,7 @@ import View from '../../../../../src/ol/View.js'; import WebGLHelper from '../../../../../src/ol/webgl/Helper.js'; import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js'; import {createCanvasContext2D} from '../../../../../src/ol/dom.js'; +import {getForViewAndSize} from '../../../../../src/ol/extent.js'; import {getRenderPixel} from '../../../../../src/ol/render.js'; describe('ol/layer/WebGLTile', function () { @@ -62,7 +63,21 @@ describe('ol/layer/WebGLTile', function () { it('creates fragment and vertex shaders', function () { const compileShaderSpy = sinon.spy(WebGLHelper.prototype, 'compileShader'); - layer.createRenderer(); + const renderer = layer.createRenderer(); + const viewState = map.getView().getState(); + const size = map.getSize(); + const frameState = { + viewState: viewState, + extent: getForViewAndSize( + viewState.center, + viewState.resolution, + viewState.rotation, + size + ), + layerStatesArray: map.getLayerGroup().getLayerStatesArray(), + layerIndex: 0, + }; + renderer.prepareFrame(frameState); compileShaderSpy.restore(); expect(compileShaderSpy.callCount).to.be(2); expect(compileShaderSpy.getCall(0).args[0].replace(/[ \n]+/g, ' ')).to.be( diff --git a/test/browser/spec/ol/renderer/webgl/pointslayer.test.js b/test/browser/spec/ol/renderer/webgl/PointsLayer.test.js similarity index 95% rename from test/browser/spec/ol/renderer/webgl/pointslayer.test.js rename to test/browser/spec/ol/renderer/webgl/PointsLayer.test.js index 32d719b6bc..171d412a2b 100644 --- a/test/browser/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/browser/spec/ol/renderer/webgl/PointsLayer.test.js @@ -75,7 +75,7 @@ const hitFragmentShader = ` gl_FragColor = v_hitColor; }`; -describe('ol.renderer.webgl.PointsLayer', function () { +describe('ol/renderer/webgl/PointsLayer', function () { describe('constructor', function () { let target; @@ -116,16 +116,16 @@ describe('ol.renderer.webgl.PointsLayer', function () { hitVertexShader: hitVertexShader, hitFragmentShader: hitFragmentShader, }); - frameState = Object.assign( - { - size: [2, 2], - extent: [-100, -100, 100, 100], - }, - baseFrameState - ); + frameState = Object.assign({}, baseFrameState, { + size: [2, 2], + extent: [-100, -100, 100, 100], + layerStatesArray: [layer.getLayerState()], + }); }); it('calls WebGlHelper#prepareDraw', function () { + renderer.prepareFrame(frameState); + const spy = sinon.spy(renderer.helper, 'prepareDraw'); renderer.prepareFrame(frameState); expect(spy.called).to.be(true); @@ -327,14 +327,12 @@ describe('ol.renderer.webgl.PointsLayer', function () { 0, 0 ); - const frameState = Object.assign( - { - extent: [-20, -20, 20, 20], - size: [40, 40], - coordinateToPixelTransform: transform, - }, - baseFrameState - ); + const frameState = Object.assign({}, baseFrameState, { + extent: [-20, -20, 20, 20], + size: [40, 40], + coordinateToPixelTransform: transform, + layerStatesArray: [layer.getLayerState()], + }); renderer.prepareFrame(frameState); renderer.worker_.addEventListener('message', function () { @@ -391,15 +389,14 @@ describe('ol.renderer.webgl.PointsLayer', function () { 0, 0 ); - const frameState = Object.assign( - { - pixelRatio: 3, - extent: [-20, -20, 20, 20], - size: [40, 40], - coordinateToPixelTransform: transform, - }, - baseFrameState - ); + const frameState = Object.assign({}, baseFrameState, { + pixelRatio: 3, + extent: [-20, -20, 20, 20], + size: [40, 40], + coordinateToPixelTransform: transform, + layerStatesArray: [layer.getLayerState()], + }); + let found; const cb = function (feature) { found = feature; @@ -446,6 +443,13 @@ describe('ol.renderer.webgl.PointsLayer', function () { fragmentShader: simpleFragmentShader, }); + const frameState = Object.assign({}, baseFrameState, { + size: [2, 2], + extent: [-100, -100, 100, 100], + layerStatesArray: [layer.getLayerState()], + }); + renderer.prepareFrame(frameState); + const spyHelper = sinon.spy(renderer.helper, 'disposeInternal'); const spyWorker = sinon.spy(renderer.worker_, 'terminate'); renderer.disposeInternal(); diff --git a/test/browser/spec/ol/renderer/webgl/tilelayer.test.js b/test/browser/spec/ol/renderer/webgl/TileLayer.test.js similarity index 96% rename from test/browser/spec/ol/renderer/webgl/tilelayer.test.js rename to test/browser/spec/ol/renderer/webgl/TileLayer.test.js index ed4a224640..2a0a7fb680 100644 --- a/test/browser/spec/ol/renderer/webgl/tilelayer.test.js +++ b/test/browser/spec/ol/renderer/webgl/TileLayer.test.js @@ -7,7 +7,7 @@ import {create} from '../../../../../../src/ol/transform.js'; import {createCanvasContext2D} from '../../../../../../src/ol/dom.js'; import {get} from '../../../../../../src/ol/proj.js'; -describe('ol.renderer.webgl.TileLayer', function () { +describe('ol/renderer/webgl/TileLayer', function () { /** @type {import("../../../../../../src/ol/renderer/webgl/TileLayer.js").default} */ let renderer; /** @type {WebGLTileLayer} */ @@ -74,6 +74,9 @@ describe('ol.renderer.webgl.TileLayer', function () { }); it('#renderFrame()', function () { + const ready = renderer.prepareFrame(frameState); + expect(ready).to.be(true); + const rendered = renderer.renderFrame(frameState); expect(rendered).to.be.a(HTMLCanvasElement); expect(frameState.tileQueue.getCount()).to.be(1); diff --git a/test/browser/spec/ol/webgl/tiletexture.test.js b/test/browser/spec/ol/webgl/TileTexture.test.js similarity index 84% rename from test/browser/spec/ol/webgl/tiletexture.test.js rename to test/browser/spec/ol/webgl/TileTexture.test.js index ef6bc29733..3ad9a1bb19 100644 --- a/test/browser/spec/ol/webgl/tiletexture.test.js +++ b/test/browser/spec/ol/webgl/TileTexture.test.js @@ -5,12 +5,16 @@ import TileState from '../../../../../src/ol/TileState.js'; import TileTexture from '../../../../../src/ol/webgl/TileTexture.js'; import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer.js'; import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js'; +import {EXTENT as EPSG3857_EXTENT} from '../../../../../src/ol/proj/epsg3857.js'; import {createCanvasContext2D} from '../../../../../src/ol/dom.js'; -describe('ol.webgl.TileTexture', function () { +describe('ol/webgl/TileTexture', function () { /** @type {TileTexture} */ let tileTexture; + /** @type {import("../../../../../src/ol/renderer/webgl/TileLayer.js").default} */ + let renderer; + beforeEach(function () { const layer = new WebGLTileLayer({ source: new DataTileSource({ @@ -24,10 +28,16 @@ describe('ol.webgl.TileTexture', function () { }, }), }); - const renderer = - /** @type {import("../../../../../src/ol/renderer/webgl/TileLayer.js").default} */ ( - layer.createRenderer() - ); + + renderer = layer.createRenderer(); + renderer.prepareFrame({ + extent: EPSG3857_EXTENT, + layerIndex: 0, + layerStatesArray: [layer.getLayerState()], + size: [256, 256], + mapId: 'map-1', + }); + tileTexture = new TileTexture( layer.getSource().getTile(3, 2, 1), layer.getSource().getTileGrid(), @@ -35,6 +45,10 @@ describe('ol.webgl.TileTexture', function () { ); }); + afterEach(() => { + renderer.dispose(); + }); + it('constructor', function () { expect(tileTexture.tile.tileCoord).to.eql([3, 2, 1]); expect(tileTexture.coords).to.be.a(WebGLArrayBuffer);