Merge pull request #9994 from jahow/add-webgl-layer-type

Add a new layer type: WebGLPointsLayer
This commit is contained in:
Olivier Guyot
2019-09-26 14:55:09 +02:00
committed by GitHub
20 changed files with 1366 additions and 633 deletions

View File

@@ -177,38 +177,47 @@ class Heatmap extends VectorLayer {
*/
createRenderer() {
return new WebGLPointsLayerRenderer(this, {
attributes: [
{
name: 'weight',
callback: this.weightFunction_
}
],
vertexShader: `
precision mediump float;
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_offsets;
attribute float a_opacity;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform float u_size;
attribute vec2 a_position;
attribute float a_index;
attribute float a_weight;
varying vec2 v_texCoord;
varying float v_opacity;
varying float v_weight;
void main(void) {
vec4 offsets = u_offsetScaleMatrix * vec4(a_offsets, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets * u_size;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
mat4 offsetMatrix = u_offsetScaleMatrix;
float offsetX = a_index == 0.0 || a_index == 3.0 ? -u_size / 2.0 : u_size / 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? -u_size / 2.0 : u_size / 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;
v_texCoord = vec2(u, v);
v_weight = a_weight;
}`,
fragmentShader: `
precision mediump float;
uniform float u_blurSlope;
varying vec2 v_texCoord;
varying float v_opacity;
varying float v_weight;
void main(void) {
vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);
float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;
float value = (1.0 - sqrt(sqRadius)) * u_blurSlope;
float alpha = smoothstep(0.0, 1.0, value) * v_opacity;
float alpha = smoothstep(0.0, 1.0, value) * v_weight;
gl_FragColor = vec4(alpha, alpha, alpha, alpha);
}`,
uniforms: {
@@ -239,8 +248,7 @@ class Heatmap extends VectorLayer {
u_gradientTexture: this.gradient_
}
}
],
opacityCallback: this.weightFunction_
]
});
}
}

133
src/ol/layer/WebGLPoints.js Normal file
View File

@@ -0,0 +1,133 @@
/**
* @module ol/layer/WebGLPoints
*/
import {assign} from '../obj.js';
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
import {
formatArray,
formatColor,
formatNumber,
getSymbolFragmentShader,
getSymbolVertexShader
} from '../webgl/ShaderBuilder.js';
import {assert} from '../asserts.js';
import {asArray} from '../color.js';
import Layer from './Layer.js';
/**
* @typedef {Object} Options
* @property {import('../style/LiteralStyle.js').LiteralStyle} style Literal style to apply to the layer features.
* @property {string} [className='ol-layer'] A CSS class name to set to the layer element.
* @property {number} [opacity=1] Opacity (0, 1).
* @property {boolean} [visible=true] Visibility.
* @property {import("../extent.js").Extent} [extent] The bounding extent for layer rendering. The layer will not be
* rendered outside of this extent.
* @property {number} [zIndex] The z-index for layer rendering. At rendering time, the layers
* will be ordered, first by Z-index and then by position. When `undefined`, a `zIndex` of 0 is assumed
* for layers that are added to the map's `layers` collection, or `Infinity` when the layer's `setMap()`
* method was used.
* @property {number} [minResolution] The minimum resolution (inclusive) at which this layer will be
* visible.
* @property {number} [maxResolution] The maximum resolution (exclusive) below which this layer will
* be visible.
* @property {import("../source/Vector.js").default} [source] Source.
*/
/**
* @classdesc
* Layer optimized for rendering large point datasets. Takes a `style` property which
* is a serializable JSON object describing how the layer should be rendered.
*
* Here are a few samples of literal style objects:
* ```js
* const style = {
* symbol: {
* symbolType: 'circle',
* size: 8,
* color: '#33AAFF',
* opacity: 0.9
* }
* }
* ```
*
* ```js
* const style = {
* symbol: {
* symbolType: 'image',
* offset: [0, 12],
* size: [4, 8],
* src: '../static/exclamation-mark.png'
* }
* }
* ```
*
* Note that any property set in the options is set as a {@link module:ol/Object~BaseObject}
* property on the layer object; for example, setting `title: 'My Title'` in the
* options means that `title` is observable, and has get/set accessors.
*
* @fires import("../render/Event.js").RenderEvent
*/
class WebGLPointsLayer extends Layer {
/**
* @param {Options} options Options.
*/
constructor(options) {
const baseOptions = assign({}, options);
super(baseOptions);
/**
* @type {import('../style/LiteralStyle.js').LiteralStyle}
*/
this.style = options.style;
assert(this.style.symbol !== undefined, 65);
}
/**
* @inheritDoc
*/
createRenderer() {
const symbolStyle = this.style.symbol;
const size = Array.isArray(symbolStyle.size) ?
formatArray(symbolStyle.size) : formatNumber(symbolStyle.size);
const color = symbolStyle.color !== undefined ?
(typeof symbolStyle.color === 'string' ? asArray(symbolStyle.color) : symbolStyle.color) :
[255, 255, 255, 1];
const texCoord = symbolStyle.textureCoord || [0, 0, 1, 1];
const offset = symbolStyle.offset || [0, 0];
const opacity = symbolStyle.opacity !== undefined ? symbolStyle.opacity : 1;
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
const params = {
uniforms: [],
colorExpression: 'vec4(' + formatColor(color) + ') * vec4(1.0, 1.0, 1.0, ' + formatNumber(opacity) + ')',
sizeExpression: 'vec2(' + size + ')',
offsetExpression: 'vec2(' + formatArray(offset) + ')',
texCoordExpression: 'vec4(' + formatArray(texCoord) + ')',
rotateWithView: !!symbolStyle.rotateWithView
};
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
const uniforms = {};
if (symbolStyle.symbolType === 'image' && symbolStyle.src) {
const texture = new Image();
texture.src = symbolStyle.src;
params.uniforms.push('sampler2D u_texture');
params.colorExpression = params.colorExpression +
' * texture2D(u_texture, v_texCoord);';
uniforms['u_texture'] = texture;
}
return new WebGLPointsLayerRenderer(this, {
vertexShader: getSymbolVertexShader(params),
fragmentShader: getSymbolFragmentShader(params),
uniforms: uniforms
});
}
}
export default WebGLPointsLayer;

