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

Add data driven styling for circle-color and circle-radius #1932

Merged
merged 32 commits into from
Apr 12, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3c87cb3
Upgrade mapbox-gl-function and mapbox-gl-style-spec
Jan 6, 2016
68f7da0
Change argument signature of calculate
Jan 20, 2016
c991e8f
Attribute-ify circle shaders
Jan 6, 2016
7d3725c
Support array attributes and attribute multipliers in Bucket
Jan 11, 2016
479d9e6
Set circle color in the buffer
Jan 11, 2016
6ceed1f
Allow attributes to be disabled
Jan 27, 2016
1718b3d
Decouple buffer attribute names from shader attribute names
Mar 2, 2016
c559352
Get rid of ElementGroup class
Mar 2, 2016
d4e6842
Add buffer offsets to element groups
Mar 2, 2016
14d0cd7
Use Bucket#bindBuffers instead of Buffer#bind in draw methods
Mar 2, 2016
64d20ae
Create StyleLayer instances for all child layers
Mar 3, 2016
009a697
Support ref families
Mar 3, 2016
48d7f13
Use a layers object instead of an array in WorkerTile
Mar 9, 2016
f2058f0
Make bucket-side style construction faster
Mar 30, 2016
4eaacdf
Remove posMatrix and exMatrix arguments from Painter#useProgram
Mar 30, 2016
73e5320
Allow token replacement in shaders
Mar 30, 2016
db2be07
Use shader source rewriting to disable attributes
Apr 4, 2016
dced254
Update buffers when data-driven paint property changes
Apr 5, 2016
ae06df3
Optimize layer family creation
Apr 7, 2016
3268906
Add circle-color property function tests to test-suite
Apr 7, 2016
9101b5f
Add unit tests for dds functionality
Apr 8, 2016
4477398
use glsl macros instead of custom templating
ansis Apr 9, 2016
7c5369c
Fix StyleDeclaration#isFeatureConstant
Apr 11, 2016
9ae3e92
Enabled data-driven styling for circle-radius
Apr 11, 2016
41f6561
disable attributes based on their paint property
ansis Apr 9, 2016
5a08b97
replace .isLayerConstant with .paintProperty
ansis Apr 11, 2016
1f5afa7
separate u_opacity from u_color
ansis Apr 11, 2016
d4208bf
use function instead of string of code for getValue
ansis Apr 11, 2016
bba8b99
define vertex add methods as regular functions
ansis Apr 11, 2016
816bf29
remove elementAddMethod abstraction
ansis Apr 12, 2016
0b17310
split paint attributes into a separate buffer
ansis Apr 12, 2016
31dc4ac
move setVertexAttribPointers to Buffer
ansis Apr 10, 2016
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
20 changes: 16 additions & 4 deletions bench/benchmarks/buffer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ var Protobuf = require('pbf');
var assert = require('assert');

