From af3bf9ccc8ae8b9d5bd934500d4a6d0359d828c0 Mon Sep 17 00:00:00 2001 From: Ansis Brammanis Date: Mon, 11 Apr 2016 23:00:39 -0700 Subject: [PATCH] split paint attributes into a separate buffer - avoids the need to namespace attribute names with string concatenation - this let's us use attribute.name as the shader attribute name - this will let us use instanced rendering (#1898) with a shorter buffer when that extension is available. --- js/data/bucket.js | 144 +++++++++++++++++++------------- js/data/bucket/circle_bucket.js | 6 +- js/data/bucket/fill_bucket.js | 2 +- js/data/bucket/line_bucket.js | 4 +- js/data/bucket/symbol_bucket.js | 14 ++-- js/render/draw_circle.js | 4 +- js/util/struct_array.js | 19 ++++- shaders/circle.vertex.glsl | 8 +- 8 files changed, 121 insertions(+), 80 deletions(-) diff --git a/js/data/bucket.js b/js/data/bucket.js index e03485b2da0..71f0e1f7957 100644 --- a/js/data/bucket.js +++ b/js/data/bucket.js @@ -78,7 +78,7 @@ function Bucket(options) { this.elementGroups = options.elementGroups; this.buffers = util.mapObject(options.arrays, function(array, bufferName) { var arrayType = options.arrayTypes[bufferName]; - var type = (arrayType.members[0].name === 'vertices' ? Buffer.BufferType.ELEMENT : Buffer.BufferType.VERTEX); + var type = (arrayType.members.length && arrayType.members[0].name === 'vertices' ? Buffer.BufferType.ELEMENT : Buffer.BufferType.VERTEX); return new Buffer(array, arrayType, type); }); } @@ -150,12 +150,25 @@ Bucket.prototype.createArrays = function() { var vertexBufferName = this.getBufferName(programName, 'vertex'); var VertexArrayType = new StructArrayType({ - members: this.attributes[programName].enabled, + members: this.attributes[programName].coreAttributes, alignment: Buffer.VERTEX_ATTRIBUTE_ALIGNMENT }); arrays[vertexBufferName] = new VertexArrayType(); arrayTypes[vertexBufferName] = VertexArrayType.serialize(); + + var layerPaintAttributes = this.attributes[programName].paintAttributes; + for (var layerName in layerPaintAttributes) { + var paintVertexBufferName = this.getBufferName(layerName, programName); + + var PaintVertexArrayType = new StructArrayType({ + members: layerPaintAttributes[layerName].enabled, + alignment: Buffer.VERTEX_ATTRIBUTE_ALIGNMENT + }); + + arrays[paintVertexBufferName] = new PaintVertexArrayType(); + arrayTypes[paintVertexBufferName] = PaintVertexArrayType.serialize(); + } } if (programInterface.elementBuffer) { @@ -208,30 +221,40 @@ var AttributeType = { * @param {number} offset The offset of the attribute data in the currently bound GL buffer. * @param {Array} arguments to be passed to disabled attribute value functions */ -Bucket.prototype.setAttribPointers = function(programName, gl, program, offset, layer, globalProperties) { +Bucket.prototype.setAttribPointers = function(programName, gl, program, offset, layer, globalProperties, bindPaint) { var attribute; - // Set disabled attributes - var disabledAttributes = this.attributes[programName].disabled; - for (var i = 0; i < disabledAttributes.length; i++) { - attribute = disabledAttributes[i]; - if (layer.id !== attribute.layerId) continue; - - var attributeId = program[attribute.programName]; - gl['uniform' + attribute.components + 'fv'](attributeId, attribute.getValue(layer, globalProperties)); + if (bindPaint) { + // Set disabled attributes + var disabledAttributes = this.attributes[programName].paintAttributes[layer.id].disabled; + for (var i = 0; i < disabledAttributes.length; i++) { + attribute = disabledAttributes[i]; + var attributeId = program[attribute.name]; + gl['uniform' + attribute.components + 'fv'](attributeId, attribute.getValue(layer, globalProperties)); + } } // Set enabled attributes - var enabledAttributes = this.attributes[programName].enabled; - var vertexBuffer = this.buffers[this.getBufferName(programName, 'vertex')]; + var enabledAttributes = bindPaint ? + this.attributes[programName].paintAttributes[layer.id].enabled : + this.attributes[programName].coreAttributes; + + var vertexBuffer = bindPaint ? + this.buffers[this.getBufferName(layer.id, programName)] : + this.buffers[this.getBufferName(programName, 'vertex')]; + + if (bindPaint) { + // TODO remove + var bytesPerElement = this.buffers[this.getBufferName(programName, 'vertex')].itemSize; + offset = offset / bytesPerElement * vertexBuffer.itemSize; + } for (var j = 0; j < enabledAttributes.length; j++) { attribute = enabledAttributes[j]; - if (attribute.paintProperty && layer.id !== attribute.layerId) continue; if (!getMember(attribute.name)) continue; gl.vertexAttribPointer( - program[attribute.programName], + program[attribute.name], attribute.components, gl[AttributeType[attribute.type]], false, @@ -270,6 +293,10 @@ Bucket.prototype.bindBuffers = function(programInterfaceName, gl, options) { } }; +Bucket.prototype.bindPaintBuffer = function(programInterfaceName, gl, layerID) { + this.buffers[this.getBufferName(layerID, programInterfaceName)].bind(gl); +}; + /** * Get the name of a buffer. * @param {string} programName The name of the program that will use the buffer @@ -289,6 +316,7 @@ Bucket.prototype.serialize = function() { return array.serialize(); }), arrayTypes: this.arrayTypes, + childLayerIds: this.childLayers.map(function(layer) { return layer.id; }) @@ -310,32 +338,34 @@ Bucket.prototype.recalculateStyleLayers = function() { Bucket.prototype.getProgramMacros = function(programInterface, layer) { var macros = []; - var enabledAttributes = this.attributes[programInterface].enabled; + var enabledAttributes = this.attributes[programInterface].paintAttributes[layer.id].enabled; for (var i = 0; i < enabledAttributes.length; i++) { - var enabledAttribute = enabledAttributes[i]; - if (enabledAttribute.paintProperty && enabledAttribute.layerId === layer.id) { - macros.push(enabledAttribute.macro); - } + macros.push('ATTRIBUTE_' + enabledAttributes[i].name.toUpperCase()); } return macros; }; Bucket.prototype.addPaintAttributes = function(interfaceName, globalProperties, featureProperties, startIndex, endIndex) { - var enabled = this.attributes[interfaceName].enabled; - var vertexArray = this.arrays[this.getBufferName(interfaceName, 'vertex')]; - for (var m = 0; m < enabled.length; m++) { - var attribute = enabled[m]; - - if (attribute.paintProperty === undefined) continue; - - var value = attribute.getValue(this.childLayers[attribute.layerIndex], globalProperties, featureProperties); - var multiplier = attribute.multiplier || 1; - - for (var i = startIndex; i < endIndex; i++) { - var vertex = vertexArray.get(i); - for (var c = 0; c < attribute.components; c++) { - var memberName = attribute.components > 1 ? (attribute.name + c) : attribute.name; - vertex[memberName] = value[c] * multiplier; + for (var l = 0; l < this.childLayers.length; l++) { + var layer = this.childLayers[l]; + var length = this.arrays[this.getBufferName(interfaceName, 'vertex')].length; + var vertexArray = this.arrays[this.getBufferName(layer.id, interfaceName)]; + var enabled = this.attributes[interfaceName].paintAttributes[layer.id].enabled; + for (var m = 0; m < enabled.length; m++) { + var attribute = enabled[m]; + + if (attribute.paintProperty === undefined) continue; + + var value = attribute.getValue(layer, globalProperties, featureProperties); + var multiplier = attribute.multiplier || 1; + + vertexArray.resize(length); + for (var i = startIndex; i < endIndex; i++) { + var vertex = vertexArray.get(i); + for (var c = 0; c < attribute.components; c++) { + var memberName = attribute.components > 1 ? (attribute.name + c) : attribute.name; + vertex[memberName] = value[c] * multiplier; + } } } } @@ -358,32 +388,30 @@ function capitalize(string) { function createAttributes(bucket) { var attributes = {}; for (var interfaceName in bucket.programInterfaces) { - var interfaceAttributes = attributes[interfaceName] = { enabled: [], disabled: [] }; + var interfaceAttributes = attributes[interfaceName] = { coreAttributes: [], paintAttributes: {} }; + var layerPaintAttributes = interfaceAttributes.paintAttributes; + + for (var c = 0; c < bucket.childLayers.length; c++) { + var childLayer = bucket.childLayers[c]; + layerPaintAttributes[childLayer.id] = { enabled: [], disabled: [] }; + } + var interface_ = bucket.programInterfaces[interfaceName]; for (var i = 0; i < interface_.attributes.length; i++) { var attribute = interface_.attributes[i]; - for (var j = 0; j < bucket.childLayers.length; j++) { - var layer = bucket.childLayers[j]; - if (!attribute.paintProperty && layer.id !== bucket.layer.id) continue; - if (attribute.paintProperty && layer.isPaintValueFeatureConstant(attribute.paintProperty)) { - interfaceAttributes.disabled.push(util.extend({}, attribute, { - getValue: attribute.getValue, - name: layer.id + '__' + attribute.name, - programName: 'a_' + attribute.name, - layerId: layer.id, - layerIndex: j, - components: attribute.components || 1 - })); - } else { - interfaceAttributes.enabled.push(util.extend({}, attribute, { - getValue: attribute.getValue, - name: layer.id + '__' + attribute.name, - programName: 'a_' + attribute.name, - layerId: layer.id, - layerIndex: j, - macro: 'ATTRIBUTE_' + attribute.name.toUpperCase(), - components: attribute.components || 1 - })); + + if (attribute.paintProperty === undefined) { + interfaceAttributes.coreAttributes.push(attribute); + } else { + for (var j = 0; j < bucket.childLayers.length; j++) { + var layer = bucket.childLayers[j]; + var paintAttributes = layerPaintAttributes[layer.id]; + + if (layer.isPaintValueFeatureConstant(attribute.paintProperty)) { + paintAttributes.disabled.push(attribute); + } else { + paintAttributes.enabled.push(attribute); + } } } } diff --git a/js/data/bucket/circle_bucket.js b/js/data/bucket/circle_bucket.js index 510c50771c7..21d0b9952b9 100644 --- a/js/data/bucket/circle_bucket.js +++ b/js/data/bucket/circle_bucket.js @@ -32,11 +32,11 @@ CircleBucket.prototype.programInterfaces = { elementBuffer: true, attributes: [{ - name: 'pos', + name: 'a_pos', components: 2, type: 'Int16' }, { - name: 'color', + name: 'a_color', components: 4, type: 'Uint8', getValue: function(layer, globalProperties, featureProperties) { @@ -45,7 +45,7 @@ CircleBucket.prototype.programInterfaces = { multiplier: 255, paintProperty: 'circle-color' }, { - name: 'radius', + name: 'a_radius', components: 1, type: 'Uint16', isLayerConstant: false, diff --git a/js/data/bucket/fill_bucket.js b/js/data/bucket/fill_bucket.js index a479556834a..6b874562f1f 100644 --- a/js/data/bucket/fill_bucket.js +++ b/js/data/bucket/fill_bucket.js @@ -24,7 +24,7 @@ FillBucket.prototype.programInterfaces = { secondElementBufferComponents: 2, attributes: [{ - name: 'pos', + name: 'a_pos', components: 2, type: 'Int16' }] diff --git a/js/data/bucket/line_bucket.js b/js/data/bucket/line_bucket.js index 1dc7ef67a57..a87cfc48000 100644 --- a/js/data/bucket/line_bucket.js +++ b/js/data/bucket/line_bucket.js @@ -74,11 +74,11 @@ LineBucket.prototype.programInterfaces = { elementBuffer: true, attributes: [{ - name: 'pos', + name: 'a_pos', components: 2, type: 'Int16' }, { - name: 'data', + name: 'a_data', components: 4, type: 'Uint8' }] diff --git a/js/data/bucket/symbol_bucket.js b/js/data/bucket/symbol_bucket.js index ff111f588ff..7b424b34d22 100644 --- a/js/data/bucket/symbol_bucket.js +++ b/js/data/bucket/symbol_bucket.js @@ -34,19 +34,19 @@ function SymbolBucket(options) { SymbolBucket.prototype = util.inherit(Bucket, {}); var programAttributes = [{ - name: 'pos', + name: 'a_pos', components: 2, type: 'Int16' }, { - name: 'offset', + name: 'a_offset', components: 2, type: 'Int16' }, { - name: 'data1', + name: 'a_data1', components: 4, type: 'Uint8' }, { - name: 'data2', + name: 'a_data2', components: 2, type: 'Uint8' }]; @@ -100,15 +100,15 @@ SymbolBucket.prototype.programInterfaces = { vertexBuffer: true, attributes: [{ - name: 'pos', + name: 'a_pos', components: 2, type: 'Int16' }, { - name: 'extrude', + name: 'a_extrude', components: 2, type: 'Int16' }, { - name: 'data', + name: 'a_data', components: 2, type: 'Uint8' }] diff --git a/js/render/draw_circle.js b/js/render/draw_circle.js index 0dc846e1502..1ae1e15ddd0 100644 --- a/js/render/draw_circle.js +++ b/js/render/draw_circle.js @@ -39,11 +39,13 @@ function drawCircles(painter, source, layer, coords) { )); painter.setExMatrix(painter.transform.exMatrix); - bucket.bindBuffers('circle', gl); for (var k = 0; k < elementGroups.length; k++) { var group = elementGroups[k]; var count = group.elementLength * 3; + bucket.bindBuffers('circle', gl); bucket.setAttribPointers('circle', gl, program, group.vertexOffset, layer, {zoom: painter.transform.zoom}); + bucket.bindPaintBuffer('circle', gl, layer.id); + bucket.setAttribPointers('circle', gl, program, group.vertexOffset, layer, {zoom: painter.transform.zoom}, true); gl.drawElements(gl.TRIANGLES, count, gl.UNSIGNED_SHORT, group.elementOffset); } } diff --git a/js/util/struct_array.js b/js/util/struct_array.js index 8cfe39d3156..8c04d97e112 100644 --- a/js/util/struct_array.js +++ b/js/util/struct_array.js @@ -170,7 +170,7 @@ function createEmplaceBack(members, bytesPerElement) { var body = '' + 'var i = this.length;\n' + 'this.length++;\n' + - 'if (this.length > this.capacity) this._resize(this.length);\n'; + 'if (this.length > this.capacity) this.reserve(this.length);\n'; for (var m = 0; m < members.length; m++) { var member = members[m]; @@ -242,7 +242,7 @@ function StructArray(serialized) { } else { this.length = 0; this.capacity = 0; - this._resize(this.DEFAULT_CAPACITY); + this.reserve(this.DEFAULT_CAPACITY); } } @@ -296,9 +296,9 @@ StructArray.prototype.trim = function() { /** * Resize the array so that it fits at least `n` elements. * @private - * @param {number} n The number of elements that must fit in the array after the resize. + * @param {number} n The number of elements that must fit in allocated buffer. */ -StructArray.prototype._resize = function(n) { +StructArray.prototype.reserve = function(n) { this.capacity = Math.max(n, Math.floor(this.capacity * this.RESIZE_MULTIPLIER)); this.arrayBuffer = new ArrayBuffer(this.capacity * this.bytesPerElement); @@ -307,6 +307,17 @@ StructArray.prototype._resize = function(n) { if (oldUint8Array) this.uint8.set(oldUint8Array); }; +/** + * Resize the array. + * If `n` is greater than the current length then additional elements with undefined values are added. + * If `n` is less than the current length then the array will be reduced to the first `n` elements. + * @param {number} n The new size of the array. + */ +StructArray.prototype.resize = function(n) { + this.length = n; + if (this.length > this.capacity) this.reserve(n); +}; + /** * Create TypedArray views for the current ArrayBuffer. * @private diff --git a/shaders/circle.vertex.glsl b/shaders/circle.vertex.glsl index 99b2c885f44..f607f3a6940 100644 --- a/shaders/circle.vertex.glsl +++ b/shaders/circle.vertex.glsl @@ -6,13 +6,13 @@ uniform float u_devicepixelratio; attribute vec2 a_pos; -#ifdef ATTRIBUTE_COLOR +#ifdef ATTRIBUTE_A_COLOR attribute lowp vec4 a_color; #else uniform lowp vec4 a_color; #endif -#ifdef ATTRIBUTE_RADIUS +#ifdef ATTRIBUTE_A_RADIUS attribute mediump float a_radius; #else uniform mediump float a_radius; @@ -24,7 +24,7 @@ varying lowp float v_antialiasblur; void main(void) { -#ifdef ATTRIBUTE_RADIUS +#ifdef ATTRIBUTE_A_RADIUS mediump float radius = a_radius / 10.0; #else mediump float radius = a_radius; @@ -42,7 +42,7 @@ void main(void) { // Multiply the extrude by it so that it isn't affected by it. gl_Position += extrude * gl_Position.w; -#ifdef ATTRIBUTE_COLOR +#ifdef ATTRIBUTE_A_COLOR v_color = a_color / 255.0; #else v_color = a_color;