Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored bucket classes [do not merge] #1620

Closed
wants to merge 52 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
f29bb9d
Added Bucket2 class, migrated circle
Oct 7, 2015
b11f639
Use layer type uniform values
Oct 7, 2015
0f60afa
Removed custom push methods
Oct 9, 2015
efe428b
Added special push object to layer type iterator
Oct 9, 2015
ab28da2
Automatically handle element group lengths
Oct 9, 2015
f53e106
Added better api
Oct 10, 2015
28161eb
Converted fill
Oct 10, 2015
1132909
Convertex line buckets
Oct 12, 2015
f93438d
Fixed circle uniforms
Oct 12, 2015
99b4aaf
Rewrote symbol bucket
Oct 12, 2015
a612611
Lint cleanup, little bug cleanup
Oct 13, 2015
a117acb
Removed create_bucket
Oct 13, 2015
6252953
Renamed bucket2 to bucket
Oct 13, 2015
92386a2
Use objects for Buffer::push
Oct 13, 2015
814f139
Started refactoring buffer_builder WIP
Oct 13, 2015
d3586a9
Extracted BufferBuilder classes
Oct 13, 2015
19ef1f2
Move BufferBuilder::layers definition
Oct 13, 2015
0a13cbc
Minor style changes
Oct 13, 2015
4a161c2
fixed featuresat
Oct 13, 2015
645b5ed
Completely moved over to LayerType buffer layout definitions
Oct 13, 2015
551d767
Completely removed buffer_set
Oct 13, 2015
3845296
Moved buffer builder classes into data dir
Oct 13, 2015
d9e02c4
Use composition instead of util.extend for layer type in BufferBuilder
Oct 13, 2015
bf4c8b0
Removed uniform calculation in LayerType
Oct 13, 2015
4287447
Renamed LayerType files
Oct 13, 2015
479c840
Misc PR cleanup
Oct 13, 2015
0aefc27
Added BufferBuilder::makeRoomFor method
Oct 13, 2015
3795ebb
Removed stray TODOs
Oct 13, 2015
1aa2cd7
Removed extraneous whitespace
Oct 13, 2015
4adf75d
Check itemSize after setting it
Oct 14, 2015
4b77680
Simplified BufferBuilder._createBuffers
Oct 14, 2015
d7db04a
Use array instead of an object in Buffer::push
Oct 14, 2015
a14456d
Fixed incorrect makeRoomFor calls
Oct 14, 2015
afd728d
😈
Oct 14, 2015
4b8987d
Simplified createElementAddMethod
Oct 14, 2015
85e5689
Remove BufferBuilder class methods
Oct 15, 2015
92f87ef
Refactoring
Oct 15, 2015
dfdc2b6
Refactoring
Oct 15, 2015
55cc147
Skip _createPushMethod in main thread
Oct 15, 2015
128d67f
Only call _refreshViews in worker thread
Oct 15, 2015
0a3bba0
Improved documentation on Buffer methods
Oct 15, 2015
790ee8d
Minor refactoring
Oct 15, 2015
6c352bf
Made BufferBuilder::type set by subclasses
Oct 15, 2015
5519090
Removed fill layer type uniforms
Oct 15, 2015
ccb1620
Added basic BufferBuilder tests
Oct 15, 2015
2ef457f
Added documentation to BufferBuilder
Oct 15, 2015
c6cda4b
Remove all references to "bucket"
Oct 15, 2015
d27f159
PR cleanup
Oct 16, 2015
1cf8ae8
Revert bench changes
Oct 16, 2015
b5512a7
Revert bucket-stats changes
Oct 19, 2015
6e9bca9
Fixed typo in line_buffer_builder
Oct 19, 2015
3e49540
Fix reloadSymbolData error
Oct 21, 2015
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 37 additions & 16 deletions js/data/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

// Note: all "sizes" are measured in bytes

var util = require('../util/util');
var assert = require('assert');

