diff --git a/src/data/array_group.js b/src/data/array_group.js index 784be935c33..9559bb333fe 100644 --- a/src/data/array_group.js +++ b/src/data/array_group.js @@ -140,7 +140,6 @@ class ArrayGroup { layerData.paintVertexArray, layerData.paintPropertyStatistics, this.layoutVertexArray.length, - this.globalProperties, featureProperties); } } diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index f714b691a97..10d54681789 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -15,13 +15,13 @@ const circleInterface = { elementArrayType: createElementArrayType(), paintAttributes: [ - {property: 'circle-color', type: 'Uint8'}, - {property: 'circle-radius', type: 'Uint16', multiplier: 10}, - {property: 'circle-blur', type: 'Uint16', multiplier: 10}, - {property: 'circle-opacity', type: 'Uint8', multiplier: 255}, - {property: 'circle-stroke-color', type: 'Uint8'}, - {property: 'circle-stroke-width', type: 'Uint16', multiplier: 10}, - {property: 'circle-stroke-opacity', type: 'Uint8', multiplier: 255} + {property: 'circle-color'}, + {property: 'circle-radius'}, + {property: 'circle-blur'}, + {property: 'circle-opacity'}, + {property: 'circle-stroke-color'}, + {property: 'circle-stroke-width'}, + {property: 'circle-stroke-opacity'} ] }; diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 656161144bf..e453980afd9 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -19,9 +19,9 @@ const fillInterface = { elementArrayType2: createElementArrayType(2), paintAttributes: [ - {property: 'fill-color', type: 'Uint8'}, - {property: 'fill-outline-color', type: 'Uint8'}, - {property: 'fill-opacity', type: 'Uint8', multiplier: 255} + {property: 'fill-color'}, + {property: 'fill-outline-color'}, + {property: 'fill-opacity'} ] }; diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index 5556c18e6a5..608f7e61784 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -21,9 +21,9 @@ const fillExtrusionInterface = { elementArrayType: createElementArrayType(3), paintAttributes: [ - {property: 'fill-extrusion-base', type: 'Uint16'}, - {property: 'fill-extrusion-height', type: 'Uint16'}, - {property: 'fill-extrusion-color', type: 'Uint8'} + {property: 'fill-extrusion-base'}, + {property: 'fill-extrusion-height'}, + {property: 'fill-extrusion-color'} ] }; diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index 59c0f038bfa..a7b2ddd2e87 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -50,13 +50,13 @@ const lineInterface = { {name: 'a_data', components: 4, type: 'Uint8'} ], paintAttributes: [ - {property: 'line-color', type: 'Uint8'}, - {property: 'line-blur', multiplier: 10, type: 'Uint8'}, - {property: 'line-opacity', multiplier: 10, type: 'Uint8'}, - {property: 'line-gap-width', multiplier: 10, type: 'Uint8', name: 'a_gapwidth'}, - {property: 'line-offset', multiplier: 1, type: 'Int8'}, - {property: 'line-width', multiplier: 10, type: 'Uint8', name: 'a_width'}, - {property: 'line-width', multiplier: 10, type: 'Uint8', name: 'a_floorwidth', useIntegerZoom: true}, + {property: 'line-color'}, + {property: 'line-blur'}, + {property: 'line-opacity'}, + {property: 'line-gap-width', name: 'gapwidth'}, + {property: 'line-offset'}, + {property: 'line-width'}, + {property: 'line-width', name: 'floorwidth', useIntegerZoom: true}, ], elementArrayType: createElementArrayType() }; diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index d35722354d4..33f382410eb 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -92,11 +92,11 @@ const symbolInterfaces = { dynamicLayoutAttributes: dynamicLayoutAttributes, elementArrayType: elementArrayType, paintAttributes: [ - {name: 'a_fill_color', property: 'text-color', type: 'Uint8'}, - {name: 'a_halo_color', property: 'text-halo-color', type: 'Uint8'}, - {name: 'a_halo_width', property: 'text-halo-width', type: 'Uint16', multiplier: 10}, - {name: 'a_halo_blur', property: 'text-halo-blur', type: 'Uint16', multiplier: 10}, - {name: 'a_opacity', property: 'text-opacity', type: 'Uint8', multiplier: 255} + {property: 'text-color', name: 'fill_color'}, + {property: 'text-halo-color', name: 'halo_color'}, + {property: 'text-halo-width', name: 'halo_width'}, + {property: 'text-halo-blur', name: 'halo_blur'}, + {property: 'text-opacity', name: 'opacity'} ] }, icon: { @@ -104,11 +104,11 @@ const symbolInterfaces = { dynamicLayoutAttributes: dynamicLayoutAttributes, elementArrayType: elementArrayType, paintAttributes: [ - {name: 'a_fill_color', property: 'icon-color', type: 'Uint8'}, - {name: 'a_halo_color', property: 'icon-halo-color', type: 'Uint8'}, - {name: 'a_halo_width', property: 'icon-halo-width', type: 'Uint16', multiplier: 10}, - {name: 'a_halo_blur', property: 'icon-halo-blur', type: 'Uint16', multiplier: 10}, - {name: 'a_opacity', property: 'icon-opacity', type: 'Uint8', multiplier: 255} + {property: 'icon-color', name: 'fill_color'}, + {property: 'icon-halo-color', name: 'halo_color'}, + {property: 'icon-halo-width', name: 'halo_width'}, + {property: 'icon-halo-blur', name: 'halo_blur'}, + {property: 'icon-opacity', name: 'opacity'} ] }, collisionBox: { // used to render collision boxes for debugging purposes diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 776bc005508..4bef4935fb2 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -1,8 +1,8 @@ // @flow -const assert = require('assert'); const createVertexArrayType = require('./vertex_array_type'); -const util = require('../util/util'); +const interpolationFactor = require('../style-spec/function').interpolationFactor; +const packUint8ToFloat = require('../shaders/encode_attribute').packUint8ToFloat; import type StyleLayer from '../style/style_layer'; import type {ViewType, StructArray} from '../util/struct_array'; @@ -14,52 +14,11 @@ type LayoutAttribute = { } type PaintAttribute = { - name?: string, - type: ViewType, property: string, - components?: number, - multiplier?: number, - dimensions?: number, - zoomStops?: Array, + name?: string, useIntegerZoom?: boolean } -type Attribute = { - name: string, - type: ViewType, - property: string, - components: number, - multiplier: number, - dimensions: number, - zoomStops: Array, - useIntegerZoom: boolean -} - -type Uniform = { - name: string, - property: string -} - -type InterpolationUniform = { - name: string, - property: string, - stopOffset: number, - useIntegerZoom: boolean -} - -type Pragmas = { - define: Array, - initialize: Array, - vertex: { - define: Array, - initialize: Array, - }, - fragment: { - define: Array, - initialize: Array, - } -} - export type PaintPropertyStatistics = { [property: string]: { max: number } } @@ -76,6 +35,162 @@ export type Program = { [string]: any } +function packColor(color: [number, number, number, number]): [number, number] { + return [ + packUint8ToFloat(255 * color[0], 255 * color[1]), + packUint8ToFloat(255 * color[2], 255 * color[3]) + ]; +} + +interface Binder { + property: string; + + populatePaintArray(layer: StyleLayer, + paintArray: StructArray, + statistics: PaintPropertyStatistics, + start: number, + length: number, + featureProperties: Object): void; + + defines(): Array; + + setUniforms(gl: WebGLRenderingContext, + program: Program, + layer: StyleLayer, + globalProperties: { zoom: number }): void; +} + +class ConstantBinder implements Binder { + name: string; + type: string; + property: string; + useIntegerZoom: boolean; + + constructor(name: string, type: string, property: string, useIntegerZoom: boolean) { + this.name = name; + this.type = type; + this.property = property; + this.useIntegerZoom = useIntegerZoom; + } + + defines() { + return [`#define HAS_UNIFORM_u_${this.name}`]; + } + + populatePaintArray() {} + + setUniforms(gl: WebGLRenderingContext, program: Program, layer: StyleLayer, {zoom}: { zoom: number }) { + const value = layer.getPaintValue(this.property, { zoom: this.useIntegerZoom ? Math.floor(zoom) : zoom }); + if (this.type === 'color') { + gl.uniform4fv(program[`u_${this.name}`], value); + } else { + gl.uniform1f(program[`u_${this.name}`], value); + } + } +} + +class SourceFunctionBinder implements Binder { + name: string; + type: string; + property: string; + + constructor(name: string, type: string, property: string) { + this.name = name; + this.type = type; + this.property = property; + } + + defines() { + return []; + } + + populatePaintArray(layer: StyleLayer, + paintArray: StructArray, + statistics: PaintPropertyStatistics, + start: number, + length: number, + featureProperties: Object) { + const value = layer.getPaintValue(this.property, {}, featureProperties); + + if (this.type === 'color') { + const color = packColor(value); + for (let i = start; i < length; i++) { + const struct: any = paintArray.get(i); + struct[`a_${this.name}0`] = color[0]; + struct[`a_${this.name}1`] = color[1]; + } + } else { + for (let i = start; i < length; i++) { + const struct: any = paintArray.get(i); + struct[`a_${this.name}`] = value; + } + + const stats = statistics[this.property]; + stats.max = Math.max(stats.max, value); + } + } + + setUniforms(gl: WebGLRenderingContext, program: Program) { + gl.uniform1f(program[`a_${this.name}_t`], 0); + } +} + +class CompositeFunctionBinder implements Binder { + name: string; + type: string; + property: string; + useIntegerZoom: boolean; + zoom: number; + + constructor(name: string, type: string, property: string, useIntegerZoom: boolean, zoom: number) { + this.name = name; + this.type = type; + this.property = property; + this.useIntegerZoom = useIntegerZoom; + this.zoom = zoom; + } + + defines() { + return []; + } + + populatePaintArray(layer: StyleLayer, + paintArray: StructArray, + statistics: PaintPropertyStatistics, + start: number, + length: number, + featureProperties: Object) { + const min = layer.getPaintValue(this.property, {zoom: this.zoom }, featureProperties); + const max = layer.getPaintValue(this.property, {zoom: this.zoom + 1}, featureProperties); + + if (this.type === 'color') { + const minColor = packColor(min); + const maxColor = packColor(max); + for (let i = start; i < length; i++) { + const struct: any = paintArray.get(i); + struct[`a_${this.name}0`] = minColor[0]; + struct[`a_${this.name}1`] = minColor[1]; + struct[`a_${this.name}2`] = maxColor[0]; + struct[`a_${this.name}3`] = maxColor[1]; + } + } else { + for (let i = start; i < length; i++) { + const struct: any = paintArray.get(i); + struct[`a_${this.name}0`] = min; + struct[`a_${this.name}1`] = max; + } + + const stats = statistics[this.property]; + stats.max = Math.max(stats.max, min, max); + } + } + + setUniforms(gl: WebGLRenderingContext, program: Program, layer: StyleLayer, {zoom}: { zoom: number }) { + const f = interpolationFactor(this.useIntegerZoom ? Math.floor(zoom) : zoom, 1, this.zoom, this.zoom + 1); + gl.uniform1f(program[`a_${this.name}_t`], f); + } +} + /** * ProgramConfiguration contains the logic for binding style layer properties and tile * layer feature data into GL program uniforms and vertex attributes. @@ -97,188 +212,64 @@ export type Program = { * @private */ class ProgramConfiguration { - attributes: Array; - uniforms: Array; - interpolationUniforms: Array; - pragmas: { [string]: Pragmas }; + binders: { [string]: Binder }; cacheKey: string; interface: ?ProgramInterface; PaintVertexArray: Class; constructor() { - this.attributes = []; - this.uniforms = []; - this.interpolationUniforms = []; - this.pragmas = {}; + this.binders = {}; this.cacheKey = ''; } static createDynamic(programInterface: ProgramInterface, layer: StyleLayer, zoom: number) { const self = new ProgramConfiguration(); - - for (const attributeConfig of programInterface.paintAttributes || []) { - const attribute = normalizePaintAttribute(attributeConfig, layer); - assert(/^a_/.test(attribute.name)); - const name = attribute.name.slice(2); - - if (layer.isPaintValueFeatureConstant(attribute.property)) { - self.addZoomAttribute(name, attribute); - } else if (layer.isPaintValueZoomConstant(attribute.property)) { - self.addPropertyAttribute(name, attribute); + const attributes = []; + + for (const attribute of programInterface.paintAttributes || []) { + const property = attribute.property; + const useIntegerZoom = attribute.useIntegerZoom || false; + const name = attribute.name || property.replace(`${layer.type}-`, '').replace(/-/g, '_'); + const type = layer._paintSpecifications[property].type; + + if (layer.isPaintValueFeatureConstant(property)) { + self.binders[name] = new ConstantBinder(name, type, property, useIntegerZoom); + self.cacheKey += `/u_${name}`; + } else if (layer.isPaintValueZoomConstant(property)) { + self.binders[name] = new SourceFunctionBinder(name, type, property); + self.cacheKey += `/a_${name}`; + attributes.push({ + name: `a_${name}`, + type: 'Float32', + components: type === 'color' ? 2 : 1 + }); } else { - self.addZoomAndPropertyAttribute(name, attribute, layer, zoom); + self.binders[name] = new CompositeFunctionBinder(name, type, property, useIntegerZoom, zoom); + self.cacheKey += `/z_${name}`; + attributes.push({ + name: `a_${name}`, + type: 'Float32', + components: type === 'color' ? 4 : 2 + }); } } - self.PaintVertexArray = createVertexArrayType(self.attributes); + + self.PaintVertexArray = createVertexArrayType(attributes); self.interface = programInterface; return self; } - static createStatic(uniformNames: Array) { + static createBasicFill() { const self = new ProgramConfiguration(); - for (const name of uniformNames) { - self.addUniform(name, `u_${name}`); - } - - return self; - } - - addUniform(name: string, inputName: string) { - const pragmas = this.getPragmas(name); - - pragmas.define.push(`uniform {precision} {type} ${inputName};`); - pragmas.initialize.push(`{precision} {type} ${name} = ${inputName};`); - - this.cacheKey += `/u_${name}`; - } - - addZoomAttribute(name: string, attribute: Attribute) { - this.uniforms.push(attribute); - this.addUniform(name, attribute.name); - } - - addPropertyAttribute(name: string, attribute: Attribute) { - const pragmas = this.getPragmas(name); - - this.attributes.push(attribute); - - pragmas.define.push(`varying {precision} {type} ${name};`); - - pragmas.vertex.define.push(`attribute {precision} {type} ${attribute.name};`); - pragmas.vertex.initialize.push(`${name} = ${attribute.name} / ${attribute.multiplier}.0;`); - - this.cacheKey += `/a_${name}`; - } - - /* - * For composite functions, the idea here is to provide the shader with a - * _partially evaluated_ function for each feature (or rather, for each - * vertex associated with a feature). If the composite function is - * F(properties, zoom), then for each feature we'll provide the following - * as a vertex attribute, built at layout time: - * [ - * F(feature.properties, zA), - * F(feature.properties, zB), - * F(feature.properties, zC), - * F(feature.properties, zD) - * ] - * where zA, zB, zC, zD are specific zoom stops defined in the composite - * function. - * - * And then, at render time, we'll set a corresonding 'interpolation - * uniform', determined by the currently rendered zoom level, which is - * essentially a possibly-fractional index into the above vector. By - * interpolating between the appropriate pair of values, the shader can - * thus obtain the value of F(feature.properties, currentZoom). - * - * @private - */ - addZoomAndPropertyAttribute(name: string, attribute: Attribute, layer: StyleLayer, zoom: number) { - const pragmas = this.getPragmas(name); - - pragmas.define.push(`varying {precision} {type} ${name};`); - - // Pick the index of the first zoom stop to add to the buffers - const zoomLevels = layer.getPaintValueStopZoomLevels(attribute.property); - let stopOffset = 0; - if (zoomLevels.length > 4) { - while (stopOffset < (zoomLevels.length - 2) && - zoomLevels[stopOffset] < zoom) stopOffset++; - } - - - const tName = `u_${name}_t`; - - pragmas.vertex.define.push(`uniform lowp float ${tName};`); + self.binders.color = new ConstantBinder('color', 'color', 'fill-color', false); + self.cacheKey += `/u_color`; - this.interpolationUniforms.push({ - name: tName, - property: attribute.property, - useIntegerZoom: attribute.useIntegerZoom, - stopOffset - }); + self.binders.opacity = new ConstantBinder('opacity', 'number', 'fill-opacity', false); + self.cacheKey += `/u_opacity`; - // Find the four closest stops, ideally with two on each side of the zoom level. - const zoomStops = []; - for (let s = 0; s < 4; s++) { - zoomStops.push(zoomLevels[Math.min(stopOffset + s, zoomLevels.length - 1)]); - } - - const componentNames = []; - - if (attribute.components === 1) { - this.attributes.push(util.extend({}, attribute, { - components: 4, - zoomStops - })); - pragmas.vertex.define.push(`attribute {precision} vec4 ${attribute.name};`); - componentNames.push(attribute.name); - - } else { - for (let k = 0; k < 4; k++) { - const componentName = attribute.name + k; - componentNames.push(componentName); - - this.attributes.push(util.extend({}, attribute, { - name: componentName, - zoomStops: [zoomStops[k]] - })); - pragmas.vertex.define.push(`attribute {precision} {type} ${componentName};`); - } - } - pragmas.vertex.initialize.push(`${name} = evaluate_zoom_function_${attribute.components}(\ - ${componentNames.join(', ')}, ${tName}) / ${attribute.multiplier}.0;`); - - this.cacheKey += `/z_${name}`; - } - - getPragmas(name: string) { - if (!this.pragmas[name]) { - this.pragmas[name] = { - define: [], - initialize: [], - fragment: { - define: [], - initialize: [] - }, - vertex: { - define: [], - initialize: [] - } - }; - } - return this.pragmas[name]; - } - - applyPragmas(source: string, shaderType: 'vertex' | 'fragment') { - return source.replace(/#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g, (match, operation, precision, type, name) => { - return this.pragmas[name][operation].concat(this.pragmas[name][shaderType][operation]) - .join('\n') - .replace(/{type}/g, type) - .replace(/{precision}/g, precision); - }); + return self; } // Since this object is accessed frequently during populatePaintArray, it @@ -286,93 +277,44 @@ class ProgramConfiguration { // 'hidden class' optimizations to take effect createPaintPropertyStatistics() { const paintPropertyStatistics: PaintPropertyStatistics = {}; - for (const attribute of this.attributes) { - if (attribute.dimensions !== 1) continue; - paintPropertyStatistics[attribute.property] = { + for (const name in this.binders) { + paintPropertyStatistics[this.binders[name].property] = { max: -Infinity }; } return paintPropertyStatistics; } - populatePaintArray(layer: StyleLayer, paintArray: any, paintPropertyStatistics: PaintPropertyStatistics, length: number, globalProperties: { zoom: number }, featureProperties: Object) { + populatePaintArray(layer: StyleLayer, + paintArray: StructArray, + statistics: PaintPropertyStatistics, + length: number, + featureProperties: Object) { const start = paintArray.length; paintArray.resize(length); - for (const attribute of this.attributes) { - const zoomBase = attribute.useIntegerZoom ? { zoom: Math.floor(globalProperties.zoom) } : globalProperties; - const value: any = getPaintAttributeValue(attribute, layer, zoomBase, featureProperties); - - for (let i = start; i < length; i++) { - const vertex = paintArray.get(i); - if (attribute.components === 4) { - for (let c = 0; c < 4; c++) { - vertex[attribute.name + c] = value[c] * attribute.multiplier; - } - } else { - vertex[attribute.name] = value * attribute.multiplier; - } - if (attribute.dimensions === 1) { - const stats = paintPropertyStatistics[attribute.property]; - stats.max = Math.max(stats.max, - attribute.components === 1 ? value : Math.max.apply(Math, value)); - } - } + for (const name in this.binders) { + this.binders[name].populatePaintArray( + layer, paintArray, + statistics, + start, length, + featureProperties); } } - setUniforms(gl: WebGLRenderingContext, program: Program, layer: StyleLayer, globalProperties: { zoom: number }) { - for (const uniform of this.uniforms) { - const zoomBase = uniform.useIntegerZoom ? { zoom: Math.floor(globalProperties.zoom) } : globalProperties; - const value = layer.getPaintValue(uniform.property, zoomBase); - - if (uniform.components === 4) { - gl.uniform4fv(program[uniform.name], value); - } else { - gl.uniform1f(program[uniform.name], value); - } - } - for (const uniform of this.interpolationUniforms) { - const zoomBase = uniform.useIntegerZoom ? { zoom: Math.floor(globalProperties.zoom) } : globalProperties; - // stopInterp indicates which stops need to be interpolated. - // If stopInterp is 3.5 then interpolate half way between stops 3 and 4. - const stopInterp = layer.getPaintInterpolationT(uniform.property, zoomBase); - // We can only store four stop values in the buffers. stopOffset is the number of stops that come - // before the stops that were added to the buffers. - gl.uniform1f(program[uniform.name], Math.max(0, Math.min(3, stopInterp - uniform.stopOffset))); + defines(): Array { + const result = []; + for (const name in this.binders) { + result.push.apply(result, this.binders[name].defines()); } + return result; } -} -function getPaintAttributeValue(attribute: Attribute, layer: StyleLayer, globalProperties: { zoom: number }, featureProperties: Object) { - if (!attribute.zoomStops) { - return layer.getPaintValue(attribute.property, globalProperties, featureProperties); - } - // add one multi-component value like color0, or pack multiple single-component values into a four component attribute - const values = attribute.zoomStops.map((zoom) => layer.getPaintValue( - attribute.property, util.extend({}, globalProperties, {zoom}), featureProperties)); - - return values.length === 1 ? values[0] : values; -} - -function normalizePaintAttribute(attribute: PaintAttribute, layer: StyleLayer): Attribute { - let name = attribute.name; - - // by default, construct the shader variable name for paint attribute - // `layertype-some-property` as `some_property` - if (!name) { - name = attribute.property.replace(`${layer.type}-`, '').replace(/-/g, '_'); + setUniforms(gl: WebGLRenderingContext, program: Program, layer: StyleLayer, globalProperties: { zoom: number }) { + for (const name in this.binders) { + this.binders[name].setUniforms(gl, program, layer, globalProperties); + } } - const isColor = layer._paintSpecifications[attribute.property].type === 'color'; - - return util.extend({ - name: `a_${name}`, - components: isColor ? 4 : 1, - multiplier: isColor ? 255 : 1, - // distinct from `components`, because components can be overridden for - // zoom interpolation - dimensions: isColor ? 4 : 1 - }, attribute); } module.exports = ProgramConfiguration; diff --git a/src/render/painter.js b/src/render/painter.js index 2bb8af82e47..297e7c8cf46 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -12,7 +12,7 @@ const VertexArrayObject = require('./vertex_array_object'); const RasterBoundsArray = require('../data/raster_bounds_array'); const PosArray = require('../data/pos_array'); const ProgramConfiguration = require('../data/program_configuration'); -const shaders = require('./shaders'); +const shaders = require('../shaders'); const assert = require('assert'); const draw = { @@ -101,7 +101,7 @@ class Painter { this.lineWidthRange = gl.getParameter(gl.ALIASED_LINE_WIDTH_RANGE); - this.basicFillProgramConfiguration = ProgramConfiguration.createStatic(['color', 'opacity']); + this.basicFillProgramConfiguration = ProgramConfiguration.createBasicFill(); this.emptyProgramConfiguration = new ProgramConfiguration(); } @@ -418,15 +418,15 @@ class Painter { createProgram(name: string, configuration: ProgramConfiguration): Program { const gl = this.gl; const program = gl.createProgram(); - const definition = shaders[name]; - let definesSource = `#define MAPBOX_GL_JS\n#define DEVICE_PIXEL_RATIO ${browser.devicePixelRatio.toFixed(1)}\n`; + const defines = configuration.defines().concat( + `#define DEVICE_PIXEL_RATIO ${browser.devicePixelRatio.toFixed(1)}`); if (this._showOverdrawInspector) { - definesSource += '#define OVERDRAW_INSPECTOR;\n'; + defines.push('#define OVERDRAW_INSPECTOR;'); } - const fragmentSource = configuration.applyPragmas(definesSource + shaders.prelude.fragmentSource + definition.fragmentSource, 'fragment'); - const vertexSource = configuration.applyPragmas(definesSource + shaders.prelude.vertexSource + definition.vertexSource, 'vertex'); + const fragmentSource = defines.concat(shaders.prelude.fragmentSource, shaders[name].fragmentSource).join('\n'); + const vertexSource = defines.concat(shaders.prelude.vertexSource, shaders[name].vertexSource).join('\n'); const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(fragmentShader, fragmentSource); diff --git a/src/shaders/_prelude.vertex.glsl b/src/shaders/_prelude.vertex.glsl index 32d2abc1707..de4d40eb891 100644 --- a/src/shaders/_prelude.vertex.glsl +++ b/src/shaders/_prelude.vertex.glsl @@ -16,25 +16,6 @@ precision highp float; #endif -float evaluate_zoom_function_1(const vec4 values, const float t) { - if (t < 1.0) { - return mix(values[0], values[1], t); - } else if (t < 2.0) { - return mix(values[1], values[2], t - 1.0); - } else { - return mix(values[2], values[3], t - 2.0); - } -} -vec4 evaluate_zoom_function_4(const vec4 value0, const vec4 value1, const vec4 value2, const vec4 value3, const float t) { - if (t < 1.0) { - return mix(value0, value1, t); - } else if (t < 2.0) { - return mix(value1, value2, t - 1.0); - } else { - return mix(value2, value3, t - 2.0); - } -} - // Unpack a pair of values that have been packed into a single float. // The packed values are assumed to be 8-bit unsigned integers, and are // packed like so: @@ -46,8 +27,8 @@ vec2 unpack_float(const float packedValue) { } -// To minimize the number of attributes needed in the mapbox-gl-native shaders, -// we encode a 4-component color into a pair of floats (i.e. a vec2) as follows: +// To minimize the number of attributes needed, we encode a 4-component +// color into a pair of floats (i.e. a vec2) as follows: // [ floor(color.r * 255) * 256 + color.g * 255, // floor(color.b * 255) * 256 + color.g * 255 ] vec4 decode_color(const vec2 encodedColor) { diff --git a/src/render/shaders.js b/src/shaders/index.js similarity index 66% rename from src/render/shaders.js rename to src/shaders/index.js index e134b2e4966..8a8707d8cc0 100644 --- a/src/render/shaders.js +++ b/src/shaders/index.js @@ -75,3 +75,75 @@ module.exports = { vertexSource: fs.readFileSync(__dirname + '/../shaders/symbol_sdf.vertex.glsl', 'utf8') } }; + +// Expand #pragmas to #ifdefs. + +const re = /#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; + +for (const programName in module.exports) { + const program = module.exports[programName]; + const fragmentPragmas = {}; + + program.fragmentSource = program.fragmentSource.replace(re, (match, operation, precision, type, name) => { + fragmentPragmas[name] = true; + if (operation === 'define') { + return ` +#ifndef HAS_UNIFORM_u_${name} +varying ${precision} ${type} ${name}; +#else +uniform ${precision} ${type} u_${name}; +#endif +`; + } else if (operation === 'initialize') { + return ` +#ifdef HAS_UNIFORM_u_${name} + ${precision} ${type} ${name} = u_${name}; +#endif +`; + } + }); + + program.vertexSource = program.vertexSource.replace(re, (match, operation, precision, type, name) => { + const attrType = type === 'float' ? 'vec2' : 'vec4'; + if (fragmentPragmas[name]) { + if (operation === 'define') { + return ` +#ifndef HAS_UNIFORM_u_${name} +uniform lowp float a_${name}_t; +attribute ${precision} ${attrType} a_${name}; +varying ${precision} ${type} ${name}; +#else +uniform ${precision} ${type} u_${name}; +#endif +`; + } else if (operation === 'initialize') { + return ` +#ifndef HAS_UNIFORM_u_${name} + ${name} = unpack_mix_${attrType}(a_${name}, a_${name}_t); +#else + ${precision} ${type} ${name} = u_${name}; +#endif +`; + } + } else { + if (operation === 'define') { + return ` +#ifndef HAS_UNIFORM_u_${name} +uniform lowp float a_${name}_t; +attribute ${precision} ${attrType} a_${name}; +#else +uniform ${precision} ${type} u_${name}; +#endif +`; + } else if (operation === 'initialize') { + return ` +#ifndef HAS_UNIFORM_u_${name} + ${precision} ${type} ${name} = unpack_mix_${attrType}(a_${name}, a_${name}_t); +#else + ${precision} ${type} ${name} = u_${name}; +#endif +`; + } + } + }); +} diff --git a/test/unit/data/bucket.test.js b/test/unit/data/bucket.test.js index 3648d8a8cbb..b445755edd3 100644 --- a/test/unit/data/bucket.test.js +++ b/test/unit/data/bucket.test.js @@ -41,10 +41,7 @@ test('Bucket', (t) => { elementArrayType: createElementArrayType(), elementArrayType2: createElementArrayType(2), - paintAttributes: options.paintAttributes || [{ - property: 'circle-opacity', - type: 'Int16' - }] + paintAttributes: options.paintAttributes || [{ property: 'circle-opacity' }] }; class Class extends Bucket { @@ -91,6 +88,7 @@ test('Bucket', (t) => { const v0 = testVertex.get(0); t.equal(v0.a_box0, 34); t.equal(v0.a_box1, 84); + const paintVertex = bucket.arrays.layerData.layerid.paintVertexArray; t.equal(paintVertex.length, 1); const p0 = paintVertex.get(0); @@ -133,10 +131,7 @@ test('Bucket', (t) => { t.test('add features, disabled attribute', (t) => { const bucket = create({ - paintAttributes: [{ - property: 'circle-opacity', - type: 'Int16' - }], + paintAttributes: [{ property: 'circle-opacity' }], layoutAttributes: [], layers: [ { id: 'one', type: 'circle', paint: constantPaint } diff --git a/test/unit/data/fill_bucket.test.js b/test/unit/data/fill_bucket.test.js index 5b027833253..f164bc66498 100644 --- a/test/unit/data/fill_bucket.test.js +++ b/test/unit/data/fill_bucket.test.js @@ -104,16 +104,5 @@ test('FillBucket segmentation', (t) => { primitiveLength: 126 }); - t.equal(arrays.layerData.test.paintVertexArray.length, 266); - for (let i = 0; i < arrays.layerData.test.paintVertexArray.length; i++) { - const vertex = arrays.layerData.test.paintVertexArray.get(i); - t.deepEqual([ - vertex['a_color0'], - vertex['a_color1'], - vertex['a_color2'], - vertex['a_color3'] - ], [0, 0, 255, 255]); - } - t.end(); }); diff --git a/test/unit/data/symbol_bucket.test.js b/test/unit/data/symbol_bucket.test.js index 3156b7dd1ca..12887c45791 100644 --- a/test/unit/data/symbol_bucket.test.js +++ b/test/unit/data/symbol_bucket.test.js @@ -135,12 +135,9 @@ test('SymbolBucket#getPaintPropertyStatistics()', (t) => { }); bucket.place(collision); - t.deepEqual(bucket.getPaintPropertyStatistics(), { - test: { - 'text-halo-width': { max: 4 }, - 'icon-halo-width': { max: 5 } - } - }); + const stats = bucket.getPaintPropertyStatistics().test; + t.deepEqual(stats['text-halo-width'], { max: 4 }); + t.deepEqual(stats['icon-halo-width'], { max: 5 }); t.end(); });