View File

@@ -71,6 +71,7 @@ class WebGLLayerRenderer extends LayerRenderer {
* @inheritDoc
*/
disposeInternal() {
this.helper.disposeInternal();
super.disposeInternal();
}
@@ -85,66 +86,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 bufferPositions_ = {vertexPosition: 0, indexPosition: 0};
export const POINT_INSTRUCTIONS_COUNT = 13;
export const POINT_VERTEX_STRIDE = 12;
function writePointVertex(buffer, pos, x, y, offsetX, offsetY, u, v, opacity, rotateWithView, red, green, blue, alpha) {
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;
}
function writeCustomAttrs(buffer, pos, customAttrs) {
if (customAttrs.length) {
buffer.set(customAttrs, pos);
}
buffer[pos + 2] = index;
}
/**
@@ -160,42 +108,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;
@@ -203,20 +136,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);
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);
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);
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);
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;

View File

@@ -2,121 +2,42 @@
* @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, 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';
import WebGLRenderTarget from '../../webgl/RenderTarget.js';
import {assert} from '../../asserts.js';
const VERTEX_SHADER = `
precision mediump float;
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute float a_rotateWithView;
attribute vec2 a_offsets;
attribute float a_opacity;
attribute vec4 a_color;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
varying vec2 v_texCoord;
varying float v_opacity;
varying vec4 v_color;
void main(void) {
mat4 offsetMatrix = u_offsetScaleMatrix;
if (a_rotateWithView == 1.0) {
offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
}
vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
v_texCoord = a_texCoord;
v_opacity = a_opacity;
v_color = a_color;
}`;
const FRAGMENT_SHADER = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
varying float v_opacity;
varying vec4 v_color;
void main(void) {
if (v_opacity == 0.0) {
discard;
}
vec4 textureColor = texture2D(u_texture, v_texCoord);
gl_FragColor = v_color * textureColor;
gl_FragColor.a *= v_opacity;
gl_FragColor.rgb *= gl_FragColor.a;
}`;
const HIT_FRAGMENT_SHADER = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
varying float v_opacity;
varying vec4 v_color;
void main(void) {
if (v_opacity == 0.0) {
discard;
}
vec4 textureColor = texture2D(u_texture, v_texCoord);
if (textureColor.a < 0.1) {
discard;
}
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<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} [fragmentShader] Fragment shader source
* @property {Array<CustomAttribute>} [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, mandatory.
* @property {string} fragmentShader Fragment shader source, mandatory.
* @property {string} [hitVertexShader] Vertex shader source for hit detection rendering.
* @property {string} [hitFragmentShader] Fragment shader source for hit detection rendering.
* @property {Object.<string,import("../../webgl/Helper").UniformValue>} [uniforms] Uniform definitions for the post process steps
* Please note that `u_texture` is reserved for the main texture slot.
* @property {Array<import("./Layer").PostProcessesOptions>} [postProcesses] Post-processes definitions
@@ -128,13 +49,38 @@ const HIT_FRAGMENT_SHADER = `
* All features will be rendered as quads (two triangles forming a square). New data will be flushed to the GPU
* every time the vector source changes.
*
* Use shaders to customize the final output. The following attributes are available:
* * `vec2 a_position`
* * `vec2 a_texCoord`
* * `vec2 a_offsets`
* * `float a_rotateWithView`
* * `float a_opacity`
* * `float a_color`
* You need to provide vertex and fragment shaders for rendering. This can be done using
* {@link module:ol/webgl/ShaderBuilder} utilities. These shaders shall expect a `a_position` attribute
* containing the screen-space projected center of the quad, as well as a `a_index` attribute
* whose value (0, 1, 2 or 3) indicates which quad vertex is currently getting processed (see structure below).
*
* To include variable attributes in the shaders, you need to declare them using the `attributes` property of
* the options object like so:
* ```js
* new WebGLPointsLayerRenderer(layer, {
* attributes: [
* {
* name: 'size',
* callback: function(feature) {
* // compute something with the feature
* }
* },
* {
* name: 'weight',
* callback: function(feature) {
* // compute something with the feature
* }
* },
* ],
* vertexShader:
* // shader using attribute a_weight and a_size
* fragmentShader:
* // shader using varying v_weight and v_size
* ```
*
* To enable hit detection, you must as well provide dedicated shaders using the `hitVertexShader`
* and `hitFragmentShader` properties. These shall expect the `a_hitColor` attribute to contain
* the final color that will have to be output for hit detection to work.
*
* The following uniform is used for the main texture: `u_texture`.
*
@@ -157,74 +103,16 @@ const HIT_FRAGMENT_SHADER = `
*
* This uses {@link module:ol/webgl/Helper~WebGLHelper} internally.
*
* Default shaders are shown hereafter:
*
* * Vertex shader:
* ```
* precision mediump float;
*
* attribute vec2 a_position;
* attribute vec2 a_texCoord;
* attribute float a_rotateWithView;
* attribute vec2 a_offsets;
* attribute float a_opacity;
* attribute vec4 a_color;
*
* uniform mat4 u_projectionMatrix;
* uniform mat4 u_offsetScaleMatrix;
* uniform mat4 u_offsetRotateMatrix;
*
* varying vec2 v_texCoord;
* varying float v_opacity;
* varying vec4 v_color;
*
* void main(void) {
* mat4 offsetMatrix = u_offsetScaleMatrix;
* if (a_rotateWithView == 1.0) {
* offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
* }
* vec4 offsets = offsetMatrix * vec4(a_offsets, 0.0, 0.0);
* gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
* v_texCoord = a_texCoord;
* v_opacity = a_opacity;
* v_color = a_color;
* }
* ```
*
* * Fragment shader:
* ```
* precision mediump float;
*
* uniform sampler2D u_texture;
*
* varying vec2 v_texCoord;
* varying float v_opacity;
* varying vec4 v_color;
*
* void main(void) {
* if (v_opacity == 0.0) {
* discard;
* }
* vec4 textureColor = texture2D(u_texture, v_texCoord);
* gl_FragColor = v_color * textureColor;
* gl_FragColor.a *= v_opacity;
* gl_FragColor.rgb *= gl_FragColor.a;
* }
* ```
*
* @api
*/
class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
/**
* @param {import("../../layer/Vector.js").default} vectorLayer Vector layer.
* @param {Options=} [opt_options] Options.
* @param {Options=} options Options.
*/
constructor(vectorLayer, opt_options) {
const options = opt_options || {};
constructor(vectorLayer, options) {
const uniforms = options.uniforms || {};
uniforms.u_texture = options.texture || getBlankImageData();
const projectionMatrixTransform = createTransform();
uniforms[DefaultUniform.PROJECTION_MATRIX] = projectionMatrixTransform;
@@ -240,36 +128,68 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.indicesBuffer_ = new WebGLArrayBuffer(ELEMENT_ARRAY_BUFFER, DYNAMIC_DRAW);
this.program_ = this.helper.getProgram(
options.fragmentShader || FRAGMENT_SHADER,
options.vertexShader || VERTEX_SHADER
);
this.hitProgram_ = this.helper.getProgram(
HIT_FRAGMENT_SHADER,
VERTEX_SHADER
options.fragmentShader,
options.vertexShader
);
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;
};
/**
* @type {boolean}
* @private
*/
this.hitDetectionEnabled_ = options.hitFragmentShader && options.hitVertexShader ? true : false;
this.colorArray_ = [1, 1, 1, 1];
this.colorCallback_ = options.colorCallback || function(feature, color) {
return this.colorArray_;
};
this.hitProgram_ = this.hitDetectionEnabled_ && this.helper.getProgram(
options.hitFragmentShader,
options.hitVertexShader
);
this.rotateWithViewCallback_ = options.rotateWithViewCallback || function() {
return false;
};
const customAttributes = options.attributes ?
options.attributes.map(function(attribute) {
return {
name: 'a_' + attribute.name,
size: 1,
type: AttributeType.FLOAT
};
}) : [];
/**
* 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<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);
/**
* 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_hitColor',
size: 4,
type: AttributeType.FLOAT
}, {
name: 'a_featureUid',
size: 1,
type: AttributeType.FLOAT
}].concat(customAttributes);
this.customAttributes = options.attributes ? options.attributes : [];
this.previousExtent_ = createEmpty();
@@ -312,7 +232,7 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
* @type {WebGLRenderTarget}
* @private
*/
this.hitRenderTarget_ = new WebGLRenderTarget(this.helper);
this.hitRenderTarget_ = this.hitDetectionEnabled_ && new WebGLRenderTarget(this.helper);
this.worker_ = createWebGLWorker();
this.worker_.addEventListener('message', function(event) {
@@ -357,8 +277,10 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
canvas.style.opacity = opacity;
}
this.renderHitDetection(frameState);
this.hitRenderTarget_.clearCachedData();
if (this.hitDetectionEnabled_) {
this.renderHitDetection(frameState);
this.hitRenderTarget_.clearCachedData();
}
return canvas;
}
@@ -371,8 +293,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,13 +321,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.enableAttributes(this.attributes);
return true;
}
@@ -426,76 +340,72 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.helper.makeProjectionTransform(frameState, projectionTransform);
const features = vectorSource.getFeatures();
const totalInstructionsCount = POINT_INSTRUCTIONS_COUNT * features.length;
// here we anticipate the amount of render instructions that we well generate
// this can be done since we know that for normal render we only have x, y as base instructions,
// and x, y, r, g, b, a and featureUid for hit render instructions
// and we also know the amount of custom attributes to append to these
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);
if (this.hitDetectionEnabled_) {
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)
);
if (this.hitDetectionEnabled_) {
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;
if (this.hitDetectionEnabled_) {
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;
@@ -503,20 +413,24 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
this.renderInstructions_ = null;
/** @type import('./Layer').WebGLWorkerGenerateBuffersMessage */
const hitMessage = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
renderInstructions: this.hitRenderInstructions_.buffer
};
hitMessage['projectionTransform'] = projectionTransform;
hitMessage['hitDetection'] = true;
this.worker_.postMessage(hitMessage, [this.hitRenderInstructions_.buffer]);
this.hitRenderInstructions_ = null;
if (this.hitDetectionEnabled_) {
const hitMessage = {
type: WebGLWorkerMessageType.GENERATE_BUFFERS,
renderInstructions: this.hitRenderInstructions_.buffer,
customAttributesCount: 5 + this.customAttributes.length
};
hitMessage['projectionTransform'] = projectionTransform;
hitMessage['hitDetection'] = true;
this.worker_.postMessage(hitMessage, [this.hitRenderInstructions_.buffer]);
this.hitRenderInstructions_ = null;
}
}
/**
* @inheritDoc
*/
forEachFeatureAtCoordinate(coordinate, frameState, hitTolerance, callback, declutteredFeatures) {
assert(this.hitDetectionEnabled_, 66);
if (!this.hitRenderInstructions_) {
return;
}
@@ -546,9 +460,8 @@ class WebGLPointsLayerRenderer extends WebGLLayerRenderer {
* @param {import("../../PluggableMap.js").FrameState} frameState current frame state
*/
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()) {
// skip render entirely if vertex buffers not ready/generated yet
if (!this.hitVerticesBuffer_.getSize()) {
return;
}
@@ -560,18 +473,19 @@ 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.enableAttributes(this.hitDetectionAttributes);
const renderCount = this.indicesBuffer_.getSize();
this.helper.drawElements(0, renderCount);
}
/**
* @inheritDoc
*/
disposeInternal() {
this.worker_.terminate();
super.disposeInternal();
}
}
export default WebGLPointsLayerRenderer;

