Merge pull request #9562 from jahow/webgl-worker

Optimize the WebGL points renderer using a worker
This commit is contained in:
Olivier Guyot
2019-06-06 08:48:29 +02:00
committed by GitHub
14 changed files with 732 additions and 314 deletions

View File

@@ -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.

View File

@@ -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
---
<div id="map" class="map"></div>

View File

@@ -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'
})
}),

View File

@@ -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')
]
}]
}
};

View File

@@ -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<string>} [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<number>} 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<string>} [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_;
}
/**

View File

@@ -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<string, import("../../format/GeoJSON").GeoJSONFeature>}
* @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;

View File

@@ -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<number>=} 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<number>}
* @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<number>} 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<number>} 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;
}
}

View File

@@ -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;

48
src/ol/worker/webgl.js Normal file
View File

@@ -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;

View File

@@ -20,6 +20,7 @@ async function build(input, {minify = true} = {}) {
common(),
resolve(),
babel({
'externalHelpers': true,
'presets': [
[
'@babel/preset-env',

View File

@@ -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() {

View File

@@ -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() {

View File

@@ -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);
});
});

View File

@@ -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);
});
});
});
});