Lazily create the WebGL helper

This commit is contained in:
Tim Schaub
2021-11-13 11:14:59 -07:00
parent 1d94477ed3
commit 0dfbedb099
10 changed files with 236 additions and 72 deletions

View File

@@ -100,7 +100,6 @@ class WebGLPointsLayer extends Layer {
*/ */
createRenderer() { createRenderer() {
return new WebGLPointsLayerRenderer(this, { return new WebGLPointsLayerRenderer(this, {
className: this.getClassName(),
vertexShader: this.parseResult_.builder.getSymbolVertexShader(), vertexShader: this.parseResult_.builder.getSymbolVertexShader(),
fragmentShader: this.parseResult_.builder.getSymbolFragmentShader(), fragmentShader: this.parseResult_.builder.getSymbolFragmentShader(),
hitVertexShader: hitVertexShader:

View File

@@ -311,7 +311,6 @@ class WebGLTileLayer extends BaseTileLayer {
vertexShader: parsedStyle.vertexShader, vertexShader: parsedStyle.vertexShader,
fragmentShader: parsedStyle.fragmentShader, fragmentShader: parsedStyle.fragmentShader,
uniforms: parsedStyle.uniforms, uniforms: parsedStyle.uniforms,
className: this.getClassName(),
cacheSize: this.cacheSize_, cacheSize: this.cacheSize_,
}); });
} }

View File

@@ -41,7 +41,6 @@ export const WebGLWorkerMessageType = {
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {string} [className='ol-layer'] A CSS class name to set to the canvas element.
* @property {Object<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps * @property {Object<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
* @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions * @property {Array<PostProcessesOptions>} [postProcesses] Post-processes definitions
*/ */
@@ -70,27 +69,73 @@ class WebGLLayerRenderer extends LayerRenderer {
*/ */
this.inversePixelTransform_ = createTransform(); this.inversePixelTransform_ = createTransform();
/**
* @private
*/
this.postProcesses_ = options.postProcesses;
/**
* @private
*/
this.uniforms_ = options.uniforms;
/** /**
* @type {WebGLHelper} * @type {WebGLHelper}
* @protected * @protected
*/ */
this.helper;
}
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({ this.helper = new WebGLHelper({
postProcesses: options.postProcesses, postProcesses: this.postProcesses_,
uniforms: options.uniforms, uniforms: this.uniforms_,
}); });
if (options.className !== undefined) { const className = this.getLayer().getClassName();
this.helper.getCanvas().className = options.className; 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. * Clean up.
*/ */
disposeInternal() { disposeInternal() {
this.helper.dispose(); this.removeHelper_();
delete this.helper;
super.disposeInternal(); super.disposeInternal();
} }

View File

@@ -132,7 +132,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform; uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
super(layer, { super(layer, {
className: options.className,
uniforms: uniforms, uniforms: uniforms,
postProcesses: options.postProcesses, postProcesses: options.postProcesses,
}); });
@@ -146,10 +145,21 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
DYNAMIC_DRAW DYNAMIC_DRAW
); );
this.program_ = this.helper.getProgram( /**
options.fragmentShader, * @private
options.vertexShader */
); this.vertexShader_ = options.vertexShader;
/**
* @private
*/
this.fragmentShader_ = options.fragmentShader;
/**
* @type {WebGLProgram}
* @private
*/
this.program_;
/** /**
* @type {boolean} * @type {boolean}
@@ -158,12 +168,21 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.hitDetectionEnabled_ = this.hitDetectionEnabled_ =
options.hitFragmentShader && options.hitVertexShader ? true : false; options.hitFragmentShader && options.hitVertexShader ? true : false;
this.hitProgram_ = /**
this.hitDetectionEnabled_ && * @private
this.helper.getProgram( */
options.hitFragmentShader, this.hitVertexShader_ = options.hitVertexShader;
options.hitVertexShader
); /**
* @private
*/
this.hitFragmentShader_ = options.hitFragmentShader;
/**
* @type {WebGLProgram}
* @private
*/
this.hitProgram_;
const customAttributes = options.attributes const customAttributes = options.attributes
? options.attributes.map(function (attribute) { ? options.attributes.map(function (attribute) {
@@ -263,8 +282,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
* @type {WebGLRenderTarget} * @type {WebGLRenderTarget}
* @private * @private
*/ */
this.hitRenderTarget_ = this.hitRenderTarget_;
this.hitDetectionEnabled_ && new WebGLRenderTarget(this.helper);
this.worker_ = createWebGLWorker(); this.worker_ = createWebGLWorker();
this.worker_.addEventListener( 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. * @param {import("../../source/Vector.js").VectorSourceEvent} event Event.
* @private * @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. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {boolean} Layer is ready to be rendered. * @return {boolean} Layer is ready to be rendered.
*/ */
prepareFrame(frameState) { prepareFrameInternal(frameState) {
const layer = this.getLayer(); const layer = this.getLayer();
const vectorSource = layer.getSource(); const vectorSource = layer.getSource();
const viewState = frameState.viewState; const viewState = frameState.viewState;

View File

@@ -103,7 +103,6 @@ function getRenderExtent(frameState, extent) {
* @property {string} fragmentShader Fragment shader source. * @property {string} fragmentShader Fragment shader source.
* @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms * @property {Object<string, import("../../webgl/Helper").UniformValue>} [uniforms] Additional uniforms
* made available to shaders. * 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. * @property {number} [cacheSize=512] The texture cache size.
*/ */
@@ -120,7 +119,6 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
constructor(tileLayer, options) { constructor(tileLayer, options) {
super(tileLayer, { super(tileLayer, {
uniforms: options.uniforms, uniforms: options.uniforms,
className: options.className,
}); });
/** /**
@@ -154,10 +152,21 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
*/ */
this.tempSize_ = [0, 0]; this.tempSize_ = [0, 0];
this.program_ = this.helper.getProgram( /**
options.fragmentShader, * @type {WebGLProgram}
options.vertexShader * @private
); */
this.program_;
/**
* @private
*/
this.vertexShader_ = options.vertexShader;
/**
* @private
*/
this.fragmentShader_ = options.fragmentShader;
/** /**
* Tiles are rendered as a quad with the following structure: * Tiles are rendered as a quad with the following structure:
@@ -173,11 +182,11 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
* *
* Triangle A: P0, P1, P3 * Triangle A: P0, P1, P3
* Triangle B: P1, P2, P3 * Triangle B: P1, P2, P3
*
* @private
*/ */
const indices = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW); this.indices_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, STATIC_DRAW);
indices.fromArray([0, 1, 3, 1, 2, 3]); this.indices_.fromArray([0, 1, 3, 1, 2, 3]);
this.helper.flushBufferData(indices);
this.indices_ = indices;
const cacheSize = options.cacheSize !== undefined ? options.cacheSize : 512; const cacheSize = options.cacheSize !== undefined ? options.cacheSize : 512;
@@ -187,9 +196,22 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer {
*/ */
this.tileTextureCache_ = new LRUCache(cacheSize); this.tileTextureCache_ = new LRUCache(cacheSize);
/**
* @type {number}
* @private
*/
this.renderedOpacity_ = NaN; this.renderedOpacity_ = NaN;
} }
afterHelperCreated() {
this.program_ = this.helper.getProgram(
this.fragmentShader_,
this.vertexShader_
);
this.helper.flushBufferData(this.indices_);
}
/** /**
* @protected * @protected
* @param {import("../../Tile.js").default} tile Tile. * @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. * @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
* @return {boolean} Layer is ready to be rendered. * @return {boolean} Layer is ready to be rendered.
*/ */
prepareFrame(frameState) { prepareFrameInternal(frameState) {
if (isEmpty(getRenderExtent(frameState, frameState.extent))) { if (isEmpty(getRenderExtent(frameState, frameState.extent))) {
return false; return false;
} }

View File

@@ -5,24 +5,53 @@ import Point from '../../../../../src/ol/geom/Point.js';
import VectorSource from '../../../../../src/ol/source/Vector.js'; import VectorSource from '../../../../../src/ol/source/Vector.js';
import View from '../../../../../src/ol/View.js'; import View from '../../../../../src/ol/View.js';
describe('ol.layer.Heatmap', function () { describe('ol/layer/Heatmap', function () {
describe('constructor', 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 () { it('can be constructed without arguments', function () {
const instance = new HeatmapLayer(); const instance = new HeatmapLayer();
expect(instance).to.be.an(HeatmapLayer); expect(instance).to.be.an(HeatmapLayer);
}); });
it('has a default className', function () { it('has a default className', function () {
const layer = new HeatmapLayer({ const layer = new HeatmapLayer({
source: new VectorSource(), source: new VectorSource(),
}); });
map.addLayer(layer);
map.renderSync();
const canvas = layer.getRenderer().helper.getCanvas(); const canvas = layer.getRenderer().helper.getCanvas();
expect(canvas.className).to.eql('ol-layer'); expect(canvas.className).to.eql('ol-layer');
}); });
it('accepts a custom className', function () { it('accepts a custom className', function () {
const layer = new HeatmapLayer({ const layer = new HeatmapLayer({
source: new VectorSource(), source: new VectorSource(),
className: 'a-class-name', className: 'a-class-name',
}); });
map.addLayer(layer);
map.renderSync();
const canvas = layer.getRenderer().helper.getCanvas(); const canvas = layer.getRenderer().helper.getCanvas();
expect(canvas.className).to.eql('a-class-name'); expect(canvas.className).to.eql('a-class-name');
}); });

View File

@@ -4,6 +4,7 @@ import View from '../../../../../src/ol/View.js';
import WebGLHelper from '../../../../../src/ol/webgl/Helper.js'; import WebGLHelper from '../../../../../src/ol/webgl/Helper.js';
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js'; import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.js';
import {createCanvasContext2D} from '../../../../../src/ol/dom.js'; import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
import {getForViewAndSize} from '../../../../../src/ol/extent.js';
import {getRenderPixel} from '../../../../../src/ol/render.js'; import {getRenderPixel} from '../../../../../src/ol/render.js';
describe('ol/layer/WebGLTile', function () { describe('ol/layer/WebGLTile', function () {
@@ -62,7 +63,21 @@ describe('ol/layer/WebGLTile', function () {
it('creates fragment and vertex shaders', function () { it('creates fragment and vertex shaders', function () {
const compileShaderSpy = sinon.spy(WebGLHelper.prototype, 'compileShader'); 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(); compileShaderSpy.restore();
expect(compileShaderSpy.callCount).to.be(2); expect(compileShaderSpy.callCount).to.be(2);
expect(compileShaderSpy.getCall(0).args[0].replace(/[ \n]+/g, ' ')).to.be( expect(compileShaderSpy.getCall(0).args[0].replace(/[ \n]+/g, ' ')).to.be(

View File

@@ -75,7 +75,7 @@ const hitFragmentShader = `
gl_FragColor = v_hitColor; gl_FragColor = v_hitColor;
}`; }`;
describe('ol.renderer.webgl.PointsLayer', function () { describe('ol/renderer/webgl/PointsLayer', function () {
describe('constructor', function () { describe('constructor', function () {
let target; let target;
@@ -116,16 +116,16 @@ describe('ol.renderer.webgl.PointsLayer', function () {
hitVertexShader: hitVertexShader, hitVertexShader: hitVertexShader,
hitFragmentShader: hitFragmentShader, hitFragmentShader: hitFragmentShader,
}); });
frameState = Object.assign( frameState = Object.assign({}, baseFrameState, {
{
size: [2, 2], size: [2, 2],
extent: [-100, -100, 100, 100], extent: [-100, -100, 100, 100],
}, layerStatesArray: [layer.getLayerState()],
baseFrameState });
);
}); });
it('calls WebGlHelper#prepareDraw', function () { it('calls WebGlHelper#prepareDraw', function () {
renderer.prepareFrame(frameState);
const spy = sinon.spy(renderer.helper, 'prepareDraw'); const spy = sinon.spy(renderer.helper, 'prepareDraw');
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
expect(spy.called).to.be(true); expect(spy.called).to.be(true);
@@ -327,14 +327,12 @@ describe('ol.renderer.webgl.PointsLayer', function () {
0, 0,
0 0
); );
const frameState = Object.assign( const frameState = Object.assign({}, baseFrameState, {
{
extent: [-20, -20, 20, 20], extent: [-20, -20, 20, 20],
size: [40, 40], size: [40, 40],
coordinateToPixelTransform: transform, coordinateToPixelTransform: transform,
}, layerStatesArray: [layer.getLayerState()],
baseFrameState });
);
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
renderer.worker_.addEventListener('message', function () { renderer.worker_.addEventListener('message', function () {
@@ -391,15 +389,14 @@ describe('ol.renderer.webgl.PointsLayer', function () {
0, 0,
0 0
); );
const frameState = Object.assign( const frameState = Object.assign({}, baseFrameState, {
{
pixelRatio: 3, pixelRatio: 3,
extent: [-20, -20, 20, 20], extent: [-20, -20, 20, 20],
size: [40, 40], size: [40, 40],
coordinateToPixelTransform: transform, coordinateToPixelTransform: transform,
}, layerStatesArray: [layer.getLayerState()],
baseFrameState });
);
let found; let found;
const cb = function (feature) { const cb = function (feature) {
found = feature; found = feature;
@@ -446,6 +443,13 @@ describe('ol.renderer.webgl.PointsLayer', function () {
fragmentShader: simpleFragmentShader, 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 spyHelper = sinon.spy(renderer.helper, 'disposeInternal');
const spyWorker = sinon.spy(renderer.worker_, 'terminate'); const spyWorker = sinon.spy(renderer.worker_, 'terminate');
renderer.disposeInternal(); renderer.disposeInternal();

View File

@@ -7,7 +7,7 @@ import {create} from '../../../../../../src/ol/transform.js';
import {createCanvasContext2D} from '../../../../../../src/ol/dom.js'; import {createCanvasContext2D} from '../../../../../../src/ol/dom.js';
import {get} from '../../../../../../src/ol/proj.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} */ /** @type {import("../../../../../../src/ol/renderer/webgl/TileLayer.js").default} */
let renderer; let renderer;
/** @type {WebGLTileLayer} */ /** @type {WebGLTileLayer} */
@@ -74,6 +74,9 @@ describe('ol.renderer.webgl.TileLayer', function () {
}); });
it('#renderFrame()', function () { it('#renderFrame()', function () {
const ready = renderer.prepareFrame(frameState);
expect(ready).to.be(true);
const rendered = renderer.renderFrame(frameState); const rendered = renderer.renderFrame(frameState);
expect(rendered).to.be.a(HTMLCanvasElement); expect(rendered).to.be.a(HTMLCanvasElement);
expect(frameState.tileQueue.getCount()).to.be(1); expect(frameState.tileQueue.getCount()).to.be(1);

View File

@@ -5,12 +5,16 @@ import TileState from '../../../../../src/ol/TileState.js';
import TileTexture from '../../../../../src/ol/webgl/TileTexture.js'; import TileTexture from '../../../../../src/ol/webgl/TileTexture.js';
import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer.js'; import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer.js';
import WebGLTileLayer from '../../../../../src/ol/layer/WebGLTile.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'; import {createCanvasContext2D} from '../../../../../src/ol/dom.js';
describe('ol.webgl.TileTexture', function () { describe('ol/webgl/TileTexture', function () {
/** @type {TileTexture} */ /** @type {TileTexture} */
let tileTexture; let tileTexture;
/** @type {import("../../../../../src/ol/renderer/webgl/TileLayer.js").default} */
let renderer;
beforeEach(function () { beforeEach(function () {
const layer = new WebGLTileLayer({ const layer = new WebGLTileLayer({
source: new DataTileSource({ source: new DataTileSource({
@@ -24,10 +28,16 @@ describe('ol.webgl.TileTexture', function () {
}, },
}), }),
}); });
const renderer =
/** @type {import("../../../../../src/ol/renderer/webgl/TileLayer.js").default} */ ( renderer = layer.createRenderer();
layer.createRenderer() renderer.prepareFrame({
); extent: EPSG3857_EXTENT,
layerIndex: 0,
layerStatesArray: [layer.getLayerState()],
size: [256, 256],
mapId: 'map-1',
});
tileTexture = new TileTexture( tileTexture = new TileTexture(
layer.getSource().getTile(3, 2, 1), layer.getSource().getTile(3, 2, 1),
layer.getSource().getTileGrid(), layer.getSource().getTileGrid(),
@@ -35,6 +45,10 @@ describe('ol.webgl.TileTexture', function () {
); );
}); });
afterEach(() => {
renderer.dispose();
});
it('constructor', function () { it('constructor', function () {
expect(tileTexture.tile.tileCoord).to.eql([3, 2, 1]); expect(tileTexture.tile.tileCoord).to.eql([3, 2, 1]);
expect(tileTexture.coords).to.be.a(WebGLArrayBuffer); expect(tileTexture.coords).to.be.a(WebGLArrayBuffer);