var WorkerTile = require('../../js/source/worker_tile');
var Worker = require('../../js/source/worker');
var ajax = require('../../js/util/ajax');
var Style = require('../../js/style/style');
var util = require('../../js/util/util');
Expand Down Expand Up @@ -125,9 +126,7 @@ function preloadAssets(stylesheet, callback) {
function runSample(stylesheet, getGlyphs, getIcons, getTile, callback) {
var timeStart = performance.now();

var layers = stylesheet.layers.filter(function(layer) {
return !layer.ref && (layer.type === 'fill' || layer.type === 'line' || layer.type === 'circle' || layer.type === 'symbol');
});
var layerFamilies = createLayerFamilies(stylesheet.layers);

util.asyncAll(coordinates, function(coordinate, eachCallback) {
var url = 'https://a.tiles.mapbox.com/v4/mapbox.mapbox-terrain-v2,mapbox.mapbox-streets-v6/' + coordinate.zoom + '/' + coordinate.row + '/' + coordinate.column + '.vector.pbf?access_token=' + config.ACCESS_TOKEN;
Expand Down Expand Up @@ -159,7 +158,7 @@ function runSample(stylesheet, getGlyphs, getIcons, getTile, callback) {
getTile(url, function(err, response) {
if (err) throw err;
var data = new VT.VectorTile(new Protobuf(new Uint8Array(response)));
workerTile.parse(data, layers, actor, null, function(err) {
workerTile.parse(data, layerFamilies, actor, null, function(err) {
if (err) return callback(err);
eachCallback();
});
Expand All @@ -180,3 +179,16 @@ function asyncTimesSeries(times, work, callback) {
callback();
}
}

var createLayerFamiliesCacheKey;
var createLayerFamiliesCacheValue;
function createLayerFamilies(layers) {
if (layers !== createLayerFamiliesCacheKey) {
var worker = new Worker({addEventListener: function() {} });
worker['set layers'](layers);

createLayerFamiliesCacheKey = layers;
createLayerFamiliesCacheValue = worker.layerFamilies;
}
return createLayerFamiliesCacheValue;
}
2 changes: 1 addition & 1 deletion debug/random.geojson

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion debug/site.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ map.on('load', function() {
"source": "geojson-random-points",
"paint": {
"circle-radius": 5,
"circle-color": "#f0f"
"circle-opacity": 0.5,
"circle-color": {
stops: [[0, 'red'], [100, 'violet']],
property: 'mapbox'
}
}
});

Expand Down
215 changes: 149 additions & 66 deletions js/data/bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

var featureFilter = require('feature-filter');
var Buffer = require('./buffer');
var StyleLayer = require('../style/style_layer');
var util = require('../util/util');
var StructArrayType = require('../util/struct_array');

Expand Down Expand Up @@ -48,9 +47,6 @@ Bucket.EXTENT = 8192;
* style spec layer type. Because `Bucket` is an abstract class,
* instances should be created via the `Bucket.create` method.
*
* For performance reasons, `Bucket` creates its "add"s methods at
* runtime using `new Function(...)`.
*
* @class Bucket
* @private
* @param options
Expand All @@ -65,8 +61,8 @@ function Bucket(options) {
this.zoom = options.zoom;
this.overscaling = options.overscaling;
this.layer = options.layer;
this.childLayers = options.childLayers;
Copy link
Contributor

Choose a reason for hiding this comment

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

would layers be a better name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Having Bucket#layers and Bucket#layer could be confusing. I'd be happy with any naming scheme that has a modifier on one of the two, such as Bucket#parentLayer and Bucket#layers.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing the child/parent relationship but I agree that the one character difference could be confusing. This is fine as is

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I find the term refLayer confusing because it can refer either to a layer with a ref property or one referred to by a ref property.

I have taken to calling layers with a ref property "ref children" and layers referred to by a ref property "ref parents".

Copy link
Contributor

Choose a reason for hiding this comment

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

👍


this.layerIDs = [this.layer.id];
this.type = this.layer.type;
this.features = [];
this.id = this.layer.id;
Expand All @@ -76,11 +72,13 @@ function Bucket(options) {
this.minZoom = this.layer.minzoom;
this.maxZoom = this.layer.maxzoom;

this.attributes = createAttributes(this);

if (options.elementGroups) {
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 All @@ -91,8 +89,8 @@ function Bucket(options) {
* @private
*/
Bucket.prototype.populateBuffers = function() {
this.createStyleLayer();
this.createArrays();
this.recalculateStyleLayers();

for (var i = 0; i < this.features.length; i++) {
this.addFeature(this.features[i]);
Expand All @@ -118,11 +116,17 @@ Bucket.prototype.makeRoomFor = function(programName, numVertices) {
var elementArray = this.arrays[this.getBufferName(programName, 'element')];
var secondElementArray = this.arrays[this.getBufferName(programName, 'secondElement')];

currentGroup = new ElementGroup(
vertexArray.length,
elementArray && elementArray.length,
secondElementArray && secondElementArray.length
);
currentGroup = {
vertexStartIndex: vertexArray.length,
elementStartIndex: elementArray && elementArray.length,
secondElementStartIndex: secondElementArray && secondElementArray.length,
elementLength: 0,
vertexLength: 0,
secondElementLength: 0,
elementOffset: elementArray && elementArray.length * elementArray.bytesPerElement,
secondElementOffset: secondElementArray && secondElementArray.length * secondElementArray.bytesPerElement,
vertexOffset: vertexArray && vertexArray.length * vertexArray.bytesPerElement
};
groups.push(currentGroup);
}

Expand All @@ -144,37 +148,41 @@ Bucket.prototype.createArrays = function() {

if (programInterface.vertexBuffer) {
var vertexBufferName = this.getBufferName(programName, 'vertex');
var vertexAddMethodName = this.getAddMethodName(programName, 'vertex');

var VertexArrayType = new StructArrayType({
members: programInterface.attributes,
members: this.attributes[programName].layoutAttributes,
alignment: Buffer.VERTEX_ATTRIBUTE_ALIGNMENT
});

arrays[vertexBufferName] = new VertexArrayType();
arrayTypes[vertexBufferName] = VertexArrayType.serialize();

this[vertexAddMethodName] = this[vertexAddMethodName] || createVertexAddMethod(
programName,
programInterface,
this.getBufferName(programName, 'vertex')
);
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) {
var elementBufferName = this.getBufferName(programName, 'element');
var ElementArrayType = createElementBufferType(programInterface.elementBufferComponents);
arrays[elementBufferName] = new ElementArrayType();
arrayTypes[elementBufferName] = ElementArrayType.serialize();
this[this.getAddMethodName(programName, 'element')] = createElementAddMethod(this.arrays[elementBufferName]);
}

if (programInterface.secondElementBuffer) {
var secondElementBufferName = this.getBufferName(programName, 'secondElement');
var SecondElementArrayType = createElementBufferType(programInterface.secondElementBufferComponents);
arrays[secondElementBufferName] = new SecondElementArrayType();
arrayTypes[secondElementBufferName] = SecondElementArrayType.serialize();
this[this.getAddMethodName(programName, 'secondElement')] = createElementAddMethod(this.arrays[secondElementBufferName]);
}

elementGroups[programName] = [];
Expand All @@ -194,13 +202,49 @@ Bucket.prototype.trimArrays = function() {
};

/**
* Get the name of the method used to add an item to a buffer.
* @param {string} programName The name of the program that will use the buffer
* @param {string} type One of "vertex", "element", or "secondElement"
* @returns {string}
* Set the attribute pointers in a WebGL context
* @private
* @param gl The WebGL context
* @param program The active WebGL program
* @param {number} offset The offset of the attribute data in the currently bound GL buffer.
*/
Bucket.prototype.getAddMethodName = function(programName, type) {
return 'add' + capitalize(programName) + capitalize(type);
Bucket.prototype.setAttribPointers = function(programName, gl, program, offset) {
var vertexBuffer = this.buffers[this.getBufferName(programName, 'vertex')];
vertexBuffer.setVertexAttribPointers(gl, program, offset / vertexBuffer.itemSize);
};

Bucket.prototype.setUniforms = function(gl, programName, program, layer, globalProperties) {
var disabledAttributes = this.attributes[programName].paintAttributes[layer.id].disabled;
for (var i = 0; i < disabledAttributes.length; i++) {
var attribute = disabledAttributes[i];
var attributeId = program[attribute.name];
gl['uniform' + attribute.components + 'fv'](attributeId, attribute.getValue(layer, globalProperties));
}
};

Bucket.prototype.bindLayoutBuffers = function(programInterfaceName, gl, options) {
var programInterface = this.programInterfaces[programInterfaceName];

if (programInterface.vertexBuffer) {
var vertexBuffer = this.buffers[this.getBufferName(programInterfaceName, 'vertex')];
vertexBuffer.bind(gl);
}

if (programInterface.elementBuffer && (!options || !options.secondElement)) {
var elementBuffer = this.buffers[this.getBufferName(programInterfaceName, 'element')];
elementBuffer.bind(gl);
}

if (programInterface.secondElementBuffer && (options && options.secondElement)) {
var secondElementBuffer = this.buffers[this.getBufferName(programInterfaceName, 'secondElement')];
secondElementBuffer.bind(gl);
}
};

Bucket.prototype.bindPaintBuffer = function(gl, interfaceName, layerID, program, vertexStartIndex) {
var buffer = this.buffers[this.getBufferName(layerID, interfaceName)];
buffer.bind(gl);
buffer.setVertexAttribPointers(gl, program, vertexStartIndex);
};

/**
Expand All @@ -215,24 +259,18 @@ Bucket.prototype.getBufferName = function(programName, type) {

Bucket.prototype.serialize = function() {
return {
layer: {
id: this.layer.id,
type: this.layer.type
},
layerId: this.layer.id,
zoom: this.zoom,
elementGroups: this.elementGroups,
arrays: util.mapObject(this.arrays, function(array) {
return array.serialize();
}),
arrayTypes: this.arrayTypes
};
};
arrayTypes: this.arrayTypes,

Bucket.prototype.createStyleLayer = function() {
if (!(this.layer instanceof StyleLayer)) {
this.layer = StyleLayer.create(this.layer);
this.layer.recalculate(this.zoom, { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 });
}
childLayerIds: this.childLayers.map(function(layer) {
return layer.id;
})
};
};

Bucket.prototype.createFilter = function() {
Expand All @@ -241,48 +279,93 @@ Bucket.prototype.createFilter = function() {
}
};


var createVertexAddMethodCache = {};
function createVertexAddMethod(programName, programInterface, bufferName) {
var pushArgs = [];
for (var i = 0; i < programInterface.attributes.length; i++) {
pushArgs = pushArgs.concat(programInterface.attributes[i].value);
var FAKE_ZOOM_HISTORY = { lastIntegerZoom: Infinity, lastIntegerZoomTime: 0, lastZoom: 0 };
Bucket.prototype.recalculateStyleLayers = function() {
for (var i = 0; i < this.childLayers.length; i++) {
this.childLayers[i].recalculate(this.zoom, FAKE_ZOOM_HISTORY);
}
};

var body = 'return this.arrays.' + bufferName + '.emplaceBack(' + pushArgs.join(', ') + ');';

if (!createVertexAddMethodCache[body]) {
createVertexAddMethodCache[body] = new Function(programInterface.attributeArgs, body);
Bucket.prototype.getProgramMacros = function(programInterface, layer) {
var macros = [];
var enabledAttributes = this.attributes[programInterface].paintAttributes[layer.id].enabled;
for (var i = 0; i < enabledAttributes.length; i++) {
macros.push('ATTRIBUTE_' + enabledAttributes[i].name.toUpperCase());
}
return macros;
};

return createVertexAddMethodCache[body];
}

function createElementAddMethod(buffer) {
return function(one, two, three) {
return buffer.emplaceBack(one, two, three);
};
}
Bucket.prototype.addPaintAttributes = function(interfaceName, globalProperties, featureProperties, startIndex, endIndex) {
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;
var components = attribute.components || 1;

vertexArray.resize(length);
for (var i = startIndex; i < endIndex; i++) {
var vertex = vertexArray.get(i);
for (var c = 0; c < components; c++) {
var memberName = components > 1 ? (attribute.name + c) : attribute.name;
vertex[memberName] = value[c] * multiplier;
}
}
}
}
};

function createElementBufferType(components) {
return new StructArrayType({
members: [{
type: Buffer.ELEMENT_ATTRIBUTE_TYPE,
name: 'vertices',
components: components || 3
}]});
}]
});
}

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

function ElementGroup(vertexStartIndex, elementStartIndex, secondElementStartIndex) {
// the offset into the vertex buffer of the first vertex in this group
this.vertexStartIndex = vertexStartIndex;
this.elementStartIndex = elementStartIndex;
this.secondElementStartIndex = secondElementStartIndex;
this.elementLength = 0;
this.vertexLength = 0;
this.secondElementLength = 0;
function createAttributes(bucket) {
var attributes = {};
for (var interfaceName in bucket.programInterfaces) {
var interfaceAttributes = attributes[interfaceName] = { layoutAttributes: [], 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];

if (attribute.paintProperty === undefined) {
interfaceAttributes.layoutAttributes.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);
}
}
}
}
}
return attributes;
}
Loading