From 532b8194b1684d693c1157fd9a1eae6170a9528f Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 16 May 2019 22:46:44 +0200 Subject: [PATCH] Webgl points / use the new typed-array based utils for buffers `rebuildBuffer` is still a very CPU intensive task and is blocking the main thread. --- src/ol/renderer/webgl/PointsLayer.js | 99 ++++++++++++------- .../ol/renderer/webgl/pointslayer.test.js | 29 +++--- 2 files changed, 72 insertions(+), 56 deletions(-) diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 65a25a8a9c..26dd3e24e3 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -5,9 +5,11 @@ import WebGLArrayBuffer from '../../webgl/Buffer.js'; import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl.js'; import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper.js'; import GeometryType from '../../geom/GeometryType.js'; -import WebGLLayerRenderer, {getBlankTexture, pushFeatureToBuffer} from './Layer.js'; -import GeoJSON from '../../format/GeoJSON.js'; -import {getUid} from '../../util.js'; +import WebGLLayerRenderer, { + getBlankTexture, + POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, + writePointFeatureInstructions, writePointFeatureToBuffers +} from './Layer.js'; import ViewHint from '../../ViewHint.js'; import {createEmpty, equals} from '../../extent.js'; import { @@ -241,14 +243,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { return false; }; - this.geojsonFormat_ = new GeoJSON(); - - /** - * @type {Object} - * @private - */ - this.geojsonFeatureCache_ = {}; - this.previousExtent_ = createEmpty(); /** @@ -272,6 +266,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { * @private */ this.invertRenderTransform_ = createTransform(); + + /** + * @type {Float32Array} + * @private + */ + this.renderInstructions_ = new Float32Array(0); } /** @@ -306,8 +306,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const vectorSource = vectorLayer.getSource(); const viewState = frameState.viewState; - // TODO: get this from somewhere... - const stride = 12; + const stride = POINT_VERTEX_STRIDE; // the source has changed: clear the feature cache & reload features const sourceChanged = this.sourceRevision_ < vectorSource.getRevision(); @@ -357,46 +356,70 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const vectorLayer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); const vectorSource = vectorLayer.getSource(); - this.verticesBuffer_.getArray().length = 0; - this.indicesBuffer_.getArray().length = 0; - // saves the projection transform for the current frame state - this.helper_.makeProjectionTransform(frameState, this.renderTransform_); - makeInverseTransform(this.invertRenderTransform_, this.renderTransform_); + const projectionTransform = createTransform(); + this.helper_.makeProjectionTransform(frameState, projectionTransform); + + const features = vectorSource.getFeatures(); + const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length; + if (this.renderInstructions_.length !== totalInstructionsCount) { + this.renderInstructions_ = new Float32Array(totalInstructionsCount); + } // loop on features to fill the buffer - const features = vectorSource.getFeatures(); let feature; + const tmpCoords = []; + let elementIndex = 0; for (let i = 0; i < features.length; i++) { feature = features[i]; if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { continue; } - let geojsonFeature = this.geojsonFeatureCache_[getUid(feature)]; - if (!geojsonFeature) { - geojsonFeature = this.geojsonFormat_.writeFeatureObject(feature); - this.geojsonFeatureCache_[getUid(feature)] = geojsonFeature; - } + tmpCoords[0] = this.coordCallback_(feature, 0); + tmpCoords[1] = this.coordCallback_(feature, 1); + applyTransform(projectionTransform, tmpCoords); - geojsonFeature.geometry.coordinates[0] = this.coordCallback_(feature, 0); - geojsonFeature.geometry.coordinates[1] = this.coordCallback_(feature, 1); - applyTransform(this.renderTransform_, geojsonFeature.geometry.coordinates); - 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; - - pushFeatureToBuffer(this.verticesBuffer_, this.indicesBuffer_, geojsonFeature); + elementIndex = writePointFeatureInstructions( + this.renderInstructions_, + elementIndex, + tmpCoords[0], + tmpCoords[1], + this.texCoordCallback_(feature, 0), + this.texCoordCallback_(feature, 1), + this.texCoordCallback_(feature, 2), + this.texCoordCallback_(feature, 3), + this.sizeCallback_(feature), + this.opacityCallback_(feature), + this.rotateWithViewCallback_(feature), + this.colorCallback_(feature, this.colorArray_) + ); } + const elementsCount = this.renderInstructions_.length / POINT_INSTRUCTIONS_COUNT; + const indexBuffer = new Uint32Array(elementsCount * 6); + const vertexBuffer = new Float32Array(elementsCount * 4 * POINT_VERTEX_STRIDE); + + let bufferPositions = null; + for (let i = 0; i < this.renderInstructions_.length; i += POINT_INSTRUCTIONS_COUNT) { + bufferPositions = writePointFeatureToBuffers( + this.renderInstructions_, + i, + vertexBuffer, + indexBuffer, + bufferPositions, + POINT_INSTRUCTIONS_COUNT); + } + + // TODO: improve the WebGLBuffer private api: we shouldn't need to switch back to plain Arrays + // also we need to handle the case where Uint32 array cannot be used + this.verticesBuffer_.arr_ = Array.from(vertexBuffer); + this.indicesBuffer_.arr_ = Array.from(indexBuffer); this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_); this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + this.renderTransform_ = projectionTransform; + makeInverseTransform(this.invertRenderTransform_, this.renderTransform_); } } diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index f0d104a70c..cdf8b3e6e3 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -1,11 +1,9 @@ import Feature from '../../../../../src/ol/Feature.js'; import Point from '../../../../../src/ol/geom/Point.js'; -import LineString from '../../../../../src/ol/geom/LineString.js'; import VectorLayer from '../../../../../src/ol/layer/Vector.js'; import VectorSource from '../../../../../src/ol/source/Vector.js'; import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js'; import {get as getProjection} from '../../../../../src/ol/proj.js'; -import Polygon from '../../../../../src/ol/geom/Polygon.js'; import ViewHint from '../../../../../src/ol/ViewHint.js'; @@ -52,9 +50,9 @@ describe('ol.renderer.webgl.PointsLayer', function() { projection: projection, resolution: 1, rotation: 0, - center: [10, 10] + center: [0, 0] }, - size: [256, 256], + size: [2, 2], extent: [-100, -100, 100, 100] }; }); @@ -69,24 +67,19 @@ describe('ol.renderer.webgl.PointsLayer', function() { layer.getSource().addFeature(new Feature({ geometry: new Point([10, 20]) })); - renderer.prepareFrame(frameState); - - const attributePerVertex = 12; - expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex); - expect(renderer.indicesBuffer_.getArray().length).to.eql(6); - }); - - it('ignores geometries other than points', function() { layer.getSource().addFeature(new Feature({ - geometry: new LineString([[10, 20], [30, 20]]) - })); - layer.getSource().addFeature(new Feature({ - geometry: new Polygon([[10, 20], [30, 20], [30, 10], [10, 20]]) + geometry: new Point([30, 40]) })); renderer.prepareFrame(frameState); - expect(renderer.verticesBuffer_.getArray().length).to.eql(0); - expect(renderer.indicesBuffer_.getArray().length).to.eql(0); + const attributePerVertex = POINT_VERTEX_STRIDE; + expect(renderer.verticesBuffer_.getArray().length).to.eql(2 * 4 * attributePerVertex); + expect(renderer.indicesBuffer_.getArray().length).to.eql(2 * 6); + + expect(renderer.verticesBuffer_.getArray()[0]).to.eql(10); + expect(renderer.verticesBuffer_.getArray()[1]).to.eql(20); + expect(renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 0]).to.eql(30); + expect(renderer.verticesBuffer_.getArray()[4 * attributePerVertex + 1]).to.eql(40); }); it('clears the buffers when the features are gone', function() {