/**
Expand Down Expand Up @@ -46,26 +45,30 @@ function Buffer(options) {
// element buffer attributes do not need to be aligned.
var attributeAlignment = this.type === Buffer.BufferType.VERTEX ? Buffer.VERTEX_ATTRIBUTE_ALIGNMENT : 1;

for (var i = 0; i < options.attributes.length; i++) {
var attribute = util.extend({}, options.attributes[i]);

attribute.components = attribute.components || 1;
attribute.type = attribute.type || Buffer.AttributeType.UNSIGNED_BYTE;
this.attributes = options.attributes.map(function(attributeOptions) {
var attribute = {};

attribute.name = attributeOptions.name;
attribute.components = attributeOptions.components || 1;
attribute.type = attributeOptions.type || Buffer.AttributeType.UNSIGNED_BYTE;
attribute.size = attribute.type.size * attribute.components;
attribute.offset = this.itemSize;

this.attributes.push(attribute);
this.itemSize = align(attribute.offset + attribute.size, attributeAlignment);

assert(!isNaN(this.itemSize));
assert(!isNaN(attribute.size));
assert(attribute.type.name in Buffer.AttributeType);
}

return attribute;
}, this);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry about undoing your work here @mourner . We need to construct attribute from scratch because it sometimes includes some extraneous non-serializable fields (namely the value property which is a function)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this looks good. One minor remark is that asserting itemSize should probably happen after it gets its new value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch.


// These are expensive calls. Because we only push things to buffers in
// the worker thread, we can skip in the "clone an existing buffer" case.
this._createPushMethod();
this._refreshViews();
}

}

/**
Expand Down Expand Up @@ -117,12 +120,14 @@ Buffer.prototype.setAttribPointers = function(gl, shader, offset) {
};

/**
* Get an item from the `ArrayBuffer`.
* Get an item from the `ArrayBuffer`. Only used for debugging.
* @private
* @param {number} index The index of the item to get
* @returns {Object.<string, Array.<number>>}
*/
Buffer.prototype.get = function(index) {
this._refreshViews();

var item = {};
var offset = index * this.itemSize;

Expand All @@ -138,6 +143,23 @@ Buffer.prototype.get = function(index) {
return item;
};

/**
* Check that a buffer item is well formed and throw an error if not. Only
* used for debugging.
* @private
* @param {number} args The "arguments" object from Buffer::push
*/
Buffer.prototype.validate = function(args) {
assert(args.length === this.attributes.length);
for (var i = 0; i < args.length; i++) {
var attribute = this.attributes[i];
assert(args[i].length === attribute.components);
for (var j = 0; j < attribute.components; j++) {
assert(!isNaN(args[i][j]));
}
}
};

Buffer.prototype._resize = function(capacity) {
var old = this.views.UNSIGNED_BYTE;
this.capacity = align(capacity, Buffer.CAPACITY_ALIGNMENT);
Expand All @@ -157,7 +179,7 @@ Buffer.prototype._refreshViews = function() {

Buffer.prototype._createPushMethod = function() {
var body = '';
var argNames = [];
var argIds = [];

body += 'var index = this.length++;\n';
body += 'var offset = index * ' + this.itemSize + ';\n';
Expand All @@ -166,22 +188,21 @@ Buffer.prototype._createPushMethod = function() {
for (var i = 0; i < this.attributes.length; i++) {
var attribute = this.attributes[i];
var offsetId = 'offset' + i;
var argId = 'value' + i;
argIds.push(argId);

body += '\nvar ' + offsetId + ' = (offset + ' + attribute.offset + ') / ' + attribute.type.size + ';\n';

for (var j = 0; j < attribute.components; j++) {
var valueId = 'value' + i + '_' + j;
var rvalue = argId + '[' + j + ']';
var lvalue = 'this.views.' + attribute.type.name + '[' + offsetId + ' + ' + j + ']';

body += lvalue + ' = ' + valueId + ';\n';

argNames.push(valueId);
body += lvalue + ' = ' + rvalue + ';\n';
}
}

body += '\nreturn index;\n';

this.push = new Function(argNames, body);
this.push = new Function(argIds, body);
};

/**
Expand Down
255 changes: 255 additions & 0 deletions js/data/buffer_builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
'use strict';

var featureFilter = require('feature-filter');

var StyleDeclarationSet = require('../style/style_declaration_set');
var LayoutProperties = require('../style/layout_properties');
var ElementGroups = require('./element_groups');
var Buffer = require('./buffer');

module.exports = BufferBuilder;

/**
* Instantiate the appropriate subclass of `BufferBuilder` for `options`.
* @private
* @param options See `BufferBuilder` constructor options
* @returns {BufferBuilder}
*/
BufferBuilder.create = function(options) {
var Classes = {
fill: require('./fill_buffer_builder'),
line: require('./line_buffer_builder'),
circle: require('./circle_buffer_builder'),
symbol: require('./symbol_buffer_builder')
};
return new Classes[options.layer.type](options);
};

/**
* The `BufferBuilder` class builds a set of `Buffer`s for a set of vector tile
* features.
*
* `BufferBuilder` is an abstract class. A subclass exists for each Mapbox GL
* style spec layer type. Because `BufferBuilder` is an abstract class,
* instances should be created via the `BufferBuilder.create` method.
*
* For performance reasons, `BufferBuilder` creates its "add"s methods at
* runtime using `new Function(...)`.
*
* @class BufferBuilder
* @private
* @param options
* @param {number} options.zoom Zoom level of the buffers being built. May be
* a fractional zoom level.
* @param options.layer A Mapbox GL style layer object
* @param {Object.<string, Buffer>} options.buffers The set of `Buffer`s being
* built for this tile. This object facilitates sharing of `Buffer`s be
between `BufferBuilder`s.
*/
function BufferBuilder(options) {
this.layer = options.layer;
this.zoom = options.zoom;

this.layers = [this.layer.id];
this.features = [];
this.id = this.layer.id;
this['source-layer'] = this.layer['source-layer'];
this.interactive = this.layer.interactive;
this.minZoom = this.layer.minzoom;
this.maxZoom = this.layer.maxzoom;
this.filter = featureFilter(this.layer.filter);

this.resetBuffers(options.buffers);

this.layoutProperties = createLayoutProperties(this.layer, this.zoom);

for (var shaderName in this.type.shaders) {
var shader = this.type.shaders[shaderName];

var vertexName = this.getAddVertexMethodName(shaderName);
var elementName = this.getAddElementMethodName(shaderName, false);
var secondElementName = this.getAddElementMethodName(shaderName, true);

this[vertexName] = createVertexAddMethod(shaderName, shader);
this[elementName] = createElementAddMethod(shaderName, shader, false);
this[secondElementName] = createElementAddMethod(shaderName, shader, true);
}
}

/**
* Build the buffers! Features are set directly to the `features` property.
* @private
*/
BufferBuilder.prototype.addFeatures = function() {
for (var i = 0; i < this.features.length; i++) {
this.addFeature(this.features[i]);
}
};

/**
* Check if there is enough space available in the current element group for
* `vertexLength` vertices. If not, append a new elementGroup. Should be called
* by `addFeatures` and its callees.
* @private
* @param {string} shaderName the name of the shader associated with the buffer that will receive the vertices
* @param {number} vertexLength The number of vertices that will be inserted to the buffer.
*/
BufferBuilder.prototype.makeRoomFor = function(shaderName, vertexLength) {
this.elementGroups[shaderName].makeRoomFor(vertexLength);
};

/**
* Get the name of the generated "add vertex" method for a particular shader.
* @private
* @param {string} shaderName The name of the shader
* @returns {string} The name of the method
*/
BufferBuilder.prototype.getAddVertexMethodName = function(shaderName) {
var shader = this.type.shaders[shaderName];
return 'add' + capitalize(shader.vertexBuffer);
};

/**
* Get the name of the generated "add element" method for a particular shader.
* @private
* @param {string} shaderName The name of the shader
* @param {bool} isSecond If true, return the name of the method for the second element buffer.
* @returns {string} The name of the method
*/
BufferBuilder.prototype.getAddElementMethodName = function(shaderName, isSecond) {
var shader = this.type.shaders[shaderName];
var bufferName = isSecond ? shader.secondElementBuffer : shader.elementBuffer;

if (bufferName) return 'add' + capitalize(bufferName);
else return null;
};

/**
* Start using a new shared `buffers` object and recreate instances of `Buffer`
* as necessary.
* @private
* @param {Object.<string, Buffer>} buffers
*/
BufferBuilder.prototype.resetBuffers = function(buffers) {
this.buffers = buffers;

for (var shaderName in this.type.shaders) {
var shader = this.type.shaders[shaderName];

var vertexBufferName = shader.vertexBuffer;
if (vertexBufferName && !buffers[vertexBufferName]) {
buffers[vertexBufferName] = new Buffer({
type: Buffer.BufferType.VERTEX,
attributes: shader.attributes
});
}

var elementBufferName = shader.elementBuffer;
if (elementBufferName && !buffers[elementBufferName]) {
buffers[elementBufferName] = createElementBuffer(shader.elementBufferComponents);
}

var secondElementBufferName = shader.secondElementBuffer;
if (secondElementBufferName && !buffers[secondElementBufferName]) {
buffers[secondElementBufferName] = createElementBuffer(shader.secondElementBufferComponents);
}
}

this.elementGroups = createElementGroups(this.type.shaders, this.buffers);
};

function createLayoutProperties(layer, zoom) {
var values = new StyleDeclarationSet('layout', layer.type, layer.layout, {}).values();
var fakeZoomHistory = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 };

var layout = {};
for (var k in values) {
layout[k] = values[k].calculate(zoom, fakeZoomHistory);
}

if (layer.type === 'symbol') {
// To reduce the number of labels that jump around when zooming we need
// to use a text-size value that is the same for all zoom levels.
// This calculates text-size at a high zoom level so that all tiles can
// use the same value when calculating anchor positions.
if (values['text-size']) {
layout['text-max-size'] = values['text-size'].calculate(18, fakeZoomHistory);
layout['text-size'] = values['text-size'].calculate(zoom + 1, fakeZoomHistory);
}
if (values['icon-size']) {
layout['icon-max-size'] = values['icon-size'].calculate(18, fakeZoomHistory);
layout['icon-size'] = values['icon-size'].calculate(zoom + 1, fakeZoomHistory);
}
}

return new LayoutProperties[layer.type](layout);
}

function createVertexAddMethod(shaderName, shader) {
if (!shader.vertexBuffer) return null;

// Find max arg length of all attribute value functions
var argCount = 0;
for (var i = 0; i < shader.attributes.length; i++) {
var attribute = shader.attributes[i];
argCount = Math.max(attribute.value.length, argCount);
}

var argIds = [];
for (var j = 0; j < argCount; j++) {
argIds.push('a' + j);
}

var body = '';
body += 'var attributes = this.type.shaders.' + shaderName + '.attributes;\n';
body += 'var elementGroups = this.elementGroups.' + shaderName + ';\n';
body += 'elementGroups.current.vertexLength++;\n';
body += 'return this.buffers.' + shader.vertexBuffer + '.push(\n';

for (var k = 0; k < shader.attributes.length; k++) {
body += ' attributes[' + k + '].value(' + argIds.join(', ') + ')';
body += (k !== shader.attributes.length - 1) ? ',\n' : '';
}
body += '\n) - elementGroups.current.vertexStartIndex;';

return new Function(argIds, body);
}

function createElementAddMethod(shaderName, shader, isSecond) {
var bufferName = isSecond ? shader.secondElementBuffer : shader.elementBuffer;
if (!bufferName) return null;
var lengthName = isSecond ? 'secondElementLength' : 'elementLength';

return function() {
this.elementGroups[shaderName].current[lengthName]++;
return this.buffers[bufferName].push(arguments);
};
}

function createElementGroups(shaders, buffers) {
var elementGroups = {};
for (var shaderName in shaders) {
var shader = shaders[shaderName];
elementGroups[shaderName] = new ElementGroups(
buffers[shader.vertexBuffer],
buffers[shader.elementBuffer],
buffers[shader.secondElementBuffer]
);
}
return elementGroups;
}

function createElementBuffer(components) {
return new Buffer({
type: Buffer.BufferType.ELEMENT,
attributes: [{
name: 'vertices',
components: components || 3,
type: Buffer.ELEMENT_ATTRIBUTE_TYPE
}]
});
}

function capitalize(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
Loading