Webgl / Adapt the points renderer to use custom attributes

Now most attributes will be custom ones defined by the user of the renderer.
The hit detection is broken for now and still has to be fixed.
Also it is not possible anymore to give a texture to the renderer,
this will have to be fixed as well.
This commit is contained in:
Olivier Guyot
2019-09-24 23:24:49 +02:00
parent 076b674d6f
commit 2b36445ecc
6 changed files with 192 additions and 377 deletions
+18 -87
View File
@@ -85,67 +85,13 @@ class WebGLLayerRenderer extends LayerRenderer {
} }
/**
* @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 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 tmpArray_ = [];
const bufferPositions_ = {vertexPosition: 0, indexPosition: 0}; const bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
export const POINT_INSTRUCTIONS_COUNT = 13; function writePointVertex(buffer, pos, x, y, index) {
export const POINT_VERTEX_STRIDE = 13;
function writePointVertex(buffer, pos, x, y, offsetX, offsetY, u, v, opacity, rotateWithView, red, green, blue, alpha, index) {
buffer[pos + 0] = x; buffer[pos + 0] = x;
buffer[pos + 1] = y; buffer[pos + 1] = y;
buffer[pos + 2] = offsetX; buffer[pos + 2] = index;
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;
buffer[pos + 12] = index;
}
function writeCustomAttrs(buffer, pos, customAttrs) {
if (customAttrs.length) {
buffer.set(customAttrs, pos);
}
} }
/** /**
@@ -161,42 +107,27 @@ function writeCustomAttrs(buffer, pos, customAttrs) {
* @param {number} elementIndex Index from which render instructions will be read. * @param {number} elementIndex Index from which render instructions will be read.
* @param {Float32Array} vertexBuffer Buffer in the form of a typed array. * @param {Float32Array} vertexBuffer Buffer in the form of a typed array.
* @param {Uint32Array} indexBuffer Buffer in the form of a typed array. * @param {Uint32Array} indexBuffer Buffer in the form of a typed array.
* @param {number} customAttributesCount Amount of custom attributes for each element.
* @param {BufferPositions} [bufferPositions] Buffer write positions; if not specified, positions will be set at 0. * @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 * @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} 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. * @property {number} indexPosition New position in the index buffer where future writes should start.
* @private * @private
*/ */
export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, bufferPositions, count) { export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, customAttributesCount, bufferPositions) {
const count_ = count > POINT_INSTRUCTIONS_COUNT ? count : POINT_INSTRUCTIONS_COUNT; // This is for x, y and index
const baseVertexAttrsCount = 3;
const baseInstructionsCount = 2;
const stride = baseVertexAttrsCount + customAttributesCount;
const x = instructions[elementIndex + 0]; const x = instructions[elementIndex + 0];
const y = instructions[elementIndex + 1]; 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 // read custom numerical attributes on the feature
const customAttrs = tmpArray_; const customAttrs = tmpArray_;
customAttrs.length = count_ - POINT_INSTRUCTIONS_COUNT; customAttrs.length = customAttributesCount;
for (let i = 0; i < customAttrs.length; i++) { for (let i = 0; i < customAttrs.length; i++) {
customAttrs[i] = instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + i]; customAttrs[i] = instructions[elementIndex + baseInstructionsCount + i];
} }
let vPos = bufferPositions ? bufferPositions.vertexPosition : 0; let vPos = bufferPositions ? bufferPositions.vertexPosition : 0;
@@ -204,20 +135,20 @@ export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuf
const baseIndex = vPos / stride; const baseIndex = vPos / stride;
// push vertices for each of the four quad corners (first standard then custom attributes) // push vertices for each of the four quad corners (first standard then custom attributes)
writePointVertex(vertexBuffer, vPos, x, y, -size / 2, -size / 2, u0, v0, opacity, rotateWithView, red, green, blue, alpha, 0); writePointVertex(vertexBuffer, vPos, x, y, 0);
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride; vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha, 1); writePointVertex(vertexBuffer, vPos, x, y, 1);
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride; vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha, 2); writePointVertex(vertexBuffer, vPos, x, y, 2);
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride; vPos += stride;
writePointVertex(vertexBuffer, vPos, x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha, 3); writePointVertex(vertexBuffer, vPos, x, y, 3);
writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount);
vPos += stride; vPos += stride;
indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3; indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3;
+101 -120
View File
@@ -2,23 +2,17 @@
* @module ol/renderer/webgl/PointsLayer * @module ol/renderer/webgl/PointsLayer
*/ */
import WebGLArrayBuffer from '../../webgl/Buffer.js'; import WebGLArrayBuffer from '../../webgl/Buffer.js';
import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl.js'; import {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js';
import {DefaultAttrib, DefaultUniform} from '../../webgl/Helper.js'; import {AttributeType, DefaultUniform} from '../../webgl/Helper.js';
import GeometryType from '../../geom/GeometryType.js'; import GeometryType from '../../geom/GeometryType.js';
import WebGLLayerRenderer, { import WebGLLayerRenderer, {colorDecodeId, colorEncodeId, getBlankImageData, WebGLWorkerMessageType} from './Layer.js';
colorDecodeId,
colorEncodeId,
getBlankImageData,
POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, WebGLWorkerMessageType,
writePointFeatureInstructions
} from './Layer.js';
import ViewHint from '../../ViewHint.js'; import ViewHint from '../../ViewHint.js';
import {createEmpty, equals} from '../../extent.js'; import {createEmpty, equals} from '../../extent.js';
import { import {
apply as applyTransform,
create as createTransform, create as createTransform,
makeInverse as makeInverseTransform, makeInverse as makeInverseTransform,
multiply as multiplyTransform, multiply as multiplyTransform
apply as applyTransform
} from '../../transform.js'; } from '../../transform.js';
import {create as createWebGLWorker} from '../../worker/webgl.js'; import {create as createWebGLWorker} from '../../worker/webgl.js';
import {getUid} from '../../util.js'; import {getUid} from '../../util.js';
@@ -92,29 +86,21 @@ const HIT_FRAGMENT_SHADER = `
gl_FragColor = v_color; gl_FragColor = v_color;
}`; }`;
/**
* @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):number} callback This callback computes the numerical value of the
* attribute for a given feature.
*/
/** /**
* @typedef {Object} Options * @typedef {Object} Options
* @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the * @property {Array<CustomAttribute>} [attributes] These attributes will be read from the features in the source and then
* source to compute the size of the quad on screen (in pixels). This is only done on source change. * passed to the GPU. The `name` property of each attribute will serve as its identifier:
* @property {function(import("../../Feature").default, number):number} [coordCallback] Will be called on every feature in the * * In the vertex shader as an `attribute` by prefixing it with `a_`
* source to compute the coordinate of the quad center on screen (in pixels). This is only done on source change. * * In the fragment shader as a `varying` by prefixing it with `v_`
* The second argument is 0 for `x` component and 1 for `y`. * Please note that these can only be numerical values.
* @property {function(import("../../Feature").default, number):number} [texCoordCallback] Will be called on every feature in the
* source to compute the texture coordinates of each corner of the quad (without effect if no `texture` option defined). This is only done on source change.
* The second argument is 0 for `u0` component, 1 for `v0`, 2 for `u1`, and 3 for `v1`.
* @property {function(import("../../Feature").default, Array<number>=):Array<number>} [colorCallback] Will be called on every feature in the
* source to compute the color for use in the fragment shader (available as the `v_color` varying). This is only done on source change.
* The return value should be between an array of R, G, B, A values between 0 and 1. To reduce unnecessary
* allocation, the function is called with a reusable array that can serve as the return value after updating
* the R, G, B, and A values.
* @property {function(import("../../Feature").default):number} [opacityCallback] Will be called on every feature in the
* source to compute the opacity of the quad on screen (from 0 to 1). This is only done on source change.
* Note: this is multiplied with the color of the point which can also have an alpha value < 1.
* @property {function(import("../../Feature").default):boolean} [rotateWithViewCallback] Will be called on every feature in the
* source to compute whether the quad on screen must stay upwards (`false`) or follow the view rotation (`true`). Default is `false`.
* This is only done on source change.
* @property {HTMLCanvasElement|HTMLImageElement|ImageData} [texture] Texture to use on points. `texCoordCallback` and `sizeCallback`
* must be defined for this to have any effect.
* @property {string} [vertexShader] Vertex shader source * @property {string} [vertexShader] Vertex shader source
* @property {string} [fragmentShader] Fragment shader source * @property {string} [fragmentShader] Fragment shader source
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps * @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
@@ -224,7 +210,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
const options = opt_options || {}; const options = opt_options || {};
const uniforms = options.uniforms || {}; const uniforms = options.uniforms || {};
uniforms.u_texture = options.texture || getBlankImageData(); uniforms.u_texture = getBlankImageData();
const projectionMatrixTransform = createTransform(); const projectionMatrixTransform = createTransform();
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform; uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
@@ -248,28 +234,53 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
VERTEX_SHADER VERTEX_SHADER
); );
this.sizeCallback_ = options.sizeCallback || function() { const customAttributes = options.attributes ?
return 1; options.attributes.map(function(attribute) {
}; return {
this.coordCallback_ = options.coordCallback || function(feature, index) { name: 'a_' + attribute.name,
const geom = feature.getGeometry(); size: 1,
return geom.getCoordinates()[index]; type: AttributeType.FLOAT
}; };
this.opacityCallback_ = options.opacityCallback || function() { }) : [];
return 1;
};
this.texCoordCallback_ = options.texCoordCallback || function(feature, index) {
return index < 2 ? 0 : 1;
};
this.colorArray_ = [1, 1, 1, 1]; /**
this.colorCallback_ = options.colorCallback || function(feature, color) { * A list of attributes used by the renderer. By default only the position and
return this.colorArray_; * index of the vertex (0 to 3) are required.
}; * @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
*/
this.attributes = [{
name: 'a_position',
size: 2,
type: AttributeType.FLOAT
}, {
name: 'a_index',
size: 1,
type: AttributeType.FLOAT
}].concat(customAttributes);
this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() { /**
return false; * A list of attributes used for hit detection.
}; * @type {Array<import('../../webgl/Helper.js').AttributeDescription>}
*/
this.hitDetectionAttributes = [{
name: 'a_position',
size: 2,
type: AttributeType.FLOAT
}, {
name: 'a_index',
size: 1,
type: AttributeType.FLOAT
}, {
name: 'a_color',
size: 4,
type: AttributeType.FLOAT
}, {
name: 'a_featureUid',
size: 1,
type: AttributeType.FLOAT
}].concat(customAttributes);
this.customAttributes = options.attributes ? options.attributes : [];
this.previousExtent_ = createEmpty(); this.previousExtent_ = createEmpty();
@@ -371,8 +382,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
const vectorSource = vectorLayer.getSource(); const vectorSource = vectorLayer.getSource();
const viewState = frameState.viewState; const viewState = frameState.viewState;
const stride = POINT_VERTEX_STRIDE;
// the source has changed: clear the feature cache & reload features // the source has changed: clear the feature cache & reload features
const sourceChanged = this.sourceRevision_ < vectorSource.getRevision(); const sourceChanged = this.sourceRevision_ < vectorSource.getRevision();
if (sourceChanged) { if (sourceChanged) {
@@ -401,14 +410,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.helper.bindBuffer(this.verticesBuffer_); this.helper.bindBuffer(this.verticesBuffer_);
this.helper.bindBuffer(this.indicesBuffer_); this.helper.bindBuffer(this.indicesBuffer_);
const bytesPerFloat = Float32Array.BYTES_PER_ELEMENT; this.helper.enableAttributes(this.attributes);
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('a_index', 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 12);
return true; return true;
} }
@@ -427,76 +429,61 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.helper.makeProjectionTransform(frameState, projectionTransform); this.helper.makeProjectionTransform(frameState, projectionTransform);
const features = vectorSource.getFeatures(); const features = vectorSource.getFeatures();
const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length; const totalInstructionsCount = (2 + this.customAttributes.length) * features.length;
if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) { if (!this.renderInstructions_ || this.renderInstructions_.length !== totalInstructionsCount) {
this.renderInstructions_ = new Float32Array(totalInstructionsCount); this.renderInstructions_ = new Float32Array(totalInstructionsCount);
} }
if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalInstructionsCount) { const totalHitInstructionsCount = (7 + this.customAttributes.length) * features.length;
this.hitRenderInstructions_ = new Float32Array(totalInstructionsCount); if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalHitInstructionsCount) {
this.hitRenderInstructions_ = new Float32Array(totalHitInstructionsCount);
} }
// loop on features to fill the buffer // loop on features to fill the buffer
let feature; let feature;
const tmpCoords = []; const tmpCoords = [];
const tmpColor = []; const tmpColor = [];
let elementIndex = 0; let renderIndex = 0;
let u0, v0, u1, v1, size, opacity, rotateWithView, color; let hitIndex = 0;
let hitColor;
for (let i = 0; i < features.length; i++) { for (let i = 0; i < features.length; i++) {
feature = features[i]; feature = features[i];
if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) {
continue; continue;
} }
tmpCoords[0] = this.coordCallback_(feature, 0); tmpCoords[0] = feature.getGeometry().getFlatCoordinates()[0];
tmpCoords[1] = this.coordCallback_(feature, 1); tmpCoords[1] = feature.getGeometry().getFlatCoordinates()[1];
applyTransform(projectionTransform, tmpCoords); applyTransform(projectionTransform, tmpCoords);
u0 = this.texCoordCallback_(feature, 0); hitColor = colorEncodeId(hitIndex + 6, tmpColor);
v0 = this.texCoordCallback_(feature, 1);
u1 = this.texCoordCallback_(feature, 2);
v1 = this.texCoordCallback_(feature, 3);
size = this.sizeCallback_(feature);
opacity = this.opacityCallback_(feature);
rotateWithView = this.rotateWithViewCallback_(feature);
color = this.colorCallback_(feature, this.colorArray_);
writePointFeatureInstructions( this.renderInstructions_[renderIndex++] = tmpCoords[0];
this.renderInstructions_, this.renderInstructions_[renderIndex++] = tmpCoords[1];
elementIndex,
tmpCoords[0],
tmpCoords[1],
u0,
v0,
u1,
v1,
size,
opacity,
rotateWithView,
color
);
// for hit detection, the feature uid is saved in the opacity value // for hit detection, the feature uid is saved in the opacity value
// and the index of the opacity value is encoded in the color values // and the index of the opacity value is encoded in the color values
elementIndex = writePointFeatureInstructions( this.hitRenderInstructions_[hitIndex++] = tmpCoords[0];
this.hitRenderInstructions_, this.hitRenderInstructions_[hitIndex++] = tmpCoords[1];
elementIndex, this.hitRenderInstructions_[hitIndex++] = hitColor[0];
tmpCoords[0], this.hitRenderInstructions_[hitIndex++] = hitColor[1];
tmpCoords[1], this.hitRenderInstructions_[hitIndex++] = hitColor[2];
u0, this.hitRenderInstructions_[hitIndex++] = hitColor[3];
v0, this.hitRenderInstructions_[hitIndex++] = Number(getUid(feature));
u1,
v1, // pushing custom attributes
size, let value;
opacity > 0 ? Number(getUid(feature)) : 0, for (let j = 0; j < this.customAttributes.length; j++) {
rotateWithView, value = this.customAttributes[j].callback(feature);
colorEncodeId(elementIndex + 7, tmpColor) this.renderInstructions_[renderIndex++] = value;
); this.hitRenderInstructions_[hitIndex++] = value;
}
} }
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
const message = { const message = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS, type: WebGLWorkerMessageType.GENERATE_BUFFERS,
renderInstructions: this.renderInstructions_.buffer renderInstructions: this.renderInstructions_.buffer,
customAttributesCount: this.customAttributes.length
}; };
// additional properties will be sent back as-is by the worker // additional properties will be sent back as-is by the worker
message['projectionTransform'] = projectionTransform; message['projectionTransform'] = projectionTransform;
@@ -506,7 +493,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
const hitMessage = { const hitMessage = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS, type: WebGLWorkerMessageType.GENERATE_BUFFERS,
renderInstructions: this.hitRenderInstructions_.buffer renderInstructions: this.hitRenderInstructions_.buffer,
customAttributesCount: 5 + this.customAttributes.length
}; };
hitMessage['projectionTransform'] = projectionTransform; hitMessage['projectionTransform'] = projectionTransform;
hitMessage['hitDetection'] = true; hitMessage['hitDetection'] = true;
@@ -549,9 +537,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
renderHitDetection(frameState) { renderHitDetection(frameState) {
// skip render entirely if vertices buffers for display & hit detection have different sizes // skip render entirely if vertices buffers for display & hit detection have different sizes
// this typically means both buffers are temporarily out of sync // this typically means both buffers are temporarily out of sync
if (this.hitVerticesBuffer_.getSize() !== this.verticesBuffer_.getSize()) { // FIXME: adapt this to the new points renderer behaviour
return; // if (this.hitVerticesBuffer_.getSize() !== this.verticesBuffer_.getSize()) {
} // return;
// }
this.hitRenderTarget_.setSize(frameState.size); this.hitRenderTarget_.setSize(frameState.size);
@@ -561,15 +550,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.helper.bindBuffer(this.hitVerticesBuffer_); this.helper.bindBuffer(this.hitVerticesBuffer_);
this.helper.bindBuffer(this.indicesBuffer_); this.helper.bindBuffer(this.indicesBuffer_);
const stride = POINT_VERTEX_STRIDE; this.helper.enableAttributes(this.hitDetectionAttributes);
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('a_index', 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 12);
const renderCount = this.indicesBuffer_.getSize(); const renderCount = this.indicesBuffer_.getSize();
this.helper.drawElements(0, renderCount); this.helper.drawElements(0, renderCount);
+1 -15
View File
@@ -45,20 +45,6 @@ export const DefaultUniform = {
OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix' OFFSET_ROTATION_MATRIX: 'u_offsetRotateMatrix'
}; };
/**
* Attribute names used in the default shaders: `POSITION`, `TEX_COORD`, `OPACITY`,
* `ROTATE_WITH_VIEW`, `OFFSETS` and `COLOR`
* @enum {string}
*/
export const DefaultAttrib = {
POSITION: 'a_position',
TEX_COORD: 'a_texCoord',
OPACITY: 'a_opacity',
ROTATE_WITH_VIEW: 'a_rotateWithView',
OFFSETS: 'a_offsets',
COLOR: 'a_color'
};
/** /**
* Attribute types, either `UNSIGNED_BYTE`, `UNSIGNED_SHORT`, `UNSIGNED_INT` or `FLOAT` * Attribute types, either `UNSIGNED_BYTE`, `UNSIGNED_SHORT`, `UNSIGNED_INT` or `FLOAT`
* Note: an attribute stored in a `Float32Array` should be of type `FLOAT`. * Note: an attribute stored in a `Float32Array` should be of type `FLOAT`.
@@ -860,7 +846,7 @@ class WebGLHelper extends Disposable {
} }
/** /**
* Compute a stride based on a list of attributes * Compute a stride in bytes based on a list of attributes
* @param {Array<AttributeDescription>} attributes Ordered list of attributes * @param {Array<AttributeDescription>} attributes Ordered list of attributes
* @returns {number} Stride, ie amount of values for each vertex in the vertex buffer * @returns {number} Stride, ie amount of values for each vertex in the vertex buffer
* @api * @api
+9 -7
View File
@@ -3,8 +3,6 @@
* @module ol/worker/webgl * @module ol/worker/webgl
*/ */
import { import {
POINT_INSTRUCTIONS_COUNT,
POINT_VERTEX_STRIDE,
WebGLWorkerMessageType, WebGLWorkerMessageType,
writePointFeatureToBuffers writePointFeatureToBuffers
} from '../renderer/webgl/Layer.js'; } from '../renderer/webgl/Layer.js';
@@ -16,13 +14,17 @@ const worker = self;
worker.onmessage = event => { worker.onmessage = event => {
const received = event.data; const received = event.data;
if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) { if (received.type === WebGLWorkerMessageType.GENERATE_BUFFERS) {
// This is specific to point features
const baseVertexAttrsCount = 3;
const baseInstructionsCount = 2;
const customAttrsCount = received.customAttributesCount;
const instructionsCount = baseInstructionsCount + customAttrsCount;
const renderInstructions = new Float32Array(received.renderInstructions); const renderInstructions = new Float32Array(received.renderInstructions);
const customAttributesCount = received.customAttributesCount || 0;
const instructionsCount = POINT_INSTRUCTIONS_COUNT + customAttributesCount;
const elementsCount = renderInstructions.length / instructionsCount; const elementsCount = renderInstructions.length / instructionsCount;
const indicesCount = elementsCount * 6; const indicesCount = elementsCount * 6;
const verticesCount = elementsCount * 4 * (POINT_VERTEX_STRIDE + customAttributesCount); const verticesCount = elementsCount * 4 * (customAttrsCount + baseVertexAttrsCount);
const indexBuffer = new Uint32Array(indicesCount); const indexBuffer = new Uint32Array(indicesCount);
const vertexBuffer = new Float32Array(verticesCount); const vertexBuffer = new Float32Array(verticesCount);
@@ -33,8 +35,8 @@ worker.onmessage = event => {
i, i,
vertexBuffer, vertexBuffer,
indexBuffer, indexBuffer,
bufferPositions, customAttrsCount,
instructionsCount); bufferPositions);
} }
/** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */ /** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */
+55 -141
View File
@@ -1,8 +1,8 @@
import WebGLLayerRenderer, { import WebGLLayerRenderer, {
colorDecodeId, colorDecodeId,
colorEncodeId, colorEncodeId,
getBlankImageData, POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, getBlankImageData,
writePointFeatureInstructions, writePointFeatureToBuffers writePointFeatureToBuffers
} from '../../../../../src/ol/renderer/webgl/Layer.js'; } from '../../../../../src/ol/renderer/webgl/Layer.js';
import Layer from '../../../../../src/ol/layer/Layer.js'; import Layer from '../../../../../src/ol/layer/Layer.js';
@@ -32,60 +32,6 @@ describe('ol.renderer.webgl.Layer', function() {
}); });
describe('writePointFeatureInstructions', function() {
let instructions;
beforeEach(function() {
instructions = new Float32Array(100);
});
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('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() { describe('writePointFeatureToBuffers', function() {
let vertexBuffer, indexBuffer, instructions, elementIndex; let vertexBuffer, indexBuffer, instructions, elementIndex;
@@ -93,50 +39,29 @@ describe('ol.renderer.webgl.Layer', function() {
vertexBuffer = new Float32Array(100); vertexBuffer = new Float32Array(100);
indexBuffer = new Uint32Array(100); indexBuffer = new Uint32Array(100);
instructions = new Float32Array(100); instructions = new Float32Array(100);
elementIndex = 3;
writePointFeatureInstructions(instructions, elementIndex, instructions.set([0, 0, 0, 0, 10, 11]);
1, 2, 3, 4, 5, 6,
7, 8, true, [10, 11, 12, 13]);
}); });
it('writes correctly to the buffers (without custom attributes)', function() { it('writes correctly to the buffers (without custom attributes)', function() {
const stride = POINT_VERTEX_STRIDE; const stride = 3;
const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer); const positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0);
expect(vertexBuffer[0]).to.eql(1); expect(vertexBuffer[0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(2); expect(vertexBuffer[1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(-3.5); expect(vertexBuffer[2]).to.eql(0);
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[stride + 0]).to.eql(1); expect(vertexBuffer[stride + 0]).to.eql(10);
expect(vertexBuffer[stride + 1]).to.eql(2); expect(vertexBuffer[stride + 1]).to.eql(11);
expect(vertexBuffer[stride + 2]).to.eql(+3.5); expect(vertexBuffer[stride + 2]).to.eql(1);
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 + 0]).to.eql(10);
expect(vertexBuffer[stride * 2 + 1]).to.eql(2); expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
expect(vertexBuffer[stride * 2 + 2]).to.eql(+3.5); expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
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 + 0]).to.eql(10);
expect(vertexBuffer[stride * 3 + 1]).to.eql(2); expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
expect(vertexBuffer[stride * 3 + 2]).to.eql(-3.5); expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
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[0]).to.eql(0);
expect(indexBuffer[1]).to.eql(1); expect(indexBuffer[1]).to.eql(1);
@@ -149,43 +74,34 @@ describe('ol.renderer.webgl.Layer', function() {
expect(positions.vertexPosition).to.eql(stride * 4); expect(positions.vertexPosition).to.eql(stride * 4);
}); });
it('writes correctly to the buffers (with custom attributes)', function() { it('writes correctly to the buffers (with 2 custom attributes)', function() {
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT] = 101; instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]);
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 1] = 102; const stride = 5;
instructions[elementIndex + POINT_INSTRUCTIONS_COUNT + 2] = 103; const positions = writePointFeatureToBuffers(instructions, 8, vertexBuffer, indexBuffer, 2);
const stride = POINT_VERTEX_STRIDE + 3; expect(vertexBuffer[0]).to.eql(10);
const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, expect(vertexBuffer[1]).to.eql(11);
undefined, POINT_INSTRUCTIONS_COUNT + 3); expect(vertexBuffer[2]).to.eql(0);
expect(vertexBuffer[3]).to.eql(12);
expect(vertexBuffer[4]).to.eql(13);
expect(vertexBuffer[0]).to.eql(1); expect(vertexBuffer[stride + 0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(2); expect(vertexBuffer[stride + 1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(-3.5); expect(vertexBuffer[stride + 2]).to.eql(1);
expect(vertexBuffer[3]).to.eql(-3.5); expect(vertexBuffer[stride + 3]).to.eql(12);
expect(vertexBuffer[4]).to.eql(3); expect(vertexBuffer[stride + 4]).to.eql(13);
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[stride * 2 + 0]).to.eql(10);
expect(vertexBuffer[13]).to.eql(102); expect(vertexBuffer[stride * 2 + 1]).to.eql(11);
expect(vertexBuffer[14]).to.eql(103); expect(vertexBuffer[stride * 2 + 2]).to.eql(2);
expect(vertexBuffer[stride * 2 + 3]).to.eql(12);
expect(vertexBuffer[stride * 2 + 4]).to.eql(13);
expect(vertexBuffer[stride + 12]).to.eql(101); expect(vertexBuffer[stride * 3 + 0]).to.eql(10);
expect(vertexBuffer[stride + 13]).to.eql(102); expect(vertexBuffer[stride * 3 + 1]).to.eql(11);
expect(vertexBuffer[stride + 14]).to.eql(103); expect(vertexBuffer[stride * 3 + 2]).to.eql(3);
expect(vertexBuffer[stride * 3 + 3]).to.eql(12);
expect(vertexBuffer[stride * 2 + 12]).to.eql(101); expect(vertexBuffer[stride * 3 + 4]).to.eql(13);
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[0]).to.eql(0);
expect(indexBuffer[1]).to.eql(1); expect(indexBuffer[1]).to.eql(1);
@@ -199,25 +115,23 @@ describe('ol.renderer.webgl.Layer', function() {
}); });
it('correctly chains buffer writes', function() { it('correctly chains buffer writes', function() {
const stride = POINT_VERTEX_STRIDE; instructions.set([10, 11, 20, 21, 30, 31]);
let positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer); const stride = 3;
positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions); let positions = writePointFeatureToBuffers(instructions, 0, vertexBuffer, indexBuffer, 0);
positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, positions); positions = writePointFeatureToBuffers(instructions, 2, vertexBuffer, indexBuffer, 0, positions);
positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0, positions);
expect(vertexBuffer[0]).to.eql(1); expect(vertexBuffer[0]).to.eql(10);
expect(vertexBuffer[1]).to.eql(2); expect(vertexBuffer[1]).to.eql(11);
expect(vertexBuffer[2]).to.eql(-3.5); expect(vertexBuffer[2]).to.eql(0);
expect(vertexBuffer[3]).to.eql(-3.5);
expect(vertexBuffer[stride * 4 + 0]).to.eql(1); expect(vertexBuffer[stride * 4 + 0]).to.eql(20);
expect(vertexBuffer[stride * 4 + 1]).to.eql(2); expect(vertexBuffer[stride * 4 + 1]).to.eql(21);
expect(vertexBuffer[stride * 4 + 2]).to.eql(-3.5); expect(vertexBuffer[stride * 4 + 2]).to.eql(0);
expect(vertexBuffer[stride * 4 + 3]).to.eql(-3.5);
expect(vertexBuffer[stride * 8 + 0]).to.eql(1); expect(vertexBuffer[stride * 8 + 0]).to.eql(30);
expect(vertexBuffer[stride * 8 + 1]).to.eql(2); expect(vertexBuffer[stride * 8 + 1]).to.eql(31);
expect(vertexBuffer[stride * 8 + 2]).to.eql(-3.5); expect(vertexBuffer[stride * 8 + 2]).to.eql(0);
expect(vertexBuffer[stride * 8 + 3]).to.eql(-3.5);
expect(indexBuffer[6 + 0]).to.eql(4); expect(indexBuffer[6 + 0]).to.eql(4);
expect(indexBuffer[6 + 1]).to.eql(5); expect(indexBuffer[6 + 1]).to.eql(5);
@@ -5,8 +5,9 @@ import VectorSource from '../../../../../src/ol/source/Vector.js';
import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js'; import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js';
import {get as getProjection} from '../../../../../src/ol/proj.js'; import {get as getProjection} from '../../../../../src/ol/proj.js';
import ViewHint from '../../../../../src/ol/ViewHint.js'; import ViewHint from '../../../../../src/ol/ViewHint.js';
import {POINT_VERTEX_STRIDE, WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js'; import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js';
import {create as createTransform, compose as composeTransform} from '../../../../../src/ol/transform.js'; import {compose as composeTransform, create as createTransform} from '../../../../../src/ol/transform.js';
import {getSymbolVertexShader} from '../../../../../src/ol/webgl/ShaderBuilder.js';
const baseFrameState = { const baseFrameState = {
viewHints: [], viewHints: [],
@@ -77,7 +78,7 @@ describe('ol.renderer.webgl.PointsLayer', function() {
})); }));
renderer.prepareFrame(frameState); renderer.prepareFrame(frameState);
const attributePerVertex = POINT_VERTEX_STRIDE; const attributePerVertex = 3;
renderer.worker_.addEventListener('message', function(event) { renderer.worker_.addEventListener('message', function(event) {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
@@ -109,7 +110,7 @@ describe('ol.renderer.webgl.PointsLayer', function() {
if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) {
return; return;
} }
const attributePerVertex = 12; const attributePerVertex = 3;
expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex); expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex);
expect(renderer.indicesBuffer_.getArray().length).to.eql(6); expect(renderer.indicesBuffer_.getArray().length).to.eql(6);
done(); done();
@@ -162,9 +163,9 @@ describe('ol.renderer.webgl.PointsLayer', function() {
}) })
}); });
renderer = new WebGLPointsLayerRenderer(layer, { renderer = new WebGLPointsLayerRenderer(layer, {
sizeCallback: function() { vertexShader: getSymbolVertexShader({
return 4; size: 4
} })
}); });
}); });