diff --git a/src/data/bucket/circle_bucket.js b/src/data/bucket/circle_bucket.js index e9f86df667c..f86e63f53de 100644 --- a/src/data/bucket/circle_bucket.js +++ b/src/data/bucket/circle_bucket.js @@ -72,7 +72,7 @@ class CircleBucket implements Bucke this.layoutVertexArray = new CircleLayoutArray(); this.indexArray = new TriangleIndexArray(); this.segments = new SegmentVector(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); } diff --git a/src/data/bucket/fill_bucket.js b/src/data/bucket/fill_bucket.js index 297ee0a872c..d46c7e0af09 100644 --- a/src/data/bucket/fill_bucket.js +++ b/src/data/bucket/fill_bucket.js @@ -68,7 +68,7 @@ class FillBucket implements Bucket { this.layoutVertexArray = new FillLayoutArray(); this.indexArray = new TriangleIndexArray(); this.indexArray2 = new LineIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.segments2 = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/fill_extrusion_bucket.js b/src/data/bucket/fill_extrusion_bucket.js index bf507349946..0378758ba62 100644 --- a/src/data/bucket/fill_extrusion_bucket.js +++ b/src/data/bucket/fill_extrusion_bucket.js @@ -82,7 +82,7 @@ class FillExtrusionBucket implements Bucket { this.layoutVertexArray = new FillExtrusionLayoutArray(); this.indexArray = new TriangleIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/line_bucket.js b/src/data/bucket/line_bucket.js index d8a99723174..5764ddd4a9e 100644 --- a/src/data/bucket/line_bucket.js +++ b/src/data/bucket/line_bucket.js @@ -111,7 +111,7 @@ class LineBucket implements Bucket { this.layoutVertexArray = new LineLayoutArray(); this.indexArray = new TriangleIndexArray(); - this.programConfigurations = new ProgramConfigurationSet(layoutAttributes, options.layers, options.zoom); + this.programConfigurations = new ProgramConfigurationSet(options.layers, options.zoom); this.segments = new SegmentVector(); this.stateDependentLayerIds = this.layers.filter((l) => l.isStateDependent()).map((l) => l.id); diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index cd7aadfaf66..02d96009206 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -382,8 +382,8 @@ class SymbolBucket implements Bucket { } createArrays() { - this.text = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^text/.test(property))); - this.icon = new SymbolBuffers(new ProgramConfigurationSet(symbolLayoutAttributes.members, this.layers, this.zoom, property => /^icon/.test(property))); + this.text = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^text/.test(property))); + this.icon = new SymbolBuffers(new ProgramConfigurationSet(this.layers, this.zoom, property => /^icon/.test(property))); this.glyphOffsetArray = new GlyphOffsetArray(); this.lineVertexArray = new SymbolLineVertexArray(); diff --git a/src/data/program_configuration.js b/src/data/program_configuration.js index 2d8c1578041..0ea6ee93895 100644 --- a/src/data/program_configuration.js +++ b/src/data/program_configuration.js @@ -398,13 +398,11 @@ class CrossFadedCompositeBinder implements AttributeBinder { export default class ProgramConfiguration { binders: {[_: string]: (AttributeBinder | UniformBinder) }; cacheKey: string; - layoutAttributes: Array; _buffers: Array; - constructor(layer: TypedStyleLayer, zoom: number, filterProperties: (_: string) => boolean, layoutAttributes: Array) { + constructor(layer: TypedStyleLayer, zoom: number, filterProperties: (_: string) => boolean) { this.binders = {}; - this.layoutAttributes = layoutAttributes; this._buffers = []; const keys = []; @@ -500,6 +498,36 @@ export default class ProgramConfiguration { return result; } + getBinderAttributes(): Array { + const result = []; + for (const property in this.binders) { + const binder = this.binders[property]; + if (binder instanceof SourceExpressionBinder || binder instanceof CompositeExpressionBinder) { + for (let i = 0; i < binder.paintVertexAttributes.length; i++) { + result.push(binder.paintVertexAttributes[i].name); + } + } else if (binder instanceof CrossFadedCompositeBinder) { + for (let i = 0; i < patternAttributes.members.length; i++) { + result.push(patternAttributes.members[i].name); + } + } + } + return result; + } + + getBinderUniforms(): Array { + const uniforms = []; + for (const property in this.binders) { + const binder = this.binders[property]; + if (binder instanceof ConstantBinder || binder instanceof CrossFadedConstantBinder || binder instanceof CompositeExpressionBinder) { + for (const uniformName of binder.uniformNames) { + uniforms.push(uniformName); + } + } + } + return uniforms; + } + getPaintVertexBuffers(): Array { return this._buffers; } @@ -567,10 +595,10 @@ export class ProgramConfigurationSet { _featureMap: FeaturePositionMap; _bufferOffset: number; - constructor(layoutAttributes: Array, layers: $ReadOnlyArray, zoom: number, filterProperties: (_: string) => boolean = () => true) { + constructor(layers: $ReadOnlyArray, zoom: number, filterProperties: (_: string) => boolean = () => true) { this.programConfigurations = {}; for (const layer of layers) { - this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties, layoutAttributes); + this.programConfigurations[layer.id] = new ProgramConfiguration(layer, zoom, filterProperties); } this.needsUpload = false; this._featureMap = new FeaturePositionMap(); diff --git a/src/render/painter.js b/src/render/painter.js index 165a1e6e91c..56507434800 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -599,7 +599,7 @@ class Painter { this.cache = this.cache || {}; const key = `${name}${programConfiguration ? programConfiguration.cacheKey : ''}${this._showOverdrawInspector ? '/overdraw' : ''}`; if (!this.cache[key]) { - this.cache[key] = new Program(this.context, shaders[name], programConfiguration, programUniforms[name], this._showOverdrawInspector); + this.cache[key] = new Program(this.context, name, shaders[name], programConfiguration, programUniforms[name], this._showOverdrawInspector); } return this.cache[key]; } diff --git a/src/render/program.js b/src/render/program.js index 962988a78c5..6345516f619 100644 --- a/src/render/program.js +++ b/src/render/program.js @@ -21,6 +21,16 @@ export type DrawMode = | $PropertyType | $PropertyType; +function getTokenizedAttributesAndUniforms (array: Array): Array { + const result = []; + + for (let i = 0; i < array.length; i++) { + if (array[i] === null) continue; + const token = array[i].split(' '); + result.push(token.pop()); + } + return result; +} class Program { program: WebGLProgram; attributes: {[_: string]: number}; @@ -30,13 +40,27 @@ class Program { failedToCreate: boolean; constructor(context: Context, - source: {fragmentSource: string, vertexSource: string}, - configuration: ?ProgramConfiguration, - fixedUniforms: (Context, UniformLocations) => Us, - showOverdrawInspector: boolean) { + name: string, + source: {fragmentSource: string, vertexSource: string, staticAttributes: Array, staticUniforms: Array}, + configuration: ?ProgramConfiguration, + fixedUniforms: (Context, UniformLocations) => Us, + showOverdrawInspector: boolean) { const gl = context.gl; this.program = gl.createProgram(); + const staticAttrInfo = getTokenizedAttributesAndUniforms(source.staticAttributes); + const dynamicAttrInfo = configuration ? configuration.getBinderAttributes() : []; + const allAttrInfo = staticAttrInfo.concat(dynamicAttrInfo); + + const staticUniformsInfo = source.staticUniforms ? getTokenizedAttributesAndUniforms(source.staticUniforms) : []; + const dynamicUniformsInfo = configuration ? configuration.getBinderUniforms() : []; + // remove duplicate uniforms + const uniformList = staticUniformsInfo.concat(dynamicUniformsInfo); + const allUniformsInfo = []; + for (const uniform of uniformList) { + if (allUniformsInfo.indexOf(uniform) < 0) allUniformsInfo.push(uniform); + } + const defines = configuration ? configuration.defines() : []; if (showOverdrawInspector) { defines.push('#define OVERDRAW_INSPECTOR;'); @@ -64,13 +88,16 @@ class Program { assert(gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS), (gl.getShaderInfoLog(vertexShader): any)); gl.attachShader(this.program, vertexShader); - // Manually bind layout attributes in the order defined by their - // ProgramInterface so that we don't dynamically link an unused - // attribute at position 0, which can cause rendering to fail for an - // entire layer (see #4607, #4728) - const layoutAttributes = configuration ? configuration.layoutAttributes : []; - for (let i = 0; i < layoutAttributes.length; i++) { - gl.bindAttribLocation(this.program, i, layoutAttributes[i].name); + this.attributes = {}; + const uniformLocations = {}; + + this.numAttributes = allAttrInfo.length; + + for (let i = 0; i < this.numAttributes; i++) { + if (allAttrInfo[i]) { + gl.bindAttribLocation(this.program, i, allAttrInfo[i]); + this.attributes[allAttrInfo[i]] = i; + } } gl.linkProgram(this.program); @@ -79,23 +106,14 @@ class Program { gl.deleteShader(vertexShader); gl.deleteShader(fragmentShader); - this.numAttributes = gl.getProgramParameter(this.program, gl.ACTIVE_ATTRIBUTES); - - this.attributes = {}; - const uniformLocations = {}; - - for (let i = 0; i < this.numAttributes; i++) { - const attribute = gl.getActiveAttrib(this.program, i); - if (attribute) { - this.attributes[attribute.name] = gl.getAttribLocation(this.program, attribute.name); - } - } - - const numUniforms = gl.getProgramParameter(this.program, gl.ACTIVE_UNIFORMS); - for (let i = 0; i < numUniforms; i++) { - const uniform = gl.getActiveUniform(this.program, i); - if (uniform) { - uniformLocations[uniform.name] = gl.getUniformLocation(this.program, uniform.name); + const uniformsArray = Array.from(allUniformsInfo); + for (let it = 0; it < uniformsArray.length; it++) { + const uniform = uniformsArray[it]; + if (uniform && !uniformLocations[uniform]) { + const uniformLocation = gl.getUniformLocation(this.program, uniform); + if (uniformLocation) { + uniformLocations[uniform] = uniformLocation; + } } } diff --git a/src/shaders/shaders.js b/src/shaders/shaders.js index 221728dfddd..25f1fabdbcb 100644 --- a/src/shaders/shaders.js +++ b/src/shaders/shaders.js @@ -87,6 +87,11 @@ export const symbolTextAndIcon = compile(symbolTextAndIconFrag, symbolTextAndIco function compile(fragmentSource, vertexSource) { const re = /#pragma mapbox: ([\w]+) ([\w]+) ([\w]+) ([\w]+)/g; + const staticAttributes = vertexSource.match(/attribute ([\w]+) ([\w]+)/g); + const fragmentUniforms = fragmentSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); + const vertexUniforms = vertexSource.match(/uniform ([\w]+) ([\w]+)([\s]*)([\w]*)/g); + const staticUniforms = vertexUniforms ? vertexUniforms.concat(fragmentUniforms) : fragmentUniforms; + const fragmentPragmas = {}; fragmentSource = fragmentSource.replace(re, (match, operation, precision, type, name) => { @@ -176,5 +181,5 @@ uniform ${precision} ${type} u_${name}; } }); - return {fragmentSource, vertexSource}; + return {fragmentSource, vertexSource, staticAttributes, staticUniforms}; }