From 523097903a7810fc965dca6d1ec250c0674ed770 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Tue, 14 May 2019 15:07:46 +0200 Subject: [PATCH] WebGL points / rebuild buffers on every non animation frame --- examples/filter-points-webgl.js | 1 - src/ol/renderer/webgl/PointsLayer.js | 88 +++++++++++++------ .../ol/renderer/webgl/pointslayer.test.js | 22 ++++- 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/examples/filter-points-webgl.js b/examples/filter-points-webgl.js index 75bfe406d1..76e2deab0a 100644 --- a/examples/filter-points-webgl.js +++ b/examples/filter-points-webgl.js @@ -11,7 +11,6 @@ import {clamp, lerp} from '../src/ol/math'; import Stamen from '../src/ol/source/Stamen'; const vectorSource = new Vector({ - features: [], attributions: 'NASA' }); diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 89dd68f8d0..d4525cd9f3 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -7,6 +7,8 @@ import {DefaultAttrib} from '../../webgl/Helper'; import GeometryType from '../../geom/GeometryType'; import WebGLLayerRenderer, {getBlankTexture, pushFeatureInBuffer} from './Layer'; import GeoJSON from '../../format/GeoJSON'; +import {getUid} from '../../util'; +import ViewHint from '../../ViewHint'; const VERTEX_SHADER = ` precision mediump float; @@ -232,6 +234,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { }; this.geojsonFormat_ = new GeoJSON(); + + /** + * @type {Object} + * @private + */ + this.geojsonFeatureCache_ = {}; } /** @@ -263,47 +271,25 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { prepareFrame(frameState) { const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); const vectorSource = vectorLayer.getSource(); + const viewState = frameState.viewState; // TODO: get this from somewhere... const stride = 12; this.helper_.prepareDraw(frameState); + // the source has changed: clear the feature cache & reload features if (this.sourceRevision_ < vectorSource.getRevision()) { this.sourceRevision_ = vectorSource.getRevision(); - this.verticesBuffer_.getArray().length = 0; - this.indicesBuffer_.getArray().length = 0; + this.geojsonFeatureCache_ = {}; - const viewState = frameState.viewState; const projection = viewState.projection; const resolution = viewState.resolution; - - // loop on features to fill the buffer vectorSource.loadFeatures([-Infinity, -Infinity, Infinity, Infinity], resolution, projection); - vectorSource.forEachFeature((feature) => { - if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { - return; - } + } - const geojsonFeature = this.geojsonFormat_.writeFeatureObject(feature); - - geojsonFeature.geometry.coordinates[0] = this.coordCallback_(feature, 0); - geojsonFeature.geometry.coordinates[1] = this.coordCallback_(feature, 1); - geojsonFeature.properties = geojsonFeature.properties || {}; - geojsonFeature.properties.color = this.colorCallback_(feature, this.colorArray_); - geojsonFeature.properties.u0 = this.texCoordCallback_(feature, 0); - geojsonFeature.properties.v0 = this.texCoordCallback_(feature, 1); - geojsonFeature.properties.u1 = this.texCoordCallback_(feature, 2); - geojsonFeature.properties.v1 = this.texCoordCallback_(feature, 3); - geojsonFeature.properties.size = this.sizeCallback_(feature); - geojsonFeature.properties.opacity = this.opacityCallback_(feature); - geojsonFeature.properties.rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0; - - pushFeatureInBuffer(this.verticesBuffer_, this.indicesBuffer_, geojsonFeature); - }); - - this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_); - this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + if (!frameState.viewHints[ViewHint.ANIMATING] && !frameState.viewHints[ViewHint.INTERACTING]) { + this.rebuildBuffers_(frameState); } // write new data @@ -320,6 +306,52 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { return true; } + + /** + * Rebuild internal webgl buffers based on current view extent; costly, should not be called too much + * @param {import("../../PluggableMap.js").FrameState} frameState + * @private + */ + rebuildBuffers_(frameState) { + const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); + const vectorSource = vectorLayer.getSource(); + + this.verticesBuffer_.getArray().length = 0; + this.indicesBuffer_.getArray().length = 0; + + // loop on features to fill the buffer + const features = vectorSource.getFeatures(); + let feature; + for (let i = 0; i < features.length; i++) { + feature = features[i]; + if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { + return; + } + + let geojsonFeature = this.geojsonFeatureCache_[getUid(feature)]; + if (!geojsonFeature) { + geojsonFeature = this.geojsonFormat_.writeFeatureObject(feature); + this.geojsonFeatureCache_[getUid(feature)] = geojsonFeature; + } + + geojsonFeature.geometry.coordinates[0] = this.coordCallback_(feature, 0); + geojsonFeature.geometry.coordinates[1] = this.coordCallback_(feature, 1); + geojsonFeature.properties = geojsonFeature.properties || {}; + geojsonFeature.properties.color = this.colorCallback_(feature, this.colorArray_); + geojsonFeature.properties.u0 = this.texCoordCallback_(feature, 0); + geojsonFeature.properties.v0 = this.texCoordCallback_(feature, 1); + geojsonFeature.properties.u1 = this.texCoordCallback_(feature, 2); + geojsonFeature.properties.v1 = this.texCoordCallback_(feature, 3); + geojsonFeature.properties.size = this.sizeCallback_(feature); + geojsonFeature.properties.opacity = this.opacityCallback_(feature); + geojsonFeature.properties.rotateWithView = this.rotateWithViewCallback_(feature) ? 1 : 0; + + pushFeatureInBuffer(this.verticesBuffer_, this.indicesBuffer_, geojsonFeature); + } + + this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_); + this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + } } export default WebGLPointsLayerRenderer; diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index 4afa629605..a54339d228 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -6,6 +6,7 @@ import VectorSource from '../../../../../src/ol/source/Vector.js'; import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer'; import {get as getProjection} from '../../../../../src/ol/proj'; import Polygon from '../../../../../src/ol/geom/Polygon'; +import ViewHint from '../../../../../src/ol/ViewHint'; describe('ol.renderer.webgl.PointsLayer', function() { @@ -53,7 +54,8 @@ describe('ol.renderer.webgl.PointsLayer', function() { rotation: 0, center: [10, 10] }, - size: [256, 256] + size: [256, 256], + extent: [-100, -100, 100, 100] }; }); @@ -103,6 +105,24 @@ describe('ol.renderer.webgl.PointsLayer', function() { expect(renderer.indicesBuffer_.getArray().length).to.eql(6); }); + it('rebuilds the buffers only when not interacting or animating', function() { + const spy = sinon.spy(renderer, 'rebuildBuffers_'); + frameState.viewHints[ViewHint.INTERACTING] = 1; + frameState.viewHints[ViewHint.ANIMATING] = 0; + renderer.prepareFrame(frameState); + expect(spy.called).to.be(false); + + frameState.viewHints[ViewHint.INTERACTING] = 0; + frameState.viewHints[ViewHint.ANIMATING] = 1; + renderer.prepareFrame(frameState); + expect(spy.called).to.be(false); + + frameState.viewHints[ViewHint.INTERACTING] = 0; + frameState.viewHints[ViewHint.ANIMATING] = 0; + renderer.prepareFrame(frameState); + expect(spy.called).to.be(true); + }); + }); });