diff --git a/js/data/bucket.js b/js/data/bucket.js index f2b99eb751b..18c8641d4ab 100644 --- a/js/data/bucket.js +++ b/js/data/bucket.js @@ -2,9 +2,8 @@ const ArrayGroup = require('./array_group'); const BufferGroup = require('./buffer_group'); -const VertexArrayType = require('./vertex_array_type'); +const ProgramConfiguration = require('./program_configuration'); const util = require('../util/util'); -const assert = require('assert'); const FAKE_ZOOM_HISTORY = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 }; @@ -43,7 +42,13 @@ class Bucket { this.minZoom = this.layer.minzoom; this.maxZoom = this.layer.maxzoom; - this.paintAttributes = createPaintAttributes(this); + this.programConfigurations = util.mapObject(this.programInterfaces, (programInterface) => { + const result = {}; + for (const layer of options.childLayers) { + result[layer.id] = ProgramConfiguration.createDynamic(programInterface.paintAttributes || [], layer, options); + } + return result; + }); if (options.arrays) { const programInterfaces = this.programInterfaces; @@ -119,13 +124,10 @@ class Bucket { for (const programName in this.programInterfaces) { this.arrayGroups[programName] = []; - - const paintVertexArrayTypes = this.paintVertexArrayTypes[programName] = {}; - const layerPaintAttributes = this.paintAttributes[programName]; - - for (const layerName in layerPaintAttributes) { - paintVertexArrayTypes[layerName] = new VertexArrayType(layerPaintAttributes[layerName].attributes); - } + this.paintVertexArrayTypes[programName] = util.mapObject( + this.programConfigurations[programName], (programConfiguration) => { + return programConfiguration.paintVertexArrayType(); + }); } } @@ -168,15 +170,6 @@ class Bucket { } } - setUniforms(gl, programName, program, layer, globalProperties) { - const uniforms = this.paintAttributes[programName][layer.id].uniforms; - for (let i = 0; i < uniforms.length; i++) { - const uniform = uniforms[i]; - const uniformLocation = program[uniform.name]; - gl[`uniform${uniform.components}fv`](uniformLocation, uniform.getValue(layer, globalProperties)); - } - } - serialize() { return { layerId: this.layer.id, @@ -205,32 +198,25 @@ class Bucket { } populatePaintArrays(interfaceName, globalProperties, featureProperties, startGroup, startIndex) { - for (let l = 0; l < this.childLayers.length; l++) { - const layer = this.childLayers[l]; - const groups = this.arrayGroups[interfaceName]; + const groups = this.arrayGroups[interfaceName]; + const programConfiguration = this.programConfigurations[interfaceName]; + + for (const layer of this.childLayers) { for (let g = startGroup.index; g < groups.length; g++) { const group = groups[g]; + const start = g === startGroup.index ? startIndex : 0; const length = group.layoutVertexArray.length; + const paintArray = group.paintVertexArrays[layer.id]; paintArray.resize(length); - const attributes = this.paintAttributes[interfaceName][layer.id].attributes; - for (let m = 0; m < attributes.length; m++) { - const attribute = attributes[m]; - - const value = attribute.getValue(layer, globalProperties, featureProperties); - const multiplier = attribute.multiplier || 1; - const components = attribute.components || 1; - - const start = g === startGroup.index ? startIndex : 0; - for (let i = start; i < length; i++) { - const vertex = paintArray.get(i); - for (let c = 0; c < components; c++) { - const memberName = components > 1 ? (attribute.name + c) : attribute.name; - vertex[memberName] = value[c] * multiplier; - } - } - } + programConfiguration[layer.id].populatePaintArray( + layer, + paintArray, + start, + length, + globalProperties, + featureProperties); } } } @@ -260,136 +246,3 @@ Bucket.create = function(options) { } return new subclasses[type](options); }; - -function createPaintAttributes(bucket) { - const attributes = {}; - for (const interfaceName in bucket.programInterfaces) { - attributes[interfaceName] = {}; - - for (const childLayer of bucket.childLayers) { - attributes[interfaceName][childLayer.id] = { - attributes: [], - uniforms: [], - defines: [], - vertexPragmas: { define: {}, initialize: {} }, - fragmentPragmas: { define: {}, initialize: {} } - }; - } - - const interfacePaintAttributes = bucket.programInterfaces[interfaceName].paintAttributes; - if (!interfacePaintAttributes) continue; - - // "{precision} {type} tokens are replaced by arguments to the pragma - // https://github.com/mapbox/mapbox-gl-shaders#pragmas - - for (const attribute of interfacePaintAttributes) { - attribute.multiplier = attribute.multiplier || 1; - - for (const layer of bucket.childLayers) { - const paintAttributes = attributes[interfaceName][layer.id]; - const fragmentInit = paintAttributes.fragmentPragmas.initialize; - const fragmentDefine = paintAttributes.fragmentPragmas.define; - const vertexInit = paintAttributes.vertexPragmas.initialize; - const vertexDefine = paintAttributes.vertexPragmas.define; - - const inputName = attribute.name; - assert(attribute.name.slice(0, 2) === 'a_'); - const name = attribute.name.slice(2); - const multiplier = attribute.multiplier.toFixed(1); - - fragmentInit[name] = ''; - - if (layer.isPaintValueFeatureConstant(attribute.paintProperty)) { - paintAttributes.uniforms.push(attribute); - - fragmentDefine[name] = vertexDefine[name] = `uniform {precision} {type} ${inputName};\n`; - fragmentInit[name] = vertexInit[name] = `{precision} {type} ${name} = ${inputName};\n`; - - } else if (layer.isPaintValueZoomConstant(attribute.paintProperty)) { - paintAttributes.attributes.push(util.extend({}, attribute, {name: inputName})); - - fragmentDefine[name] = `varying {precision} {type} ${name};\n`; - vertexDefine[name] = `varying {precision} {type} ${name};\n attribute {precision} {type} ${inputName};\n`; - vertexInit[name] = `${name} = ${inputName} / ${multiplier};\n`; - - } else { - // Pick the index of the first offset to add to the buffers. - // Find the four closest stops, ideally with two on each side of the zoom level. - let numStops = 0; - const zoomLevels = layer.getPaintValueStopZoomLevels(attribute.paintProperty); - while (numStops < zoomLevels.length && zoomLevels[numStops] < bucket.zoom) numStops++; - const stopOffset = Math.max(0, Math.min(zoomLevels.length - 4, numStops - 2)); - - const fourZoomLevels = []; - for (let s = 0; s < 4; s++) { - fourZoomLevels.push(zoomLevels[Math.min(stopOffset + s, zoomLevels.length - 1)]); - } - - const tName = `u_${name}_t`; - - fragmentDefine[name] = `varying {precision} {type} ${name};\n`; - vertexDefine[name] = `varying {precision} {type} ${name};\n uniform lowp float ${tName};\n`; - - paintAttributes.uniforms.push(util.extend({}, attribute, { - name: tName, - getValue: createGetUniform(attribute, stopOffset), - components: 1 - })); - - if (attribute.components === 1) { - paintAttributes.attributes.push(util.extend({}, attribute, { - getValue: createFunctionGetValue(attribute, fourZoomLevels), - isFunction: true, - components: attribute.components * 4 - })); - - vertexDefine[name] += `attribute {precision} vec4 ${inputName};\n`; - vertexInit[name] = `${name} = evaluate_zoom_function_1(${inputName}, ${tName}) / ${multiplier};\n`; - - } else { - const inputNames = []; - for (let k = 0; k < 4; k++) { - inputNames.push(inputName + k); - paintAttributes.attributes.push(util.extend({}, attribute, { - getValue: createFunctionGetValue(attribute, [fourZoomLevels[k]]), - isFunction: true, - name: inputName + k - })); - vertexDefine[name] += `attribute {precision} {type} ${inputName + k};\n`; - } - vertexInit[name] = `${name} = evaluate_zoom_function_4(${inputNames.join(', ')}, ${tName}) / ${multiplier};\n`; - } - } - } - } - } - return attributes; -} - -function createFunctionGetValue(attribute, stopZoomLevels) { - return function(layer, globalProperties, featureProperties) { - if (stopZoomLevels.length === 1) { - // return one multi-component value like color0 - return attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevels[0] }), featureProperties); - } else { - // pack multiple single-component values into a four component attribute - const values = []; - for (let z = 0; z < stopZoomLevels.length; z++) { - const stopZoomLevel = stopZoomLevels[z]; - values.push(attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevel }), featureProperties)[0]); - } - return values; - } - }; -} - -function createGetUniform(attribute, stopOffset) { - return function(layer, 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(attribute.paintProperty, globalProperties.zoom); - // 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. - return [Math.max(0, Math.min(4, stopInterp - stopOffset))]; - }; -} diff --git a/js/data/program_configuration.js b/js/data/program_configuration.js new file mode 100644 index 00000000000..e6fa3d39423 --- /dev/null +++ b/js/data/program_configuration.js @@ -0,0 +1,254 @@ +'use strict'; + +const VertexArrayType = require('./vertex_array_type'); +const util = require('../util/util'); +const shaders = require('mapbox-gl-shaders'); +const assert = require('assert'); + +/** + * ProgramConfiguration contains the logic for binding style layer properties and tile + * layer feature data into GL program uniforms and vertex attributes. + * + * Non-data-driven property values are bound to shader uniforms. Data-driven property + * values are bound to vertex attributes. In order to support a uniform GLSL syntax over + * both, [Mapbox GL Shaders](https://github.com/mapbox/mapbox-gl-shaders) defines a `#pragma` + * abstraction, which ProgramConfiguration is responsible for implementing. At runtime, + * it examines the attributes of a particular layer, combines this with fixed knowledge + * about how layers of the particular type are implemented, and determines which uniforms + * and vertex attributes will be required. It can then substitute the appropriate text + * into the shader source code, create and link a program, and bind the uniforms and + * vertex attributes in preparation for drawing. + * + * @private + */ +class ProgramConfiguration { + static createDynamic(attributes, layer, options) { + const self = new ProgramConfiguration(); + + self.attributes = []; + self.uniforms = []; + self.defines = []; + self.vertexPragmas = { define: {}, initialize: {} }; + self.fragmentPragmas = { define: {}, initialize: {} }; + + const fragmentInit = self.fragmentPragmas.initialize; + const fragmentDefine = self.fragmentPragmas.define; + const vertexInit = self.vertexPragmas.initialize; + const vertexDefine = self.vertexPragmas.define; + + for (const attribute of attributes) { + const inputName = attribute.name; + assert(attribute.name.slice(0, 2) === 'a_'); + const name = attribute.name.slice(2); + const multiplier = (attribute.multiplier || 1).toFixed(1); + + fragmentInit[name] = ''; + + if (layer.isPaintValueFeatureConstant(attribute.paintProperty)) { + self.uniforms.push(attribute); + + fragmentDefine[name] = vertexDefine[name] = `uniform {precision} {type} ${inputName};\n`; + fragmentInit[name] = vertexInit[name] = `{precision} {type} ${name} = ${inputName};\n`; + + } else if (layer.isPaintValueZoomConstant(attribute.paintProperty)) { + self.attributes.push(util.extend({}, attribute, {name: inputName})); + + fragmentDefine[name] = `varying {precision} {type} ${name};\n`; + vertexDefine[name] = `varying {precision} {type} ${name};\n attribute {precision} {type} ${inputName};\n`; + vertexInit[name] = `${name} = ${inputName} / ${multiplier};\n`; + + } else { + // Pick the index of the first offset to add to the buffers. + // Find the four closest stops, ideally with two on each side of the zoom level. + let numStops = 0; + const zoomLevels = layer.getPaintValueStopZoomLevels(attribute.paintProperty); + while (numStops < zoomLevels.length && zoomLevels[numStops] < options.zoom) numStops++; + const stopOffset = Math.max(0, Math.min(zoomLevels.length - 4, numStops - 2)); + + const fourZoomLevels = []; + for (let s = 0; s < 4; s++) { + fourZoomLevels.push(zoomLevels[Math.min(stopOffset + s, zoomLevels.length - 1)]); + } + + const tName = `u_${name}_t`; + + fragmentDefine[name] = `varying {precision} {type} ${name};\n`; + vertexDefine[name] = `varying {precision} {type} ${name};\n uniform lowp float ${tName};\n`; + + self.uniforms.push(util.extend({}, attribute, { + name: tName, + getValue: createGetUniform(attribute, stopOffset), + components: 1 + })); + + if (attribute.components === 1) { + self.attributes.push(util.extend({}, attribute, { + getValue: createFunctionGetValue(attribute, fourZoomLevels), + isFunction: true, + components: attribute.components * 4 + })); + + vertexDefine[name] += `attribute {precision} vec4 ${inputName};\n`; + vertexInit[name] = `${name} = evaluate_zoom_function_1(${inputName}, ${tName}) / ${multiplier};\n`; + + } else { + const inputNames = []; + for (let k = 0; k < 4; k++) { + inputNames.push(inputName + k); + self.attributes.push(util.extend({}, attribute, { + getValue: createFunctionGetValue(attribute, [fourZoomLevels[k]]), + isFunction: true, + name: inputName + k + })); + vertexDefine[name] += `attribute {precision} {type} ${inputName + k};\n`; + } + vertexInit[name] = `${name} = evaluate_zoom_function_4(${inputNames.join(', ')}, ${tName}) / ${multiplier};\n`; + } + } + } + + return self; + } + + static createStatic(uniforms) { + const self = new ProgramConfiguration(); + + const pragmas = { define: {}, initialize: {} }; + + self.attributes = []; + self.uniforms = []; + self.defines = []; + self.vertexPragmas = pragmas; + self.fragmentPragmas = pragmas; + + for (const uniform of uniforms) { + assert(uniform.name.slice(0, 2) === 'u_'); + + const type = `{precision} ${uniform.components === 1 ? 'float' : `vec${uniform.components}`}`; + pragmas.define[uniform.name.slice(2)] = `uniform ${type} ${uniform.name};\n`; + pragmas.initialize[uniform.name.slice(2)] = `${type} ${uniform.name.slice(2)} = ${uniform.name};\n`; + } + + return self; + } + + populatePaintArray(layer, paintArray, start, length, globalProperties, featureProperties) { + for (const attribute of this.attributes) { + const value = attribute.getValue(layer, globalProperties, featureProperties); + const multiplier = attribute.multiplier || 1; + const components = attribute.components || 1; + + for (let i = start; i < length; i++) { + const vertex = paintArray.get(i); + for (let c = 0; c < components; c++) { + const memberName = components > 1 ? (attribute.name + c) : attribute.name; + vertex[memberName] = value[c] * multiplier; + } + } + } + } + + paintVertexArrayType() { + return new VertexArrayType(this.attributes); + } + + programCacheKey(name, defines) { + return JSON.stringify({ + name: name, + defines: this.defines.concat(defines), + vertexPragmas: this.vertexPragmas, + fragmentPragmas: this.fragmentPragmas + }); + } + + createProgram(name, defines, gl) { + const program = gl.createProgram(); + const definition = shaders[name]; + + defines = this.defines.concat(defines); + + let definesSource = '#define MAPBOX_GL_JS;\n'; + for (let j = 0; j < defines.length; j++) { + definesSource += `#define ${defines[j]};\n`; + } + + const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.shaderSource(fragmentShader, applyPragmas(definesSource + definition.fragmentSource, this.fragmentPragmas)); + gl.compileShader(fragmentShader); + assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), gl.getShaderInfoLog(fragmentShader)); + gl.attachShader(program, fragmentShader); + + const vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.shaderSource(vertexShader, applyPragmas(definesSource + shaders.util + definition.vertexSource, this.vertexPragmas)); + gl.compileShader(vertexShader); + assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), gl.getShaderInfoLog(vertexShader)); + gl.attachShader(program, vertexShader); + + gl.linkProgram(program); + assert(gl.getProgramParameter(program, gl.LINK_STATUS), gl.getProgramInfoLog(program)); + + const attributes = {}; + const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); + for (let i = 0; i < numAttributes; i++) { + const attribute = gl.getActiveAttrib(program, i); + attributes[attribute.name] = gl.getAttribLocation(program, attribute.name); + } + + const uniforms = {}; + const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); + for (let ui = 0; ui < numUniforms; ui++) { + const uniform = gl.getActiveUniform(program, ui); + uniforms[uniform.name] = gl.getUniformLocation(program, uniform.name); + } + + return util.extend({ + program: program, + definition: definition, + attributes: attributes, + numAttributes: numAttributes + }, attributes, uniforms); + } + + setUniforms(gl, program, layer, globalProperties) { + for (const uniform of this.uniforms) { + const uniformLocation = program[uniform.name]; + gl[`uniform${uniform.components}fv`](uniformLocation, uniform.getValue(layer, globalProperties)); + } + } +} + +function createFunctionGetValue(attribute, stopZoomLevels) { + return function(layer, globalProperties, featureProperties) { + if (stopZoomLevels.length === 1) { + // return one multi-component value like color0 + return attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevels[0] }), featureProperties); + } else { + // pack multiple single-component values into a four component attribute + const values = []; + for (let z = 0; z < stopZoomLevels.length; z++) { + const stopZoomLevel = stopZoomLevels[z]; + values.push(attribute.getValue(layer, util.extend({}, globalProperties, { zoom: stopZoomLevel }), featureProperties)[0]); + } + return values; + } + }; +} + +function createGetUniform(attribute, stopOffset) { + return function(layer, 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(attribute.paintProperty, globalProperties.zoom); + // 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. + return [Math.max(0, Math.min(4, stopInterp - stopOffset))]; + }; +} + +function applyPragmas(source, pragmas) { + return source.replace(/#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g, (match, operation, precision, type, name) => { + return pragmas[operation][name].replace(/{type}/g, type).replace(/{precision}/g, precision); + }); +} + +module.exports = ProgramConfiguration; diff --git a/js/render/create_uniform_pragmas.js b/js/render/create_uniform_pragmas.js deleted file mode 100644 index b5e78ebfc23..00000000000 --- a/js/render/create_uniform_pragmas.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict'; - -const assert = require('assert'); - -module.exports = function(uniforms) { - const pragmas = { define: {}, initialize: {} }; - - for (let i = 0; i < uniforms.length; i++) { - const uniform = uniforms[i]; - assert(uniform.name.slice(0, 2) === 'u_'); - - const type = `{precision} ${uniform.components === 1 ? 'float' : `vec${uniform.components}`}`; - pragmas.define[uniform.name.slice(2)] = `uniform ${type} ${uniform.name};\n`; - pragmas.initialize[uniform.name.slice(2)] = `${type} ${uniform.name.slice(2)} = ${uniform.name};\n`; - } - - return pragmas; -}; diff --git a/js/render/draw_background.js b/js/render/draw_background.js index e1cf0a41ea4..40d7427ab48 100644 --- a/js/render/draw_background.js +++ b/js/render/draw_background.js @@ -1,7 +1,7 @@ 'use strict'; const pixelsToTileUnits = require('../source/pixels_to_tile_units'); -const createUniformPragmas = require('./create_uniform_pragmas'); +const ProgramConfiguration = require('../data/program_configuration'); const tileSize = 512; @@ -47,11 +47,11 @@ function drawBackground(painter, sourceCache, layer) { // Draw filling rectangle. if (painter.isOpaquePass !== (color[3] === 1)) return; - const pragmas = createUniformPragmas([ + program = painter.useProgram('fill', ProgramConfiguration.createStatic([ {name: 'u_color', components: 4}, {name: 'u_opacity', components: 1} - ]); - program = painter.useProgram('fill', [], pragmas, pragmas); + ])); + gl.uniform4fv(program.u_color, color); gl.uniform1f(program.u_opacity, opacity); painter.tileExtentVAO.bind(gl, program, painter.tileExtentBuffer); diff --git a/js/render/draw_circle.js b/js/render/draw_circle.js index 351c55dd875..24dc02b458d 100644 --- a/js/render/draw_circle.js +++ b/js/render/draw_circle.js @@ -25,13 +25,9 @@ function drawCircles(painter, sourceCache, layer, coords) { const bufferGroups = bucket.bufferGroups.circle; if (!bufferGroups) continue; - const programOptions = bucket.paintAttributes.circle[layer.id]; - const program = painter.useProgram( - 'circle', - programOptions.defines, - programOptions.vertexPragmas, - programOptions.fragmentPragmas - ); + const programConfiguration = bucket.programConfigurations.circle[layer.id]; + const program = painter.useProgram('circle', programConfiguration); + programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); if (layer.paint['circle-pitch-scale'] === 'map') { gl.uniform1i(program.u_scale_with_map, true); @@ -52,8 +48,6 @@ function drawCircles(painter, sourceCache, layer, coords) { layer.paint['circle-translate-anchor'] )); - bucket.setUniforms(gl, 'circle', program, layer, {zoom: painter.transform.zoom}); - for (let k = 0; k < bufferGroups.length; k++) { const group = bufferGroups[k]; group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); diff --git a/js/render/draw_extrusion.js b/js/render/draw_extrusion.js index eb4b215dee5..5c42c5d6a68 100644 --- a/js/render/draw_extrusion.js +++ b/js/render/draw_extrusion.js @@ -154,13 +154,9 @@ function drawExtrusion(painter, source, layer, coord) { const image = layer.paint['fill-pattern']; - const programOptions = bucket.paintAttributes.fillextrusion[layer.id]; - const program = painter.useProgram( - image ? 'fillExtrudePattern' : 'fillExtrude', - programOptions.defines, - programOptions.vertexPragmas, - programOptions.fragmentPragmas - ); + const programConfiguration = bucket.programConfigurations.fillextrusion[layer.id]; + const program = painter.useProgram(image ? 'fillExtrudePattern' : 'fillExtrude', programConfiguration); + programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); if (image) { setPattern(image, tile, coord, painter, program, true); @@ -169,8 +165,6 @@ function drawExtrusion(painter, source, layer, coord) { setMatrix(program, painter, coord, tile, layer); setLight(program, painter); - bucket.setUniforms(gl, 'fillextrusion', program, layer, {zoom: painter.transform.zoom}); - for (let i = 0; i < bufferGroups.length; i++) { const group = bufferGroups[i]; group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 3a3c9fad958..887a07f5586 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -60,14 +60,9 @@ function drawFill(painter, sourceCache, layer, coord) { if (!image) { - const programOptions = bucket.paintAttributes.fill[layer.id]; - program = painter.useProgram( - 'fill', - programOptions.defines, - programOptions.vertexPragmas, - programOptions.fragmentPragmas - ); - bucket.setUniforms(gl, 'fill', program, layer, {zoom: painter.transform.zoom}); + const programConfiguration = bucket.programConfigurations.fill[layer.id]; + program = painter.useProgram('fill', programConfiguration); + programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); } else { // Draw texture fill @@ -113,15 +108,10 @@ function drawStroke(painter, sourceCache, layer, coord) { gl.uniform2f(program.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); } else { - const programOptions = bucket.paintAttributes.fill[layer.id]; - program = painter.useProgram( - 'fillOutline', - programOptions.defines, - programOptions.vertexPragmas, - programOptions.fragmentPragmas - ); + const programConfiguration = bucket.programConfigurations.fill[layer.id]; + program = painter.useProgram('fillOutline', programConfiguration); + programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); gl.uniform2f(program.u_world, gl.drawingBufferWidth, gl.drawingBufferHeight); - bucket.setUniforms(gl, 'fill', program, layer, {zoom: painter.transform.zoom}); } gl.uniform1f(program.u_opacity, layer.paint['fill-opacity']); diff --git a/js/render/draw_line.js b/js/render/draw_line.js index d01bbde7a59..cc64d202d2b 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -39,10 +39,10 @@ function drawLineTile(painter, sourceCache, layer, coord) { const gl = painter.gl; const dasharray = layer.paint['line-dasharray']; const image = layer.paint['line-pattern']; - const programOptions = bucket.paintAttributes.line[layer.id]; - const program = painter.useProgram(dasharray ? 'lineSDF' : image ? 'linePattern' : 'line', - programOptions.defines, programOptions.vertexPragmas, programOptions.fragmentPragmas); + const programConfiguration = bucket.programConfigurations.line[layer.id]; + const program = painter.useProgram(dasharray ? 'lineSDF' : image ? 'linePattern' : 'line', programConfiguration); + programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); if (!image) { gl.uniform4fv(program.u_color, layer.paint['line-color']); @@ -120,8 +120,6 @@ function drawLineTile(painter, sourceCache, layer, coord) { gl.uniform1f(program.u_ratio, 1 / pixelsToTileUnits(tile, 1, painter.transform.zoom)); - bucket.setUniforms(gl, 'line', program, layer, {zoom: painter.transform.zoom}); - for (let i = 0; i < bufferGroups.length; i++) { const group = bufferGroups[i]; group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); diff --git a/js/render/painter.js b/js/render/painter.js index acd06f169cb..d87cd5e502d 100644 --- a/js/render/painter.js +++ b/js/render/painter.js @@ -11,10 +11,7 @@ const StructArrayType = require('../util/struct_array'); const Buffer = require('../data/buffer'); const VertexArrayObject = require('./vertex_array_object'); const RasterBoundsArray = require('./draw_raster').RasterBoundsArray; -const createUniformPragmas = require('./create_uniform_pragmas'); -const assert = require('assert'); -const shaders = require('mapbox-gl-shaders'); -const utilSource = shaders.util; +const ProgramConfiguration = require('../data/program_configuration'); const draw = { symbol: require('./draw_symbol'), @@ -161,11 +158,10 @@ class Painter { gl.stencilFunc(gl.ALWAYS, id, 0xF8); - const pragmas = createUniformPragmas([ + const program = this.useProgram('fill', ProgramConfiguration.createStatic([ {name: 'u_color', components: 4}, {name: 'u_opacity', components: 1} - ]); - const program = this.useProgram('fill', [], pragmas, pragmas); + ])); gl.uniformMatrix4fv(program.u_matrix, false, coord.posMatrix); // Draw the clipping mask @@ -372,78 +368,21 @@ class Painter { } } - _createProgram(name, defines, vertexPragmas, fragmentPragmas) { - const gl = this.gl; - const program = gl.createProgram(); - const definition = shaders[name]; - - let definesSource = '#define MAPBOX_GL_JS;\n'; - for (let j = 0; j < defines.length; j++) { - definesSource += `#define ${defines[j]};\n`; - } - - const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); - gl.shaderSource(fragmentShader, applyPragmas(definesSource + definition.fragmentSource, fragmentPragmas)); - gl.compileShader(fragmentShader); - assert(gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS), gl.getShaderInfoLog(fragmentShader)); - gl.attachShader(program, fragmentShader); - - const vertexShader = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(vertexShader, applyPragmas(definesSource + utilSource + definition.vertexSource, vertexPragmas)); - gl.compileShader(vertexShader); - assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), gl.getShaderInfoLog(vertexShader)); - gl.attachShader(program, vertexShader); - - gl.linkProgram(program); - assert(gl.getProgramParameter(program, gl.LINK_STATUS), gl.getProgramInfoLog(program)); - - const attributes = {}; - const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES); - for (let i = 0; i < numAttributes; i++) { - const attribute = gl.getActiveAttrib(program, i); - attributes[attribute.name] = gl.getAttribLocation(program, attribute.name); - } - - const uniforms = {}; - const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS); - for (let ui = 0; ui < numUniforms; ui++) { - const uniform = gl.getActiveUniform(program, ui); - uniforms[uniform.name] = gl.getUniformLocation(program, uniform.name); - } - - return util.extend({ - program: program, - definition: definition, - attributes: attributes, - numAttributes: numAttributes - }, attributes, uniforms); - } - - _createProgramCached(name, defines, vertexPragmas, fragmentPragmas) { + _createProgramCached(name, paintAttributeSet) { this.cache = this.cache || {}; - - const key = JSON.stringify({ - name: name, - defines: defines, - vertexPragmas: vertexPragmas, - fragmentPragmas: fragmentPragmas - }); - + const defines = this._showOverdrawInspector ? ['OVERDRAW_INSPECTOR'] : []; + const key = paintAttributeSet.programCacheKey(name, defines); if (!this.cache[key]) { - this.cache[key] = this._createProgram(name, defines, vertexPragmas, fragmentPragmas); + this.cache[key] = paintAttributeSet.createProgram(name, defines, this.gl); } return this.cache[key]; } - useProgram(nextProgramName, defines, vertexPragmas, fragmentPragmas) { + useProgram(name, paintAttributeSet) { const gl = this.gl; - defines = defines || []; - if (this._showOverdrawInspector) { - defines = defines.concat('OVERDRAW_INSPECTOR'); - } - - const nextProgram = this._createProgramCached(nextProgramName, defines, vertexPragmas, fragmentPragmas); + const nextProgram = this._createProgramCached(name, + paintAttributeSet || ProgramConfiguration.createStatic([])); const previousProgram = this.currentProgram; if (previousProgram !== nextProgram) { @@ -455,10 +394,4 @@ class Painter { } } -function applyPragmas(source, pragmas) { - return source.replace(/#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g, (match, operation, precision, type, name) => { - return pragmas[operation][name].replace(/{type}/g, type).replace(/{precision}/g, precision); - }); -} - module.exports = Painter; diff --git a/test/js/data/bucket.test.js b/test/js/data/bucket.test.js index 300ed277168..104ac6e8a1f 100644 --- a/test/js/data/bucket.test.js +++ b/test/js/data/bucket.test.js @@ -156,7 +156,7 @@ test('Bucket', (t) => { t.equal(bucket.arrayGroups.test[0].layoutVertexArray.bytesPerElement, 0); t.deepEqual( - bucket.paintAttributes.test.one.uniforms[0].getValue.call(bucket), + bucket.programConfigurations.test.one.uniforms[0].getValue.call(bucket), [5] );