Introduce batch renderers for each type of geometry
Batch renderers are responsible for generating render instructions and interacting with the worker to obtain the final webgl buffers
This commit is contained in:
197
src/ol/render/webgl/BatchRenderer.js
Normal file
197
src/ol/render/webgl/BatchRenderer.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/BatchRenderer
|
||||||
|
*/
|
||||||
|
import GeometryType from '../../geom/GeometryType.js';
|
||||||
|
import {
|
||||||
|
create as createTransform,
|
||||||
|
makeInverse as makeInverseTransform,
|
||||||
|
multiply as multiplyTransform,
|
||||||
|
} from '../../transform.js';
|
||||||
|
import {abstract} from '../../util.js';
|
||||||
|
import {WebGLWorkerMessageType} from './constants.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} CustomAttribute A description of a custom attribute to be passed on to the GPU, with a value different
|
||||||
|
* for each feature.
|
||||||
|
* @property {string} name Attribute name.
|
||||||
|
* @property {function(import("../../Feature").default, Object<string, *>):number} callback This callback computes the numerical value of the
|
||||||
|
* attribute for a given feature (properties are available as 2nd arg for quicker access).
|
||||||
|
*/
|
||||||
|
|
||||||
|
let workerMessageCounter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @classdesc Abstract class for batch renderers.
|
||||||
|
* Batch renderers are meant to render the geometries contained in a {@link module:ol/render/webgl/GeometryBatch}
|
||||||
|
* instance. They are responsible for generating render instructions and transforming them into WebGL buffers.
|
||||||
|
*/
|
||||||
|
class AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper
|
||||||
|
* @param {Worker} worker
|
||||||
|
* @param {string} vertexShader
|
||||||
|
* @param {string} fragmentShader
|
||||||
|
* @param {Array<CustomAttribute>} customAttributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
/**
|
||||||
|
* @type {import("../../webgl/Helper.js").default}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.helper_ = helper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Worker}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.worker_ = worker;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {WebGLProgram}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.program_ = this.helper_.getProgram(fragmentShader, vertexShader);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A list of attributes used by the renderer.
|
||||||
|
* @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.attributes_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {Array<CustomAttribute>}
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
this.customAttributes_ = customAttributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild rendering instructions and webgl buffers based on the provided frame state
|
||||||
|
* Note: this is a costly operation.
|
||||||
|
* @param {import("./MixedGeometryBatch.js").AbstractGeometryBatch} batch
|
||||||
|
* @param {import("../../PluggableMap").FrameState} frameState Frame state.
|
||||||
|
* @param {import("../../geom/GeometryType.js").default} geometryType
|
||||||
|
*/
|
||||||
|
rebuild(batch, frameState, geometryType) {
|
||||||
|
// store transform for rendering instructions
|
||||||
|
batch.renderInstructionsTransform = this.helper_.makeProjectionTransform(
|
||||||
|
frameState,
|
||||||
|
createTransform()
|
||||||
|
);
|
||||||
|
this.generateRenderInstructions_(batch);
|
||||||
|
this.generateBuffers_(batch, geometryType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the geometries in the batch. This will also update the current transform used for rendering according to
|
||||||
|
* the invert transform of the webgl buffers
|
||||||
|
* @param {import("./MixedGeometryBatch.js").AbstractGeometryBatch} batch
|
||||||
|
* @param {import("../../transform.js").Transform} currentTransform
|
||||||
|
* @param {import("../../PluggableMap.js").FrameState} frameState Frame state.
|
||||||
|
*/
|
||||||
|
render(batch, currentTransform, frameState) {
|
||||||
|
// multiply the current projection transform with the invert of the one used to fill buffers
|
||||||
|
this.helper_.makeProjectionTransform(frameState, currentTransform);
|
||||||
|
multiplyTransform(currentTransform, batch.invertVerticesBufferTransform);
|
||||||
|
|
||||||
|
// enable program, buffers and attributes
|
||||||
|
this.helper_.useProgram(this.program_, frameState);
|
||||||
|
this.helper_.bindBuffer(batch.verticesBuffer);
|
||||||
|
this.helper_.bindBuffer(batch.indicesBuffer);
|
||||||
|
this.helper_.enableAttributes(this.attributes_);
|
||||||
|
|
||||||
|
const renderCount = batch.indicesBuffer.getSize();
|
||||||
|
this.helper_.drawElements(0, renderCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild rendering instructions based on the provided frame state
|
||||||
|
* This is specific to the geometry type and has to be implemented by subclasses.
|
||||||
|
* @param {import("./MixedGeometryBatch.js").default} batch
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
generateRenderInstructions_(batch) {
|
||||||
|
abstract();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild internal webgl buffers for rendering based on the current rendering instructions;
|
||||||
|
* This is asynchronous: webgl buffers wil _not_ be updated right away
|
||||||
|
* @param {import("./MixedGeometryBatch.js").AbstractGeometryBatch} batch
|
||||||
|
* @param {import("../../geom/GeometryType.js").default} geometryType
|
||||||
|
* @protected
|
||||||
|
*/
|
||||||
|
generateBuffers_(batch, geometryType) {
|
||||||
|
const messageId = workerMessageCounter++;
|
||||||
|
|
||||||
|
let messageType;
|
||||||
|
switch (geometryType) {
|
||||||
|
case GeometryType.POLYGON:
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS;
|
||||||
|
break;
|
||||||
|
case GeometryType.POINT:
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_POINT_BUFFERS;
|
||||||
|
break;
|
||||||
|
case GeometryType.LINE_STRING:
|
||||||
|
messageType = WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('./constants.js').WebGLWorkerGenerateBuffersMessage} */
|
||||||
|
const message = {
|
||||||
|
id: messageId,
|
||||||
|
type: messageType,
|
||||||
|
renderInstructions: batch.renderInstructions.buffer,
|
||||||
|
renderInstructionsTransform: batch.renderInstructionsTransform,
|
||||||
|
customAttributesCount: this.customAttributes_.length,
|
||||||
|
};
|
||||||
|
// additional properties will be sent back as-is by the worker
|
||||||
|
message['projectionTransform'] = batch.renderInstructionsTransform;
|
||||||
|
this.worker_.postMessage(message, [batch.renderInstructions.buffer]);
|
||||||
|
|
||||||
|
// leave ownership of render instructions
|
||||||
|
batch.renderInstructions = null;
|
||||||
|
|
||||||
|
const handleMessage =
|
||||||
|
/**
|
||||||
|
* @param {*} event Event.
|
||||||
|
* @this {AbstractBatchRenderer}
|
||||||
|
*/
|
||||||
|
function (event) {
|
||||||
|
const received = event.data;
|
||||||
|
|
||||||
|
// this is not the response to our request: skip
|
||||||
|
if (received.id !== messageId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// we've received our response: stop listening
|
||||||
|
this.worker_.removeEventListener('message', handleMessage);
|
||||||
|
|
||||||
|
// store transform & invert transform for webgl buffers
|
||||||
|
batch.verticesBufferTransform = received.projectionTransform;
|
||||||
|
makeInverseTransform(
|
||||||
|
batch.invertVerticesBufferTransform,
|
||||||
|
batch.verticesBufferTransform
|
||||||
|
);
|
||||||
|
|
||||||
|
// copy & flush received buffers to GPU
|
||||||
|
batch.verticesBuffer.fromArrayBuffer(received.vertexBuffer);
|
||||||
|
this.helper_.flushBufferData(batch.verticesBuffer);
|
||||||
|
batch.indicesBuffer.fromArrayBuffer(received.indexBuffer);
|
||||||
|
this.helper_.flushBufferData(batch.indicesBuffer);
|
||||||
|
|
||||||
|
// take back ownership of the render instructions for further use
|
||||||
|
batch.renderInstructions = new Float32Array(
|
||||||
|
received.renderInstructions
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO: call layer.changed somehow for the layer to rerender!!!1
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
this.worker_.addEventListener('message', handleMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AbstractBatchRenderer;
|
||||||
108
src/ol/render/webgl/LineStringBatchRenderer.js
Normal file
108
src/ol/render/webgl/LineStringBatchRenderer.js
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/LineStringBatchRenderer
|
||||||
|
*/
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
|
||||||
|
class LineStringBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper
|
||||||
|
* @param {Worker} worker
|
||||||
|
* @param {string} vertexShader
|
||||||
|
* @param {string} fragmentShader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// vertices for lines must hold both a position (x,y) and an offset (dx,dy)
|
||||||
|
this.attributes_ = [
|
||||||
|
{
|
||||||
|
name: 'a_segmentStart',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'a_segmentEnd',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'a_parameters',
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for lines are structured like so:
|
||||||
|
* [ customAttr0, ... , customAttrN, numberOfVertices0, x0, y0, ... , xN, yN, numberOfVertices1, ... ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").PointGeometryBatch} batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions_(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for lines:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per line per custom attributes
|
||||||
|
// + 1 instruction per line (for vertices count)
|
||||||
|
const totalInstructionsCount =
|
||||||
|
2 * batch.verticesCount +
|
||||||
|
(1 + this.customAttributes_.length) * batch.geometriesCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const flatCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
||||||
|
transform2D(
|
||||||
|
batchEntry.flatCoordss[i],
|
||||||
|
0,
|
||||||
|
flatCoords.length,
|
||||||
|
2,
|
||||||
|
batch.renderInstructionsTransform,
|
||||||
|
flatCoords
|
||||||
|
);
|
||||||
|
|
||||||
|
// custom attributes
|
||||||
|
for (let k = 0, kk = this.customAttributes_.length; k < kk; k++) {
|
||||||
|
value = this.customAttributes_[k].callback(
|
||||||
|
batchEntry.feature,
|
||||||
|
batchEntry.properties
|
||||||
|
);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// vertices count
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords.length / 2;
|
||||||
|
|
||||||
|
// looping on points for positions
|
||||||
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 2) {
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j];
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LineStringBatchRenderer;
|
||||||
90
src/ol/render/webgl/PointBatchRenderer.js
Normal file
90
src/ol/render/webgl/PointBatchRenderer.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/PointBatchRenderer
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {apply as applyTransform} from '../../transform.js';
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
|
||||||
|
class PointBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper
|
||||||
|
* @param {Worker} worker
|
||||||
|
* @param {string} vertexShader
|
||||||
|
* @param {string} fragmentShader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// vertices for point must hold both a position (x,y) and an index (their position in the quad)
|
||||||
|
this.attributes_ = [
|
||||||
|
{
|
||||||
|
name: 'a_position',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'a_index',
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for lines are structured like so:
|
||||||
|
* [ x0, y0, customAttr0, ... , xN, yN, customAttrN ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").PointGeometryBatch} batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions_(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for points:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per vertex per custom attributes
|
||||||
|
const totalInstructionsCount =
|
||||||
|
(2 + this.customAttributes_.length) * batch.geometriesCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const tmpCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
tmpCoords[0] = batchEntry.flatCoordss[i][0];
|
||||||
|
tmpCoords[1] = batchEntry.flatCoordss[i][1];
|
||||||
|
applyTransform(batch.renderInstructionsTransform, tmpCoords);
|
||||||
|
|
||||||
|
batch.renderInstructions[renderIndex++] = tmpCoords[0];
|
||||||
|
batch.renderInstructions[renderIndex++] = tmpCoords[1];
|
||||||
|
|
||||||
|
// pushing custom attributes
|
||||||
|
for (let j = 0, jj = this.customAttributes_.length; j < jj; j++) {
|
||||||
|
value = this.customAttributes_[j].callback(
|
||||||
|
batchEntry.feature,
|
||||||
|
batchEntry.properties
|
||||||
|
);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PointBatchRenderer;
|
||||||
111
src/ol/render/webgl/PolygonBatchRenderer.js
Normal file
111
src/ol/render/webgl/PolygonBatchRenderer.js
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/render/webgl/PolygonBatchRenderer
|
||||||
|
*/
|
||||||
|
import {AttributeType} from '../../webgl/Helper.js';
|
||||||
|
import {transform2D} from '../../geom/flat/transform.js';
|
||||||
|
import AbstractBatchRenderer from './BatchRenderer.js';
|
||||||
|
|
||||||
|
class PolygonBatchRenderer extends AbstractBatchRenderer {
|
||||||
|
/**
|
||||||
|
* @param {import("../../webgl/Helper.js").default} helper
|
||||||
|
* @param {Worker} worker
|
||||||
|
* @param {string} vertexShader
|
||||||
|
* @param {string} fragmentShader
|
||||||
|
* @param {Array<import('./BatchRenderer.js').CustomAttribute>} customAttributes
|
||||||
|
*/
|
||||||
|
constructor(helper, worker, vertexShader, fragmentShader, customAttributes) {
|
||||||
|
super(helper, worker, vertexShader, fragmentShader, customAttributes);
|
||||||
|
|
||||||
|
// By default only a position attribute is required to render polygons
|
||||||
|
this.attributes_ = [
|
||||||
|
{
|
||||||
|
name: 'a_position',
|
||||||
|
size: 2,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
},
|
||||||
|
].concat(
|
||||||
|
customAttributes.map(function (attribute) {
|
||||||
|
return {
|
||||||
|
name: 'a_' + attribute.name,
|
||||||
|
size: 1,
|
||||||
|
type: AttributeType.FLOAT,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render instructions for polygons are structured like so:
|
||||||
|
* [ customAttr0, ..., customAttrN, numberOfRings, numberOfVerticesInRing0, ..., numberOfVerticesInRingN, x0, y0, ..., xN, yN, numberOfRings,... ]
|
||||||
|
* @param {import("./MixedGeometryBatch.js").PolygonGeometryBatch} batch
|
||||||
|
* @override
|
||||||
|
*/
|
||||||
|
generateRenderInstructions_(batch) {
|
||||||
|
// here we anticipate the amount of render instructions for polygons:
|
||||||
|
// 2 instructions per vertex for position (x and y)
|
||||||
|
// + 1 instruction per polygon per custom attributes
|
||||||
|
// + 1 instruction per polygon (for vertices count in polygon)
|
||||||
|
// + 1 instruction per ring (for vertices count in ring)
|
||||||
|
const totalInstructionsCount =
|
||||||
|
2 * batch.verticesCount +
|
||||||
|
(1 + this.customAttributes_.length) * batch.geometriesCount +
|
||||||
|
batch.ringsCount;
|
||||||
|
if (
|
||||||
|
!batch.renderInstructions ||
|
||||||
|
batch.renderInstructions.length !== totalInstructionsCount
|
||||||
|
) {
|
||||||
|
batch.renderInstructions = new Float32Array(totalInstructionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
// loop on features to fill the render instructions
|
||||||
|
let batchEntry;
|
||||||
|
const flatCoords = [];
|
||||||
|
let renderIndex = 0;
|
||||||
|
let value;
|
||||||
|
for (const featureUid in batch.entries) {
|
||||||
|
batchEntry = batch.entries[featureUid];
|
||||||
|
for (let i = 0, ii = batchEntry.flatCoordss.length; i < ii; i++) {
|
||||||
|
flatCoords.length = batchEntry.flatCoordss[i].length;
|
||||||
|
transform2D(
|
||||||
|
batchEntry.flatCoordss[i],
|
||||||
|
0,
|
||||||
|
flatCoords.length,
|
||||||
|
2,
|
||||||
|
batch.renderInstructionsTransform,
|
||||||
|
flatCoords
|
||||||
|
);
|
||||||
|
|
||||||
|
// custom attributes
|
||||||
|
for (let k = 0, kk = this.customAttributes_.length; k < kk; k++) {
|
||||||
|
value = this.customAttributes_[k].callback(
|
||||||
|
batchEntry.feature,
|
||||||
|
batchEntry.properties
|
||||||
|
);
|
||||||
|
batch.renderInstructions[renderIndex++] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ring count
|
||||||
|
batch.renderInstructions[renderIndex++] =
|
||||||
|
batchEntry.ringsVerticesCounts[i].length;
|
||||||
|
|
||||||
|
// vertices count in each ring
|
||||||
|
for (
|
||||||
|
let j = 0, jj = batchEntry.ringsVerticesCounts[i].length;
|
||||||
|
j < jj;
|
||||||
|
j++
|
||||||
|
) {
|
||||||
|
batch.renderInstructions[renderIndex++] =
|
||||||
|
batchEntry.ringsVerticesCounts[i][j];
|
||||||
|
}
|
||||||
|
|
||||||
|
// looping on points for positions
|
||||||
|
for (let j = 0, jj = flatCoords.length; j < jj; j += 2) {
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j];
|
||||||
|
batch.renderInstructions[renderIndex++] = flatCoords[j + 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PolygonBatchRenderer;
|
||||||
276
test/browser/spec/ol/render/webgl/batchrenderer.test.js
Normal file
276
test/browser/spec/ol/render/webgl/batchrenderer.test.js
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
import PointBatchRenderer from '../../../../../../src/ol/render/webgl/PointBatchRenderer.js';
|
||||||
|
import WebGLHelper from '../../../../../../src/ol/webgl/Helper.js';
|
||||||
|
import {create as createWebGLWorker} from '../../../../../../src/ol/worker/webgl.js';
|
||||||
|
import MixedGeometryBatch from '../../../../../../src/ol/render/webgl/MixedGeometryBatch.js';
|
||||||
|
import Feature from '../../../../../../src/ol/Feature.js';
|
||||||
|
import Point from '../../../../../../src/ol/geom/Point.js';
|
||||||
|
import Polygon from '../../../../../../src/ol/geom/Polygon.js';
|
||||||
|
import LineString from '../../../../../../src/ol/geom/LineString.js';
|
||||||
|
import GeometryType from '../../../../../../src/ol/geom/GeometryType.js';
|
||||||
|
import {create as createTransform} from '../../../../../../src/ol/transform.js';
|
||||||
|
import {WebGLWorkerMessageType} from '../../../../../../src/ol/render/webgl/constants.js';
|
||||||
|
import LineStringBatchRenderer from '../../../../../../src/ol/render/webgl/LineStringBatchRenderer.js';
|
||||||
|
import {FLOAT} from '../../../../../../src/ol/webgl.js';
|
||||||
|
import PolygonBatchRenderer from '../../../../../../src/ol/render/webgl/PolygonBatchRenderer.js';
|
||||||
|
|
||||||
|
const POINT_VERTEX_SHADER = `precision mediump float;
|
||||||
|
void main(void) {}`;
|
||||||
|
const POINT_FRAGMENT_SHADER = `precision mediump float;
|
||||||
|
void main(void) {}`;
|
||||||
|
|
||||||
|
const SAMPLE_FRAMESTATE = {
|
||||||
|
viewState: {
|
||||||
|
center: [0, 10],
|
||||||
|
resolution: 1,
|
||||||
|
rotation: 0,
|
||||||
|
},
|
||||||
|
size: [10, 10],
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Batch renderers', function () {
|
||||||
|
let batchRenderer, helper, mixedBatch, worker, attributes;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
helper = new WebGLHelper();
|
||||||
|
worker = createWebGLWorker();
|
||||||
|
attributes = [
|
||||||
|
{
|
||||||
|
name: 'test',
|
||||||
|
callback: function (feature, properties) {
|
||||||
|
return properties.test;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mixedBatch = new MixedGeometryBatch();
|
||||||
|
mixedBatch.addFeatures([
|
||||||
|
new Feature({
|
||||||
|
test: 1000,
|
||||||
|
geometry: new Point([10, 20]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 2000,
|
||||||
|
geometry: new Point([30, 40]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 3000,
|
||||||
|
geometry: new Polygon([
|
||||||
|
[
|
||||||
|
[10, 10],
|
||||||
|
[20, 10],
|
||||||
|
[30, 20],
|
||||||
|
[20, 40],
|
||||||
|
[10, 10],
|
||||||
|
],
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
new Feature({
|
||||||
|
test: 4000,
|
||||||
|
geometry: new LineString([
|
||||||
|
[100, 200],
|
||||||
|
[300, 400],
|
||||||
|
[500, 600],
|
||||||
|
]),
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PointBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new PointBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes_).to.eql([
|
||||||
|
{name: 'a_position', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_index', size: 1, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.pointBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
GeometryType.POINT
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type === WebGLWorkerMessageType.GENERATE_POINT_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(Array.from(mixedBatch.pointBatch.renderInstructions)).to.eql([
|
||||||
|
2, 2, 1000, 6, 6, 2000,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.pointBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.pointBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('updates the instructions transform', function () {
|
||||||
|
expect(mixedBatch.pointBatch.renderInstructionsTransform).to.eql([
|
||||||
|
0.2, 0, 0, 0.2, 0, -2,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#render (from parent)', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
sinon.spy(helper, 'makeProjectionTransform');
|
||||||
|
sinon.spy(helper, 'useProgram');
|
||||||
|
sinon.spy(helper, 'bindBuffer');
|
||||||
|
sinon.spy(helper, 'enableAttributes');
|
||||||
|
sinon.spy(helper, 'drawElements');
|
||||||
|
|
||||||
|
const transform = createTransform();
|
||||||
|
batchRenderer.render(
|
||||||
|
mixedBatch.pointBatch,
|
||||||
|
transform,
|
||||||
|
SAMPLE_FRAMESTATE
|
||||||
|
);
|
||||||
|
});
|
||||||
|
it('computes current transform', function () {
|
||||||
|
expect(helper.makeProjectionTransform.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
it('computes sets up render parameters', function () {
|
||||||
|
expect(helper.useProgram.calledOnce).to.be(true);
|
||||||
|
expect(helper.enableAttributes.calledOnce).to.be(true);
|
||||||
|
expect(helper.bindBuffer.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
it('renders elements', function () {
|
||||||
|
expect(helper.drawElements.calledOnce).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('LineStringBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new LineStringBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes_).to.eql([
|
||||||
|
{name: 'a_segmentStart', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_segmentEnd', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_parameters', size: 1, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.lineStringBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
GeometryType.LINE_STRING
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type ===
|
||||||
|
WebGLWorkerMessageType.GENERATE_LINE_STRING_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(
|
||||||
|
Array.from(mixedBatch.lineStringBatch.renderInstructions)
|
||||||
|
).to.eql([
|
||||||
|
3000, 5, 2, 0, 4, 0, 6, 2, 4, 6, 2, 0, 4000, 3, 20, 38, 60, 78, 100,
|
||||||
|
118,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.lineStringBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.lineStringBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('PolygonBatchRenderer', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
batchRenderer = new PolygonBatchRenderer(
|
||||||
|
helper,
|
||||||
|
worker,
|
||||||
|
POINT_VERTEX_SHADER,
|
||||||
|
POINT_FRAGMENT_SHADER,
|
||||||
|
attributes
|
||||||
|
);
|
||||||
|
});
|
||||||
|
describe('constructor', function () {
|
||||||
|
it('generates the attributes list', function () {
|
||||||
|
expect(batchRenderer.attributes_).to.eql([
|
||||||
|
{name: 'a_position', size: 2, type: FLOAT},
|
||||||
|
{name: 'a_test', size: 1, type: FLOAT},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('#rebuild', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
sinon.spy(helper, 'flushBufferData');
|
||||||
|
batchRenderer.rebuild(
|
||||||
|
mixedBatch.polygonBatch,
|
||||||
|
SAMPLE_FRAMESTATE,
|
||||||
|
GeometryType.POLYGON
|
||||||
|
);
|
||||||
|
// wait for worker response for our specific message
|
||||||
|
worker.addEventListener('message', function (event) {
|
||||||
|
if (
|
||||||
|
event.data.type ===
|
||||||
|
WebGLWorkerMessageType.GENERATE_POLYGON_BUFFERS &&
|
||||||
|
event.data.renderInstructions.byteLength > 0
|
||||||
|
) {
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('generates render instructions and updates buffers from the worker response', function () {
|
||||||
|
expect(Array.from(mixedBatch.polygonBatch.renderInstructions)).to.eql([
|
||||||
|
3000, 1, 5, 2, 0, 4, 0, 6, 2, 4, 6, 2, 0,
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('updates buffers', function () {
|
||||||
|
expect(
|
||||||
|
mixedBatch.polygonBatch.verticesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(
|
||||||
|
mixedBatch.polygonBatch.indicesBuffer.getArray().length
|
||||||
|
).to.be.greaterThan(0);
|
||||||
|
expect(helper.flushBufferData.calledTwice).to.be(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user