From eb912d95ca0d2056e1a4bf919da63942ae5ac9e3 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 16 May 2019 22:04:55 +0200 Subject: [PATCH 01/15] Webgl / refactor utilities to work only on typed arrays The base webgl renderer module now has two types of utilities: * `writeXFeatureInstructions` will write a series of values in a given typed array, which represent how a given feature will be rendered; for points, this means position, size, color, etc. * `writeXFeatureToBuffers` will fill up the given index & vertex buffers with values based on the provided render instructions As such, the logic for rendering features is: user-input style > instructions array >(*) index/vertex buffers > draw (*) this transformation is intended to be done on a worker. --- src/ol/renderer/webgl/Layer.js | 185 +++++++++----- test/spec/ol/renderer/webgl/layer.test.js | 290 +++++++++++++++------- 2 files changed, 316 insertions(+), 159 deletions(-) diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 6e2b87d9f9..2f34eb2f93 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -61,90 +61,145 @@ class WebGLLayerRenderer extends LayerRenderer { /** - * Pushes vertices and indices to the given buffers using the geometry coordinates and the following properties - * from the feature: - * - `color` - * - `opacity` - * - `size` (for points) - * - `u0`, `v0`, `u1`, `v1` (for points) - * - `rotateWithView` (for points) - * - `width` (for lines) - * Custom attributes can be designated using the `opt_attributes` argument, otherwise other properties on the - * feature will be ignored. - * @param {import("../../webgl/Buffer").default} vertexBuffer WebGL buffer in which new vertices will be pushed. - * @param {import("../../webgl/Buffer").default} indexBuffer WebGL buffer in which new indices will be pushed. - * @param {import("../../format/GeoJSON").GeoJSONFeature} geojsonFeature Feature in geojson format, coordinates - * expressed in EPSG:4326. - * @param {Array} [opt_attributes] Custom attributes. An array of properties which will be read from the - * feature and pushed in the buffer in the given order. Note: attributes can only be numerical! Any other type or - * NaN will result in `0` being pushed in the buffer. + * @param {Float32Array} instructions Instructons array in which to write. + * @param {number} elementIndex Index from which render instructions will be written. + * @param {number} x Point center X coordinate + * @param {number} y Point center Y coordinate + * @param {number} u0 Left texture coordinate + * @param {number} v0 Bottom texture coordinate + * @param {number} u1 Right texture coordinate + * @param {number} v1 Top texture coordinate + * @param {number} size Radius of the point + * @param {number} opacity Opacity + * @param {boolean} rotateWithView If true, the point will stay aligned with the view + * @param {Array} color Array holding red, green, blue, alpha values + * @return {number} Index from which the next element should be written + * @private */ -export function pushFeatureToBuffer(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes) { - if (!geojsonFeature.geometry) { - return; - } - switch (geojsonFeature.geometry.type) { - case 'Point': - pushPointFeatureToBuffer_(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes); - return; - default: - return; - } +export function writePointFeatureInstructions(instructions, elementIndex, x, y, u0, v0, u1, v1, size, opacity, rotateWithView, color) { + let i = elementIndex; + instructions[i++] = x; + instructions[i++] = y; + instructions[i++] = u0; + instructions[i++] = v0; + instructions[i++] = u1; + instructions[i++] = v1; + instructions[i++] = size; + instructions[i++] = opacity; + instructions[i++] = rotateWithView ? 1 : 0; + instructions[i++] = color[0]; + instructions[i++] = color[1]; + instructions[i++] = color[2]; + instructions[i++] = color[3]; + return i; } const tmpArray_ = []; +const bufferPositions_ = {vertexPosition: 0, indexPosition: 0}; + +export const POINT_INSTRUCTIONS_COUNT = 13; +export const POINT_VERTEX_STRIDE = 12; + +function writePointVertex(buffer, pos, x, y, offsetX, offsetY, u, v, opacity, rotateWithView, red, green, blue, alpha) { + buffer[pos + 0] = x; + buffer[pos + 1] = y; + buffer[pos + 2] = offsetX; + buffer[pos + 3] = offsetY; + buffer[pos + 4] = u; + buffer[pos + 5] = v; + buffer[pos + 6] = opacity; + buffer[pos + 7] = rotateWithView; + buffer[pos + 8] = red; + buffer[pos + 9] = green; + buffer[pos + 10] = blue; + buffer[pos + 11] = alpha; +} + +function writeCustomAttrs(buffer, pos, customAttrs) { + if (customAttrs.length) { + buffer.set(customAttrs, pos); + } +} + +/** + * An object holding positions both in an index and a vertex buffer. + * @typedef {Object} BufferPositions + * @property {number} vertexPosition Position in the vertex buffer + * @property {number} indexPosition Position in the index buffer + */ /** * Pushes a quad (two triangles) based on a point geometry - * @param {import("../../webgl/Buffer").default} vertexBuffer WebGL buffer - * @param {import("../../webgl/Buffer").default} indexBuffer WebGL buffer - * @param {import("../../format/GeoJSON").GeoJSONFeature} geojsonFeature Feature - * @param {Array} [opt_attributes] Custom attributes + * @param {Float32Array} instructions Array of render instructions for points. + * @param {number} elementIndex Index from which render instructions will be read. + * @param {Float32Array} vertexBuffer Buffer in the form of a typed array. + * @param {Uint16Array|Uint32Array} indexBuffer Buffer in the form of a typed array. + * @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0. + * @param {number} [count] Amount of render instructions that will be read. Default value is POINT_INSTRUCTIONS_COUNT + * but a higher value can be provided; all values beyond the default count will be put in the vertices buffer as + * is, thus allowing specifying custom attributes. Please note: this value should not vary inside the same buffer or + * rendering will break. + * @return {BufferPositions} New buffer positions where to write next + * @property {number} vertexPosition New position in the vertex buffer where future writes should start. + * @property {number} indexPosition New position in the index buffer where future writes should start. * @private */ -function pushPointFeatureToBuffer_(vertexBuffer, indexBuffer, geojsonFeature, opt_attributes) { - const stride = 12 + (opt_attributes !== undefined ? opt_attributes.length : 0); +export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, bufferPositions, count) { + const count_ = count > POINT_INSTRUCTIONS_COUNT ? count : POINT_INSTRUCTIONS_COUNT; - const x = geojsonFeature.geometry.coordinates[0]; - const y = geojsonFeature.geometry.coordinates[1]; - const u0 = geojsonFeature.properties.u0; - const v0 = geojsonFeature.properties.v0; - const u1 = geojsonFeature.properties.u1; - const v1 = geojsonFeature.properties.v1; - const size = geojsonFeature.properties.size; - const opacity = geojsonFeature.properties.opacity; - const rotateWithView = geojsonFeature.properties.rotateWithView; - const color = geojsonFeature.properties.color; - const red = color[0]; - const green = color[1]; - const blue = color[2]; - const alpha = color[3]; - const baseIndex = vertexBuffer.getArray().length / stride; + const x = instructions[elementIndex + 0]; + const y = instructions[elementIndex + 1]; + const u0 = instructions[elementIndex + 2]; + const v0 = instructions[elementIndex + 3]; + const u1 = instructions[elementIndex + 4]; + const v1 = instructions[elementIndex + 5]; + const size = instructions[elementIndex + 6]; + const opacity = instructions[elementIndex + 7]; + const rotateWithView = instructions[elementIndex + 8]; + const red = instructions[elementIndex + 9]; + const green = instructions[elementIndex + 10]; + const blue = instructions[elementIndex + 11]; + const alpha = instructions[elementIndex + 12]; + + // the default vertex buffer stride is 12, plus additional custom values if any + const baseStride = POINT_VERTEX_STRIDE; + const stride = baseStride + count_ - POINT_INSTRUCTIONS_COUNT; // read custom numerical attributes on the feature - const customAttributeValues = tmpArray_; - customAttributeValues.length = opt_attributes ? opt_attributes.length : 0; - for (let i = 0; i < customAttributeValues.length; i++) { - customAttributeValues[i] = parseFloat(geojsonFeature.properties[opt_attributes[i]]) || 0; + const customAttrs = tmpArray_; + customAttrs.length = count_ - POINT_INSTRUCTIONS_COUNT; + for (let i = 0; i < customAttrs.length; i++) { + customAttrs[i] = instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + i]; } + let vPos = bufferPositions ? bufferPositions.vertexPosition : 0; + let iPos = bufferPositions ? bufferPositions.indexPosition : 0; + const baseIndex = vPos / stride; + // push vertices for each of the four quad corners (first standard then custom attributes) - vertexBuffer.getArray().push(x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha); - Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues); + writePointVertex(vertexBuffer, vPos, x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha); + writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + vPos += stride; - vertexBuffer.getArray().push(x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha); - Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues); + writePointVertex(vertexBuffer, vPos, x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha); + writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + vPos += stride; - vertexBuffer.getArray().push(x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha); - Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues); + writePointVertex(vertexBuffer, vPos, x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha); + writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + vPos += stride; - vertexBuffer.getArray().push(x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha); - Array.prototype.push.apply(vertexBuffer.getArray(), customAttributeValues); + writePointVertex(vertexBuffer, vPos, x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha); + writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + vPos += stride; - indexBuffer.getArray().push( - baseIndex, baseIndex + 1, baseIndex + 3, - baseIndex + 1, baseIndex + 2, baseIndex + 3 - ); + indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3; + indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 2; indexBuffer[iPos++] = baseIndex + 3; + + bufferPositions_.vertexPosition = vPos; + bufferPositions_.indexPosition = iPos; + + return bufferPositions_; } /** diff --git a/test/spec/ol/renderer/webgl/layer.test.js b/test/spec/ol/renderer/webgl/layer.test.js index 4d48e2df0c..a2007f2206 100644 --- a/test/spec/ol/renderer/webgl/layer.test.js +++ b/test/spec/ol/renderer/webgl/layer.test.js @@ -1,5 +1,7 @@ -import WebGLLayerRenderer, {getBlankTexture, pushFeatureToBuffer} from '../../../../../src/ol/renderer/webgl/Layer.js'; -import WebGLArrayBuffer from '../../../../../src/ol/webgl/Buffer.js'; +import WebGLLayerRenderer, { + getBlankTexture, POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, + writePointFeatureInstructions, writePointFeatureToBuffers +} from '../../../../../src/ol/renderer/webgl/Layer.js'; import Layer from '../../../../../src/ol/layer/Layer.js'; @@ -28,111 +30,211 @@ describe('ol.renderer.webgl.Layer', function() { }); - describe('pushFeatureToBuffer', function() { - let vertexBuffer, indexBuffer; + describe('writePointFeatureInstructions', function() { + let instructions; beforeEach(function() { - vertexBuffer = new WebGLArrayBuffer(); - indexBuffer = new WebGLArrayBuffer(); + instructions = new Float32Array(100); }); - it('does nothing if the feature has no geometry', function() { - const feature = { - type: 'Feature', - id: 'AFG', - properties: { - color: [0.5, 1, 0.2, 0.7], - size: 3 - }, - geometry: null - }; - pushFeatureToBuffer(vertexBuffer, indexBuffer, feature); - expect(vertexBuffer.getArray().length).to.eql(0); - expect(indexBuffer.getArray().length).to.eql(0); + it('writes instructions corresponding to the given parameters', function() { + const baseIndex = 17; + writePointFeatureInstructions(instructions, baseIndex, + 1, 2, 3, 4, 5, 6, + 7, 8, true, [10, 11, 12, 13]); + expect(instructions[baseIndex + 0]).to.eql(1); + expect(instructions[baseIndex + 1]).to.eql(2); + expect(instructions[baseIndex + 2]).to.eql(3); + expect(instructions[baseIndex + 3]).to.eql(4); + expect(instructions[baseIndex + 4]).to.eql(5); + expect(instructions[baseIndex + 5]).to.eql(6); + expect(instructions[baseIndex + 6]).to.eql(7); + expect(instructions[baseIndex + 7]).to.eql(8); + expect(instructions[baseIndex + 8]).to.eql(1); + expect(instructions[baseIndex + 9]).to.eql(10); + expect(instructions[baseIndex + 10]).to.eql(11); + expect(instructions[baseIndex + 11]).to.eql(12); + expect(instructions[baseIndex + 12]).to.eql(13); }); - it('adds two triangles with the correct attributes for a point geometry', function() { - const feature = { - type: 'Feature', - id: 'AFG', - properties: { - color: [0.5, 1, 0.2, 0.7], - size: 3 - }, - geometry: { - type: 'Point', - coordinates: [-75, 47] - } - }; - const attributePerVertex = 12; - pushFeatureToBuffer(vertexBuffer, indexBuffer, feature); - expect(vertexBuffer.getArray().length).to.eql(attributePerVertex * 4); - expect(indexBuffer.getArray().length).to.eql(6); + it('correctly chains writes', function() { + let baseIndex = 0; + baseIndex = writePointFeatureInstructions(instructions, baseIndex, + 1, 2, 3, 4, 5, 6, + 7, 8, true, [10, 11, 12, 13]); + baseIndex = writePointFeatureInstructions(instructions, baseIndex, + 1, 2, 3, 4, 5, 6, + 7, 8, true, [10, 11, 12, 13]); + writePointFeatureInstructions(instructions, baseIndex, + 1, 2, 3, 4, 5, 6, + 7, 8, true, [10, 11, 12, 13]); + expect(instructions[baseIndex + 0]).to.eql(1); + expect(instructions[baseIndex + 1]).to.eql(2); + expect(instructions[baseIndex + 2]).to.eql(3); + expect(instructions[baseIndex + 3]).to.eql(4); + expect(instructions[baseIndex + 4]).to.eql(5); + expect(instructions[baseIndex + 5]).to.eql(6); + expect(instructions[baseIndex + 6]).to.eql(7); + expect(instructions[baseIndex + 7]).to.eql(8); + expect(instructions[baseIndex + 8]).to.eql(1); + expect(instructions[baseIndex + 9]).to.eql(10); + expect(instructions[baseIndex + 10]).to.eql(11); + expect(instructions[baseIndex + 11]).to.eql(12); + expect(instructions[baseIndex + 12]).to.eql(13); + }); + }); + + describe('writePointFeatureToBuffers', function() { + let vertexBuffer, indexBuffer, instructions, elementIndex; + + beforeEach(function() { + vertexBuffer = new Float32Array(100); + indexBuffer = new Uint16Array(100); + instructions = new Float32Array(100); + elementIndex = 3; + + writePointFeatureInstructions(instructions, elementIndex, + 1, 2, 3, 4, 5, 6, + 7, 8, true, [10, 11, 12, 13]); }); - it('correctly sets indices & coordinates for several features', function() { - const feature = { - type: 'Feature', - id: 'AFG', - properties: { - color: [0.5, 1, 0.2, 0.7], - size: 3 - }, - geometry: { - type: 'Point', - coordinates: [-75, 47] - } - }; - const attributePerVertex = 12; - pushFeatureToBuffer(vertexBuffer, indexBuffer, feature); - pushFeatureToBuffer(vertexBuffer, indexBuffer, feature); - expect(vertexBuffer.getArray()[0]).to.eql(-75); - expect(vertexBuffer.getArray()[1]).to.eql(47); - expect(vertexBuffer.getArray()[0 + attributePerVertex]).to.eql(-75); - expect(vertexBuffer.getArray()[1 + attributePerVertex]).to.eql(47); + it('writes correctly to the buffers (without custom attributes)', function() { + const stride = POINT_VERTEX_STRIDE; + const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer); - // first point - expect(indexBuffer.getArray()[0]).to.eql(0); - expect(indexBuffer.getArray()[1]).to.eql(1); - expect(indexBuffer.getArray()[2]).to.eql(3); - expect(indexBuffer.getArray()[3]).to.eql(1); - expect(indexBuffer.getArray()[4]).to.eql(2); - expect(indexBuffer.getArray()[5]).to.eql(3); + expect(vertexBuffer[0]).to.eql(1); + expect(vertexBuffer[1]).to.eql(2); + expect(vertexBuffer[2]).to.eql(-3.5); + expect(vertexBuffer[3]).to.eql(-3.5); + expect(vertexBuffer[4]).to.eql(3); + expect(vertexBuffer[5]).to.eql(4); + expect(vertexBuffer[6]).to.eql(8); + expect(vertexBuffer[7]).to.eql(1); + expect(vertexBuffer[8]).to.eql(10); + expect(vertexBuffer[9]).to.eql(11); + expect(vertexBuffer[10]).to.eql(12); + expect(vertexBuffer[11]).to.eql(13); - // second point - expect(indexBuffer.getArray()[6]).to.eql(4); - expect(indexBuffer.getArray()[7]).to.eql(5); - expect(indexBuffer.getArray()[8]).to.eql(7); - expect(indexBuffer.getArray()[9]).to.eql(5); - expect(indexBuffer.getArray()[10]).to.eql(6); - expect(indexBuffer.getArray()[11]).to.eql(7); + expect(vertexBuffer[stride + 0]).to.eql(1); + expect(vertexBuffer[stride + 1]).to.eql(2); + expect(vertexBuffer[stride + 2]).to.eql(+3.5); + expect(vertexBuffer[stride + 3]).to.eql(-3.5); + expect(vertexBuffer[stride + 4]).to.eql(5); + expect(vertexBuffer[stride + 5]).to.eql(4); + + expect(vertexBuffer[stride * 2 + 0]).to.eql(1); + expect(vertexBuffer[stride * 2 + 1]).to.eql(2); + expect(vertexBuffer[stride * 2 + 2]).to.eql(+3.5); + expect(vertexBuffer[stride * 2 + 3]).to.eql(+3.5); + expect(vertexBuffer[stride * 2 + 4]).to.eql(5); + expect(vertexBuffer[stride * 2 + 5]).to.eql(6); + + expect(vertexBuffer[stride * 3 + 0]).to.eql(1); + expect(vertexBuffer[stride * 3 + 1]).to.eql(2); + expect(vertexBuffer[stride * 3 + 2]).to.eql(-3.5); + expect(vertexBuffer[stride * 3 + 3]).to.eql(+3.5); + expect(vertexBuffer[stride * 3 + 4]).to.eql(3); + expect(vertexBuffer[stride * 3 + 5]).to.eql(6); + + expect(indexBuffer[0]).to.eql(0); + expect(indexBuffer[1]).to.eql(1); + expect(indexBuffer[2]).to.eql(3); + expect(indexBuffer[3]).to.eql(1); + expect(indexBuffer[4]).to.eql(2); + expect(indexBuffer[5]).to.eql(3); + + expect(positions.indexPosition).to.eql(6); + expect(positions.vertexPosition).to.eql(stride * 4); }); - it('correctly adds custom attributes', function() { - const feature = { - type: 'Feature', - id: 'AFG', - properties: { - color: [0.5, 1, 0.2, 0.7], - custom: 4, - customString: '5', - custom2: 12.4, - customString2: 'abc' - }, - geometry: { - type: 'Point', - coordinates: [-75, 47] - } - }; - const attributePerVertex = 16; - pushFeatureToBuffer(vertexBuffer, indexBuffer, feature, ['custom', 'custom2', 'customString', 'customString2']); - expect(vertexBuffer.getArray().length).to.eql(attributePerVertex * 4); - expect(indexBuffer.getArray().length).to.eql(6); - expect(vertexBuffer.getArray()[12]).to.eql(4); - expect(vertexBuffer.getArray()[13]).to.eql(12.4); - expect(vertexBuffer.getArray()[14]).to.eql(5); - expect(vertexBuffer.getArray()[15]).to.eql(0); + it('writes correctly to the buffers (with custom attributes)', function() { + instructions[elementIndex + POINT_INSTRUCTIONS_COUNT] = 101; + instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 1] = 102; + instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 2] = 103; + + const stride = POINT_VERTEX_STRIDE + 3; + const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, + undefined, POINT_INSTRUCTIONS_COUNT + 3); + + expect(vertexBuffer[0]).to.eql(1); + expect(vertexBuffer[1]).to.eql(2); + expect(vertexBuffer[2]).to.eql(-3.5); + expect(vertexBuffer[3]).to.eql(-3.5); + expect(vertexBuffer[4]).to.eql(3); + expect(vertexBuffer[5]).to.eql(4); + expect(vertexBuffer[6]).to.eql(8); + expect(vertexBuffer[7]).to.eql(1); + expect(vertexBuffer[8]).to.eql(10); + expect(vertexBuffer[9]).to.eql(11); + expect(vertexBuffer[10]).to.eql(12); + expect(vertexBuffer[11]).to.eql(13); + + expect(vertexBuffer[12]).to.eql(101); + expect(vertexBuffer[13]).to.eql(102); + expect(vertexBuffer[14]).to.eql(103); + + expect(vertexBuffer[stride + 12]).to.eql(101); + expect(vertexBuffer[stride + 13]).to.eql(102); + expect(vertexBuffer[stride + 14]).to.eql(103); + + expect(vertexBuffer[stride * 2 + 12]).to.eql(101); + expect(vertexBuffer[stride * 2 + 13]).to.eql(102); + expect(vertexBuffer[stride * 2 + 14]).to.eql(103); + + expect(vertexBuffer[stride * 3 + 12]).to.eql(101); + expect(vertexBuffer[stride * 3 + 13]).to.eql(102); + expect(vertexBuffer[stride * 3 + 14]).to.eql(103); + + expect(indexBuffer[0]).to.eql(0); + expect(indexBuffer[1]).to.eql(1); + expect(indexBuffer[2]).to.eql(3); + expect(indexBuffer[3]).to.eql(1); + expect(indexBuffer[4]).to.eql(2); + expect(indexBuffer[5]).to.eql(3); + + expect(positions.indexPosition).to.eql(6); + expect(positions.vertexPosition).to.eql(stride * 4); }); + + it('correctly chains buffer writes', function() { + const stride = POINT_VERTEX_STRIDE; + let positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer); + positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions); + positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions); + + expect(vertexBuffer[0]).to.eql(1); + expect(vertexBuffer[1]).to.eql(2); + expect(vertexBuffer[2]).to.eql(-3.5); + expect(vertexBuffer[3]).to.eql(-3.5); + + expect(vertexBuffer[stride * 4 + 0]).to.eql(1); + expect(vertexBuffer[stride * 4 + 1]).to.eql(2); + expect(vertexBuffer[stride * 4 + 2]).to.eql(-3.5); + expect(vertexBuffer[stride * 4 + 3]).to.eql(-3.5); + + expect(vertexBuffer[stride * 8 + 0]).to.eql(1); + expect(vertexBuffer[stride * 8 + 1]).to.eql(2); + expect(vertexBuffer[stride * 8 + 2]).to.eql(-3.5); + expect(vertexBuffer[stride * 8 + 3]).to.eql(-3.5); + + expect(indexBuffer[6 + 0]).to.eql(4); + expect(indexBuffer[6 + 1]).to.eql(5); + expect(indexBuffer[6 + 2]).to.eql(7); + expect(indexBuffer[6 + 3]).to.eql(5); + expect(indexBuffer[6 + 4]).to.eql(6); + expect(indexBuffer[6 + 5]).to.eql(7); + + expect(indexBuffer[6 * 2 + 0]).to.eql(8); + expect(indexBuffer[6 * 2 + 1]).to.eql(9); + expect(indexBuffer[6 * 2 + 2]).to.eql(11); + expect(indexBuffer[6 * 2 + 3]).to.eql(9); + expect(indexBuffer[6 * 2 + 4]).to.eql(10); + expect(indexBuffer[6 * 2 + 5]).to.eql(11); + + expect(positions.indexPosition).to.eql(6 * 3); + expect(positions.vertexPosition).to.eql(stride * 4 * 3); + }); + }); describe('getBlankTexture', function() { From 532b8194b1684d693c1157fd9a1eae6170a9528f Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 16 May 2019 22:46:44 +0200 Subject: [PATCH 02/15] 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() { From 65be907095fe4ff598bf8564dc5e8bec62674b86 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 16 May 2019 23:00:06 +0200 Subject: [PATCH 03/15] Webgl points / shifts the buffer write logic in a worker The worker receives a transferable array of instructions and sends back two transferable arrays (vertex and index buffer). The projection transform is also sent so that when the main thread receives the buffers from the worker it also knows which projection to apply when rendering the geometries. --- src/ol/renderer/webgl/PointsLayer.js | 54 +++++++++++++++------------- src/ol/worker/webgl.js | 38 ++++++++++++++++++++ 2 files changed, 67 insertions(+), 25 deletions(-) create mode 100644 src/ol/worker/webgl.js diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 26dd3e24e3..61b46878dc 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -8,7 +8,7 @@ import GeometryType from '../../geom/GeometryType.js'; import WebGLLayerRenderer, { getBlankTexture, POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, - writePointFeatureInstructions, writePointFeatureToBuffers + writePointFeatureInstructions } from './Layer.js'; import ViewHint from '../../ViewHint.js'; import {createEmpty, equals} from '../../extent.js'; @@ -18,6 +18,7 @@ import { multiply as multiplyTransform, apply as applyTransform } from '../../transform.js'; +import {create as createWebGLWorker} from '../../worker/webgl.js'; const VERTEX_SHADER = ` precision mediump float; @@ -272,6 +273,26 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { * @private */ this.renderInstructions_ = new Float32Array(0); + + this.worker_ = createWebGLWorker(); + this.worker_.addEventListener('message', function(event) { + if (event.data.type === 'buffers-generated') { + const vertexBuffer = Array.from(new Float32Array(event.data.vertexBuffer)); + const indexBuffer = Array.from(new Uint32Array(event.data.indexBuffer)); + const projectionTransform = event.data.projectionTransform; + + // 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_ = vertexBuffer; + this.indicesBuffer_.arr_ = indexBuffer; + this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_); + this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + + // saves the projection transform for the current frame state + this.renderTransform_ = projectionTransform; + makeInverseTransform(this.invertRenderTransform_, this.renderTransform_); + } + }.bind(this)); } /** @@ -396,31 +417,14 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { ); } - 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_); + this.worker_.postMessage({ + type: 'generate-buffer', + renderInstructions: this.renderInstructions_.buffer, + projectionTransform: projectionTransform + }, [this.renderInstructions_.buffer]); } + + } export default WebGLPointsLayerRenderer; diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js new file mode 100644 index 0000000000..174f7751f5 --- /dev/null +++ b/src/ol/worker/webgl.js @@ -0,0 +1,38 @@ +/** + * @module ol/worker/webgl + * A worker that does cpu-heavy tasks related to webgl rendering + */ +import {POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, writePointFeatureToBuffers} from '../renderer/webgl/Layer.js'; + +onmessage = event => { + if (event.data.type === 'generate-buffer') { + const renderInstructions = new Float32Array(event.data.renderInstructions); + const customAttributesCount = event.data.customAttributesCount || 0; + const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount; + const projectionTransform = event.data.projectionTransform; + + const elementsCount = renderInstructions.length / instructionsCount; + const indexBuffer = new Uint32Array(elementsCount * 6); + const vertexBuffer = new Float32Array(elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount)); + + let bufferPositions = null; + for (let i = 0; i < renderInstructions.length; i += instructionsCount) { + bufferPositions = writePointFeatureToBuffers( + renderInstructions, + i, + vertexBuffer, + indexBuffer, + bufferPositions, + instructionsCount); + } + + postMessage({ + type: 'buffers-generated', + vertexBuffer: vertexBuffer.buffer, + indexBuffer: indexBuffer.buffer, + projectionTransform + }, [vertexBuffer.buffer, indexBuffer.buffer]); + } +}; + +export let create; From 8566cfc227927d7b9646b0d660fdf254e4efea60 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Thu, 16 May 2019 23:10:16 +0200 Subject: [PATCH 04/15] Webgl points / get back the instructions array from the worker This means we won't have to recreate a binary buffer (through a typed array) on every `rebuildBuffer` call. --- src/ol/renderer/webgl/PointsLayer.js | 2 ++ src/ol/worker/webgl.js | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 61b46878dc..90d0c2fb34 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -291,6 +291,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { // saves the projection transform for the current frame state this.renderTransform_ = projectionTransform; makeInverseTransform(this.invertRenderTransform_, this.renderTransform_); + + this.renderInstructions_ = new Float32Array(event.data.renderInstructions); } }.bind(this)); } diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index 174f7751f5..046bbb7ca9 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -30,8 +30,9 @@ onmessage = event => { type: 'buffers-generated', vertexBuffer: vertexBuffer.buffer, indexBuffer: indexBuffer.buffer, + renderInstructions: renderInstructions.buffer, projectionTransform - }, [vertexBuffer.buffer, indexBuffer.buffer]); + }, [vertexBuffer.buffer, indexBuffer.buffer, renderInstructions.buffer]); } }; From 33d007ce01593c7ef602c08650d3238dbd86a4cd Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 13:03:33 +0200 Subject: [PATCH 05/15] Webgl buffer / now stores data in typed arrays The `WebGLBuffer` class API was changed in order to allow populating the internal array in different ways. --- doc/errors/index.md | 4 ++ src/ol/webgl/Buffer.js | 90 +++++++++++++++++++++++++++---- test/spec/ol/webgl/buffer.test.js | 89 ++++++++++++++++++++---------- 3 files changed, 144 insertions(+), 39 deletions(-) diff --git a/doc/errors/index.md b/doc/errors/index.md index 687d9efbf1..6aedfcb807 100644 --- a/doc/errors/index.md +++ b/doc/errors/index.md @@ -228,3 +228,7 @@ Missing or invalid `size`. ### 61 Cannot determine IIIF Image API version from provided image information JSON. + +### 62 + +A `WebGLArrayBuffer` must either be of type `ELEMENT_ARRAY_BUFFER` or `ARRAY_BUFFER`. diff --git a/src/ol/webgl/Buffer.js b/src/ol/webgl/Buffer.js index 21e5b25c8c..dc07e9c861 100644 --- a/src/ol/webgl/Buffer.js +++ b/src/ol/webgl/Buffer.js @@ -2,6 +2,9 @@ * @module ol/webgl/Buffer */ import {STATIC_DRAW, STREAM_DRAW, DYNAMIC_DRAW} from '../webgl.js'; +import {includes} from '../array.js'; +import {ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, EXTENSIONS as WEBGL_EXTENSIONS} from '../webgl.js'; +import {assert} from '../asserts.js'; /** * Used to describe the intended usage for the data: `STATIC_DRAW`, `STREAM_DRAW` @@ -17,43 +20,110 @@ export const BufferUsage = { /** * @classdesc * Object used to store an array of data as well as usage information for that data. - * See the documentation of [WebGLRenderingContext.bufferData](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData) for more info. + * Stores typed arrays internally, either Float32Array or Uint16/32Array depending on + * the buffer type (ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER) and available extensions. + * + * To populate the array, you can either use: + * * A size using `#ofSize(buffer)` + * * An `ArrayBuffer` object using `#fromArrayBuffer(buffer)` + * * A plain array using `#fromArray(array)` + * + * Note: + * See the documentation of [WebGLRenderingContext.bufferData](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/bufferData) + * for more info on buffer usage. * @api */ class WebGLArrayBuffer { /** - * @param {Array=} opt_arr Array. - * @param {number=} opt_usage Usage, either `STATIC_DRAW`, `STREAM_DRAW` or `DYNAMIC_DRAW`. Default is `DYNAMIC_DRAW`. + * @param {number} type Buffer type, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER. + * @param {number=} opt_usage Intended usage, either `STATIC_DRAW`, `STREAM_DRAW` or `DYNAMIC_DRAW`. + * Default is `DYNAMIC_DRAW`. */ - constructor(opt_arr, opt_usage) { + constructor(type, opt_usage) { /** * @private - * @type {Array} + * @type {Float32Array|Uint32Array|Uint16Array} */ - this.arr_ = opt_arr !== undefined ? opt_arr : []; + this.array = null; /** * @private * @type {number} */ - this.usage_ = opt_usage !== undefined ? opt_usage : BufferUsage.STATIC_DRAW; + this.type = type; + assert(type === ARRAY_BUFFER || type === ELEMENT_ARRAY_BUFFER, 62); + + /** + * @private + * @type {number} + */ + this.usage = opt_usage !== undefined ? opt_usage : BufferUsage.STATIC_DRAW; } /** - * @return {Array} Array. + * Populates the buffer with an array of the given size (all values will be zeroes). + * @param {number} size Array size + */ + ofSize(size) { + this.array = new (getArrayClassForType(this.type))(size); + } + + /** + * Populates the buffer with an array of the given size (all values will be zeroes). + * @param {Array} array Numerical array + */ + fromArray(array) { + this.array = (getArrayClassForType(this.type)).from(array); + } + + /** + * Populates the buffer with a raw binary array buffer. + * @param {ArrayBuffer} buffer Raw binary buffer to populate the array with. Note that this buffer must have been + * initialized for the same typed array class. + */ + fromArrayBuffer(buffer) { + this.array = new (getArrayClassForType(this.type))(buffer); + } + + /** + * @return {number} Buffer type. + */ + getType() { + return this.type; + } + + /** + * @return {Float32Array|Uint32Array|Uint16Array} Array. */ getArray() { - return this.arr_; + return this.array; } /** * @return {number} Usage. */ getUsage() { - return this.usage_; + return this.usage; + } +} + +/** + * Returns a typed array constructor based on the given buffer type + * @param {number} type Buffer type, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER. + * @returns {Float32ArrayConstructor|Uint16ArrayConstructor|Uint32ArrayConstructor} The typed array class to use for this buffer. + */ +export function getArrayClassForType(type) { + switch (type) { + case ARRAY_BUFFER: + return Float32Array; + case ELEMENT_ARRAY_BUFFER: + const hasExtension = includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'); + return hasExtension ? Uint32Array : Uint16Array; + default: + return Float32Array; } } diff --git a/test/spec/ol/webgl/buffer.test.js b/test/spec/ol/webgl/buffer.test.js index af590da0e4..8c1102b5ea 100644 --- a/test/spec/ol/webgl/buffer.test.js +++ b/test/spec/ol/webgl/buffer.test.js @@ -1,51 +1,82 @@ -import WebGLArrayBuffer from '../../../../src/ol/webgl/Buffer.js'; +import WebGLArrayBuffer, {getArrayClassForType} from '../../../../src/ol/webgl/Buffer.js'; +import { + ARRAY_BUFFER, + ELEMENT_ARRAY_BUFFER, + EXTENSIONS as WEBGL_EXTENSIONS, + STATIC_DRAW, + STREAM_DRAW +} from '../../../../src/ol/webgl.js'; describe('ol.webgl.Buffer', function() { describe('constructor', function() { - describe('without an argument', function() { - - let b; - beforeEach(function() { - b = new WebGLArrayBuffer(); - }); - - it('constructs an empty instance', function() { - expect(b.getArray()).to.be.empty(); - }); - + it('sets the default usage when not specified', function() { + const b = new WebGLArrayBuffer(ARRAY_BUFFER); + expect(b.getUsage()).to.be(STATIC_DRAW); }); - describe('with a single array argument', function() { - - let b; - beforeEach(function() { - b = new WebGLArrayBuffer([0, 1, 2, 3]); - }); - - it('constructs a populated instance', function() { - expect(b.getArray()).to.eql([0, 1, 2, 3]); - }); - + it('sets the given usage when specified', function() { + const b = new WebGLArrayBuffer(ARRAY_BUFFER, STREAM_DRAW); + expect(b.getUsage()).to.be(STREAM_DRAW); }); + it('raises an error if an incorrect type is used', function(done) { + try { + new WebGLArrayBuffer(1234); + } catch (e) { + done(); + } + done(true); + }); }); - describe('with an empty instance', function() { + describe('#getArrayClassForType', function() { + it('returns the correct typed array constructor', function() { + expect(getArrayClassForType(ARRAY_BUFFER)).to.be(Float32Array); + expect(getArrayClassForType(ELEMENT_ARRAY_BUFFER)).to.be(Uint32Array); + }); + + it('returns the correct typed array constructor (without OES uint extension)', function() { + WEBGL_EXTENSIONS.length = 0; + expect(getArrayClassForType(ELEMENT_ARRAY_BUFFER)).to.be(Uint16Array); + }); + }); + + describe('populate methods', function() { let b; beforeEach(function() { - b = new WebGLArrayBuffer(); + b = new WebGLArrayBuffer(ARRAY_BUFFER); }); - describe('getArray', function() { + it('initializes the array using a size', function() { + b.ofSize(12); + expect(b.getArray().length).to.be(12); + expect(b.getArray()[0]).to.be(0); + expect(b.getArray()[11]).to.be(0); + }); - it('returns an empty array', function() { - expect(b.getArray()).to.be.empty(); - }); + it('initializes the array using an array', function() { + b.fromArray([1, 2, 3, 4, 5]); + expect(b.getArray().length).to.be(5); + expect(b.getArray()[0]).to.be(1); + expect(b.getArray()[1]).to.be(2); + expect(b.getArray()[2]).to.be(3); + expect(b.getArray()[3]).to.be(4); + expect(b.getArray()[4]).to.be(5); + }); + it('initializes the array using a size', function() { + const a = Float32Array.of(1, 2, 3, 4, 5); + b.fromArrayBuffer(a.buffer); + expect(b.getArray().length).to.be(5); + expect(b.getArray()[0]).to.be(1); + expect(b.getArray()[1]).to.be(2); + expect(b.getArray()[2]).to.be(3); + expect(b.getArray()[3]).to.be(4); + expect(b.getArray()[4]).to.be(5); }); }); From a366803cdd9e4b2606e82ddd7703bc9836839174 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 14:45:10 +0200 Subject: [PATCH 06/15] Webgl / use the new buffer API in helper & the points renderer --- src/ol/renderer/webgl/PointsLayer.js | 26 +++++++++++--------------- src/ol/webgl/Helper.js | 22 ++++++---------------- 2 files changed, 17 insertions(+), 31 deletions(-) diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 90d0c2fb34..6befa215f6 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -211,8 +211,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.sourceRevision_ = -1; - this.verticesBuffer_ = new WebGLArrayBuffer([], DYNAMIC_DRAW); - this.indicesBuffer_ = new WebGLArrayBuffer([], DYNAMIC_DRAW); + this.verticesBuffer_ = new WebGLArrayBuffer(ARRAY_BUFFER, DYNAMIC_DRAW); + this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW); this.program_ = this.helper_.getProgram( options.fragmentShader || FRAGMENT_SHADER, @@ -277,16 +277,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.worker_ = createWebGLWorker(); this.worker_.addEventListener('message', function(event) { if (event.data.type === 'buffers-generated') { - const vertexBuffer = Array.from(new Float32Array(event.data.vertexBuffer)); - const indexBuffer = Array.from(new Uint32Array(event.data.indexBuffer)); const projectionTransform = event.data.projectionTransform; - - // 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_ = vertexBuffer; - this.indicesBuffer_.arr_ = indexBuffer; - this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_); - this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.verticesBuffer_.fromArrayBuffer(event.data.vertexBuffer); + this.indicesBuffer_.fromArrayBuffer(event.data.indexBuffer); + this.helper_.flushBufferData(this.verticesBuffer_); + this.helper_.flushBufferData(this.indicesBuffer_); // saves the projection transform for the current frame state this.renderTransform_ = projectionTransform; @@ -308,11 +303,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { * @inheritDoc */ renderFrame(frameState) { - const layerState = frameState.layerStatesArray[frameState.layerIndex]; - this.helper_.drawElements(0, this.indicesBuffer_.getArray().length); + const renderCount = this.indicesBuffer_.getArray() ? this.indicesBuffer_.getArray().length : 0; + this.helper_.drawElements(0, renderCount); this.helper_.finalizeDraw(frameState); const canvas = this.helper_.getCanvas(); + const layerState = frameState.layerStatesArray[frameState.layerIndex]; const opacity = layerState.opacity; if (opacity !== parseFloat(canvas.style.opacity)) { canvas.style.opacity = opacity; @@ -356,8 +352,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.helper_.prepareDraw(frameState); // write new data - this.helper_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_); - this.helper_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); + this.helper_.bindBuffer(this.verticesBuffer_); + this.helper_.bindBuffer(this.indicesBuffer_); const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT; this.helper_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0); diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index 1b03a5fa0d..9a0b144b43 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -7,7 +7,7 @@ import Disposable from '../Disposable.js'; import {includes} from '../array.js'; import {listen, unlistenAll} from '../events.js'; import {clear} from '../obj.js'; -import {ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, TEXTURE_2D, TEXTURE_WRAP_S, TEXTURE_WRAP_T} from '../webgl.js'; +import {TEXTURE_2D, TEXTURE_WRAP_S, TEXTURE_WRAP_T} from '../webgl.js'; import ContextEventType from '../webgl/ContextEventType.js'; import { create as createTransform, @@ -347,11 +347,10 @@ class WebGLHelper extends Disposable { * Just bind the buffer if it's in the cache. Otherwise create * the WebGL buffer, bind it, populate it, and add an entry to * the cache. - * @param {number} target Target, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER. * @param {import("./Buffer").default} buffer Buffer. * @api */ - bindBuffer(target, buffer) { + bindBuffer(buffer) { const gl = this.getGL(); const bufferKey = getUid(buffer); let bufferCache = this.bufferCache_[bufferKey]; @@ -362,28 +361,19 @@ class WebGLHelper extends Disposable { webGlBuffer: webGlBuffer }; } - gl.bindBuffer(target, bufferCache.webGlBuffer); + gl.bindBuffer(buffer.getType(), bufferCache.webGlBuffer); } /** * Update the data contained in the buffer array; this is required for the * new data to be rendered - * @param {number} target Target, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER. * @param {import("./Buffer").default} buffer Buffer. * @api */ - flushBufferData(target, buffer) { + flushBufferData(buffer) { const gl = this.getGL(); - const arr = buffer.getArray(); - this.bindBuffer(target, buffer); - let /** @type {ArrayBufferView} */ arrayBuffer; - if (target == ARRAY_BUFFER) { - arrayBuffer = new Float32Array(arr); - } else if (target == ELEMENT_ARRAY_BUFFER) { - arrayBuffer = this.hasOESElementIndexUint ? - new Uint32Array(arr) : new Uint16Array(arr); - } - gl.bufferData(target, arrayBuffer, buffer.getUsage()); + this.bindBuffer(buffer); + gl.bufferData(buffer.getType(), buffer.getArray(), buffer.getUsage()); } /** From e0983cb1c63b7970b839121c7af6fda9a1725395 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 15:40:27 +0200 Subject: [PATCH 07/15] Webgl worker / add tests, some typing and documentation The worker currently works by receiving GENERATE_BUFFERS messages and will send back the same kind of message, with the generated buffers attached. All properties of the original message are kept, so that when a GENERATE_BUFFERS message comes back to the main thread it is possible to know what and how the buffers where generated. This is typically used for the `projectionTransform` matrix, and will also be necessary when working with tiles. --- src/ol/renderer/webgl/Layer.js | 20 ++++++++++++ src/ol/renderer/webgl/PointsLayer.js | 25 +++++++++------ src/ol/worker/webgl.js | 29 +++++++++++------- test/spec/ol/worker/webgl.test.js | 46 ++++++++++++++++++++++++++++ 4 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 test/spec/ol/worker/webgl.test.js diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 2f34eb2f93..441c455e3f 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -5,6 +5,26 @@ import LayerRenderer from '../Layer.js'; import WebGLHelper from '../../webgl/Helper.js'; +/** + * @enum {string} + */ +export const WebGLWorkerMessageType = { + GENERATE_BUFFERS: 'GENERATE_BUFFERS' +}; + +/** + * @typedef {Object} WebGLWorkerGenerateBuffersMessage + * This message will trigger the generation of a vertex and an index buffer based on the given render instructions. + * When the buffers are generated, the worked will send a message of the same type to the main thread, with + * the generated buffers in it. + * Note that any addition properties present in the message *will* be sent back to the main thread. + * @property {WebGLWorkerMessageType} type Message type + * @property {ArrayBuffer} renderInstructions Render instructions raw binary buffer. + * @property {ArrayBuffer=} vertexBuffer Vertices array raw binary buffer (sent by the worker). + * @property {ArrayBuffer=} indexBuffer Indices array raw binary buffer (sent by the worker). + * @property {number=} customAttributesCount Amount of custom attributes count in the render instructions. + */ + /** * @typedef {Object} PostProcessesOptions * @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 6befa215f6..e35fbbfd26 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -7,7 +7,7 @@ import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper.js'; import GeometryType from '../../geom/GeometryType.js'; import WebGLLayerRenderer, { getBlankTexture, - POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, + POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, WebGLWorkerMessageType, writePointFeatureInstructions } from './Layer.js'; import ViewHint from '../../ViewHint.js'; @@ -276,10 +276,11 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.worker_ = createWebGLWorker(); this.worker_.addEventListener('message', function(event) { - if (event.data.type === 'buffers-generated') { - const projectionTransform = event.data.projectionTransform; - this.verticesBuffer_.fromArrayBuffer(event.data.vertexBuffer); - this.indicesBuffer_.fromArrayBuffer(event.data.indexBuffer); + const received = event.data; + if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) { + const projectionTransform = received.projectionTransform; + this.verticesBuffer_.fromArrayBuffer(received.vertexBuffer); + this.indicesBuffer_.fromArrayBuffer(received.indexBuffer); this.helper_.flushBufferData(this.verticesBuffer_); this.helper_.flushBufferData(this.indicesBuffer_); @@ -415,11 +416,15 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { ); } - this.worker_.postMessage({ - type: 'generate-buffer', - renderInstructions: this.renderInstructions_.buffer, - projectionTransform: projectionTransform - }, [this.renderInstructions_.buffer]); + /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ + const message = { + type: WebGLWorkerMessageType.GENERATE_BUFFERS, + renderInstructions: this.renderInstructions_.buffer + }; + // additional properties will be sent back as-is by the worker + message['projectionTransform'] = projectionTransform; + + this.worker_.postMessage(message, [this.renderInstructions_.buffer]); } diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index 046bbb7ca9..4e5662a174 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -1,15 +1,21 @@ /** + * A worker that does cpu-heavy tasks related to webgl rendering. * @module ol/worker/webgl - * A worker that does cpu-heavy tasks related to webgl rendering */ -import {POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, writePointFeatureToBuffers} from '../renderer/webgl/Layer.js'; +import { + POINT_INSTRUCTIONS_COUNT, + POINT_VERTEX_STRIDE, + WebGLWorkerMessageType, + writePointFeatureToBuffers +} from '../renderer/webgl/Layer.js'; +import {assign} from '../obj.js'; onmessage = event => { - if (event.data.type === 'generate-buffer') { - const renderInstructions = new Float32Array(event.data.renderInstructions); - const customAttributesCount = event.data.customAttributesCount || 0; + const received = event.data; + if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) { + const renderInstructions = new Float32Array(received.renderInstructions); + const customAttributesCount = received.customAttributesCount || 0; const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount; - const projectionTransform = event.data.projectionTransform; const elementsCount = renderInstructions.length / instructionsCount; const indexBuffer = new Uint32Array(elementsCount * 6); @@ -26,13 +32,14 @@ onmessage = event => { instructionsCount); } - postMessage({ - type: 'buffers-generated', + /** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */ + const message = assign({ vertexBuffer: vertexBuffer.buffer, indexBuffer: indexBuffer.buffer, - renderInstructions: renderInstructions.buffer, - projectionTransform - }, [vertexBuffer.buffer, indexBuffer.buffer, renderInstructions.buffer]); + renderInstructions: renderInstructions.buffer + }, received); + + postMessage(message, [vertexBuffer.buffer, indexBuffer.buffer, renderInstructions.buffer]); } }; diff --git a/test/spec/ol/worker/webgl.test.js b/test/spec/ol/worker/webgl.test.js new file mode 100644 index 0000000000..426125e960 --- /dev/null +++ b/test/spec/ol/worker/webgl.test.js @@ -0,0 +1,46 @@ +import {create} from '../../../../src/ol/worker/webgl.js'; +import {WebGLWorkerMessageType, writePointFeatureInstructions} from '../../../../src/ol/renderer/webgl/Layer'; + + +describe('ol/worker/webgl', function() { + + let worker; + beforeEach(function() { + worker = create(); + }); + + afterEach(function() { + if (worker) { + worker.terminate(); + } + worker = null; + }); + + describe('messaging', function() { + it('responds to GENERATE_BUFFERS message type', function(done) { + worker.addEventListener('error', done); + + worker.addEventListener('message', function(event) { + expect(event.data.type).to.eql(WebGLWorkerMessageType.GENERATE_BUFFERS); + expect(event.data.renderInstructions.byteLength).to.greaterThan(0); + expect(event.data.indexBuffer.byteLength).to.greaterThan(0); + expect(event.data.vertexBuffer.byteLength).to.greaterThan(0); + expect(event.data.testInt).to.be(101); + expect(event.data.testString).to.be('abcd'); + done(); + }); + + const instructions = new Float32Array(100); + + const message = { + type: WebGLWorkerMessageType.GENERATE_BUFFERS, + renderInstructions: instructions, + testInt: 101, + testString: 'abcd' + }; + + worker.postMessage(message); + }); + }); + +}); From 03e70bd10eea37ef9bd3f9f7c2828e5d4b0bde41 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 16:23:42 +0200 Subject: [PATCH 08/15] Webgl points / handle using short instead of int for indices This is controlled by the availability of the OES_element_index_uint webgl extension. --- src/ol/renderer/webgl/Layer.js | 7 ++-- src/ol/renderer/webgl/PointsLayer.js | 6 ++- src/ol/worker/webgl.js | 7 +++- test/spec/ol/worker/webgl.test.js | 62 +++++++++++++++++++--------- 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 441c455e3f..6fe4ec0b2e 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -20,9 +20,10 @@ export const WebGLWorkerMessageType = { * Note that any addition properties present in the message *will* be sent back to the main thread. * @property {WebGLWorkerMessageType} type Message type * @property {ArrayBuffer} renderInstructions Render instructions raw binary buffer. - * @property {ArrayBuffer=} vertexBuffer Vertices array raw binary buffer (sent by the worker). - * @property {ArrayBuffer=} indexBuffer Indices array raw binary buffer (sent by the worker). - * @property {number=} customAttributesCount Amount of custom attributes count in the render instructions. + * @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker). + * @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker). + * @property {number} [customAttributesCount] Amount of custom attributes count in the render instructions. + * @property {boolean} [useShortIndices] If true, Uint16Array will be used instead of Uint32Array for index buffers. */ /** diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index e35fbbfd26..718968ffe2 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -2,7 +2,7 @@ * @module ol/renderer/webgl/PointsLayer */ import WebGLArrayBuffer from '../../webgl/Buffer.js'; -import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl.js'; +import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT, EXTENSIONS as WEBGL_EXTENSIONS} from '../../webgl.js'; import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper.js'; import GeometryType from '../../geom/GeometryType.js'; import WebGLLayerRenderer, { @@ -19,6 +19,7 @@ import { apply as applyTransform } from '../../transform.js'; import {create as createWebGLWorker} from '../../worker/webgl.js'; +import {includes} from '../../array.js'; const VERTEX_SHADER = ` precision mediump float; @@ -419,7 +420,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ const message = { type: WebGLWorkerMessageType.GENERATE_BUFFERS, - renderInstructions: this.renderInstructions_.buffer + renderInstructions: this.renderInstructions_.buffer, + useShortIndices: !includes(WEBGL_EXTENSIONS, 'OES_element_index_uint') }; // additional properties will be sent back as-is by the worker message['projectionTransform'] = projectionTransform; diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index 4e5662a174..b4b04ca96d 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -16,10 +16,13 @@ onmessage = event => { const renderInstructions = new Float32Array(received.renderInstructions); const customAttributesCount = received.customAttributesCount || 0; const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount; + const useShort = received.useShortIndices; const elementsCount = renderInstructions.length / instructionsCount; - const indexBuffer = new Uint32Array(elementsCount * 6); - const vertexBuffer = new Float32Array(elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount)); + const indicesCount = elementsCount * 6; + const verticesCount = elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount); + const indexBuffer = useShort ? new Uint16Array(indicesCount) : new Uint32Array(indicesCount); + const vertexBuffer = new Float32Array(verticesCount); let bufferPositions = null; for (let i = 0; i < renderInstructions.length; i += instructionsCount) { diff --git a/test/spec/ol/worker/webgl.test.js b/test/spec/ol/worker/webgl.test.js index 426125e960..9bd8931ab4 100644 --- a/test/spec/ol/worker/webgl.test.js +++ b/test/spec/ol/worker/webgl.test.js @@ -1,5 +1,8 @@ import {create} from '../../../../src/ol/worker/webgl.js'; -import {WebGLWorkerMessageType, writePointFeatureInstructions} from '../../../../src/ol/renderer/webgl/Layer'; +import { + POINT_INSTRUCTIONS_COUNT, + WebGLWorkerMessageType +} from '../../../../src/ol/renderer/webgl/Layer.js'; describe('ol/worker/webgl', function() { @@ -17,29 +20,50 @@ describe('ol/worker/webgl', function() { }); describe('messaging', function() { - it('responds to GENERATE_BUFFERS message type', function(done) { - worker.addEventListener('error', done); + describe('GENERATE_BUFFERS', function() { + it('responds with buffer data', function(done) { + worker.addEventListener('error', done); - worker.addEventListener('message', function(event) { - expect(event.data.type).to.eql(WebGLWorkerMessageType.GENERATE_BUFFERS); - expect(event.data.renderInstructions.byteLength).to.greaterThan(0); - expect(event.data.indexBuffer.byteLength).to.greaterThan(0); - expect(event.data.vertexBuffer.byteLength).to.greaterThan(0); - expect(event.data.testInt).to.be(101); - expect(event.data.testString).to.be('abcd'); - done(); + worker.addEventListener('message', function(event) { + expect(event.data.type).to.eql(WebGLWorkerMessageType.GENERATE_BUFFERS); + expect(event.data.renderInstructions.byteLength).to.greaterThan(0); + expect(event.data.indexBuffer.byteLength).to.greaterThan(0); + expect(event.data.vertexBuffer.byteLength).to.greaterThan(0); + expect(event.data.testInt).to.be(101); + expect(event.data.testString).to.be('abcd'); + done(); + }); + + const instructions = new Float32Array(POINT_INSTRUCTIONS_COUNT); + + const message = { + type: WebGLWorkerMessageType.GENERATE_BUFFERS, + renderInstructions: instructions, + testInt: 101, + testString: 'abcd' + }; + + worker.postMessage(message); }); - const instructions = new Float32Array(100); + it('responds with buffer data (fallback to Uint16Array)', function(done) { + worker.addEventListener('error', done); - const message = { - type: WebGLWorkerMessageType.GENERATE_BUFFERS, - renderInstructions: instructions, - testInt: 101, - testString: 'abcd' - }; + worker.addEventListener('message', function(event) { + expect(event.data.indexBuffer).to.eql(Uint16Array.BYTES_PER_ELEMENT * 6); + done(); + }); - worker.postMessage(message); + const instructions = new Float32Array(POINT_INSTRUCTIONS_COUNT); + + const message = { + type: WebGLWorkerMessageType.GENERATE_BUFFERS, + renderInstructions: instructions, + useShortIndices: true + }; + + worker.postMessage(message); + }); }); }); From 698816030e66e8aa9cc3f7fdad5705f7d7f2f52e Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 18:29:10 +0200 Subject: [PATCH 09/15] Webgl points / fix unit tests --- src/ol/renderer/webgl/PointsLayer.js | 3 +- .../ol/renderer/webgl/pointslayer.test.js | 36 +++++++++++++------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 718968ffe2..62a50f4160 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -383,7 +383,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const features = vectorSource.getFeatures(); const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length; - if (this.renderInstructions_.length !== totalInstructionsCount) { + if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) { this.renderInstructions_ = new Float32Array(totalInstructionsCount); } @@ -427,6 +427,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { message['projectionTransform'] = projectionTransform; this.worker_.postMessage(message, [this.renderInstructions_.buffer]); + this.renderInstructions_ = null; } diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index cdf8b3e6e3..a08f9c2a9c 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -5,6 +5,7 @@ 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 ViewHint from '../../../../../src/ol/ViewHint.js'; +import {POINT_VERTEX_STRIDE, WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js'; describe('ol.renderer.webgl.PointsLayer', function() { @@ -63,7 +64,7 @@ describe('ol.renderer.webgl.PointsLayer', function() { expect(spy.called).to.be(true); }); - it('fills up a buffer with 2 triangles per point', function() { + it('fills up a buffer with 2 triangles per point', function(done) { layer.getSource().addFeature(new Feature({ geometry: new Point([10, 20]) })); @@ -73,16 +74,23 @@ describe('ol.renderer.webgl.PointsLayer', function() { renderer.prepareFrame(frameState); 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); + renderer.worker_.addEventListener('message', function(event) { + if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { + return; + } + 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); + done(); + }); }); - it('clears the buffers when the features are gone', function() { + it('clears the buffers when the features are gone', function(done) { const source = layer.getSource(); source.addFeature(new Feature({ geometry: new Point([10, 20]) @@ -93,9 +101,15 @@ describe('ol.renderer.webgl.PointsLayer', function() { })); renderer.prepareFrame(frameState); - const attributePerVertex = 12; - expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex); - expect(renderer.indicesBuffer_.getArray().length).to.eql(6); + renderer.worker_.addEventListener('message', function(event) { + if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { + return; + } + const attributePerVertex = 12; + expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex); + expect(renderer.indicesBuffer_.getArray().length).to.eql(6); + done(); + }); }); it('rebuilds the buffers only when not interacting or animating', function() { From 98b0c65450ee4148c17664b0d3794156d35633a3 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Sat, 18 May 2019 18:34:25 +0200 Subject: [PATCH 10/15] Rendering tests / add custom web worker loader Also includes a change in the rollup-babel plugin to avoid adding helpers as dependencies (which would give out errors). --- rendering/webpack.config.js | 13 ++++++++++++- tasks/serialize-workers.js | 1 + 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/rendering/webpack.config.js b/rendering/webpack.config.js index 948ce5c151..64fbb63861 100644 --- a/rendering/webpack.config.js +++ b/rendering/webpack.config.js @@ -22,5 +22,16 @@ module.exports = { context: __dirname, target: 'web', entry: entry, - devtool: 'source-map' + devtool: 'source-map', + module: { + rules: [{ + test: /\.js$/, + use: { + loader: path.join(__dirname, '../examples/webpack/worker-loader.js') + }, + include: [ + path.join(__dirname, '../src/ol/worker') + ] + }] + } }; diff --git a/tasks/serialize-workers.js b/tasks/serialize-workers.js index 0e2b14182f..cbf8929f34 100644 --- a/tasks/serialize-workers.js +++ b/tasks/serialize-workers.js @@ -20,6 +20,7 @@ async function build(input, {minify = true} = {}) { common(), resolve(), babel({ + 'externalHelpers': true, 'presets': [ [ '@babel/preset-env', From 7fb113c3dcece53a615d802a728afcd15c3c4ad1 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Tue, 21 May 2019 10:22:44 +0200 Subject: [PATCH 11/15] Mark 'helper' variable protected --- src/ol/renderer/webgl/Layer.js | 8 +++++-- src/ol/renderer/webgl/PointsLayer.js | 36 ++++++++++++++-------------- 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 6fe4ec0b2e..1c34c1cf43 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -57,7 +57,11 @@ class WebGLLayerRenderer extends LayerRenderer { const options = opt_options || {}; - this.helper_ = new WebGLHelper({ + /** + * @type {WebGLHelper} + * @protected + */ + this.helper = new WebGLHelper({ postProcesses: options.postProcesses, uniforms: options.uniforms }); @@ -76,7 +80,7 @@ class WebGLLayerRenderer extends LayerRenderer { * @api */ getShaderCompileErrors() { - return this.helper_.getShaderCompileErrors(); + return this.helper.getShaderCompileErrors(); } } diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 62a50f4160..314227c9d2 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -215,12 +215,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.verticesBuffer_ = new WebGLArrayBuffer(ARRAY_BUFFER, DYNAMIC_DRAW); this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW); - this.program_ = this.helper_.getProgram( + this.program_ = this.helper.getProgram( options.fragmentShader || FRAGMENT_SHADER, options.vertexShader || VERTEX_SHADER ); - this.helper_.useProgram(this.program_); + this.helper.useProgram(this.program_); this.sizeCallback_ = options.sizeCallback || function() { return 1; @@ -282,8 +282,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const projectionTransform = received.projectionTransform; this.verticesBuffer_.fromArrayBuffer(received.vertexBuffer); this.indicesBuffer_.fromArrayBuffer(received.indexBuffer); - this.helper_.flushBufferData(this.verticesBuffer_); - this.helper_.flushBufferData(this.indicesBuffer_); + this.helper.flushBufferData(this.verticesBuffer_); + this.helper.flushBufferData(this.indicesBuffer_); // saves the projection transform for the current frame state this.renderTransform_ = projectionTransform; @@ -306,9 +306,9 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { */ renderFrame(frameState) { const renderCount = this.indicesBuffer_.getArray() ? this.indicesBuffer_.getArray().length : 0; - this.helper_.drawElements(0, renderCount); - this.helper_.finalizeDraw(frameState); - const canvas = this.helper_.getCanvas(); + this.helper.drawElements(0, renderCount); + this.helper.finalizeDraw(frameState); + const canvas = this.helper.getCanvas(); const layerState = frameState.layerStatesArray[frameState.layerIndex]; const opacity = layerState.opacity; @@ -348,22 +348,22 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { } // apply the current projection transform with the invert of the one used to fill buffers - this.helper_.makeProjectionTransform(frameState, this.currentTransform_); + this.helper.makeProjectionTransform(frameState, this.currentTransform_); multiplyTransform(this.currentTransform_, this.invertRenderTransform_); - this.helper_.prepareDraw(frameState); + this.helper.prepareDraw(frameState); // write new data - this.helper_.bindBuffer(this.verticesBuffer_); - this.helper_.bindBuffer(this.indicesBuffer_); + this.helper.bindBuffer(this.verticesBuffer_); + this.helper.bindBuffer(this.indicesBuffer_); const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT; - this.helper_.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0); - this.helper_.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 2); - this.helper_.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 4); - this.helper_.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 6); - this.helper_.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 7); - this.helper_.enableAttributeArray(DefaultAttrib.COLOR, 4, FLOAT, bytesPerFloat * stride, bytesPerFloat * 8); + this.helper.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * stride, 0); + this.helper.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 2); + this.helper.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * stride, bytesPerFloat * 4); + this.helper.enableAttributeArray(DefaultAttrib.OPACITY, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 6); + this.helper.enableAttributeArray(DefaultAttrib.ROTATE_WITH_VIEW, 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 7); + this.helper.enableAttributeArray(DefaultAttrib.COLOR, 4, FLOAT, bytesPerFloat * stride, bytesPerFloat * 8); return true; } @@ -379,7 +379,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { // saves the projection transform for the current frame state const projectionTransform = createTransform(); - this.helper_.makeProjectionTransform(frameState, projectionTransform); + this.helper.makeProjectionTransform(frameState, projectionTransform); const features = vectorSource.getFeatures(); const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length; From 06be00bbd59fb9b6d52a8b544d540047e95eca65 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Tue, 21 May 2019 10:27:53 +0200 Subject: [PATCH 12/15] Webgl Helper / add an API for OESElementIndexUint extension --- src/ol/renderer/webgl/PointsLayer.js | 5 ++--- src/ol/webgl/Helper.js | 19 +++++++++++++++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 314227c9d2..2210067392 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -2,7 +2,7 @@ * @module ol/renderer/webgl/PointsLayer */ import WebGLArrayBuffer from '../../webgl/Buffer.js'; -import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT, EXTENSIONS as WEBGL_EXTENSIONS} from '../../webgl.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, { @@ -19,7 +19,6 @@ import { apply as applyTransform } from '../../transform.js'; import {create as createWebGLWorker} from '../../worker/webgl.js'; -import {includes} from '../../array.js'; const VERTEX_SHADER = ` precision mediump float; @@ -421,7 +420,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const message = { type: WebGLWorkerMessageType.GENERATE_BUFFERS, renderInstructions: this.renderInstructions_.buffer, - useShortIndices: !includes(WEBGL_EXTENSIONS, 'OES_element_index_uint') + useShortIndices: !this.helper.getElementIndexUintEnabled() }; // additional properties will be sent back as-is by the worker message['projectionTransform'] = projectionTransform; diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index 9a0b144b43..8a03534cdd 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -260,11 +260,12 @@ class WebGLHelper extends Disposable { /** * @type {boolean} + * @private */ - this.hasOESElementIndexUint = includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'); + this.hasOESElementIndexUint_ = includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'); // use the OES_element_index_uint extension if available - if (this.hasOESElementIndexUint) { + if (this.hasOESElementIndexUint_) { gl.getExtension('OES_element_index_uint'); } @@ -452,9 +453,9 @@ class WebGLHelper extends Disposable { */ drawElements(start, end) { const gl = this.getGL(); - const elementType = this.hasOESElementIndexUint ? + const elementType = this.hasOESElementIndexUint_ ? gl.UNSIGNED_INT : gl.UNSIGNED_SHORT; - const elementSize = this.hasOESElementIndexUint ? 4 : 2; + const elementSize = this.hasOESElementIndexUint_ ? 4 : 2; const numItems = end - start; const offsetInBytes = start * elementSize; @@ -746,6 +747,16 @@ class WebGLHelper extends Disposable { handleWebGLContextRestored() { } + /** + * Returns whether the `OES_element_index_uint` WebGL extension is enabled for this context. + * @return {boolean} If true, Uint16Array should be used for element array buffers + * instead of Uint8Array. + * @api + */ + getElementIndexUintEnabled() { + return this.hasOESElementIndexUint_; + } + // TODO: shutdown program /** From 87d5f4c8bc11c855b4d675954d6ca150cf7a484f Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Thu, 23 May 2019 09:07:30 +0200 Subject: [PATCH 13/15] Use 'helper' from WebGLHelper in tests --- test/spec/ol/renderer/webgl/pointslayer.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index a08f9c2a9c..e08f32ed8f 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -59,7 +59,7 @@ describe('ol.renderer.webgl.PointsLayer', function() { }); it('calls WebGlHelper#prepareDraw', function() { - const spy = sinon.spy(renderer.helper_, 'prepareDraw'); + const spy = sinon.spy(renderer.helper, 'prepareDraw'); renderer.prepareFrame(frameState); expect(spy.called).to.be(true); }); From 2412fe021170e6187b1806e740749d7270b32778 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Wed, 5 Jun 2019 14:36:02 +0200 Subject: [PATCH 14/15] Webgl / remove handling of element_index_uint extension From now on we will assume this extension is always enabled. An error message have been added in the unlikely scenario of a lack of support. --- doc/errors/index.md | 4 +++ src/ol/renderer/webgl/Layer.js | 3 +-- src/ol/renderer/webgl/PointsLayer.js | 3 +-- src/ol/webgl/Buffer.js | 12 ++++----- src/ol/webgl/Helper.js | 33 +++++------------------ src/ol/worker/webgl.js | 3 +-- test/spec/ol/renderer/webgl/layer.test.js | 2 +- test/spec/ol/webgl/buffer.test.js | 6 ----- test/spec/ol/worker/webgl.test.js | 19 ------------- 9 files changed, 20 insertions(+), 65 deletions(-) diff --git a/doc/errors/index.md b/doc/errors/index.md index 6aedfcb807..79ac5dac7c 100644 --- a/doc/errors/index.md +++ b/doc/errors/index.md @@ -232,3 +232,7 @@ Cannot determine IIIF Image API version from provided image information JSON. ### 62 A `WebGLArrayBuffer` must either be of type `ELEMENT_ARRAY_BUFFER` or `ARRAY_BUFFER`. + +### 63 + +Support for the `OES_element_index_uint` WebGL extension is mandatory for WebGL layers. diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index 1c34c1cf43..b50b54d1b2 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -23,7 +23,6 @@ export const WebGLWorkerMessageType = { * @property {ArrayBuffer} [vertexBuffer] Vertices array raw binary buffer (sent by the worker). * @property {ArrayBuffer} [indexBuffer] Indices array raw binary buffer (sent by the worker). * @property {number} [customAttributesCount] Amount of custom attributes count in the render instructions. - * @property {boolean} [useShortIndices] If true, Uint16Array will be used instead of Uint32Array for index buffers. */ /** @@ -158,7 +157,7 @@ function writeCustomAttrs(buffer, pos, customAttrs) { * @param {Float32Array} instructions Array of render instructions for points. * @param {number} elementIndex Index from which render instructions will be read. * @param {Float32Array} vertexBuffer Buffer in the form of a typed array. - * @param {Uint16Array|Uint32Array} indexBuffer Buffer in the form of a typed array. + * @param {Uint32Array} indexBuffer Buffer in the form of a typed array. * @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0. * @param {number} [count] Amount of render instructions that will be read. Default value is POINT_INSTRUCTIONS_COUNT * but a higher value can be provided; all values beyond the default count will be put in the vertices buffer as diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 2210067392..25eccac815 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -419,8 +419,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ const message = { type: WebGLWorkerMessageType.GENERATE_BUFFERS, - renderInstructions: this.renderInstructions_.buffer, - useShortIndices: !this.helper.getElementIndexUintEnabled() + renderInstructions: this.renderInstructions_.buffer }; // additional properties will be sent back as-is by the worker message['projectionTransform'] = projectionTransform; diff --git a/src/ol/webgl/Buffer.js b/src/ol/webgl/Buffer.js index dc07e9c861..735a08ffd9 100644 --- a/src/ol/webgl/Buffer.js +++ b/src/ol/webgl/Buffer.js @@ -2,8 +2,7 @@ * @module ol/webgl/Buffer */ import {STATIC_DRAW, STREAM_DRAW, DYNAMIC_DRAW} from '../webgl.js'; -import {includes} from '../array.js'; -import {ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, EXTENSIONS as WEBGL_EXTENSIONS} from '../webgl.js'; +import {ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER} from '../webgl.js'; import {assert} from '../asserts.js'; /** @@ -44,7 +43,7 @@ class WebGLArrayBuffer { /** * @private - * @type {Float32Array|Uint32Array|Uint16Array} + * @type {Float32Array|Uint32Array} */ this.array = null; @@ -96,7 +95,7 @@ class WebGLArrayBuffer { } /** - * @return {Float32Array|Uint32Array|Uint16Array} Array. + * @return {Float32Array|Uint32Array} Array. */ getArray() { return this.array; @@ -113,15 +112,14 @@ class WebGLArrayBuffer { /** * Returns a typed array constructor based on the given buffer type * @param {number} type Buffer type, either ARRAY_BUFFER or ELEMENT_ARRAY_BUFFER. - * @returns {Float32ArrayConstructor|Uint16ArrayConstructor|Uint32ArrayConstructor} The typed array class to use for this buffer. + * @returns {Float32ArrayConstructor|Uint32ArrayConstructor} The typed array class to use for this buffer. */ export function getArrayClassForType(type) { switch (type) { case ARRAY_BUFFER: return Float32Array; case ELEMENT_ARRAY_BUFFER: - const hasExtension = includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'); - return hasExtension ? Uint32Array : Uint16Array; + return Uint32Array; default: return Float32Array; } diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index 8a03534cdd..04a6f77f77 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -2,12 +2,10 @@ * @module ol/webgl/Helper */ import {getUid} from '../util.js'; -import {EXTENSIONS as WEBGL_EXTENSIONS} from '../webgl.js'; import Disposable from '../Disposable.js'; -import {includes} from '../array.js'; import {listen, unlistenAll} from '../events.js'; import {clear} from '../obj.js'; -import {TEXTURE_2D, TEXTURE_WRAP_S, TEXTURE_WRAP_T} from '../webgl.js'; +import {TEXTURE_2D, TEXTURE_WRAP_S, TEXTURE_WRAP_T, EXTENSIONS as WEBGL_EXTENSIONS} from '../webgl.js'; import ContextEventType from '../webgl/ContextEventType.js'; import { create as createTransform, @@ -19,6 +17,8 @@ import { import {create, fromTransform} from '../vec/mat4.js'; import WebGLPostProcessingPass from './PostProcessingPass.js'; import {getContext} from '../webgl.js'; +import {includes} from '../array.js'; +import {assert} from '../asserts.js'; /** @@ -258,16 +258,8 @@ class WebGLHelper extends Disposable { */ this.currentProgram_ = null; - /** - * @type {boolean} - * @private - */ - this.hasOESElementIndexUint_ = includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'); - - // use the OES_element_index_uint extension if available - if (this.hasOESElementIndexUint_) { - gl.getExtension('OES_element_index_uint'); - } + assert(includes(WEBGL_EXTENSIONS, 'OES_element_index_uint'), 63); + gl.getExtension('OES_element_index_uint'); listen(this.canvas_, ContextEventType.LOST, this.handleWebGLContextLost, this); @@ -453,9 +445,8 @@ class WebGLHelper extends Disposable { */ drawElements(start, end) { const gl = this.getGL(); - const elementType = this.hasOESElementIndexUint_ ? - gl.UNSIGNED_INT : gl.UNSIGNED_SHORT; - const elementSize = this.hasOESElementIndexUint_ ? 4 : 2; + const elementType = gl.UNSIGNED_INT; + const elementSize = 4; const numItems = end - start; const offsetInBytes = start * elementSize; @@ -747,16 +738,6 @@ class WebGLHelper extends Disposable { handleWebGLContextRestored() { } - /** - * Returns whether the `OES_element_index_uint` WebGL extension is enabled for this context. - * @return {boolean} If true, Uint16Array should be used for element array buffers - * instead of Uint8Array. - * @api - */ - getElementIndexUintEnabled() { - return this.hasOESElementIndexUint_; - } - // TODO: shutdown program /** diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index b4b04ca96d..d1542aad4e 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -16,12 +16,11 @@ onmessage = event => { const renderInstructions = new Float32Array(received.renderInstructions); const customAttributesCount = received.customAttributesCount || 0; const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount; - const useShort = received.useShortIndices; const elementsCount = renderInstructions.length / instructionsCount; const indicesCount = elementsCount * 6; const verticesCount = elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount); - const indexBuffer = useShort ? new Uint16Array(indicesCount) : new Uint32Array(indicesCount); + const indexBuffer = new Uint32Array(indicesCount); const vertexBuffer = new Float32Array(verticesCount); let bufferPositions = null; diff --git a/test/spec/ol/renderer/webgl/layer.test.js b/test/spec/ol/renderer/webgl/layer.test.js index a2007f2206..cb9f30f3c1 100644 --- a/test/spec/ol/renderer/webgl/layer.test.js +++ b/test/spec/ol/renderer/webgl/layer.test.js @@ -89,7 +89,7 @@ describe('ol.renderer.webgl.Layer', function() { beforeEach(function() { vertexBuffer = new Float32Array(100); - indexBuffer = new Uint16Array(100); + indexBuffer = new Uint32Array(100); instructions = new Float32Array(100); elementIndex = 3; diff --git a/test/spec/ol/webgl/buffer.test.js b/test/spec/ol/webgl/buffer.test.js index 8c1102b5ea..09c6daf694 100644 --- a/test/spec/ol/webgl/buffer.test.js +++ b/test/spec/ol/webgl/buffer.test.js @@ -2,7 +2,6 @@ import WebGLArrayBuffer, {getArrayClassForType} from '../../../../src/ol/webgl/B import { ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, - EXTENSIONS as WEBGL_EXTENSIONS, STATIC_DRAW, STREAM_DRAW } from '../../../../src/ol/webgl.js'; @@ -37,11 +36,6 @@ describe('ol.webgl.Buffer', function() { expect(getArrayClassForType(ARRAY_BUFFER)).to.be(Float32Array); expect(getArrayClassForType(ELEMENT_ARRAY_BUFFER)).to.be(Uint32Array); }); - - it('returns the correct typed array constructor (without OES uint extension)', function() { - WEBGL_EXTENSIONS.length = 0; - expect(getArrayClassForType(ELEMENT_ARRAY_BUFFER)).to.be(Uint16Array); - }); }); describe('populate methods', function() { diff --git a/test/spec/ol/worker/webgl.test.js b/test/spec/ol/worker/webgl.test.js index 9bd8931ab4..b15fd5c829 100644 --- a/test/spec/ol/worker/webgl.test.js +++ b/test/spec/ol/worker/webgl.test.js @@ -45,25 +45,6 @@ describe('ol/worker/webgl', function() { worker.postMessage(message); }); - - it('responds with buffer data (fallback to Uint16Array)', function(done) { - worker.addEventListener('error', done); - - worker.addEventListener('message', function(event) { - expect(event.data.indexBuffer).to.eql(Uint16Array.BYTES_PER_ELEMENT * 6); - done(); - }); - - const instructions = new Float32Array(POINT_INSTRUCTIONS_COUNT); - - const message = { - type: WebGLWorkerMessageType.GENERATE_BUFFERS, - renderInstructions: instructions, - useShortIndices: true - }; - - worker.postMessage(message); - }); }); }); From e0367677469c30acaa6ac51cd27125d4fb2cc907 Mon Sep 17 00:00:00 2001 From: Frederic Junod Date: Thu, 6 Jun 2019 08:34:31 +0200 Subject: [PATCH 15/15] Use version 4 TileJSON from mapbox --- examples/icon-sprite-webgl.html | 3 +++ examples/icon-sprite-webgl.js | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/examples/icon-sprite-webgl.html b/examples/icon-sprite-webgl.html index 7d7220d17b..f465510d4c 100644 --- a/examples/icon-sprite-webgl.html +++ b/examples/icon-sprite-webgl.html @@ -13,5 +13,8 @@ docs: > The dataset contains around 80k points and can be found here: https://www.kaggle.com/NUFORC/ufo-sightings tags: "webgl, icon, sprite, point, ufo" +cloak: + - key: pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg + value: Your Mapbox access token from https://mapbox.com/ here ---
diff --git a/examples/icon-sprite-webgl.js b/examples/icon-sprite-webgl.js index 4b8be0cb98..7c0e1da6bd 100644 --- a/examples/icon-sprite-webgl.js +++ b/examples/icon-sprite-webgl.js @@ -10,6 +10,8 @@ import {fromLonLat} from '../src/ol/proj.js'; import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js'; import {lerp} from '../src/ol/math.js'; +const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg'; + const vectorSource = new Vector({ features: [], attributions: 'National UFO Reporting Center' @@ -105,7 +107,7 @@ new Map({ layers: [ new TileLayer({ source: new TileJSON({ - url: 'https://api.tiles.mapbox.com/v3/mapbox.world-dark.json?secure', + url: 'https://api.tiles.mapbox.com/v4/mapbox.world-dark.json?access_token=' + key, crossOrigin: 'anonymous' }) }),