From a84559d1fbe283e4ef31d812745513edf6e2e8e1 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Fri, 16 Nov 2018 11:29:42 +0100 Subject: [PATCH] Added documentation & fixed linting for WebGL classes --- changelog/upgrade-notes.md | 22 ++ package.json | 1 - src/ol/layer/Heatmap.js | 10 +- src/ol/render/Event.js | 4 +- src/ol/renderer/webgl-new/PointsLayer.js | 94 ++++++++- src/ol/webgl/Helper.js | 244 +++++++++++++++++++---- src/ol/webgl/PostProcessingPass.js | 103 ++++++++-- 7 files changed, 410 insertions(+), 68 deletions(-) diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 716be8bdb2..fff1a1231d 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -10,6 +10,28 @@ Breaking change: layers can no longer be shared between several `Map` objects. Breaking change: the `Graticule` control has been replaced by a layer also called `Graticule`, found in `ol/layer/Graticule`. The API remains similar. +#### Breaking change: drop of support for most of WebGL features + +The WebGL map and layers renderers are gone, replaced by a `WebGLHelper` function that provides a lightweight, +low-level access to the WebGL API. This is implemented in a new `WebGLPointsLayer` which does simple rendering of large number +of points with custom shaders. + +This is now used in the `Heatmap` layer. + +The removed classes and components are: +* `WebGLMap` and `WebGLMapRenderer` +* `WebGLLayerRenderer` +* `WebGLImageLayer` and `WebGLImageLayerRenderer` +* `WebGLTileLayer` and `WebGLTileLayerRenderer` +* `WebGLVectorLayer` and `WebGLVectorLayerRenderer` +* `WebGLReplay` and derived classes, along with associated shaders +* `WebGLReplayGroup` +* `WebGLImmediateRenderer` +* `WebGLMap` +* The shader build process using `mustache` and the `Makefile` at the root + + + ### v5.3.0 #### The `getUid` function returns string diff --git a/package.json b/package.json index 26fbbca6a2..02f3c7b341 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "loglevelnext": "^3.0.0", "marked": "0.5.1", "mocha": "5.2.0", - "mustache": "^3.0.0", "ol-mapbox-style": "^3.3.0", "pixelmatch": "^4.0.2", "pngjs": "^3.3.3", diff --git a/src/ol/layer/Heatmap.js b/src/ol/layer/Heatmap.js index 2ba6362439..7a40ca56ff 100644 --- a/src/ol/layer/Heatmap.js +++ b/src/ol/layer/Heatmap.js @@ -6,7 +6,7 @@ import {getChangeEventType} from '../Object.js'; import {createCanvasContext2D} from '../dom.js'; import VectorLayer from './Vector.js'; import {assign} from '../obj.js'; -import WebGLPointsLayerRenderer from "../renderer/webgl-new/PointsLayer"; +import WebGLPointsLayerRenderer from '../renderer/webgl-new/PointsLayer'; /** @@ -315,11 +315,11 @@ class Heatmap extends VectorLayer { }`, scaleRatio: 0.5, uniforms: { - u_blurSize: function (frameState) { + u_blurSize: function(frameState) { return [ this.get(Property.BLUR) / frameState.size[0], this.get(Property.BLUR) / frameState.size[1] - ] + ]; }.bind(this) } }, @@ -339,13 +339,13 @@ class Heatmap extends VectorLayer { gl_FragColor.a = color.a; }`, uniforms: { - u_gradientTexture: this.gradient_, + u_gradientTexture: this.gradient_ } } ], sizeCallback: function(feature) { return this.weightFunction_(feature); - }.bind(this), + }.bind(this) }); } } diff --git a/src/ol/render/Event.js b/src/ol/render/Event.js index b7a1917a4d..6d23e04f53 100644 --- a/src/ol/render/Event.js +++ b/src/ol/render/Event.js @@ -11,7 +11,7 @@ class RenderEvent extends Event { * @param {import("./VectorContext.js").default=} opt_vectorContext Vector context. * @param {import("../PluggableMap.js").FrameState=} opt_frameState Frame state. * @param {?CanvasRenderingContext2D=} opt_context Context. - * @param {?import("../webgl/Context.js").default=} opt_glContext WebGL Context. + * @param {?import("../webgl/Helper.js").default=} opt_glContext WebGL Context. */ constructor(type, opt_vectorContext, opt_frameState, opt_context, opt_glContext) { @@ -42,7 +42,7 @@ class RenderEvent extends Event { /** * WebGL context. Only available when a WebGL renderer is used, null * otherwise. - * @type {import("../webgl/Context.js").default|null|undefined} + * @type {import("../webgl/Helper.js").default|null|undefined} * @api */ this.glContext = opt_glContext; diff --git a/src/ol/renderer/webgl-new/PointsLayer.js b/src/ol/renderer/webgl-new/PointsLayer.js index 7265181fd7..c855d45092 100644 --- a/src/ol/renderer/webgl-new/PointsLayer.js +++ b/src/ol/renderer/webgl-new/PointsLayer.js @@ -5,9 +5,9 @@ import LayerRenderer from '../Layer'; import WebGLBuffer from '../../webgl/Buffer'; import {DYNAMIC_DRAW, ARRAY_BUFFER, ELEMENT_ARRAY_BUFFER, FLOAT} from '../../webgl'; import WebGLHelper, {DefaultAttrib, DefaultUniform} from '../../webgl/Helper'; -import WebGLVertex from "../../webgl/Vertex"; -import WebGLFragment from "../../webgl/Fragment"; -import GeometryType from "../../geom/GeometryType"; +import WebGLVertex from '../../webgl/Vertex'; +import WebGLFragment from '../../webgl/Fragment'; +import GeometryType from '../../geom/GeometryType'; const VERTEX_SHADER = ` precision mediump float; @@ -47,16 +47,89 @@ const FRAGMENT_SHADER = ` gl_FragColor.a = alpha; }`; +/** + * @typedef {Object} PostProcessesOptions + * @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than + * the main canvas that will then be sampled up (useful for saving resource on blur steps). + * @property {string} [vertexShader] Vertex shader source + * @property {string} [fragmentShader] Fragment shader source + * @property {Object.} [uniforms] Uniform definitions for the post process step + */ + +/** + * @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 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 only done on source change. + * The second argument is 0 for `x` component and 1 for `y`. + * @property {string} [vertexShader] Vertex shader source + * @property {string} [fragmentShader] Fragment shader source + * @property {Object.} [uniforms] Uniform definitions for the post process steps + * @property {Array} [postProcesses] Post-processes definitions + */ + /** * @classdesc - * Webgl vector renderer optimized for points. - * All features will be rendered as points. + * WebGL vector renderer optimized for points. + * 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. + * + * 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; + * + * uniform mat4 u_projectionMatrix; + * uniform mat4 u_offsetScaleMatrix; + * uniform mat4 u_offsetRotateMatrix; + * + * varying vec2 v_texCoord; + * + * 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; + * } + * ``` + * + * * Fragment shader: + * ``` + * precision mediump float; + * uniform float u_opacity; + * + * varying vec2 v_texCoord; + * + * void main(void) { + * gl_FragColor.rgb = vec3(1.0, 1.0, 1.0); + * float alpha = u_opacity; + * if (alpha == 0.0) { + * discard; + * } + * gl_FragColor.a = alpha; + * } + * ``` + * * @api */ class WebGLPointsLayerRenderer extends LayerRenderer { /** * @param {import("../../layer/Vector.js").default} vectorLayer Vector layer. + * @param {Options=} [opt_options] Options. */ constructor(vectorLayer, opt_options) { super(vectorLayer); @@ -84,7 +157,7 @@ class WebGLPointsLayerRenderer extends LayerRenderer { this.coordCallback_ = options.coordCallback || function(feature, index) { const geom = /** @type {import("../../geom/Point").default} */ (feature.getGeometry()); return geom.getCoordinates()[index]; - } + }; } /** @@ -126,18 +199,17 @@ class WebGLPointsLayerRenderer extends LayerRenderer { if (!feature.getGeometry() || feature.getGeometry().getType() !== GeometryType.POINT) { return; } - const geom = /** @type {import("../../geom/Point").default} */ (feature.getGeometry()); const x = this.coordCallback_(feature, 0); const y = this.coordCallback_(feature, 1); const size = this.sizeCallback_(feature); - let stride = 6; - let baseIndex = this.verticesBuffer_.getArray().length / stride; + const stride = 6; + const baseIndex = this.verticesBuffer_.getArray().length / stride; this.verticesBuffer_.getArray().push( x, y, -size / 2, -size / 2, 0, 0, x, y, +size / 2, -size / 2, 1, 0, x, y, +size / 2, +size / 2, 1, 1, - x, y, -size / 2, +size / 2, 0, 1, + x, y, -size / 2, +size / 2, 0, 1 ); this.indicesBuffer_.getArray().push( baseIndex, baseIndex + 1, baseIndex + 3, @@ -150,7 +222,7 @@ class WebGLPointsLayerRenderer extends LayerRenderer { this.context_.bindBuffer(ARRAY_BUFFER, this.verticesBuffer_); this.context_.bindBuffer(ELEMENT_ARRAY_BUFFER, this.indicesBuffer_); - let bytesPerFloat = Float32Array.BYTES_PER_ELEMENT; + const 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); diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index 6e6205ce4e..da13c6f90a 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -15,10 +15,10 @@ import { rotate as rotateTransform, scale as scaleTransform, translate as translateTransform -} from "../transform"; -import {create, fromTransform} from "../vec/mat4"; -import WebGLBuffer from "./Buffer"; -import WebGLPostProcessingPass from "./PostProcessingPass"; +} from '../transform'; +import {create, fromTransform} from '../vec/mat4'; +import WebGLBuffer from './Buffer'; +import WebGLPostProcessingPass from './PostProcessingPass'; /** @@ -27,6 +27,11 @@ import WebGLPostProcessingPass from "./PostProcessingPass"; * @property {WebGLBuffer} buffer */ +/** + * Uniform names used in the default shaders. + * @const + * @type {Object.} + */ export const DefaultUniform = { PROJECTION_MATRIX: 'u_projectionMatrix', OFFSET_SCALE_MATRIX: 'u_offsetScaleMatrix', @@ -34,6 +39,11 @@ export const DefaultUniform = { OPACITY: 'u_opacity' }; +/** + * Attribute names used in the default shaders. + * @const + * @type {Object.} + */ export const DefaultAttrib = { POSITION: 'a_position', TEX_COORD: 'a_texCoord', @@ -42,15 +52,149 @@ export const DefaultAttrib = { OFFSETS: 'a_offsets' }; +/** + * @typedef {number|Array|HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} UniformLiteralValue + */ + +/** + * Uniform value can be a number, array of numbers (2 to 4), canvas element or a callback returning + * one of the previous types. + * @typedef {UniformLiteralValue|function(import("../PluggableMap.js").FrameState):UniformLiteralValue} UniformValue + */ + +/** + * @typedef {Object} PostProcessesOptions + * @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than + * the main canvas that will then be sampled up (useful for saving resource on blur steps). + * @property {string} [vertexShader] Vertex shader source + * @property {string} [fragmentShader] Fragment shader source + * @property {Object.} [uniforms] Uniform definitions for the post process step + */ + +/** + * @typedef {Object} Options + * @property {Object.} [uniforms] Uniform definitions; property namesmust math the uniform + * names in the provided or default shaders. + * @property {Array} [postProcesses] Post-processes definitions + */ + +/** + * @typedef {Object} UniformInternalDescription + * @property {string} name Name + * @property {WebGLTexture} [texture] Texture + * @private + */ + /** * @classdesc - * A WebGL context for accessing low-level WebGL capabilities. - * Will handle attributes, uniforms, buffers, textures, frame buffers. - * The context will always render to a frame buffer in order to allow post-processing. + * This class is intended to provide low-level functions related to WebGL rendering, so that accessing + * directly the WebGL API should not be required anymore. + * + * Several operations are handled by the `WebGLHelper` class: + * + * ### Define custom shaders and uniforms + * + * *Shaders* are low-level programs executed on the GPU and written in GLSL. There are two types of shaders: + * + * Vertex shaders are used to manipulate the position and attribute of *vertices* of rendered primitives (ie. corners of a square). + * Outputs are: + * + * * `gl_Position`: position of the vertex in screen space + * + * * Varyings usually prefixed with `v_` are passed on to the fragment shader + * + * Fragment shaders are used to control the actual color of the pixels rawn on screen. Their only output is `gl_FragColor`. + * + * Both shaders can take *uniforms* or *attributes* as input. Attributes are explained later. Uniforms are common, read-only values that + * can be changed at every frame and can be of type float, arrays of float or images. + * + * Shaders must be compiled and assembled into a program like so: + * ```js + * // here we simply create two shaders and assemble them in a program which is then used + * // 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); + * ``` + * + * 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(DefaultUniform.OPACITY, layerState.opacity); + * ``` + * + * ### Defining post processing passes + * + * *Post processing* describes the act of rendering primitives to a texture, and then rendering this texture to the final canvas + * while applying special effects in screen space. + * Typical uses are: blurring, color manipulation, depth of field, filtering... + * + * The `WebGLHelper` class offers the possibility to define post processes at creation time using the `postProcesses` option. + * A post process step accepts the following options: + * + * * `fragmentShader` and `vertexShader`: text literals in GLSL language that will be compiled and used in the post processing step. + * * `uniforms`: uniforms can be defined for the post processing steps just like for the main render. + * * `scaleRatio`: allows using an intermediate texture smaller or higher than the final canvas in the post processing step. + * This is typically used in blur steps to reduce the performance overhead by using an already downsampled texture as input. + * + * The {@link module:ol/webgl/PostProcessingPass~WebGLPostProcessingPass} class is used internally, refer to its documentation for more info. + * + * ### Binding WebGL buffers and flushing data into them: + * + * Data that must be passed to the GPU has to be transferred using `WebGLBuffer` objects. + * A buffer has to be created only once, but must be bound everytime the data it holds is changed. Using `WebGLHelper.bindBuffer` + * will bind the buffer and flush the new data to the GPU. + * + * For now, the `WebGLHelper` class expects {@link module:ol/webgl/Buffer~WebGLArrayBuffer} objects. + * ```js + * // at initialization phase + * this.verticesBuffer = new WebGLBuffer([], DYNAMIC_DRAW); + * this.indicesBuffer = new WebGLBuffer([], DYNAMIC_DRAW); + * + * // at rendering phase + * this.context.bindBuffer(ARRAY_BUFFER, this.verticesBuffer); + * this.context.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 `WebGLHelper.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); + * ``` + * + * ### Rendering primitives + * + * Once all the steps above have been achieved, rendering primitives to the screen is done using `WebGLHelper.prepareDraw` `drawElements` and `finalizeDraw`. + * ```js + * // frame preparation step + * this.context.prepareDraw(frameState); + * + * // call this for every data array that has to be rendered on screen + * this.context.drawElements(0, this.indicesBuffer.getArray().length); + * + * // finalize the rendering by applying post processes + * this.context.finalizeDraw(frameState); + * ``` + * + * For an example usage of this class, refer to {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}. + * + * + * @api */ class WebGLHelper extends Disposable { - /** + * @param {Options=} opt_options Options. */ constructor(opt_options) { super(); @@ -69,6 +213,7 @@ class WebGLHelper extends Disposable { * @type {WebGLRenderingContext} */ this.gl_ = this.canvas_.getContext('webgl'); + const gl = this.getGL(); /** * @private @@ -101,7 +246,7 @@ class WebGLHelper extends Disposable { // use the OES_element_index_uint extension if available if (this.hasOESElementIndexUint) { - this.gl_.getExtension('OES_element_index_uint'); + gl.getExtension('OES_element_index_uint'); } listen(this.canvas_, ContextEventType.LOST, @@ -146,8 +291,9 @@ class WebGLHelper extends Disposable { this.attribLocations_; /** - * Holds info about custom uniforms used in the post processing pass - * @type {Array<{value: *, texture?: WebGLTexture}>} + * Holds info about custom uniforms used in the post processing pass. + * If the uniform is a texture, the WebGL Texture object will be stored here. + * @type {Array} * @private */ this.uniforms_ = []; @@ -158,10 +304,14 @@ class WebGLHelper extends Disposable { }); }.bind(this)); - // initialize post processes from options - // if none given, use a default one - const gl = this.getGL(); - this.postProcessPasses = options.postProcesses ? options.postProcesses.map(function(options) { + /** + * An array of PostProcessingPass objects is kept in this variable, built from the steps provided in the + * options. If no post process was given, a default one is used (so as not to have to make an exception to + * the frame buffer logic). + * @type {Array} + * @private + */ + this.postProcessPasses_ = options.postProcesses ? options.postProcesses.map(function(options) { return new WebGLPostProcessingPass({ webGlContext: gl, scaleRatio: options.scaleRatio, @@ -169,7 +319,7 @@ class WebGLHelper extends Disposable { fragmentShader: options.fragmentShader, uniforms: options.uniforms }); - }) : [new WebGLPostProcessingPass({ webGlContext: gl })]; + }) : [new WebGLPostProcessingPass({webGlContext: gl})]; } /** @@ -178,7 +328,8 @@ class WebGLHelper extends Disposable { * the cache. * TODO: improve this, the logic is unclear: we want A/ to bind a buffer and B/ to flush data in it * @param {number} target Target. - * @param {WebGLBuffer} buf Buffer. + * @param {import("./Buffer").default} buf Buffer. + * @api */ bindBuffer(target, buf) { const gl = this.getGL(); @@ -236,7 +387,11 @@ class WebGLHelper extends Disposable { } /** - * Clear the buffer & set the viewport to draw + * Clear the buffer & set the viewport to draw. + * Post process passes will be initialized here, the first one being bound as a render target for + * subsequent draw calls. + * @param {import("../PluggableMap.js").FrameState} frameState current frame state + * @api */ prepareDraw(frameState) { const gl = this.getGL(); @@ -252,8 +407,8 @@ class WebGLHelper extends Disposable { gl.useProgram(this.currentProgram_); // loop backwards in post processes list - for (let i = this.postProcessPasses.length - 1; i >= 0; i--) { - this.postProcessPasses[i].init(size, pixelRatio); + for (let i = this.postProcessPasses_.length - 1; i >= 0; i--) { + this.postProcessPasses_[i].init(frameState); } gl.bindTexture(gl.TEXTURE_2D, null); @@ -263,14 +418,15 @@ class WebGLHelper extends Disposable { gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA); - this.applyFrameState(frameState) + this.applyFrameState(frameState); this.applyUniforms(frameState); } /** - * @protected + * Execute a draw call based on the currently bound program, texture, buffers, attributes. * @param {number} start Start index. * @param {number} end End index. + * @api */ drawElements(start, end) { const gl = this.getGL(); @@ -284,17 +440,20 @@ class WebGLHelper extends Disposable { } /** - * Copy the frame buffer to the canvas + * Apply the successive post process passes which will eventually render to the actual canvas. + * @param {import("../PluggableMap.js").FrameState} frameState current frame state + * @api */ finalizeDraw(frameState) { // apply post processes using the next one as target - for (let i = 0; i < this.postProcessPasses.length; i++) { - this.postProcessPasses[i].apply(frameState, this.postProcessPasses[i + 1] || null); + for (let i = 0; i < this.postProcessPasses_.length; i++) { + this.postProcessPasses_[i].apply(frameState, this.postProcessPasses_[i + 1] || null); } } /** * @return {HTMLCanvasElement} Canvas. + * @api */ getCanvas() { return this.canvas_; @@ -310,8 +469,9 @@ class WebGLHelper extends Disposable { } /** - * Sets the matrices uniforms for a given frame state + * Sets the default matrix uniforms for a given frame state. This is called internally in `prepareDraw`. * @param {import("../PluggableMap.js").FrameState} frameState Frame state. + * @private */ applyFrameState(frameState) { const size = frameState.size; @@ -338,7 +498,11 @@ class WebGLHelper extends Disposable { this.setUniformMatrixValue(DefaultUniform.OFFSET_ROTATION_MATRIX, fromTransform(this.tmpMat4_, offsetRotateMatrix)); } - // todo + /** + * Sets the custom uniforms based on what was given in the constructor. This is called internally in `prepareDraw`. + * @param {import("../PluggableMap.js").FrameState} frameState Frame state. + * @private + */ applyUniforms(frameState) { const gl = this.getGL(); @@ -380,6 +544,8 @@ class WebGLHelper extends Disposable { case 4: gl.uniform4f(this.getUniformLocation(uniform.name), value[0], value[1], value[2], value[3]); return; + default: + return; } } else if (typeof value === 'number') { gl.uniform1f(this.getUniformLocation(uniform.name), value); @@ -390,8 +556,10 @@ class WebGLHelper extends Disposable { /** * Get shader from the cache if it's in the cache. Otherwise, create * the WebGL shader, compile it, and add entry to cache. + * TODO: make compilation errors show up * @param {import("./Shader.js").default} shaderObject Shader object. * @return {WebGLShader} Shader. + * @api */ getShader(shaderObject) { const shaderKey = getUid(shaderObject); @@ -402,9 +570,6 @@ class WebGLHelper extends Disposable { const shader = gl.createShader(shaderObject.getType()); gl.shaderSource(shader, shaderObject.getSource()); gl.compileShader(shader); - if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) { - console.error(`Shader compilation failed - log:\n${gl.getShaderInfoLog(shader)}`); - } this.shaderCache_[shaderKey] = shader; return shader; } @@ -436,6 +601,7 @@ class WebGLHelper extends Disposable { * @param {import("./Fragment.js").default} fragmentShaderObject Fragment shader. * @param {import("./Vertex.js").default} vertexShaderObject Vertex shader. * @return {WebGLProgram} Program. + * @api */ getProgram(fragmentShaderObject, vertexShaderObject) { const programKey = getUid(fragmentShaderObject) + '/' + getUid(vertexShaderObject); @@ -456,6 +622,7 @@ class WebGLHelper extends Disposable { * Will get the location from the shader or the cache * @param {string} name Uniform name * @return {WebGLUniformLocation} uniformLocation + * @api */ getUniformLocation(name) { if (!this.uniformLocations_[name]) { @@ -468,6 +635,7 @@ class WebGLHelper extends Disposable { * Will get the location from the shader or the cache * @param {string} name Attribute name * @return {number} attribLocation + * @api */ getAttributeLocation(name) { if (!this.attribLocations_[name]) { @@ -480,6 +648,7 @@ class WebGLHelper extends Disposable { * Give a value for a standard float uniform * @param {string} uniform Uniform name * @param {number} value Value + * @api */ setUniformFloatValue(uniform, value) { this.getGL().uniform1f(this.getUniformLocation(uniform), value); @@ -489,6 +658,7 @@ class WebGLHelper extends Disposable { * Give a value for a standard matrix4 uniform * @param {string} uniform Uniform name * @param {Array} value Matrix value + * @api */ setUniformMatrixValue(uniform, value) { this.getGL().uniformMatrix4fv(this.getUniformLocation(uniform), false, value); @@ -496,11 +666,12 @@ class WebGLHelper extends Disposable { /** * Will set the currently bound buffer to an attribute of the shader program - * @param {string} attribName + * @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 */ enableAttributeArray(attribName, size, type, stride, offset) { this.getGL().enableVertexAttribArray(this.getAttributeLocation(attribName)); @@ -509,7 +680,8 @@ class WebGLHelper extends Disposable { } /** - * FIXME empty description for jsdoc + * WebGL context was lost + * @private */ handleWebGLContextLost() { clear(this.bufferCache_); @@ -519,7 +691,8 @@ class WebGLHelper extends Disposable { } /** - * FIXME empty description for jsdoc + * WebGL context was restored + * @private */ handleWebGLContextRestored() { } @@ -527,6 +700,7 @@ class WebGLHelper extends Disposable { // TODO: shutdown program /** + * TODO: these are not used and should be reworked * @param {number=} opt_wrapS wrapS. * @param {number=} opt_wrapT wrapT. * @return {WebGLTexture} The texture. @@ -551,6 +725,7 @@ class WebGLHelper extends Disposable { } /** + * TODO: these are not used and should be reworked * @param {number} width Width. * @param {number} height Height. * @param {number=} opt_wrapS wrapS. @@ -559,13 +734,14 @@ class WebGLHelper extends Disposable { */ createEmptyTexture(width, height, opt_wrapS, opt_wrapT) { const gl = this.getGL(); - const texture = this.createTextureInternal( opt_wrapS, opt_wrapT); + const texture = this.createTextureInternal(opt_wrapS, opt_wrapT); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); return texture; } /** + * TODO: these are not used and should be reworked * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} image Image. * @param {number=} opt_wrapS wrapS. * @param {number=} opt_wrapT wrapT. diff --git a/src/ol/webgl/PostProcessingPass.js b/src/ol/webgl/PostProcessingPass.js index 79396d6a97..68ff8196c4 100644 --- a/src/ol/webgl/PostProcessingPass.js +++ b/src/ol/webgl/PostProcessingPass.js @@ -31,10 +31,70 @@ const DEFAULT_FRAGMENT_SHADER = ` } `; +/** + * @typedef {Object} Options + * @property {WebGLContext} webGlContext WebGL context; mandatory. + * @property {number} [scaleRatio] Scale ratio; if < 1, the post process will render to a texture smaller than + * the main canvas that will then be sampled up (useful for saving resource on blur steps). + * @property {string} [vertexShader] Vertex shader source + * @property {string} [fragmentShader] Fragment shader source + * @property {Object.} [uniforms] Uniform definitions for the post process step + */ + +/** + * @typedef {Object} UniformInternalDescription + * @property {UniformValue} value Value + * @property {number} location Location + * @property {WebGLTexture} [texture] Texture + * @private + */ + +/** + * @classdesc + * This class is used to define Post Processing passes with custom shaders and uniforms. + * This is used internally by {@link module:ol/webgl/Helper~WebGLHelper}. + * + * Default shaders are shown hereafter: + * + * * Vertex shader: + * + * ``` + * precision mediump float; + * + * attribute vec2 a_position; + * varying vec2 v_texCoord; + * varying vec2 v_screenCoord; + * + * uniform vec2 u_screenSize; + * + * void main() { + * v_texCoord = a_position * 0.5 + 0.5; + * v_screenCoord = v_texCoord * u_screenSize; + * gl_Position = vec4(a_position, 0.0, 1.0); + * } + * ``` + * + * * Fragment shader: + * + * ``` + * precision mediump float; + * + * uniform sampler2D u_image; + * + * varying vec2 v_texCoord; + * varying vec2 v_screenCoord; + * + * void main() { + * gl_FragColor = texture2D(u_image, v_texCoord); + * } + * ``` + * + * @api + */ class WebGLPostProcessingPass { /** - * // todo + * @param {Options=} options Options. */ constructor(options) { this.gl_ = options.webGlContext; @@ -48,18 +108,13 @@ class WebGLPostProcessingPass { this.frameBuffer_ = gl.createFramebuffer(); // compile the program for the frame buffer + // TODO: make compilation errors show up const vertexShader = gl.createShader(gl.VERTEX_SHADER); gl.shaderSource(vertexShader, options.vertexShader || DEFAULT_VERTEX_SHADER); gl.compileShader(vertexShader); - if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { - console.error(`Shader compilation failed - log:\n${gl.getShaderInfoLog(vertexShader)}`); - } const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, options.fragmentShader || DEFAULT_FRAGMENT_SHADER); gl.compileShader(fragmentShader); - if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { - console.error(`Shader compilation failed - log:\n${gl.getShaderInfoLog(fragmentShader)}`); - } this.renderTargetProgram_ = gl.createProgram(); gl.attachShader(this.renderTargetProgram_, vertexShader); gl.attachShader(this.renderTargetProgram_, fragmentShader); @@ -84,7 +139,7 @@ class WebGLPostProcessingPass { /** * Holds info about custom uniforms used in the post processing pass - * @type {Array<{value: *, location: WebGLUniformLocation, texture?: WebGLTexture}>} + * @type {Array} * @private */ this.uniforms_ = []; @@ -105,11 +160,17 @@ class WebGLPostProcessingPass { return this.gl_; } - // todo - // the last postprocess initialized will be the one where the primitives are drawn - init(size) { + /** + * Initialize the render target texture of the post process, make sure it is at the + * right size and bind it as a render target for the next draw calls. + * The last step to be initialized will be the one where the primitives are rendered. + * @param {import("../PluggableMap.js").FrameState} frameState current frame state + * @api + */ + init(frameState) { const gl = this.getGL(); const canvas = gl.canvas; + const size = frameState.size; // rendering goes to my buffer gl.bindFramebuffer(gl.FRAMEBUFFER, this.getFrameBuffer()); @@ -141,8 +202,12 @@ class WebGLPostProcessingPass { } } - // todo - // render to the next postprocessing pass (or to the canvas if final pass) + /** + * Render to the next postprocessing pass (or to the canvas if final pass). + * @param {import("../PluggableMap.js").FrameState} frameState current frame state + * @param {WebGLPostProcessingPass} [nextPass] Next pass, optional + * @api + */ apply(frameState, nextPass) { const gl = this.getGL(); const canvas = gl.canvas; @@ -171,12 +236,19 @@ class WebGLPostProcessingPass { gl.drawArrays(gl.TRIANGLES, 0, 6); } - // todo + /** + * @returns {WebGLFramebuffer} Frame buffer + * @api + */ getFrameBuffer() { return this.frameBuffer_; } - // todo + /** + * Sets the custom uniforms based on what was given in the constructor. + * @param {import("../PluggableMap.js").FrameState} frameState Frame state. + * @private + */ applyUniforms(frameState) { const gl = this.getGL(); @@ -218,6 +290,7 @@ class WebGLPostProcessingPass { case 4: gl.uniform4f(uniform.location, value[0], value[1], value[2], value[3]); return; + default: return; } } else if (typeof value === 'number') { gl.uniform1f(uniform.location, value);