View File

@@ -0,0 +1,33 @@
/**
* Literal Style objects differ from standard styles in that they cannot
* be functions and are made up of simple objects instead of classes.
* @module ol/style/LiteralStyle
*/
/**
* @typedef {Object} LiteralStyle
* @property {LiteralSymbolStyle} [symbol] Symbol representation.
*/
/**
* @enum {string}
*/
export const SymbolType = {
CIRCLE: 'circle',
SQUARE: 'square',
TRIANGLE: 'triangle',
IMAGE: 'image'
};
/**
* @typedef {Object} LiteralSymbolStyle
* @property {number|Array.<number, number>} size Size, mandatory.
* @property {SymbolType} symbolType Symbol type to use, either a regular shape or an image.
* @property {string} [src] Path to the image to be used for the symbol. Only required with `symbolType: 'image'`.
* @property {import("../color.js").Color|string} [color='#FFFFFF'] Color used for the representation (either fill, line or symbol).
* @property {number} [opacity=1] Opacity.
* @property {Array.<number, number>} [offset] Offset on X and Y axis for symbols. If not specified, the symbol will be centered.
* @property {Array.<number, number, number, number>} [textureCoord] Texture coordinates. If not specified, the whole texture will be used (range for 0 to 1 on both axes).
* @property {boolean} [rotateWithView=false] Specify whether the symbol must rotate with the view or stay upwards.
*/

