From 2b36445ecca39b76b813b5dcdc21b4f7c8677bf3 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Tue, 24 Sep 2019 23:24:49 +0200 Subject: [PATCH] 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. --- src/ol/renderer/webgl/Layer.js | 105 ++------- src/ol/renderer/webgl/PointsLayer.js | 221 ++++++++---------- src/ol/webgl/Helper.js | 16 +- src/ol/worker/webgl.js | 16 +- test/spec/ol/renderer/webgl/layer.test.js | 196 +++++----------- .../ol/renderer/webgl/pointslayer.test.js | 15 +- 6 files changed, 192 insertions(+), 377 deletions(-) diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index bd731fb817..555232c179 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -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} 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 bufferPositions_ = {vertexPosition: 0, indexPosition: 0}; -export const POINT_INSTRUCTIONS_COUNT = 13; -export const POINT_VERTEX_STRIDE = 13; - -function writePointVertex(buffer, pos, x, y, offsetX, offsetY, u, v, opacity, rotateWithView, red, green, blue, alpha, index) { +function writePointVertex(buffer, pos, x, y, index) { 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; - buffer[pos + 12] = index; -} - -function writeCustomAttrs(buffer, pos, customAttrs) { - if (customAttrs.length) { - buffer.set(customAttrs, pos); - } + buffer[pos + 2] = index; } /** @@ -161,42 +107,27 @@ function writeCustomAttrs(buffer, pos, customAttrs) { * @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 {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 {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 */ -export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, bufferPositions, count) { - const count_ = count > POINT_INSTRUCTIONS_COUNT ? count : POINT_INSTRUCTIONS_COUNT; +export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, customAttributesCount, bufferPositions) { + // This is for x, y and index + const baseVertexAttrsCount = 3; + const baseInstructionsCount = 2; + const stride = baseVertexAttrsCount + customAttributesCount; 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 customAttrs = tmpArray_; - customAttrs.length = count_ - POINT_INSTRUCTIONS_COUNT; + customAttrs.length = customAttributesCount; 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; @@ -204,20 +135,20 @@ export function writePointFeatureToBuffers(instructions, elementIndex, vertexBuf const baseIndex = vPos / stride; // 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); - writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + writePointVertex(vertexBuffer, vPos, x, y, 0); + customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount); vPos += stride; - writePointVertex(vertexBuffer, vPos, x, y, +size / 2, -size / 2, u1, v0, opacity, rotateWithView, red, green, blue, alpha, 1); - writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + writePointVertex(vertexBuffer, vPos, x, y, 1); + customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount); vPos += stride; - writePointVertex(vertexBuffer, vPos, x, y, +size / 2, +size / 2, u1, v1, opacity, rotateWithView, red, green, blue, alpha, 2); - writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + writePointVertex(vertexBuffer, vPos, x, y, 2); + customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount); vPos += stride; - writePointVertex(vertexBuffer, vPos, x, y, -size / 2, +size / 2, u0, v1, opacity, rotateWithView, red, green, blue, alpha, 3); - writeCustomAttrs(vertexBuffer, vPos + baseStride, customAttrs); + writePointVertex(vertexBuffer, vPos, x, y, 3); + customAttrs.length && vertexBuffer.set(customAttrs, vPos + baseVertexAttrsCount); vPos += stride; indexBuffer[iPos++] = baseIndex; indexBuffer[iPos++] = baseIndex + 1; indexBuffer[iPos++] = baseIndex + 3; diff --git a/src/ol/renderer/webgl/PointsLayer.js b/src/ol/renderer/webgl/PointsLayer.js index 767e4c564d..c07d20c72c 100644 --- a/src/ol/renderer/webgl/PointsLayer.js +++ b/src/ol/renderer/webgl/PointsLayer.js @@ -2,23 +2,17 @@ * @module ol/renderer/webgl/PointsLayer */ 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 {ARRAY_BUFFER, DYNAMIC_DRAW, ELEMENT_ARRAY_BUFFER} from '../../webgl.js'; +import {AttributeType, DefaultUniform} from '../../webgl/Helper.js'; import GeometryType from '../../geom/GeometryType.js'; -import WebGLLayerRenderer, { - colorDecodeId, - colorEncodeId, - getBlankImageData, - POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, WebGLWorkerMessageType, - writePointFeatureInstructions -} from './Layer.js'; +import WebGLLayerRenderer, {colorDecodeId, colorEncodeId, getBlankImageData, WebGLWorkerMessageType} from './Layer.js'; import ViewHint from '../../ViewHint.js'; import {createEmpty, equals} from '../../extent.js'; import { + apply as applyTransform, create as createTransform, makeInverse as makeInverseTransform, - multiply as multiplyTransform, - apply as applyTransform + multiply as multiplyTransform } from '../../transform.js'; import {create as createWebGLWorker} from '../../worker/webgl.js'; import {getUid} from '../../util.js'; @@ -92,29 +86,21 @@ const HIT_FRAGMENT_SHADER = ` 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 - * @property {function(import("../../Feature").default):number} [sizeCallback] Will be called on every feature in the - * source to compute the size of the quad on screen (in pixels). This is only done on source change. - * @property {function(import("../../Feature").default, number):number} [coordCallback] Will be called on every feature in the - * source to compute the coordinate of the quad center on screen (in pixels). This is only done on source change. - * The second argument is 0 for `x` component and 1 for `y`. - * @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=):Array} [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 {Array} [attributes] These attributes will be read from the features in the source and then + * passed to the GPU. The `name` property of each attribute will serve as its identifier: + * * In the vertex shader as an `attribute` by prefixing it with `a_` + * * In the fragment shader as a `varying` by prefixing it with `v_` + * Please note that these can only be numerical values. * @property {string} [vertexShader] Vertex shader source * @property {string} [fragmentShader] Fragment shader source * @property {Object.} [uniforms] Uniform definitions for the post process steps @@ -224,7 +210,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const options = opt_options || {}; const uniforms = options.uniforms || {}; - uniforms.u_texture = options.texture || getBlankImageData(); + uniforms.u_texture = getBlankImageData(); const projectionMatrixTransform = createTransform(); uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform; @@ -248,28 +234,53 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { VERTEX_SHADER ); - this.sizeCallback_ = options.sizeCallback || function() { - return 1; - }; - this.coordCallback_ = options.coordCallback || function(feature, index) { - const geom = feature.getGeometry(); - return geom.getCoordinates()[index]; - }; - this.opacityCallback_ = options.opacityCallback || function() { - return 1; - }; - this.texCoordCallback_ = options.texCoordCallback || function(feature, index) { - return index < 2 ? 0 : 1; - }; + const customAttributes = options.attributes ? + options.attributes.map(function(attribute) { + return { + name: 'a_' + attribute.name, + size: 1, + type: AttributeType.FLOAT + }; + }) : []; - this.colorArray_ = [1, 1, 1, 1]; - this.colorCallback_ = options.colorCallback || function(feature, color) { - return this.colorArray_; - }; + /** + * A list of attributes used by the renderer. By default only the position and + * index of the vertex (0 to 3) are required. + * @type {Array} + */ + 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} + */ + 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(); @@ -371,8 +382,6 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { const vectorSource = vectorLayer.getSource(); const viewState = frameState.viewState; - const stride = POINT_VERTEX_STRIDE; - // the source has changed: clear the feature cache & reload features const sourceChanged = this.sourceRevision_ < vectorSource.getRevision(); if (sourceChanged) { @@ -401,14 +410,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { 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('a_index', 1, FLOAT, bytesPerFloat * stride, bytesPerFloat * 12); + this.helper.enableAttributes(this.attributes); return true; } @@ -427,76 +429,61 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.helper.makeProjectionTransform(frameState, projectionTransform); 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) { this.renderInstructions_ = new Float32Array(totalInstructionsCount); } - if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalInstructionsCount) { - this.hitRenderInstructions_ = new Float32Array(totalInstructionsCount); + const totalHitInstructionsCount = (7 + this.customAttributes.length) * features.length; + if (!this.hitRenderInstructions_ || this.hitRenderInstructions_.length !== totalHitInstructionsCount) { + this.hitRenderInstructions_ = new Float32Array(totalHitInstructionsCount); } // loop on features to fill the buffer let feature; const tmpCoords = []; const tmpColor = []; - let elementIndex = 0; - let u0, v0, u1, v1, size, opacity, rotateWithView, color; + let renderIndex = 0; + let hitIndex = 0; + let hitColor; for (let i = 0; i < features.length; i++) { feature = features[i]; if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { continue; } - tmpCoords[0] = this.coordCallback_(feature, 0); - tmpCoords[1] = this.coordCallback_(feature, 1); + tmpCoords[0] = feature.getGeometry().getFlatCoordinates()[0]; + tmpCoords[1] = feature.getGeometry().getFlatCoordinates()[1]; applyTransform(projectionTransform, tmpCoords); - u0 = this.texCoordCallback_(feature, 0); - 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_); + hitColor = colorEncodeId(hitIndex + 6, tmpColor); - writePointFeatureInstructions( - this.renderInstructions_, - elementIndex, - tmpCoords[0], - tmpCoords[1], - u0, - v0, - u1, - v1, - size, - opacity, - rotateWithView, - color - ); + this.renderInstructions_[renderIndex++] = tmpCoords[0]; + this.renderInstructions_[renderIndex++] = tmpCoords[1]; // 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 - elementIndex = writePointFeatureInstructions( - this.hitRenderInstructions_, - elementIndex, - tmpCoords[0], - tmpCoords[1], - u0, - v0, - u1, - v1, - size, - opacity > 0 ? Number(getUid(feature)) : 0, - rotateWithView, - colorEncodeId(elementIndex + 7, tmpColor) - ); + this.hitRenderInstructions_[hitIndex++] = tmpCoords[0]; + this.hitRenderInstructions_[hitIndex++] = tmpCoords[1]; + this.hitRenderInstructions_[hitIndex++] = hitColor[0]; + this.hitRenderInstructions_[hitIndex++] = hitColor[1]; + this.hitRenderInstructions_[hitIndex++] = hitColor[2]; + this.hitRenderInstructions_[hitIndex++] = hitColor[3]; + this.hitRenderInstructions_[hitIndex++] = Number(getUid(feature)); + + // pushing custom attributes + let value; + for (let j = 0; j < this.customAttributes.length; j++) { + value = this.customAttributes[j].callback(feature); + this.renderInstructions_[renderIndex++] = value; + this.hitRenderInstructions_[hitIndex++] = value; + } } /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ const message = { 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 message['projectionTransform'] = projectionTransform; @@ -506,7 +493,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { /** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */ const hitMessage = { type: WebGLWorkerMessageType.GENERATE_BUFFERS, - renderInstructions: this.hitRenderInstructions_.buffer + renderInstructions: this.hitRenderInstructions_.buffer, + customAttributesCount: 5 + this.customAttributes.length }; hitMessage['projectionTransform'] = projectionTransform; hitMessage['hitDetection'] = true; @@ -549,9 +537,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { renderHitDetection(frameState) { // skip render entirely if vertices buffers for display & hit detection have different sizes // this typically means both buffers are temporarily out of sync - if (this.hitVerticesBuffer_.getSize() !== this.verticesBuffer_.getSize()) { - return; - } + // FIXME: adapt this to the new points renderer behaviour + // if (this.hitVerticesBuffer_.getSize() !== this.verticesBuffer_.getSize()) { + // return; + // } this.hitRenderTarget_.setSize(frameState.size); @@ -561,15 +550,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer { this.helper.bindBuffer(this.hitVerticesBuffer_); this.helper.bindBuffer(this.indicesBuffer_); - const stride = POINT_VERTEX_STRIDE; - 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); + this.helper.enableAttributes(this.hitDetectionAttributes); const renderCount = this.indicesBuffer_.getSize(); this.helper.drawElements(0, renderCount); diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index 3e4b969168..517edac2b2 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -45,20 +45,6 @@ export const DefaultUniform = { 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` * 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} attributes Ordered list of attributes * @returns {number} Stride, ie amount of values for each vertex in the vertex buffer * @api diff --git a/src/ol/worker/webgl.js b/src/ol/worker/webgl.js index 93c446aefb..c88c294dcf 100644 --- a/src/ol/worker/webgl.js +++ b/src/ol/worker/webgl.js @@ -3,8 +3,6 @@ * @module ol/worker/webgl */ import { - POINT_INSTRUCTIONS_COUNT, - POINT_VERTEX_STRIDE, WebGLWorkerMessageType, writePointFeatureToBuffers } from '../renderer/webgl/Layer.js'; @@ -16,13 +14,17 @@ const worker = self; worker.onmessage = event => { const received = event.data; 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 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 verticesCount = elementsCount * 4 * (customAttrsCount + baseVertexAttrsCount); const indexBuffer = new Uint32Array(indicesCount); const vertexBuffer = new Float32Array(verticesCount); @@ -33,8 +35,8 @@ worker.onmessage = event => { i, vertexBuffer, indexBuffer, - bufferPositions, - instructionsCount); + customAttrsCount, + bufferPositions); } /** @type {import('../renderer/webgl/Layer').WebGLWorkerGenerateBuffersMessage} */ diff --git a/test/spec/ol/renderer/webgl/layer.test.js b/test/spec/ol/renderer/webgl/layer.test.js index 8bbd676850..62127025ab 100644 --- a/test/spec/ol/renderer/webgl/layer.test.js +++ b/test/spec/ol/renderer/webgl/layer.test.js @@ -1,8 +1,8 @@ import WebGLLayerRenderer, { colorDecodeId, colorEncodeId, - getBlankImageData, POINT_INSTRUCTIONS_COUNT, POINT_VERTEX_STRIDE, - writePointFeatureInstructions, writePointFeatureToBuffers + getBlankImageData, + writePointFeatureToBuffers } from '../../../../../src/ol/renderer/webgl/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() { let vertexBuffer, indexBuffer, instructions, elementIndex; @@ -93,50 +39,29 @@ describe('ol.renderer.webgl.Layer', 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]); + instructions.set([0, 0, 0, 0, 10, 11]); }); it('writes correctly to the buffers (without custom attributes)', function() { - const stride = POINT_VERTEX_STRIDE; - const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer); + const stride = 3; + const positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0); - 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[0]).to.eql(10); + expect(vertexBuffer[1]).to.eql(11); + expect(vertexBuffer[2]).to.eql(0); - 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 + 0]).to.eql(10); + expect(vertexBuffer[stride + 1]).to.eql(11); + expect(vertexBuffer[stride + 2]).to.eql(1); - 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 * 2 + 0]).to.eql(10); + expect(vertexBuffer[stride * 2 + 1]).to.eql(11); + expect(vertexBuffer[stride * 2 + 2]).to.eql(2); - 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(vertexBuffer[stride * 3 + 0]).to.eql(10); + expect(vertexBuffer[stride * 3 + 1]).to.eql(11); + expect(vertexBuffer[stride * 3 + 2]).to.eql(3); expect(indexBuffer[0]).to.eql(0); expect(indexBuffer[1]).to.eql(1); @@ -149,43 +74,34 @@ describe('ol.renderer.webgl.Layer', function() { expect(positions.vertexPosition).to.eql(stride * 4); }); - 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; + it('writes correctly to the buffers (with 2 custom attributes)', function() { + instructions.set([0, 0, 0, 0, 0, 0, 0, 0, 10, 11, 12, 13]); + const stride = 5; + const positions = writePointFeatureToBuffers(instructions, 8, vertexBuffer, indexBuffer, 2); - const stride = POINT_VERTEX_STRIDE + 3; - const positions = writePointFeatureToBuffers(instructions, elementIndex, vertexBuffer, indexBuffer, - undefined, POINT_INSTRUCTIONS_COUNT + 3); + expect(vertexBuffer[0]).to.eql(10); + expect(vertexBuffer[1]).to.eql(11); + 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[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[stride + 0]).to.eql(10); + expect(vertexBuffer[stride + 1]).to.eql(11); + expect(vertexBuffer[stride + 2]).to.eql(1); + expect(vertexBuffer[stride + 3]).to.eql(12); + expect(vertexBuffer[stride + 4]).to.eql(13); - expect(vertexBuffer[12]).to.eql(101); - expect(vertexBuffer[13]).to.eql(102); - expect(vertexBuffer[14]).to.eql(103); + expect(vertexBuffer[stride * 2 + 0]).to.eql(10); + expect(vertexBuffer[stride * 2 + 1]).to.eql(11); + 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 + 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(vertexBuffer[stride * 3 + 0]).to.eql(10); + expect(vertexBuffer[stride * 3 + 1]).to.eql(11); + expect(vertexBuffer[stride * 3 + 2]).to.eql(3); + expect(vertexBuffer[stride * 3 + 3]).to.eql(12); + expect(vertexBuffer[stride * 3 + 4]).to.eql(13); expect(indexBuffer[0]).to.eql(0); expect(indexBuffer[1]).to.eql(1); @@ -199,25 +115,23 @@ describe('ol.renderer.webgl.Layer', function() { }); 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); + instructions.set([10, 11, 20, 21, 30, 31]); + const stride = 3; + let positions = writePointFeatureToBuffers(instructions, 0, vertexBuffer, indexBuffer, 0); + positions = writePointFeatureToBuffers(instructions, 2, vertexBuffer, indexBuffer, 0, positions); + positions = writePointFeatureToBuffers(instructions, 4, vertexBuffer, indexBuffer, 0, 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[0]).to.eql(10); + expect(vertexBuffer[1]).to.eql(11); + expect(vertexBuffer[2]).to.eql(0); - 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 * 4 + 0]).to.eql(20); + expect(vertexBuffer[stride * 4 + 1]).to.eql(21); + expect(vertexBuffer[stride * 4 + 2]).to.eql(0); - 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(vertexBuffer[stride * 8 + 0]).to.eql(30); + expect(vertexBuffer[stride * 8 + 1]).to.eql(31); + expect(vertexBuffer[stride * 8 + 2]).to.eql(0); expect(indexBuffer[6 + 0]).to.eql(4); expect(indexBuffer[6 + 1]).to.eql(5); diff --git a/test/spec/ol/renderer/webgl/pointslayer.test.js b/test/spec/ol/renderer/webgl/pointslayer.test.js index 53b36a8041..e99ede9f34 100644 --- a/test/spec/ol/renderer/webgl/pointslayer.test.js +++ b/test/spec/ol/renderer/webgl/pointslayer.test.js @@ -5,8 +5,9 @@ import VectorSource from '../../../../../src/ol/source/Vector.js'; import WebGLPointsLayerRenderer from '../../../../../src/ol/renderer/webgl/PointsLayer.js'; import {get as getProjection} from '../../../../../src/ol/proj.js'; import ViewHint from '../../../../../src/ol/ViewHint.js'; -import {POINT_VERTEX_STRIDE, WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js'; -import {create as createTransform, compose as composeTransform} from '../../../../../src/ol/transform.js'; +import {WebGLWorkerMessageType} from '../../../../../src/ol/renderer/webgl/Layer.js'; +import {compose as composeTransform, create as createTransform} from '../../../../../src/ol/transform.js'; +import {getSymbolVertexShader} from '../../../../../src/ol/webgl/ShaderBuilder.js'; const baseFrameState = { viewHints: [], @@ -77,7 +78,7 @@ describe('ol.renderer.webgl.PointsLayer', function() { })); renderer.prepareFrame(frameState); - const attributePerVertex = POINT_VERTEX_STRIDE; + const attributePerVertex = 3; renderer.worker_.addEventListener('message', function(event) { if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { @@ -109,7 +110,7 @@ describe('ol.renderer.webgl.PointsLayer', function() { if (event.data.type !== WebGLWorkerMessageType.GENERATE_BUFFERS) { return; } - const attributePerVertex = 12; + const attributePerVertex = 3; expect(renderer.verticesBuffer_.getArray().length).to.eql(4 * attributePerVertex); expect(renderer.indicesBuffer_.getArray().length).to.eql(6); done(); @@ -162,9 +163,9 @@ describe('ol.renderer.webgl.PointsLayer', function() { }) }); renderer = new WebGLPointsLayerRenderer(layer, { - sizeCallback: function() { - return 4; - } + vertexShader: getSymbolVertexShader({ + size: 4 + }) }); });