diff --git a/doc/errors/index.md b/doc/errors/index.md
index 687d9efbf1..79ac5dac7c 100644
--- a/doc/errors/index.md
+++ b/doc/errors/index.md
@@ -228,3 +228,11 @@ 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`.
+
+### 63
+
+Support for the `OES_element_index_uint` WebGL extension is mandatory for WebGL layers.
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'
})
}),
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/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js
index 6e2b87d9f9..b50b54d1b2 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
@@ -36,7 +56,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
});
@@ -55,96 +79,151 @@ class WebGLLayerRenderer extends LayerRenderer {
* @api
*/
getShaderCompileErrors() {
- return this.helper_.getShaderCompileErrors();
+ return this.helper.getShaderCompileErrors();
}
}
/**
- * 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 {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/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js
index 65a25a8a9c..25eccac815 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, WebGLWorkerMessageType,
+ writePointFeatureInstructions
+} from './Layer.js';
import ViewHint from '../../ViewHint.js';
import {createEmpty, equals} from '../../extent.js';
import {
@@ -16,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;
@@ -208,15 +211,15 @@ 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(
+ 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;
@@ -241,14 +244,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
return false;
};
- this.geojsonFormat_ = new GeoJSON();
-
- /**
- * @type {Object}
- * @private
- */
- this.geojsonFeatureCache_ = {};
-
this.previousExtent_ = createEmpty();
/**
@@ -272,6 +267,30 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
* @private
*/
this.invertRenderTransform_ = createTransform();
+
+ /**
+ * @type {Float32Array}
+ * @private
+ */
+ this.renderInstructions_ = new Float32Array(0);
+
+ this.worker_ = createWebGLWorker();
+ this.worker_.addEventListener('message', function(event) {
+ 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_);
+
+ // 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));
}
/**
@@ -285,11 +304,12 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
* @inheritDoc
*/
renderFrame(frameState) {
- const layerState = frameState.layerStatesArray[frameState.layerIndex];
- this.helper_.drawElements(0, this.indicesBuffer_.getArray().length);
- this.helper_.finalizeDraw(frameState);
- const canvas = this.helper_.getCanvas();
+ 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;
@@ -306,8 +326,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();
@@ -328,22 +347,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(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);
- 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;
}
@@ -357,47 +376,59 @@ 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_ || 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_)
+ );
}
- this.helper_.flushBufferData(ARRAY_BUFFER, this.verticesBuffer_);
- this.helper_.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_);
+ /** @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]);
+ this.renderInstructions_ = null;
}
+
+
}
export default WebGLPointsLayerRenderer;
diff --git a/src/ol/webgl/Buffer.js b/src/ol/webgl/Buffer.js
index 21e5b25c8c..735a08ffd9 100644
--- a/src/ol/webgl/Buffer.js
+++ b/src/ol/webgl/Buffer.js
@@ -2,6 +2,8 @@
* @module ol/webgl/Buffer
*/
import {STATIC_DRAW, STREAM_DRAW, DYNAMIC_DRAW} from '../webgl.js';
+import {ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER} from '../webgl.js';
+import {assert} from '../asserts.js';
/**
* Used to describe the intended usage for the data: `STATIC_DRAW`, `STREAM_DRAW`
@@ -17,43 +19,109 @@ 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}
*/
- 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} 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|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:
+ return Uint32Array;
+ default:
+ return Float32Array;
}
}
diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js
index d9c2c8d638..b6b80865bd 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 {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, EXTENSIONS as WEBGL_EXTENSIONS} from '../webgl.js';
import ContextEventType from '../webgl/ContextEventType.js';
import {
compose as composeTransform,
@@ -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,15 +258,8 @@ class WebGLHelper extends Disposable {
*/
this.currentProgram_ = null;
- /**
- * @type {boolean}
- */
- 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);
@@ -347,11 +340,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 +354,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());
}
/**
@@ -462,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;
diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js
new file mode 100644
index 0000000000..d1542aad4e
--- /dev/null
+++ b/src/ol/worker/webgl.js
@@ -0,0 +1,48 @@
+/**
+ * A worker that does cpu-heavy tasks related to webgl rendering.
+ * @module ol/worker/webgl
+ */
+import {
+ POINT_INSTRUCTIONS_COUNT,
+ POINT_VERTEX_STRIDE,
+ WebGLWorkerMessageType,
+ writePointFeatureToBuffers
+} from '../renderer/webgl/Layer.js';
+import {assign} from '../obj.js';
+
+onmessage = event => {
+ 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 elementsCount = renderInstructions.length / instructionsCount;
+ const indicesCount = elementsCount * 6;
+ const verticesCount = elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount);
+ const indexBuffer = new Uint32Array(indicesCount);
+ const vertexBuffer = new Float32Array(verticesCount);
+
+ let bufferPositions = null;
+ for (let i = 0; i < renderInstructions.length; i += instructionsCount) {
+ bufferPositions = writePointFeatureToBuffers(
+ renderInstructions,
+ i,
+ vertexBuffer,
+ indexBuffer,
+ bufferPositions,
+ instructionsCount);
+ }
+
+ /** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
+ const message = assign({
+ vertexBuffer: vertexBuffer.buffer,
+ indexBuffer: indexBuffer.buffer,
+ renderInstructions: renderInstructions.buffer
+ }, received);
+
+ postMessage(message, [vertexBuffer.buffer, indexBuffer.buffer, renderInstructions.buffer]);
+ }
+};
+
+export let create;
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',
diff --git a/test/spec/ol/renderer/webgl/layer.test.js b/test/spec/ol/renderer/webgl/layer.test.js
index 4d48e2df0c..cb9f30f3c1 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 Uint32Array(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() {
diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js
index f0d104a70c..e08f32ed8f 100644
--- a/test/spec/ol/renderer/webgl/pointslayer.test.js
+++ b/test/spec/ol/renderer/webgl/pointslayer.test.js
@@ -1,12 +1,11 @@
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';
+import {POINT_VERTEX_STRIDE, WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
describe('ol.renderer.webgl.PointsLayer', function() {
@@ -52,44 +51,46 @@ 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]
};
});
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);
});
- 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])
}));
- 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;
+
+ 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])
@@ -100,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() {
diff --git a/test/spec/ol/webgl/buffer.test.js b/test/spec/ol/webgl/buffer.test.js
index af590da0e4..09c6daf694 100644
--- a/test/spec/ol/webgl/buffer.test.js
+++ b/test/spec/ol/webgl/buffer.test.js
@@ -1,51 +1,76 @@
-import WebGLArrayBuffer from '../../../../src/ol/webgl/Buffer.js';
+import WebGLArrayBuffer, {getArrayClassForType} from '../../../../src/ol/webgl/Buffer.js';
+import {
+ ARRAY_BUFFER,
+ ELEMENT_ARRAY_BUFFER,
+ 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);
+ });
+ });
+
+ 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);
});
});
diff --git a/test/spec/ol/worker/webgl.test.js b/test/spec/ol/worker/webgl.test.js
new file mode 100644
index 0000000000..b15fd5c829
--- /dev/null
+++ b/test/spec/ol/worker/webgl.test.js
@@ -0,0 +1,51 @@
+import {create} from '../../../../src/ol/worker/webgl.js';
+import {
+ POINT_INSTRUCTIONS_COUNT,
+ WebGLWorkerMessageType
+} from '../../../../src/ol/renderer/webgl/Layer.js';
+
+
+describe('ol/worker/webgl', function() {
+
+ let worker;
+ beforeEach(function() {
+ worker = create();
+ });
+
+ afterEach(function() {
+ if (worker) {
+ worker.terminate();
+ }
+ worker = null;
+ });
+
+ describe('messaging', function() {
+ 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();
+ });
+
+ const instructions = new Float32Array(POINT_INSTRUCTIONS_COUNT);
+
+ const message = {
+ type: WebGLWorkerMessageType.GENERATE_BUFFERS,
+ renderInstructions: instructions,
+ testInt: 101,
+ testString: 'abcd'
+ };
+
+ worker.postMessage(message);
+ });
+ });
+ });
+
+});