Skip to content

Commit

Permalink
split paint attributes into a separate buffer
Browse files Browse the repository at this point in the history
- 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.
  • Loading branch information
ansis committed Apr 12, 2016
1 parent b63526c commit af3bf9c
Show file tree
Hide file tree
Showing 8 changed files with 121 additions and 80 deletions.
144 changes: 86 additions & 58 deletions js/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -289,6 +316,7 @@ Bucket.prototype.serialize = function() {
return array.serialize();
}),
arrayTypes: this.arrayTypes,

childLayerIds: this.childLayers.map(function(layer) {
return layer.id;
})
Expand All @@ -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;
}
}
}
}
Expand All @@ -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);
}
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions js/data/bucket/circle_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -45,7 +45,7 @@ CircleBucket.prototype.programInterfaces = {
multiplier: 255,
paintProperty: 'circle-color'
}, {
name: 'radius',
name: 'a_radius',
components: 1,
type: 'Uint16',
isLayerConstant: false,
Expand Down
2 changes: 1 addition & 1 deletion js/data/bucket/fill_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ FillBucket.prototype.programInterfaces = {
secondElementBufferComponents: 2,

attributes: [{
name: 'pos',
name: 'a_pos',
components: 2,
type: 'Int16'
}]
Expand Down
4 changes: 2 additions & 2 deletions js/data/bucket/line_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}]
Expand Down
14 changes: 7 additions & 7 deletions js/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}];
Expand Down Expand Up @@ -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'
}]
Expand Down
4 changes: 3 additions & 1 deletion js/render/draw_circle.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down
19 changes: 15 additions & 4 deletions js/util/struct_array.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -242,7 +242,7 @@ function StructArray(serialized) {
} else {
this.length = 0;
this.capacity = 0;
this._resize(this.DEFAULT_CAPACITY);
this.reserve(this.DEFAULT_CAPACITY);
}
}

Expand Down Expand Up @@ -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);

Expand All @@ -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
Expand Down
Loading

0 comments on commit af3bf9c

Please sign in to comment.