View File

@@ -14,7 +14,7 @@ import {
} from '../transform.js';
import {create, fromTransform} from '../vec/mat4.js';
import WebGLPostProcessingPass from './PostProcessingPass.js';
import {getContext, getSupportedExtensions} from '../webgl.js';
import {FLOAT, getContext, getSupportedExtensions, UNSIGNED_BYTE, UNSIGNED_INT, UNSIGNED_SHORT} from '../webgl.js';
import {includes} from '../array.js';
import {assert} from '../asserts.js';
@@ -46,19 +46,27 @@ export const DefaultUniform = {
};
/**
* Attribute names used in the default shaders: `POSITION`, `TEX_COORD`, `OPACITY`,
* `ROTATE_WITH_VIEW`, `OFFSETS` and `COLOR`
* @enum {string}
* Attribute types, either `UNSIGNED_BYTE`, `UNSIGNED_SHORT`, `UNSIGNED_INT` or `FLOAT`
* Note: an attribute stored in a `Float32Array` should be of type `FLOAT`.
* @enum {number}
*/
export const DefaultAttrib = {
POSITION: 'a_position',
TEX_COORD: 'a_texCoord',
OPACITY: 'a_opacity',
ROTATE_WITH_VIEW: 'a_rotateWithView',
OFFSETS: 'a_offsets',
COLOR: 'a_color'
export const AttributeType = {
UNSIGNED_BYTE: UNSIGNED_BYTE,
UNSIGNED_SHORT: UNSIGNED_SHORT,
UNSIGNED_INT: UNSIGNED_INT,
FLOAT: FLOAT
};
/**
* Description of an attribute in a buffer
* @typedef {Object} AttributeDescription
* @property {string} name Attribute name to use in shaders
* @property {number} size Number of components per attributes
* @property {AttributeType} [type] Attribute type, i.e. number of bytes used to store the value. This is
* determined by the class of typed array which the buffer uses (eg. `Float32Array` for a `FLOAT` attribute).
* Default is `FLOAT`.
*/
/**
* @typedef {number|Array<number>|HTMLCanvasElement|HTMLImageElement|ImageData|import("../transform").Transform} UniformLiteralValue
*/
@@ -122,14 +130,14 @@ export const DefaultAttrib = {
* // for subsequent rendering calls
* const vertexShader = new WebGLVertex(VERTEX_SHADER);
* const fragmentShader = new WebGLFragment(FRAGMENT_SHADER);
* this.program = this.context.getProgram(fragmentShader, vertexShader);
* this.context.useProgram(this.program);
* const program = this.context.getProgram(fragmentShader, vertexShader);
* helper.useProgram(this.program);
* ```
*
* Uniforms are defined using the `uniforms` option and can either be explicit values or callbacks taking the frame state as argument.
* You can also change their value along the way like so:
* ```js
* this.context.setUniformFloatValue('u_value', valueAsNumber);
* helper.setUniformFloatValue('u_value', valueAsNumber);
* ```
*
* ### Defining post processing passes
@@ -162,32 +170,42 @@ export const DefaultAttrib = {
* Examples below:
* ```js
* // at initialization phase
* this.verticesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
* this.indicesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
* const verticesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
* const indicesBuffer = new WebGLArrayBuffer([], DYNAMIC_DRAW);
*
* // when array values have changed
* this.context.flushBufferData(ARRAY_BUFFER, this.verticesBuffer);
* this.context.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
* helper.flushBufferData(ARRAY_BUFFER, this.verticesBuffer);
* helper.flushBufferData(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
*
* // at rendering phase
* this.context.bindBuffer(ARRAY_BUFFER, this.verticesBuffer);
* this.context.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
* helper.bindBuffer(ARRAY_BUFFER, this.verticesBuffer);
* helper.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer);
* ```
*
* ### Specifying attributes
*
* The GPU only receives the data as arrays of numbers. These numbers must be handled differently depending on what it describes (position, texture coordinate...).
* Attributes are used to specify these uses. Use {@link enableAttributeArray} and either
* Attributes are used to specify these uses. Use {@link enableAttributeArray_} and either
* the default attribute names in {@link module:ol/webgl/Helper.DefaultAttrib} or custom ones.
*
* Please note that you will have to specify the type and offset of the attributes in the data array. You can refer to the documentation of [WebGLRenderingContext.vertexAttribPointer](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/vertexAttribPointer) for more explanation.
* ```js
* // here we indicate that the data array has the following structure:
* // [posX, posY, offsetX, offsetY, texCoordU, texCoordV, posX, posY, ...]
* let bytesPerFloat = Float32Array.BYTES_PER_ELEMENT;
* this.context.enableAttributeArray(DefaultAttrib.POSITION, 2, FLOAT, bytesPerFloat * 6, 0);
* this.context.enableAttributeArray(DefaultAttrib.OFFSETS, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 2);
* this.context.enableAttributeArray(DefaultAttrib.TEX_COORD, 2, FLOAT, bytesPerFloat * 6, bytesPerFloat * 4);
* helper.enableAttributes([
* {
* name: 'a_position',
* size: 2
* },
* {
* name: 'a_offset',
* size: 2
* },
* {
* name: 'a_texCoord',
* size: 2
* }
* ])
* ```
*
* ### Rendering primitives
@@ -195,13 +213,13 @@ export const DefaultAttrib = {
* Once all the steps above have been achieved, rendering primitives to the screen is done using {@link prepareDraw}, {@link drawElements} and {@link finalizeDraw}.
* ```js
* // frame preparation step
* this.context.prepareDraw(frameState);
* helper.prepareDraw(frameState);
*
* // call this for every data array that has to be rendered on screen
* this.context.drawElements(0, this.indicesBuffer.getArray().length);
* helper.drawElements(0, this.indicesBuffer.getArray().length);
*
* // finalize the rendering by applying post processes
* this.context.finalizeDraw(frameState);
* helper.finalizeDraw(frameState);
* ```
*
* For an example usage of this class, refer to {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
@@ -543,6 +561,7 @@ class WebGLHelper extends Disposable {
let textureSlot = 0;
this.uniforms_.forEach(function(uniform) {
value = typeof uniform.value === 'function' ? uniform.value(frameState) : uniform.value;
// apply value based on type
if (value instanceof HTMLCanvasElement || value instanceof HTMLImageElement || value instanceof ImageData) {
// create a texture & put data
@@ -734,15 +753,16 @@ class WebGLHelper extends Disposable {
}
/**
* Will set the currently bound buffer to an attribute of the shader program
* Will set the currently bound buffer to an attribute of the shader program. Used by `#enableAttributes`
* internally.
* @param {string} attribName Attribute name
* @param {number} size Number of components per attributes
* @param {number} type UNSIGNED_INT, UNSIGNED_BYTE, UNSIGNED_SHORT or FLOAT
* @param {number} stride Stride in bytes (0 means attribs are packed)
* @param {number} offset Offset in bytes
* @api
* @private
*/
enableAttributeArray(attribName, size, type, stride, offset) {
enableAttributeArray_(attribName, size, type, stride, offset) {
const location = this.getAttributeLocation(attribName);
// the attribute has not been found in the shaders; do not enable it
if (location < 0) {
@@ -753,6 +773,23 @@ class WebGLHelper extends Disposable {
false, stride, offset);
}
/**
* Will enable the following attributes to be read from the currently bound buffer,
* i.e. tell the GPU where to read the different attributes in the buffer. An error in the
* size/type/order of attributes will most likely break the rendering and throw a WebGL exception.
* @param {Array<AttributeDescription>} attributes Ordered list of attributes to read from the buffer
* @api
*/
enableAttributes(attributes) {
const stride = computeAttributesStride(attributes);
let offset = 0;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
this.enableAttributeArray_(attr.name, attr.size, attr.type || FLOAT, stride, offset);
offset += attr.size * getByteSizeFromType(attr.type);
}
}
/**
* WebGL context was lost
* @private
@@ -808,4 +845,34 @@ class WebGLHelper extends Disposable {
}
}
/**
* Compute a stride in bytes based on a 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
* @api
*/
export function computeAttributesStride(attributes) {
let stride = 0;
for (let i = 0; i < attributes.length; i++) {
const attr = attributes[i];
stride += attr.size * getByteSizeFromType(attr.type);
}
return stride;
}
/**
* Computes the size in byte of an attribute type.
* @param {AttributeType} type Attribute type
* @returns {number} The size in bytes
*/
function getByteSizeFromType(type) {
switch (type) {
case AttributeType.UNSIGNED_BYTE: return Uint8Array.BYTES_PER_ELEMENT;
case AttributeType.UNSIGNED_SHORT: return Uint16Array.BYTES_PER_ELEMENT;
case AttributeType.UNSIGNED_INT: return Uint32Array.BYTES_PER_ELEMENT;
case AttributeType.FLOAT:
default: return Float32Array.BYTES_PER_ELEMENT;
}
}
export default WebGLHelper;

View File

@@ -0,0 +1,143 @@
/**
* Utilities for generating shaders from literal style objects
* @module ol/webgl/ShaderBuilder
*/
/**
* Will return the number as a float with a dot separator, which is required by GLSL.
* @param {number} v Numerical value.
* @returns {string} The value as string.
*/
export function formatNumber(v) {
const s = v.toString();
return s.indexOf('.') === -1 ? s + '.0' : s;
}
/**
* Will return the number array as a float with a dot separator, concatenated with ', '.
* @param {Array<number>} array Numerical values array.
* @returns {string} The array as string, e. g.: `1.0, 2.0, 3.0`.
*/
export function formatArray(array) {
return array.map(formatNumber).join(', ');
}
/**
* Will normalize and converts to string a color array compatible with GLSL.
* @param {Array<number>} colorArray Color in [r, g, b, a] array form, with RGB components in the
* 0..255 range and the alpha component in the 0..1 range. Note that if the A component is
* missing, only 3 values will be output.
* @returns {string} The color components concatenated in `1.0, 1.0, 1.0, 1.0` form.
*/
export function formatColor(colorArray) {
return colorArray.map(function(c, i) {
return i < 3 ? c / 255 : c;
}).map(formatNumber).join(', ');
}
/**
* @typedef {Object} VaryingDescription
* @property {string} name Varying name, as will be declared in the header.
* @property {string} type Varying type, either `float`, `vec2`, `vec4`...
* @property {string} expression Expression which will be assigned to the varying in the vertex shader, and
* passed on to the fragment shader.
*/
/**
* @typedef {Object} ShaderParameters
* @property {Array<string>} [uniforms] Uniforms; these will be declared in the header (should include the type).
* @property {Array<string>} [attributes] Attributes; these will be declared in the header (should include the type).
* @property {Array<VaryingDescription>} [varyings] Varyings with a name, a type and an expression.
* @property {string} sizeExpression This will be assigned to a `vec2 size` variable.
* @property {string} offsetExpression This will be assigned to a `vec2 offset` variable.
* @property {string} colorExpression This will be the value assigned to gl_FragColor
* @property {string} texCoordExpression This will be the value assigned to the `vec4 v_texCoord` varying.
* @property {boolean} [rotateWithView=false] Whether symbols should rotate with view
*/
/**
* Generates a symbol vertex shader from a set of parameters,
* intended to be used on point geometries.
*
* Three uniforms are hardcoded in all shaders: `u_projectionMatrix`, `u_offsetScaleMatrix` and
* `u_offsetRotateMatrix`.
*
* The following attributes are hardcoded and expected to be present in the vertex buffers:
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
*
* @param {ShaderParameters} parameters Parameters for the shader.
* @returns {string} The full shader as a string.
*/
export function getSymbolVertexShader(parameters) {
const offsetMatrix = parameters.rotateWithView ?
'u_offsetScaleMatrix * u_offsetRotateMatrix' :
'u_offsetScaleMatrix';
const uniforms = parameters.uniforms || [];
const attributes = parameters.attributes || [];
const varyings = parameters.varyings || [];
const body = `precision mediump float;
uniform mat4 u_projectionMatrix;
uniform mat4 u_offsetScaleMatrix;
uniform mat4 u_offsetRotateMatrix;
${uniforms.map(function(uniform) {
return 'uniform ' + uniform + ';';
}).join('\n')}
attribute vec2 a_position;
attribute float a_index;
${attributes.map(function(attribute) {
return 'attribute ' + attribute + ';';
}).join('\n')}
varying vec2 v_texCoord;
${varyings.map(function(varying) {
return 'varying ' + varying.type + ' ' + varying.name + ';';
}).join('\n')}
void main(void) {
mat4 offsetMatrix = ${offsetMatrix};
vec2 size = ${parameters.sizeExpression};
vec2 offset = ${parameters.offsetExpression};
float offsetX = a_index == 0.0 || a_index == 3.0 ? offset.x - size.x / 2.0 : offset.x + size.x / 2.0;
float offsetY = a_index == 0.0 || a_index == 1.0 ? offset.y - size.y / 2.0 : offset.y + size.y / 2.0;
vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);
gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;
vec4 texCoord = ${parameters.texCoordExpression};
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
v_texCoord = vec2(u, v);
${varyings.map(function(varying) {
return ' ' + varying.name + ' = ' + varying.expression + ';';
}).join('\n')}
}`;
return body;
}
/**
* Generates a symbol fragment shader intended to be used on point geometries.
*
* Expected the following varyings to be transmitted by the vertex shader:
* `vec2 v_texCoord`
*
* @param {ShaderParameters} parameters Parameters for the shader.
* @returns {string} The full shader as a string.
*/
export function getSymbolFragmentShader(parameters) {
const uniforms = parameters.uniforms || [];
const varyings = parameters.varyings || [];
const body = `precision mediump float;
${uniforms.map(function(uniform) {
return 'uniform ' + uniform + ';';
}).join('\n')}
varying vec2 v_texCoord;
${varyings.map(function(varying) {
return 'varying ' + varying.type + ' ' + varying.name + ';';
}).join('\n')}
void main(void) {
gl_FragColor = ${parameters.colorExpression};
gl_FragColor.rgb *= gl_FragColor.a;
}`;
return body;
}

View File

@@ -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 (x, y, index)
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} */