From 9ee93cd2cf5c4ef9d3e774370cc96c318a1433d9 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Tue, 22 Oct 2019 11:12:04 +0200 Subject: [PATCH] ShaderBuilder / refactoring to use an actual builder pattern There is now a ShaderBuilder class with chainable methods to specify more easily the contents of the final shaders. This is to avoid passing around large objects to different functions, and allow for a richer API. The documentation has also been corrected and clarified. --- src/ol/layer/WebGLPoints.js | 6 +- src/ol/webgl/ShaderBuilder.js | 464 ++++++++++++++++------- test/spec/ol/webgl/shaderbuilder.test.js | 181 ++++----- 3 files changed, 402 insertions(+), 249 deletions(-) diff --git a/src/ol/layer/WebGLPoints.js b/src/ol/layer/WebGLPoints.js index f5b375f163..32bc23da4b 100644 --- a/src/ol/layer/WebGLPoints.js +++ b/src/ol/layer/WebGLPoints.js @@ -3,7 +3,7 @@ */ import {assign} from '../obj.js'; import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js'; -import {getSymbolFragmentShader, getSymbolVertexShader, parseSymbolStyle} from '../webgl/ShaderBuilder.js'; +import {parseSymbolStyle} from '../webgl/ShaderBuilder.js'; import Layer from './Layer.js'; @@ -82,8 +82,8 @@ class WebGLPointsLayer extends Layer { */ createRenderer() { return new WebGLPointsLayerRenderer(this, { - vertexShader: getSymbolVertexShader(this.parseResult_.params), - fragmentShader: getSymbolFragmentShader(this.parseResult_.params), + vertexShader: this.parseResult_.builder.getSymbolVertexShader(), + fragmentShader: this.parseResult_.builder.getSymbolFragmentShader(), uniforms: this.parseResult_.uniforms, attributes: this.parseResult_.attributes }); diff --git a/src/ol/webgl/ShaderBuilder.js b/src/ol/webgl/ShaderBuilder.js index 20f5cd2750..9224c829b7 100644 --- a/src/ol/webgl/ShaderBuilder.js +++ b/src/ol/webgl/ShaderBuilder.js @@ -1,5 +1,5 @@ /** - * Utilities for generating shaders from literal style objects + * Classes and utilities for generating shaders from literal style objects * @module ol/webgl/ShaderBuilder */ @@ -37,121 +37,6 @@ export function formatColor(colorArray) { }).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} [uniforms] Uniforms; these will be declared in the header (should include the type). - * @property {Array} [attributes] Attributes; these will be declared in the header (should include the type). - * @property {Array} [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). - * - * The following varyings are hardcoded and gives the coordinate of the pixel both in the quad on the texture: - * `vec2 v_quadCoord`, `vec2 v_texCoord` - * - * @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; -varying vec2 v_quadCoord; -${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); - u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0; - v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0; - v_quadCoord = 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; -varying vec2 v_quadCoord; -${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; -} - /** * Base type for values fed to operators; can be a number literal or the output of another operator * @typedef {Array<*>|number} OperatorValue @@ -178,7 +63,7 @@ void main(void) { * the final assignment string. * * @param {OperatorValue} value Either literal or an operator. - * @param {Array} attributes Array containing the attribute names prefixed with `a_`; it + * @param {Array} attributes Array containing the attribute names **without a prefix**; * it passed along recursively * @param {string} attributePrefix Prefix added to attribute names in the final output (typically `a_` or `v_`). * @returns {string} Assignment string. @@ -208,16 +93,304 @@ export function parse(value, attributes, attributePrefix) { } } +/** + * @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. + */ + +/** + * @classdesc + * This class implements a classic builder pattern for generating many different types of shaders. + * Methods can be chained, e. g.: + * + * ```js + * const shader = new ShaderBuilder() + * .addVarying('v_width', 'float', 'a_width') + * .addUniform('u_time') + * .setColorExpression('...') + * .setSizeExpression('...') + * .outputSymbolFragmentShader(); + * ``` + */ +export class ShaderBuilder { + constructor() { + /** + * Uniforms; these will be declared in the header (should include the type). + * @type {Array} + * @private + */ + this.uniforms = []; + + /** + * Attributes; these will be declared in the header (should include the type). + * @type {Array} + * @private + */ + this.attributes = []; + + /** + * Varyings with a name, a type and an expression. + * @type {Array} + * @private + */ + this.varyings = []; + + /** + * @type {string} + * @private + */ + this.sizeExpression = 'vec2(1.0)'; + + /** + * @type {string} + * @private + */ + this.offsetExpression = 'vec2(0.0)'; + + /** + * @type {string} + * @private + */ + this.colorExpression = 'vec4(1.0)'; + + /** + * @type {string} + * @private + */ + this.texCoordExpression = 'vec4(0.0, 0.0, 1.0, 1.0)'; + + /** + * @type {boolean} + * @private + */ + this.rotateWithView = false; + } + + /** + * Adds a uniform accessible in both fragment and vertex shaders. + * The given name should include a type, such as `sampler2D u_texture`. + * @param {string} name Uniform name + * @return {ShaderBuilder} the builder object + */ + addUniform(name) { + this.uniforms.push(name); + return this; + } + + /** + * Adds an attribute accessible in the vertex shader, read from the geometry buffer. + * The given name should include a type, such as `vec2 a_position`. + * @param {string} name Attribute name + * @return {ShaderBuilder} the builder object + */ + addAttribute(name) { + this.attributes.push(name); + return this; + } + + /** + * Adds a varying defined in the vertex shader and accessible from the fragment shader. + * The type and expression of the varying have to be specified separately. + * @param {string} name Varying name + * @param {'float'|'vec2'|'vec3'|'vec4'} type Type + * @param {string} expression Expression used to assign a value to the varying. + * @return {ShaderBuilder} the builder object + */ + addVarying(name, type, expression) { + this.varyings.push({ + name: name, + type: type, + expression: expression + }); + return this; + } + + /** + * Sets an expression to compute the size of the shape. + * This expression can use all the uniforms and attributes available + * in the vertex shader, and should evaluate to a `vec2` value. + * @param {string} expression Size expression + * @return {ShaderBuilder} the builder object + */ + setSizeExpression(expression) { + this.sizeExpression = expression; + return this; + } + + /** + * Sets an expression to compute the offset of the symbol from the point center. + * This expression can use all the uniforms and attributes available + * in the vertex shader, and should evaluate to a `vec2` value. + * Note: will only be used for point geometry shaders. + * @param {string} expression Offset expression + * @return {ShaderBuilder} the builder object + */ + setSymbolOffsetExpression(expression) { + this.offsetExpression = expression; + return this; + } + + /** + * Sets an expression to compute the color of the shape. + * This expression can use all the uniforms, varyings and attributes available + * in the fragment shader, and should evaluate to a `vec4` value. + * @param {string} expression Color expression + * @return {ShaderBuilder} the builder object + */ + setColorExpression(expression) { + this.colorExpression = expression; + return this; + } + + /** + * Sets an expression to compute the texture coordinates of the vertices. + * This expression can use all the uniforms and attributes available + * in the vertex shader, and should evaluate to a `vec4` value. + * @param {string} expression Texture coordinate expression + * @return {ShaderBuilder} the builder object + */ + setTextureCoordinateExpression(expression) { + this.texCoordExpression = expression; + return this; + } + + /** + * Sets whether the symbols should rotate with the view or stay aligned with the map. + * Note: will only be used for point geometry shaders. + * @param {boolean} rotateWithView Rotate with view + * @return {ShaderBuilder} the builder object + */ + setSymbolRotateWithView(rotateWithView) { + this.rotateWithView = rotateWithView; + return this; + } + + /** + * @returns {string} Previously set size expression + */ + getSizeExpression() { + return this.sizeExpression; + } + + /** + * @returns {string} Previously set symbol offset expression + */ + getOffsetExpression() { + return this.offsetExpression; + } + + /** + * @returns {string} Previously set color expression + */ + getColorExpression() { + return this.colorExpression; + } + + /** + * @returns {string} Previously set texture coordinate expression + */ + getTextureCoordinateExpression() { + return this.texCoordExpression; + } + + /** + * Generates a symbol vertex shader from the builder 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). + * + * The following varyings are hardcoded and gives the coordinate of the pixel both in the quad and on the texture: + * `vec2 v_quadCoord`, `vec2 v_texCoord` + * + * @returns {string} The full shader as a string. + */ + getSymbolVertexShader() { + const offsetMatrix = this.rotateWithView ? + 'u_offsetScaleMatrix * u_offsetRotateMatrix' : + 'u_offsetScaleMatrix'; + + return `precision mediump float; +uniform mat4 u_projectionMatrix; +uniform mat4 u_offsetScaleMatrix; +uniform mat4 u_offsetRotateMatrix; +${this.uniforms.map(function(uniform) { + return 'uniform ' + uniform + ';'; + }).join('\n')} +attribute vec2 a_position; +attribute float a_index; +${this.attributes.map(function(attribute) { + return 'attribute ' + attribute + ';'; + }).join('\n')} +varying vec2 v_texCoord; +varying vec2 v_quadCoord; +${this.varyings.map(function(varying) { + return 'varying ' + varying.type + ' ' + varying.name + ';'; + }).join('\n')} +void main(void) { + mat4 offsetMatrix = ${offsetMatrix}; + vec2 size = ${this.sizeExpression}; + vec2 offset = ${this.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 = ${this.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); + u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0; + v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0; + v_quadCoord = vec2(u, v); +${this.varyings.map(function(varying) { + return ' ' + varying.name + ' = ' + varying.expression + ';'; + }).join('\n')} +}`; + } + + /** + * Generates a symbol fragment shader from the builder parameters, + * intended to be used on point geometries. + * + * Expects the following varyings to be transmitted by the vertex shader: + * `vec2 v_quadCoord`, `vec2 v_texCoord` + * + * @returns {string} The full shader as a string. + */ + getSymbolFragmentShader() { + return `precision mediump float; +${this.uniforms.map(function(uniform) { + return 'uniform ' + uniform + ';'; + }).join('\n')} +varying vec2 v_texCoord; +varying vec2 v_quadCoord; +${this.varyings.map(function(varying) { + return 'varying ' + varying.type + ' ' + varying.name + ';'; + }).join('\n')} +void main(void) { + gl_FragColor = ${this.colorExpression}; + gl_FragColor.rgb *= gl_FragColor.a; +}`; + } +} + /** * @typedef {Object} StyleParseResult - * @property {ShaderParameters} params Symbol shader params. + * @property {ShaderBuilder} builder Shader builder pre-configured according to a given style * @property {Object.} uniforms Uniform definitions. * @property {Array} attributes Attribute descriptions. */ /** - * Parses a {@link import("../style/LiteralStyle").LiteralSymbolStyle} object and outputs shader parameters to be - * then fed to {@link getSymbolVertexShader} and {@link getSymbolFragmentShader}. + * Parses a {@link import("../style/LiteralStyle").LiteralSymbolStyle} object and returns a {@link ShaderBuilder} + * object that has been configured according to the given style, as well as `attributes` and `uniforms` + * arrays to be fed to the `WebGLPointsRenderer` class. * * Also returns `uniforms` and `attributes` properties as expected by the * {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}. @@ -237,7 +410,7 @@ export function parseSymbolStyle(style) { const offset = style.offset || [0, 0]; const opacity = style.opacity !== undefined ? style.opacity : 1; - let attributes = []; + const attributes = []; const varyings = []; function pA(value) { return parse(value, attributes, 'a_'); @@ -264,45 +437,44 @@ export function parseSymbolStyle(style) { default: throw new Error('Unexpected symbol type: ' + style.symbolType); } - /** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */ - const params = { - uniforms: [], - colorExpression: `vec4(${pV(color[0])}, ${pV(color[1])}, ${pV(color[2])}, ${pV(color[3])})` + - ` * vec4(1.0, 1.0, 1.0, ${pV(opacity)} * ${opacityFilter})`, - sizeExpression: `vec2(${pA(size[0])}, ${pA(size[1])})`, - offsetExpression: `vec2(${pA(offset[0])}, ${pA(offset[1])})`, - texCoordExpression: `vec4(${pA(texCoord[0])}, ${pA(texCoord[1])}, ${pA(texCoord[2])}, ${pA(texCoord[3])})`, - rotateWithView: !!style.rotateWithView - }; + const builder = new ShaderBuilder() + .setSizeExpression(`vec2(${pA(size[0])}, ${pA(size[1])})`) + .setSymbolOffsetExpression(`vec2(${pA(offset[0])}, ${pA(offset[1])})`) + .setTextureCoordinateExpression( + `vec4(${pA(texCoord[0])}, ${pA(texCoord[1])}, ${pA(texCoord[2])}, ${pA(texCoord[3])})`) + .setSymbolRotateWithView(!!style.rotateWithView) + .setColorExpression(`vec4(${pV(color[0])}, ${pV(color[1])}, ${pV(color[2])}, ${pV(color[3])})` + + ` * vec4(1.0, 1.0, 1.0, ${pV(opacity)} * ${opacityFilter})`); - attributes = attributes.concat(varyings).filter(function(attrName, index, arr) { - return arr.indexOf(attrName) === index; + // define the varyings that will be used to pass data from the vertex to the fragment shaders + // note: these should also be defined as attributes (if not already) + varyings.forEach(function(varyingName) { + if (attributes.indexOf(varyingName) === -1) { + attributes.push(varyingName); + } + builder.addVarying(`v_${varyingName}`, 'float', `a_${varyingName}`); }); - params.attributes = attributes.map(function(attributeName) { - return `float a_${attributeName}`; - }); - params.varyings = varyings.map(function(attributeName) { - return { - name: `v_${attributeName}`, - type: 'float', - expression: `a_${attributeName}` - }; + + // define the attributes from the features in the buffer + attributes.forEach(function(attributeName) { + builder.addAttribute(`float a_${attributeName}`); }); + /** @type {Object.} */ const uniforms = {}; if (style.symbolType === 'image' && style.src) { const texture = new Image(); texture.src = style.src; - params.uniforms.push('sampler2D u_texture'); - params.colorExpression = params.colorExpression + - ' * texture2D(u_texture, v_texCoord)'; + builder.addUniform('sampler2D u_texture') + .setColorExpression(builder.getColorExpression() + + ' * texture2D(u_texture, v_texCoord)'); uniforms['u_texture'] = texture; } return { - params: params, + builder: builder, attributes: attributes.map(function(attributeName) { return { name: attributeName, diff --git a/test/spec/ol/webgl/shaderbuilder.test.js b/test/spec/ol/webgl/shaderbuilder.test.js index e29f426b32..402eda8ae5 100644 --- a/test/spec/ol/webgl/shaderbuilder.test.js +++ b/test/spec/ol/webgl/shaderbuilder.test.js @@ -1,8 +1,10 @@ import { - getSymbolVertexShader, + formatArray, + formatColor, formatNumber, - getSymbolFragmentShader, - formatColor, formatArray, parse, parseSymbolStyle + parse, + parseSymbolStyle, + ShaderBuilder } from '../../../../src/ol/webgl/ShaderBuilder.js'; describe('ol.webgl.ShaderBuilder', function() { @@ -31,24 +33,15 @@ describe('ol.webgl.ShaderBuilder', function() { describe('getSymbolVertexShader', function() { it('generates a symbol vertex shader (with varying)', function() { - const parameters = { - varyings: [{ - name: 'v_opacity', - type: 'float', - expression: formatNumber(0.4) - }, { - name: 'v_test', - type: 'vec3', - expression: 'vec3(' + formatArray([1, 2, 3]) + ')' - }], - sizeExpression: 'vec2(' + formatNumber(6) + ')', - offsetExpression: 'vec2(' + formatArray([5, -7]) + ')', - colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')', - texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')', - rotateWithView: false - }; + const builder = new ShaderBuilder(); + builder.addVarying('v_opacity', 'float', formatNumber(0.4)); + builder.addVarying('v_test', 'vec3', 'vec3(' + formatArray([1, 2, 3]) + ')'); + builder.setSizeExpression('vec2(' + formatNumber(6) + ')'); + builder.setSymbolOffsetExpression('vec2(' + formatArray([5, -7]) + ')'); + builder.setColorExpression('vec4(' + formatColor([80, 0, 255, 1]) + ')'); + builder.setTextureCoordinateExpression('vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'); - expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float; + expect(builder.getSymbolVertexShader()).to.eql(`precision mediump float; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; @@ -80,16 +73,15 @@ void main(void) { }`); }); it('generates a symbol vertex shader (with uniforms and attributes)', function() { - const parameters = { - uniforms: ['float u_myUniform'], - attributes: ['vec2 a_myAttr'], - sizeExpression: 'vec2(' + formatNumber(6) + ')', - offsetExpression: 'vec2(' + formatArray([5, -7]) + ')', - colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')', - texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')' - }; + const builder = new ShaderBuilder(); + builder.addUniform('float u_myUniform'); + builder.addAttribute('vec2 a_myAttr'); + builder.setSizeExpression('vec2(' + formatNumber(6) + ')'); + builder.setSymbolOffsetExpression('vec2(' + formatArray([5, -7]) + ')'); + builder.setColorExpression('vec4(' + formatColor([80, 0, 255, 1]) + ')'); + builder.setTextureCoordinateExpression('vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'); - expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float; + expect(builder.getSymbolVertexShader()).to.eql(`precision mediump float; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; @@ -119,15 +111,14 @@ void main(void) { }`); }); it('generates a symbol vertex shader (with rotateWithView)', function() { - const parameters = { - sizeExpression: 'vec2(' + formatNumber(6) + ')', - offsetExpression: 'vec2(' + formatArray([5, -7]) + ')', - colorExpression: 'vec4(' + formatColor([80, 0, 255, 1]) + ')', - texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')', - rotateWithView: true - }; + const builder = new ShaderBuilder(); + builder.setSizeExpression('vec2(' + formatNumber(6) + ')'); + builder.setSymbolOffsetExpression('vec2(' + formatArray([5, -7]) + ')'); + builder.setColorExpression('vec4(' + formatColor([80, 0, 255, 1]) + ')'); + builder.setTextureCoordinateExpression('vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'); + builder.setSymbolRotateWithView(true); - expect(getSymbolVertexShader(parameters)).to.eql(`precision mediump float; + expect(builder.getSymbolVertexShader()).to.eql(`precision mediump float; uniform mat4 u_projectionMatrix; uniform mat4 u_offsetScaleMatrix; uniform mat4 u_offsetRotateMatrix; @@ -160,24 +151,15 @@ void main(void) { describe('getSymbolFragmentShader', function() { it('generates a symbol fragment shader (with varying)', function() { - const parameters = { - varyings: [{ - name: 'v_opacity', - type: 'float', - expression: formatNumber(0.4) - }, { - name: 'v_test', - type: 'vec3', - expression: 'vec3(' + formatArray([1, 2, 3]) + ')' - }], - sizeExpression: 'vec2(' + formatNumber(6) + ')', - offsetExpression: 'vec2(' + formatArray([5, -7]) + ')', - colorExpression: 'vec4(' + formatColor([80, 0, 255]) + ', v_opacity)', - texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')', - rotateWithView: false - }; + const builder = new ShaderBuilder(); + builder.addVarying('v_opacity', 'float', formatNumber(0.4)); + builder.addVarying('v_test', 'vec3', 'vec3(' + formatArray([1, 2, 3]) + ')'); + builder.setSizeExpression('vec2(' + formatNumber(6) + ')'); + builder.setSymbolOffsetExpression('vec2(' + formatArray([5, -7]) + ')'); + builder.setColorExpression('vec4(' + formatColor([80, 0, 255]) + ', v_opacity)'); + builder.setTextureCoordinateExpression('vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'); - expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float; + expect(builder.getSymbolFragmentShader()).to.eql(`precision mediump float; varying vec2 v_texCoord; varying vec2 v_quadCoord; @@ -189,15 +171,15 @@ void main(void) { }`); }); it('generates a symbol fragment shader (with uniforms)', function() { - const parameters = { - uniforms: ['float u_myUniform', 'vec2 u_myUniform2'], - sizeExpression: 'vec2(' + formatNumber(6) + ')', - offsetExpression: 'vec2(' + formatArray([5, -7]) + ')', - colorExpression: 'vec4(' + formatColor([255, 255, 255, 1]) + ')', - texCoordExpression: 'vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')' - }; + const builder = new ShaderBuilder(); + builder.addUniform('float u_myUniform'); + builder.addUniform('vec2 u_myUniform2'); + builder.setSizeExpression('vec2(' + formatNumber(6) + ')'); + builder.setSymbolOffsetExpression('vec2(' + formatArray([5, -7]) + ')'); + builder.setColorExpression('vec4(' + formatColor([255, 255, 255, 1]) + ')'); + builder.setTextureCoordinateExpression('vec4(' + formatArray([0, 0.5, 0.5, 1]) + ')'); - expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float; + expect(builder.getSymbolFragmentShader()).to.eql(`precision mediump float; uniform float u_myUniform; uniform vec2 u_myUniform2; varying vec2 v_texCoord; @@ -245,16 +227,15 @@ void main(void) { color: '#336699', rotateWithView: true }); - expect(result.params).to.eql({ - uniforms: [], - attributes: [], - varyings: [], - colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)', - sizeExpression: 'vec2(4.0, 8.0)', - offsetExpression: 'vec2(0.0, 0.0)', - texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)', - rotateWithView: true - }); + + expect(result.builder.uniforms).to.eql([]); + expect(result.builder.attributes).to.eql([]); + expect(result.builder.varyings).to.eql([]); + expect(result.builder.colorExpression).to.eql('vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)'); + expect(result.builder.sizeExpression).to.eql('vec2(4.0, 8.0)'); + expect(result.builder.offsetExpression).to.eql('vec2(0.0, 0.0)'); + expect(result.builder.texCoordExpression).to.eql('vec4(0.0, 0.0, 1.0, 1.0)'); + expect(result.builder.rotateWithView).to.eql(true); expect(result.attributes).to.eql([]); expect(result.uniforms).to.eql({}); }); @@ -269,24 +250,24 @@ void main(void) { textureCoord: [0.5, 0.5, 0.5, 1], offset: [3, ['get', 'attr3']] }); - expect(result.params).to.eql({ - uniforms: [], - attributes: ['float a_attr1', 'float a_attr3', 'float a_attr2'], - varyings: [{ - name: 'v_attr1', - type: 'float', - expression: 'a_attr1' - }, { - name: 'v_attr2', - type: 'float', - expression: 'a_attr2' - }], - colorExpression: 'vec4(1.0, 0.0, 0.5, v_attr2) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)', - sizeExpression: 'vec2(a_attr1, a_attr1)', - offsetExpression: 'vec2(3.0, a_attr3)', - texCoordExpression: 'vec4(0.5, 0.5, 0.5, 1.0)', - rotateWithView: false - }); + + expect(result.builder.uniforms).to.eql([]); + expect(result.builder.attributes).to.eql(['float a_attr1', 'float a_attr3', 'float a_attr2']); + expect(result.builder.varyings).to.eql([{ + name: 'v_attr1', + type: 'float', + expression: 'a_attr1' + }, { + name: 'v_attr2', + type: 'float', + expression: 'a_attr2' + }]); + expect(result.builder.colorExpression).to.eql( + 'vec4(1.0, 0.0, 0.5, v_attr2) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)'); + expect(result.builder.sizeExpression).to.eql('vec2(a_attr1, a_attr1)'); + expect(result.builder.offsetExpression).to.eql('vec2(3.0, a_attr3)'); + expect(result.builder.texCoordExpression).to.eql('vec4(0.5, 0.5, 0.5, 1.0)'); + expect(result.builder.rotateWithView).to.eql(false); expect(result.attributes.length).to.eql(3); expect(result.attributes[0].name).to.eql('attr1'); expect(result.attributes[1].name).to.eql('attr3'); @@ -302,16 +283,16 @@ void main(void) { color: '#336699', opacity: 0.5 }); - expect(result.params).to.eql({ - uniforms: ['sampler2D u_texture'], - attributes: [], - varyings: [], - colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 0.5 * 1.0) * texture2D(u_texture, v_texCoord)', - sizeExpression: 'vec2(6.0, 6.0)', - offsetExpression: 'vec2(0.0, 0.0)', - texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)', - rotateWithView: false - }); + + expect(result.builder.uniforms).to.eql(['sampler2D u_texture']); + expect(result.builder.attributes).to.eql([]); + expect(result.builder.varyings).to.eql([]); + expect(result.builder.colorExpression).to.eql( + 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 0.5 * 1.0) * texture2D(u_texture, v_texCoord)'); + expect(result.builder.sizeExpression).to.eql('vec2(6.0, 6.0)'); + expect(result.builder.offsetExpression).to.eql('vec2(0.0, 0.0)'); + expect(result.builder.texCoordExpression).to.eql('vec4(0.0, 0.0, 1.0, 1.0)'); + expect(result.builder.rotateWithView).to.eql(false); expect(result.attributes).to.eql([]); expect(result.uniforms).to.have.property('u_texture'); });