diff --git a/js/data/array_group.js b/js/data/array_group.js index ba1a7a4c280..bda15bacf9e 100644 --- a/js/data/array_group.js +++ b/js/data/array_group.js @@ -2,39 +2,84 @@ const util = require('../util/util'); +class Segment { + constructor(vertexOffset, primitiveOffset) { + this.vertexOffset = vertexOffset; + this.primitiveOffset = primitiveOffset; + this.vertexLength = 0; + this.primitiveLength = 0; + } +} + /** - * A class that manages vertex and element arrays for a range of features. It handles initialization, + * A class that manages vertex and element arrays for a bucket. It handles initialization, * serialization for transfer to the main thread, and certain intervening mutations. * - * Array elements are broken into array groups based on inherent limits of WebGL. Within a group is: + * A group has: * - * * A "layout" vertex array, with fixed layout, containing values calculated from layout properties. - * * Zero, one, or two element arrays, with fixed layout, typically for eventual use in - * `gl.drawElements(gl.TRIANGLES, ...)`. + * * A "layout" vertex array, with fixed attributes, containing values calculated from layout properties. + * * Zero, one, or two element arrays, with fixed layout, for eventual `gl.drawElements` use. * * Zero or more "paint" vertex arrays keyed by layer ID, each with a dynamic layout which depends * on which paint properties of that layer use data-driven-functions (property functions or * property-and-zoom functions). Values are calculated by evaluating those functions. * + * Because indexed rendering is best done with 16 bit indices (and in fact, in WebGL, 16 bit + * indices are the only choice), a form of segmented addressing is used. Each group + * contains an `Array` of `Segment`s. A segment contains a vertex array offset, which forms + * the "base address" of indices within this segment. Each segment is drawn separately. + * * @private */ class ArrayGroup { - constructor(arrayTypes) { - const LayoutVertexArrayType = arrayTypes.layoutVertexArrayType; + constructor(programInterface, programConfigurations) { + const LayoutVertexArrayType = programInterface.layoutVertexArrayType; this.layoutVertexArray = new LayoutVertexArrayType(); - const ElementArrayType = arrayTypes.elementArrayType; + const ElementArrayType = programInterface.elementArrayType; if (ElementArrayType) this.elementArray = new ElementArrayType(); - const ElementArrayType2 = arrayTypes.elementArrayType2; + const ElementArrayType2 = programInterface.elementArrayType2; if (ElementArrayType2) this.elementArray2 = new ElementArrayType2(); - this.paintVertexArrays = util.mapObject(arrayTypes.paintVertexArrayTypes, (PaintVertexArrayType) => { - return new PaintVertexArrayType(); + this.paintVertexArrays = util.mapObject(programConfigurations, (programConfiguration) => { + const PaintVertexArrayType = programConfiguration.paintVertexArrayType(); + const paintVertexArray = new PaintVertexArrayType(); + paintVertexArray.programConfiguration = programConfiguration; + return paintVertexArray; }); + + this.segments = []; + this.segments2 = []; + } + + prepareSegment(numVertices) { + let segment = this.segments[this.segments.length - 1]; + if (!segment || segment.vertexLength + numVertices > ArrayGroup.MAX_VERTEX_ARRAY_LENGTH) { + segment = new Segment(this.layoutVertexArray.length, this.elementArray.length); + this.segments.push(segment); + } + return segment; + } + + prepareSegment2(numVertices) { + let segment = this.segments2[this.segments2.length - 1]; + if (!segment || segment.vertexLength + numVertices > ArrayGroup.MAX_VERTEX_ARRAY_LENGTH) { + segment = new Segment(this.layoutVertexArray.length, this.elementArray2.length); + this.segments2.push(segment); + } + return segment; } - hasCapacityFor(numVertices) { - return this.layoutVertexArray.length + numVertices <= ArrayGroup.MAX_VERTEX_ARRAY_LENGTH; + populatePaintArrays(layers, globalProperties, featureProperties) { + for (const layer of layers) { + const paintArray = this.paintVertexArrays[layer.id]; + paintArray.programConfiguration.populatePaintArray( + layer, + paintArray, + this.layoutVertexArray.length, + globalProperties, + featureProperties); + } } isEmpty() { @@ -63,8 +108,13 @@ class ArrayGroup { elementArray: this.elementArray && this.elementArray.serialize(), elementArray2: this.elementArray2 && this.elementArray2.serialize(), paintVertexArrays: util.mapObject(this.paintVertexArrays, (array) => { - return array.serialize(); - }) + return { + array: array.serialize(), + type: array.constructor.serialize() + }; + }), + segments: this.segments, + segments2: this.segments2 }; } diff --git a/js/data/bucket.js b/js/data/bucket.js index af96f63cf3b..5cc04a3ff41 100644 --- a/js/data/bucket.js +++ b/js/data/bucket.js @@ -30,30 +30,20 @@ class Bucket { constructor (options) { this.zoom = options.zoom; this.overscaling = options.overscaling; - this.layer = options.layer; - this.childLayers = options.childLayers; - + this.layers = options.layers; this.index = options.index; + this.programConfigurations = util.mapObject(this.programInterfaces, (programInterface) => { const result = {}; - for (const layer of options.childLayers) { + for (const layer of this.layers) { result[layer.id] = ProgramConfiguration.createDynamic(programInterface.paintAttributes || [], layer, options); } return result; }); if (options.arrays) { - this.bufferGroups = util.mapObject(options.arrays, (programArrayGroups, programName) => { - const programInterface = this.programInterfaces[programName]; - const paintVertexArrayTypes = options.paintVertexArrayTypes[programName]; - return programArrayGroups.map((arrayGroup) => { - return new BufferGroup(arrayGroup, { - layoutVertexArrayType: programInterface.layoutVertexArrayType.serialize(), - elementArrayType: programInterface.elementArrayType && programInterface.elementArrayType.serialize(), - elementArrayType2: programInterface.elementArrayType2 && programInterface.elementArrayType2.serialize(), - paintVertexArrayTypes: paintVertexArrayTypes - }); - }); + this.bufferGroups = util.mapObject(options.arrays, (arrayGroup, programName) => { + return new BufferGroup(arrayGroup, this.programInterfaces[programName]); }); } } @@ -63,7 +53,7 @@ class Bucket { this.recalculateStyleLayers(); for (const feature of features) { - if (this.layer.filter(feature)) { + if (this.layers[0].filter(feature)) { this.addFeature(feature); options.featureIndex.insert(feature, this.index); } @@ -72,143 +62,53 @@ class Bucket { this.trimArrays(); } - /** - * Check if there is enough space available in the current array group for - * `vertexLength` vertices. If not, append a new array group. Should be called - * by `populateArrays` and its callees. - * - * Array groups are added to this.arrayGroups[programName]. - * - * @param {string} programName the name of the program associated with the buffer that will receive the vertices - * @param {number} vertexLength The number of vertices that will be inserted to the buffer. - * @returns The current array group - */ - prepareArrayGroup(programName, numVertices) { - const groups = this.arrayGroups[programName]; - let currentGroup = groups.length && groups[groups.length - 1]; - - if (!currentGroup || !currentGroup.hasCapacityFor(numVertices)) { - currentGroup = new ArrayGroup({ - layoutVertexArrayType: this.programInterfaces[programName].layoutVertexArrayType, - elementArrayType: this.programInterfaces[programName].elementArrayType, - elementArrayType2: this.programInterfaces[programName].elementArrayType2, - paintVertexArrayTypes: this.paintVertexArrayTypes[programName] - }); - - currentGroup.index = groups.length; - - groups.push(currentGroup); - } - - return currentGroup; - } - - /** - * Sets up `this.paintVertexArrayTypes` as { [programName]: { [layerName]: PaintArrayType, ... }, ... } - * - * And `this.arrayGroups` as { [programName]: [], ... }; these get populated - * with array group structure over in `prepareArrayGroup`. - */ createArrays() { - this.arrayGroups = {}; - this.paintVertexArrayTypes = {}; - + this.arrays = {}; for (const programName in this.programInterfaces) { - this.arrayGroups[programName] = []; - this.paintVertexArrayTypes[programName] = util.mapObject( - this.programConfigurations[programName], (programConfiguration) => { - return programConfiguration.paintVertexArrayType(); - }); + this.arrays[programName] = new ArrayGroup( + this.programInterfaces[programName], + this.programConfigurations[programName]); } } destroy() { for (const programName in this.bufferGroups) { - const programBufferGroups = this.bufferGroups[programName]; - for (let i = 0; i < programBufferGroups.length; i++) { - programBufferGroups[i].destroy(); - } + this.bufferGroups[programName].destroy(); } } trimArrays() { - for (const programName in this.arrayGroups) { - const arrayGroups = this.arrayGroups[programName]; - for (let i = 0; i < arrayGroups.length; i++) { - arrayGroups[i].trim(); - } + for (const programName in this.arrays) { + this.arrays[programName].trim(); } } isEmpty() { - for (const programName in this.arrayGroups) { - const arrayGroups = this.arrayGroups[programName]; - for (let i = 0; i < arrayGroups.length; i++) { - if (!arrayGroups[i].isEmpty()) { - return false; - } + for (const programName in this.arrays) { + if (!this.arrays[programName].isEmpty()) { + return false; } } return true; } getTransferables(transferables) { - for (const programName in this.arrayGroups) { - const arrayGroups = this.arrayGroups[programName]; - for (let i = 0; i < arrayGroups.length; i++) { - arrayGroups[i].getTransferables(transferables); - } + for (const programName in this.arrays) { + this.arrays[programName].getTransferables(transferables); } } serialize() { return { - layerId: this.layer.id, zoom: this.zoom, - arrays: util.mapObject(this.arrayGroups, (programArrayGroups) => { - return programArrayGroups.map((arrayGroup) => { - return arrayGroup.serialize(); - }); - }), - paintVertexArrayTypes: util.mapObject(this.paintVertexArrayTypes, (arrayTypes) => { - return util.mapObject(arrayTypes, (arrayType) => { - return arrayType.serialize(); - }); - }), - - childLayerIds: this.childLayers.map((layer) => { - return layer.id; - }) + layerIds: this.layers.map((l) => l.id), + arrays: util.mapObject(this.arrays, (a) => a.serialize()) }; } recalculateStyleLayers() { - for (let i = 0; i < this.childLayers.length; i++) { - this.childLayers[i].recalculate(this.zoom, FAKE_ZOOM_HISTORY); - } - } - - populatePaintArrays(interfaceName, globalProperties, featureProperties, startGroup, startIndex) { - const groups = this.arrayGroups[interfaceName]; - const programConfiguration = this.programConfigurations[interfaceName]; - - for (const layer of this.childLayers) { - for (let g = startGroup.index; g < groups.length; g++) { - const group = groups[g]; - const start = g === startGroup.index ? startIndex : 0; - const length = group.layoutVertexArray.length; - - const paintArray = group.paintVertexArrays[layer.id]; - paintArray.resize(length); - - programConfiguration[layer.id].populatePaintArray( - layer, - paintArray, - start, - length, - globalProperties, - featureProperties); - } + for (const layer of this.layers) { + layer.recalculate(this.zoom, FAKE_ZOOM_HISTORY); } } } @@ -229,11 +129,32 @@ const subclasses = { * @returns {Bucket} */ Bucket.create = function(options) { - let type = options.layer.type; - if (type === 'fill' && (!options.layer.isPaintValueFeatureConstant('fill-extrude-height') || - !options.layer.isPaintValueZoomConstant('fill-extrude-height') || - options.layer.getPaintValue('fill-extrude-height', {zoom: options.zoom}) !== 0)) { + const layer = options.layers[0]; + let type = layer.type; + if (type === 'fill' && (!layer.isPaintValueFeatureConstant('fill-extrude-height') || + !layer.isPaintValueZoomConstant('fill-extrude-height') || + layer.getPaintValue('fill-extrude-height', {zoom: options.zoom}) !== 0)) { type = 'fillextrusion'; } return new subclasses[type](options); }; + +Bucket.deserialize = function(input, style) { + // Guard against the case where the map's style has been set to null while + // this bucket has been parsing. + if (!style) return; + + const output = {}; + for (const serialized of input) { + const layers = serialized.layerIds + .map((id) => style.getLayer(id)) + .filter(Boolean); + + if (layers.length === 0) { + continue; + } + + output[layers[0].id] = Bucket.create(util.extend({layers}, serialized)); + } + return output; +}; diff --git a/js/data/bucket/circle_bucket.js b/js/data/bucket/circle_bucket.js index c35c22fe92f..eede026f9d8 100644 --- a/js/data/bucket/circle_bucket.js +++ b/js/data/bucket/circle_bucket.js @@ -59,7 +59,7 @@ const circleInterfaces = { }; function addCircleVertex(layoutVertexArray, x, y, extrudeX, extrudeY) { - return layoutVertexArray.emplaceBack( + layoutVertexArray.emplaceBack( (x * 2) + ((extrudeX + 1) / 2), (y * 2) + ((extrudeY + 1) / 2)); } @@ -78,8 +78,7 @@ class CircleBucket extends Bucket { } addFeature(feature) { - const startGroup = this.prepareArrayGroup('circle', 0); - const startIndex = startGroup.layoutVertexArray.length; + const arrays = this.arrays.circle; for (const ring of loadGeometry(feature)) { for (const point of ring) { @@ -98,20 +97,23 @@ class CircleBucket extends Bucket { // │ 0 1 │ // └─────────┘ - const group = this.prepareArrayGroup('circle', 4); - const layoutVertexArray = group.layoutVertexArray; + const segment = arrays.prepareSegment(4); + const index = segment.vertexLength; - const index = addCircleVertex(layoutVertexArray, x, y, -1, -1); - addCircleVertex(layoutVertexArray, x, y, 1, -1); - addCircleVertex(layoutVertexArray, x, y, 1, 1); - addCircleVertex(layoutVertexArray, x, y, -1, 1); + addCircleVertex(arrays.layoutVertexArray, x, y, -1, -1); + addCircleVertex(arrays.layoutVertexArray, x, y, 1, -1); + addCircleVertex(arrays.layoutVertexArray, x, y, 1, 1); + addCircleVertex(arrays.layoutVertexArray, x, y, -1, 1); - group.elementArray.emplaceBack(index, index + 1, index + 2); - group.elementArray.emplaceBack(index, index + 3, index + 2); + arrays.elementArray.emplaceBack(index, index + 1, index + 2); + arrays.elementArray.emplaceBack(index, index + 3, index + 2); + + segment.vertexLength += 4; + segment.primitiveLength += 2; } } - this.populatePaintArrays('circle', {zoom: this.zoom}, feature.properties, startGroup, startIndex); + arrays.populatePaintArrays(this.layers, {zoom: this.zoom}, feature.properties); } } diff --git a/js/data/bucket/fill_bucket.js b/js/data/bucket/fill_bucket.js index 36eb5a03165..bdd659af94e 100644 --- a/js/data/bucket/fill_bucket.js +++ b/js/data/bucket/fill_bucket.js @@ -6,6 +6,7 @@ const ElementArrayType = require('../element_array_type'); const loadGeometry = require('../load_geometry'); const earcut = require('earcut'); const classifyRings = require('../../util/classify_rings'); +const assert = require('assert'); const EARCUT_MAX_RINGS = 500; const fillInterfaces = { @@ -15,7 +16,7 @@ const fillInterfaces = { components: 2, type: 'Int16' }]), - elementArrayType: new ElementArrayType(1), + elementArrayType: new ElementArrayType(3), elementArrayType2: new ElementArrayType(2), paintAttributes: [{ @@ -55,8 +56,7 @@ class FillBucket extends Bucket { } addFeature(feature) { - const startGroup = this.prepareArrayGroup('fill', 0); - const startIndex = startGroup.layoutVertexArray.length; + const arrays = this.arrays.fill; for (const polygon of classifyRings(loadGeometry(feature), EARCUT_MAX_RINGS)) { let numVertices = 0; @@ -64,40 +64,55 @@ class FillBucket extends Bucket { numVertices += ring.length; } - const group = this.prepareArrayGroup('fill', numVertices); - const startIndex = group.layoutVertexArray.length; + const triangleSegment = arrays.prepareSegment(numVertices); + const triangleIndex = triangleSegment.vertexLength; const flattened = []; const holeIndices = []; - for (let r = 0; r < polygon.length; r++) { - const ring = polygon[r]; + for (const ring of polygon) { + if (ring.length === 0) { + continue; + } - if (r > 0) { + if (ring !== polygon[0]) { holeIndices.push(flattened.length / 2); } - for (let p = 0; p < ring.length; p++) { - const point = ring[p]; - - const index = group.layoutVertexArray.emplaceBack(point.x, point.y); + const lineSegment = arrays.prepareSegment2(ring.length); + const lineIndex = lineSegment.vertexLength; - if (p >= 1) { - group.elementArray2.emplaceBack(index - 1, index); - } + arrays.layoutVertexArray.emplaceBack(ring[0].x, ring[0].y); + arrays.elementArray2.emplaceBack(lineIndex + ring.length - 1, lineIndex); + flattened.push(ring[0].x); + flattened.push(ring[0].y); - // convert to format used by earcut - flattened.push(point.x); - flattened.push(point.y); + for (let i = 1; i < ring.length; i++) { + arrays.layoutVertexArray.emplaceBack(ring[i].x, ring[i].y); + arrays.elementArray2.emplaceBack(lineIndex + i - 1, lineIndex + i); + flattened.push(ring[i].x); + flattened.push(ring[i].y); } + + lineSegment.vertexLength += ring.length; + lineSegment.primitiveLength += ring.length; } - for (const index of earcut(flattened, holeIndices)) { - group.elementArray.emplaceBack(index + startIndex); + const indices = earcut(flattened, holeIndices); + assert(indices.length % 3 === 0); + + for (let i = 0; i < indices.length; i += 3) { + arrays.elementArray.emplaceBack( + triangleIndex + indices[i], + triangleIndex + indices[i + 1], + triangleIndex + indices[i + 2]); } + + triangleSegment.vertexLength += numVertices; + triangleSegment.primitiveLength += indices.length / 3; } - this.populatePaintArrays('fill', {zoom: this.zoom}, feature.properties, startGroup, startIndex); + arrays.populatePaintArrays(this.layers, {zoom: this.zoom}, feature.properties); } } diff --git a/js/data/bucket/fill_extrusion_bucket.js b/js/data/bucket/fill_extrusion_bucket.js index 5da1f4072bd..4d772266406 100644 --- a/js/data/bucket/fill_extrusion_bucket.js +++ b/js/data/bucket/fill_extrusion_bucket.js @@ -7,6 +7,7 @@ const loadGeometry = require('../load_geometry'); const EXTENT = require('../extent'); const earcut = require('earcut'); const classifyRings = require('../../util/classify_rings'); +const assert = require('assert'); const EARCUT_MAX_RINGS = 500; const fillExtrusionInterfaces = { @@ -62,7 +63,7 @@ const fillExtrusionInterfaces = { const FACTOR = Math.pow(2, 13); function addVertex(vertexArray, x, y, nx, ny, nz, t, e) { - return vertexArray.emplaceBack( + vertexArray.emplaceBack( // a_pos x, y, @@ -82,33 +83,36 @@ class FillExtrusionBucket extends Bucket { } addFeature(feature) { - const startGroup = this.prepareArrayGroup('fillextrusion', 0); - const startIndex = startGroup.layoutVertexArray.length; + const arrays = this.arrays.fillextrusion; for (const polygon of classifyRings(loadGeometry(feature), EARCUT_MAX_RINGS)) { let numVertices = 0; for (const ring of polygon) { numVertices += ring.length; } - numVertices *= 5; - const group = this.prepareArrayGroup('fillextrusion', numVertices); + const segment = arrays.prepareSegment(numVertices * 5); + const flattened = []; const holeIndices = []; const indices = []; - for (let r = 0; r < polygon.length; r++) { - const ring = polygon[r]; + for (const ring of polygon) { + if (ring.length === 0) { + continue; + } - if (r > 0) holeIndices.push(flattened.length / 2); + if (ring !== polygon[0]) { + holeIndices.push(flattened.length / 2); + } let edgeDistance = 0; for (let p = 0; p < ring.length; p++) { const p1 = ring[p]; - const index = addVertex(group.layoutVertexArray, p1.x, p1.y, 0, 0, 1, 1, 0); - indices.push(index); + addVertex(arrays.layoutVertexArray, p1.x, p1.y, 0, 0, 1, 1, 0); + indices.push(segment.vertexLength++); if (p >= 1) { const p2 = ring[p - 1]; @@ -116,16 +120,21 @@ class FillExtrusionBucket extends Bucket { if (!isBoundaryEdge(p1, p2)) { const perp = p1.sub(p2)._perp()._unit(); - const bottomRight = addVertex(group.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 0, edgeDistance); - addVertex(group.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 1, edgeDistance); + addVertex(arrays.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 0, edgeDistance); + addVertex(arrays.layoutVertexArray, p1.x, p1.y, perp.x, perp.y, 0, 1, edgeDistance); edgeDistance += p2.dist(p1); - addVertex(group.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 0, edgeDistance); - addVertex(group.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 1, edgeDistance); + addVertex(arrays.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 0, edgeDistance); + addVertex(arrays.layoutVertexArray, p2.x, p2.y, perp.x, perp.y, 0, 1, edgeDistance); - group.elementArray.emplaceBack(bottomRight, bottomRight + 1, bottomRight + 2); - group.elementArray.emplaceBack(bottomRight + 1, bottomRight + 2, bottomRight + 3); + const bottomRight = segment.vertexLength; + + arrays.elementArray.emplaceBack(bottomRight, bottomRight + 1, bottomRight + 2); + arrays.elementArray.emplaceBack(bottomRight + 1, bottomRight + 2, bottomRight + 3); + + segment.vertexLength += 4; + segment.primitiveLength += 2; } } @@ -136,15 +145,19 @@ class FillExtrusionBucket extends Bucket { } const triangleIndices = earcut(flattened, holeIndices); + assert(triangleIndices.length % 3 === 0); - for (let j = 0; j < triangleIndices.length - 2; j += 3) { - group.elementArray.emplaceBack(indices[triangleIndices[j]], + for (let j = 0; j < triangleIndices.length; j += 3) { + arrays.elementArray.emplaceBack( + indices[triangleIndices[j]], indices[triangleIndices[j + 1]], indices[triangleIndices[j + 2]]); } + + segment.primitiveLength += triangleIndices.length / 3; } - this.populatePaintArrays('fillextrusion', {zoom: this.zoom}, feature.properties, startGroup, startIndex); + arrays.populatePaintArrays(this.layers, {zoom: this.zoom}, feature.properties); } } diff --git a/js/data/bucket/line_bucket.js b/js/data/bucket/line_bucket.js index 7cc68764719..6110be955a7 100644 --- a/js/data/bucket/line_bucket.js +++ b/js/data/bucket/line_bucket.js @@ -65,7 +65,7 @@ const lineInterfaces = { }; function addLineVertex(layoutVertexBuffer, point, extrude, tx, ty, dir, linesofar) { - return layoutVertexBuffer.emplaceBack( + layoutVertexBuffer.emplaceBack( // a_pos (point.x << 1) | tx, (point.y << 1) | ty, @@ -92,10 +92,11 @@ class LineBucket extends Bucket { } addFeature(feature) { - const join = this.layer.layout['line-join']; - const cap = this.layer.layout['line-cap']; - const miterLimit = this.layer.layout['line-miter-limit']; - const roundLimit = this.layer.layout['line-round-limit']; + const layout = this.layers[0].layout; + const join = layout['line-join']; + const cap = layout['line-cap']; + const miterLimit = layout['line-miter-limit']; + const roundLimit = layout['line-round-limit']; for (const line of loadGeometry(feature, LINE_DISTANCE_BUFFER_BITS)) { this.addLine(line, feature.properties, join, cap, miterLimit, roundLimit); @@ -121,9 +122,10 @@ class LineBucket extends Bucket { lastVertex = vertices[len - 1], closed = firstVertex.equals(lastVertex); + const arrays = this.arrays.line; + // we could be more precise, but it would only save a negligible amount of space - const group = this.prepareArrayGroup('line', len * 10); - const startIndex = group.layoutVertexArray.length; + const segment = arrays.prepareSegment('line', len * 10); // a line may not have coincident points if (len === 2 && closed) return; @@ -193,7 +195,7 @@ class LineBucket extends Bucket { if (prevSegmentLength > 2 * sharpCornerOffset) { const newPrevVertex = currentVertex.sub(currentVertex.sub(prevVertex)._mult(sharpCornerOffset / prevSegmentLength)._round()); this.distance += newPrevVertex.dist(prevVertex); - this.addCurrentVertex(newPrevVertex, this.distance, prevNormal.mult(1), 0, 0, false); + this.addCurrentVertex(newPrevVertex, this.distance, prevNormal.mult(1), 0, 0, false, segment); prevVertex = newPrevVertex; } } @@ -230,7 +232,7 @@ class LineBucket extends Bucket { if (currentJoin === 'miter') { joinNormal._mult(miterLength); - this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false, segment); } else if (currentJoin === 'flipbevel') { // miter is too big, flip the direction to make a beveled join @@ -244,8 +246,8 @@ class LineBucket extends Bucket { const bevelLength = miterLength * prevNormal.add(nextNormal).mag() / prevNormal.sub(nextNormal).mag(); joinNormal._perp()._mult(bevelLength * direction); } - this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false); - this.addCurrentVertex(currentVertex, this.distance, joinNormal.mult(-1), 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, joinNormal, 0, 0, false, segment); + this.addCurrentVertex(currentVertex, this.distance, joinNormal.mult(-1), 0, 0, false, segment); } else if (currentJoin === 'bevel' || currentJoin === 'fakeround') { const lineTurnsLeft = (prevNormal.x * nextNormal.y - prevNormal.y * nextNormal.x) > 0; @@ -260,7 +262,7 @@ class LineBucket extends Bucket { // Close previous segment with a bevel if (!startOfLine) { - this.addCurrentVertex(currentVertex, this.distance, prevNormal, offsetA, offsetB, false); + this.addCurrentVertex(currentVertex, this.distance, prevNormal, offsetA, offsetB, false, segment); } if (currentJoin === 'fakeround') { @@ -276,38 +278,38 @@ class LineBucket extends Bucket { for (let m = 0; m < n; m++) { approxFractionalJoinNormal = nextNormal.mult((m + 1) / (n + 1))._add(prevNormal)._unit(); - this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft); + this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft, segment); } - this.addPieSliceVertex(currentVertex, this.distance, joinNormal, lineTurnsLeft); + this.addPieSliceVertex(currentVertex, this.distance, joinNormal, lineTurnsLeft, segment); for (let k = n - 1; k >= 0; k--) { approxFractionalJoinNormal = prevNormal.mult((k + 1) / (n + 1))._add(nextNormal)._unit(); - this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft); + this.addPieSliceVertex(currentVertex, this.distance, approxFractionalJoinNormal, lineTurnsLeft, segment); } } // Start next segment if (nextVertex) { - this.addCurrentVertex(currentVertex, this.distance, nextNormal, -offsetA, -offsetB, false); + this.addCurrentVertex(currentVertex, this.distance, nextNormal, -offsetA, -offsetB, false, segment); } } else if (currentJoin === 'butt') { if (!startOfLine) { // Close previous segment with a butt - this.addCurrentVertex(currentVertex, this.distance, prevNormal, 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, prevNormal, 0, 0, false, segment); } // Start next segment with a butt if (nextVertex) { - this.addCurrentVertex(currentVertex, this.distance, nextNormal, 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, nextNormal, 0, 0, false, segment); } } else if (currentJoin === 'square') { if (!startOfLine) { // Close previous segment with a square cap - this.addCurrentVertex(currentVertex, this.distance, prevNormal, 1, 1, false); + this.addCurrentVertex(currentVertex, this.distance, prevNormal, 1, 1, false, segment); // The segment is done. Unset vertices to disconnect segments. this.e1 = this.e2 = -1; @@ -315,17 +317,17 @@ class LineBucket extends Bucket { // Start next segment if (nextVertex) { - this.addCurrentVertex(currentVertex, this.distance, nextNormal, -1, -1, false); + this.addCurrentVertex(currentVertex, this.distance, nextNormal, -1, -1, false, segment); } } else if (currentJoin === 'round') { if (!startOfLine) { // Close previous segment with butt - this.addCurrentVertex(currentVertex, this.distance, prevNormal, 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, prevNormal, 0, 0, false, segment); // Add round cap or linejoin at end of segment - this.addCurrentVertex(currentVertex, this.distance, prevNormal, 1, 1, true); + this.addCurrentVertex(currentVertex, this.distance, prevNormal, 1, 1, true, segment); // The segment is done. Unset vertices to disconnect segments. this.e1 = this.e2 = -1; @@ -335,9 +337,9 @@ class LineBucket extends Bucket { // Start next segment with a butt if (nextVertex) { // Add round cap before first segment - this.addCurrentVertex(currentVertex, this.distance, nextNormal, -1, -1, true); + this.addCurrentVertex(currentVertex, this.distance, nextNormal, -1, -1, true, segment); - this.addCurrentVertex(currentVertex, this.distance, nextNormal, 0, 0, false); + this.addCurrentVertex(currentVertex, this.distance, nextNormal, 0, 0, false, segment); } } @@ -346,7 +348,7 @@ class LineBucket extends Bucket { if (nextSegmentLength > 2 * sharpCornerOffset) { const newCurrentVertex = currentVertex.add(nextVertex.sub(currentVertex)._mult(sharpCornerOffset / nextSegmentLength)._round()); this.distance += newCurrentVertex.dist(currentVertex); - this.addCurrentVertex(newCurrentVertex, this.distance, nextNormal.mult(1), 0, 0, false); + this.addCurrentVertex(newCurrentVertex, this.distance, nextNormal.mult(1), 0, 0, false, segment); currentVertex = newCurrentVertex; } } @@ -354,12 +356,7 @@ class LineBucket extends Bucket { startOfLine = false; } - this.populatePaintArrays( - 'line', {zoom: this.zoom}, - featureProperties, - group, - startIndex - ); + arrays.populatePaintArrays(this.layers, {zoom: this.zoom}, featureProperties); } /** @@ -372,27 +369,31 @@ class LineBucket extends Bucket { * @param {boolean} round whether this is a round cap * @private */ - addCurrentVertex(currentVertex, distance, normal, endLeft, endRight, round) { + addCurrentVertex(currentVertex, distance, normal, endLeft, endRight, round, segment) { const tx = round ? 1 : 0; let extrude; - const arrayGroup = this.arrayGroups.line[this.arrayGroups.line.length - 1]; - const layoutVertexArray = arrayGroup.layoutVertexArray; - const elementArray = arrayGroup.elementArray; + const arrays = this.arrays.line; + const layoutVertexArray = arrays.layoutVertexArray; + const elementArray = arrays.elementArray; extrude = normal.clone(); if (endLeft) extrude._sub(normal.perp()._mult(endLeft)); - this.e3 = addLineVertex(layoutVertexArray, currentVertex, extrude, tx, 0, endLeft, distance); + addLineVertex(layoutVertexArray, currentVertex, extrude, tx, 0, endLeft, distance); + this.e3 = segment.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { elementArray.emplaceBack(this.e1, this.e2, this.e3); + segment.primitiveLength++; } this.e1 = this.e2; this.e2 = this.e3; extrude = normal.mult(-1); if (endRight) extrude._sub(normal.perp()._mult(endRight)); - this.e3 = addLineVertex(layoutVertexArray, currentVertex, extrude, tx, 1, -endRight, distance); + addLineVertex(layoutVertexArray, currentVertex, extrude, tx, 1, -endRight, distance); + this.e3 = segment.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { elementArray.emplaceBack(this.e1, this.e2, this.e3); + segment.primitiveLength++; } this.e1 = this.e2; this.e2 = this.e3; @@ -403,7 +404,7 @@ class LineBucket extends Bucket { // to `linesofar`. if (distance > MAX_LINE_DISTANCE / 2) { this.distance = 0; - this.addCurrentVertex(currentVertex, this.distance, normal, endLeft, endRight, round); + this.addCurrentVertex(currentVertex, this.distance, normal, endLeft, endRight, round, segment); } } @@ -417,17 +418,18 @@ class LineBucket extends Bucket { * @param {boolean} whether the line is turning left or right at this angle * @private */ - addPieSliceVertex(currentVertex, distance, extrude, lineTurnsLeft) { + addPieSliceVertex(currentVertex, distance, extrude, lineTurnsLeft, segment) { const ty = lineTurnsLeft ? 1 : 0; extrude = extrude.mult(lineTurnsLeft ? -1 : 1); - const arrayGroup = this.arrayGroups.line[this.arrayGroups.line.length - 1]; - const layoutVertexArray = arrayGroup.layoutVertexArray; - const elementArray = arrayGroup.elementArray; - - this.e3 = addLineVertex(layoutVertexArray, currentVertex, extrude, 0, ty, 0, distance); + const arrays = this.arrays.line; + const layoutVertexArray = arrays.layoutVertexArray; + const elementArray = arrays.elementArray; + addLineVertex(layoutVertexArray, currentVertex, extrude, 0, ty, 0, distance); + this.e3 = segment.vertexLength++; if (this.e1 >= 0 && this.e2 >= 0) { elementArray.emplaceBack(this.e1, this.e2, this.e3); + segment.primitiveLength++; } if (lineTurnsLeft) { diff --git a/js/data/bucket/symbol_bucket.js b/js/data/bucket/symbol_bucket.js index 488e0e7ba85..4faced3a6ce 100644 --- a/js/data/bucket/symbol_bucket.js +++ b/js/data/bucket/symbol_bucket.js @@ -66,12 +66,13 @@ const symbolInterfaces = { name: 'a_data', components: 2, type: 'Uint8' - }]) + }]), + elementArrayType: new ElementArrayType(2) } }; function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, labelangle) { - return array.emplaceBack( + array.emplaceBack( // a_pos x, y, @@ -91,6 +92,19 @@ function addVertex(array, x, y, ox, oy, tx, ty, minzoom, maxzoom, labelminzoom, Math.min(maxzoom || 25, 25) * 10); // maxzoom } +function addCollisionBoxVertex(layoutVertexArray, point, extrude, maxZoom, placementZoom) { + return layoutVertexArray.emplaceBack( + // pos + point.x, + point.y, + // extrude + Math.round(extrude.x), + Math.round(extrude.y), + // data + maxZoom * 10, + placementZoom * 10); +} + class SymbolBucket extends Bucket { constructor(options) { super(options); @@ -104,6 +118,7 @@ class SymbolBucket extends Bucket { this.adjustedTextSize = options.adjustedTextSize; this.adjustedIconSize = options.adjustedIconSize; this.fontstack = options.fontstack; + this.layer = this.layers[0]; } get programInterfaces() { @@ -120,19 +135,6 @@ class SymbolBucket extends Bucket { return serialized; } - addCollisionBoxVertex(layoutVertexArray, point, extrude, maxZoom, placementZoom) { - return layoutVertexArray.emplaceBack( - // pos - point.x, - point.y, - // extrude - Math.round(extrude.x), - Math.round(extrude.y), - // data - maxZoom * 10, - placementZoom * 10); - } - populate(features, options) { this.recalculateStyleLayers(); @@ -524,11 +526,9 @@ class SymbolBucket extends Bucket { } addSymbols(programName, quadsStart, quadsEnd, scale, keepUpright, alongLine, placementAngle) { - - const group = this.prepareArrayGroup(programName, 4 * (quadsEnd - quadsStart)); - - const elementArray = group.elementArray; - const layoutVertexArray = group.layoutVertexArray; + const arrays = this.arrays[programName]; + const elementArray = arrays.elementArray; + const layoutVertexArray = arrays.layoutVertexArray; const zoom = this.zoom; const placementZoom = Math.max(Math.log(scale) / Math.LN2 + zoom, 0); @@ -559,19 +559,27 @@ class SymbolBucket extends Bucket { // Encode angle of glyph const glyphAngle = Math.round((symbol.glyphAngle / (Math.PI * 2)) * 256); - const index = addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom, glyphAngle); + const segment = arrays.prepareSegment(4); + const index = segment.vertexLength; + + addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tl.x, tl.y, tex.x, tex.y, minZoom, maxZoom, placementZoom, glyphAngle); addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, tr.x, tr.y, tex.x + tex.w, tex.y, minZoom, maxZoom, placementZoom, glyphAngle); addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, bl.x, bl.y, tex.x, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle); addVertex(layoutVertexArray, anchorPoint.x, anchorPoint.y, br.x, br.y, tex.x + tex.w, tex.y + tex.h, minZoom, maxZoom, placementZoom, glyphAngle); elementArray.emplaceBack(index, index + 1, index + 2); elementArray.emplaceBack(index + 1, index + 2, index + 3); + + segment.vertexLength += 4; + segment.primitiveLength += 2; } } addToDebugBuffers(collisionTile) { - const group = this.prepareArrayGroup('collisionBox', 0); - const layoutVertexArray = group.layoutVertexArray; + const arrays = this.arrays.collisionBox; + const layoutVertexArray = arrays.layoutVertexArray; + const elementArray = arrays.elementArray; + const angle = -collisionTile.angle; const yStretch = collisionTile.yStretch; @@ -596,14 +604,21 @@ class SymbolBucket extends Bucket { const maxZoom = Math.max(0, Math.min(25, this.zoom + Math.log(box.maxScale) / Math.LN2)); const placementZoom = Math.max(0, Math.min(25, this.zoom + Math.log(box.placementScale) / Math.LN2)); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom); - this.addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom); + const segment = arrays.prepareSegment(4); + const index = segment.vertexLength; + + addCollisionBoxVertex(layoutVertexArray, anchorPoint, tl, maxZoom, placementZoom); + addCollisionBoxVertex(layoutVertexArray, anchorPoint, tr, maxZoom, placementZoom); + addCollisionBoxVertex(layoutVertexArray, anchorPoint, br, maxZoom, placementZoom); + addCollisionBoxVertex(layoutVertexArray, anchorPoint, bl, maxZoom, placementZoom); + + elementArray.emplaceBack(index, index + 1); + elementArray.emplaceBack(index + 1, index + 2); + elementArray.emplaceBack(index + 2, index + 3); + elementArray.emplaceBack(index + 3, index); + + segment.vertexLength += 4; + segment.primitiveLength += 4; } } } diff --git a/js/data/buffer.js b/js/data/buffer.js index f1dc724cee4..5dfbfa4e7a4 100644 --- a/js/data/buffer.js +++ b/js/data/buffer.js @@ -56,8 +56,9 @@ class Buffer { * Set the attribute pointers in a WebGL context * @param gl The WebGL context * @param program The active WebGL program + * @param vertexOffset Index of the starting vertex of the segment */ - setVertexAttribPointers(gl, program) { + setVertexAttribPointers(gl, program, vertexOffset) { for (let j = 0; j < this.attributes.length; j++) { const member = this.attributes[j]; const attribIndex = program[member.name]; @@ -69,7 +70,7 @@ class Buffer { gl[AttributeType[member.type]], false, this.arrayType.bytesPerElement, - member.offset + member.offset + (this.arrayType.bytesPerElement * vertexOffset || 0) ); } } diff --git a/js/data/buffer_group.js b/js/data/buffer_group.js index 59b31958e88..b8cb2a67b94 100644 --- a/js/data/buffer_group.js +++ b/js/data/buffer_group.js @@ -6,31 +6,34 @@ const VertexArrayObject = require('../render/vertex_array_object'); class BufferGroup { - constructor(arrayGroup, arrayTypes) { + constructor(arrayGroup, programInterface) { this.layoutVertexBuffer = new Buffer(arrayGroup.layoutVertexArray, - arrayTypes.layoutVertexArrayType, Buffer.BufferType.VERTEX); + programInterface.layoutVertexArrayType.serialize(), Buffer.BufferType.VERTEX); if (arrayGroup.elementArray) { this.elementBuffer = new Buffer(arrayGroup.elementArray, - arrayTypes.elementArrayType, Buffer.BufferType.ELEMENT); + programInterface.elementArrayType.serialize(), Buffer.BufferType.ELEMENT); } - const vaos = this.vaos = {}; - let secondVaos; - if (arrayGroup.elementArray2) { this.elementBuffer2 = new Buffer(arrayGroup.elementArray2, - arrayTypes.elementArrayType2, Buffer.BufferType.ELEMENT); - secondVaos = this.secondVaos = {}; + programInterface.elementArrayType2.serialize(), Buffer.BufferType.ELEMENT); } - this.paintVertexBuffers = util.mapObject(arrayGroup.paintVertexArrays, (array, name) => { - vaos[name] = new VertexArrayObject(); - if (arrayGroup.elementArray2) { - secondVaos[name] = new VertexArrayObject(); - } - return new Buffer(array, arrayTypes.paintVertexArrayTypes[name], Buffer.BufferType.VERTEX); + this.paintVertexBuffers = util.mapObject(arrayGroup.paintVertexArrays, (array) => { + return new Buffer(array.array, array.type, Buffer.BufferType.VERTEX); }); + + this.segments = arrayGroup.segments; + this.segments2 = arrayGroup.segments2; + + for (const segments of [this.segments, this.segments2]) { + for (const segment of segments || []) { + segment.vaos = util.mapObject(arrayGroup.paintVertexArrays, () => { + return new VertexArrayObject(); + }); + } + } } destroy() { @@ -44,11 +47,12 @@ class BufferGroup { for (const n in this.paintVertexBuffers) { this.paintVertexBuffers[n].destroy(); } - for (const j in this.vaos) { - this.vaos[j].destroy(); - } - for (const k in this.secondVaos) { - this.secondVaos[k].destroy(); + for (const segments of [this.segments, this.segments2]) { + for (const segment of segments || []) { + for (const k in segment.vaos) { + segment.vaos[k].destroy(); + } + } } } } diff --git a/js/data/program_configuration.js b/js/data/program_configuration.js index e6fa3d39423..afa8c356191 100644 --- a/js/data/program_configuration.js +++ b/js/data/program_configuration.js @@ -132,7 +132,10 @@ class ProgramConfiguration { return self; } - populatePaintArray(layer, paintArray, start, length, globalProperties, featureProperties) { + populatePaintArray(layer, paintArray, length, globalProperties, featureProperties) { + const start = paintArray.length; + paintArray.resize(length); + for (const attribute of this.attributes) { const value = attribute.getValue(layer, globalProperties, featureProperties); const multiplier = attribute.multiplier || 1; diff --git a/js/render/draw_circle.js b/js/render/draw_circle.js index 24dc02b458d..10857014d42 100644 --- a/js/render/draw_circle.js +++ b/js/render/draw_circle.js @@ -22,9 +22,8 @@ function drawCircles(painter, sourceCache, layer, coords) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; - const bufferGroups = bucket.bufferGroups.circle; - if (!bufferGroups) continue; + const buffers = bucket.bufferGroups.circle; const programConfiguration = bucket.programConfigurations.circle[layer.id]; const program = painter.useProgram('circle', programConfiguration); programConfiguration.setUniforms(gl, program, layer, {zoom: painter.transform.zoom}); @@ -48,10 +47,9 @@ function drawCircles(painter, sourceCache, layer, coords) { layer.paint['circle-translate-anchor'] )); - for (let k = 0; k < bufferGroups.length; k++) { - const group = bufferGroups[k]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, buffers.paintVertexBuffers[layer.id], segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } } diff --git a/js/render/draw_collision_debug.js b/js/render/draw_collision_debug.js index 34152069de1..1cd363d6939 100644 --- a/js/render/draw_collision_debug.js +++ b/js/render/draw_collision_debug.js @@ -12,11 +12,6 @@ function drawCollisionDebug(painter, sourceCache, layer, coords) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) continue; - const bufferGroups = bucket.bufferGroups.collisionBox; - - if (!bufferGroups || !bufferGroups.length) continue; - const group = bufferGroups[0]; - if (group.layoutVertexBuffer.length === 0) continue; gl.uniformMatrix4fv(program.u_matrix, false, coord.posMatrix); @@ -27,7 +22,10 @@ function drawCollisionDebug(painter, sourceCache, layer, coords) { gl.uniform1f(program.u_zoom, painter.transform.zoom * 10); gl.uniform1f(program.u_maxzoom, (tile.coord.z + 1) * 10); - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer); - gl.drawArrays(gl.LINES, 0, group.layoutVertexBuffer.length); + const buffers = bucket.bufferGroups.collisionBox; + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, null, segment.vertexOffset); + gl.drawElements(gl.LINES, segment.primitiveLength * 2, gl.UNSIGNED_SHORT, segment.primitiveOffset * 2 * 2); + } } } diff --git a/js/render/draw_extrusion.js b/js/render/draw_extrusion.js index 5c42c5d6a68..f7bc7da196f 100644 --- a/js/render/draw_extrusion.js +++ b/js/render/draw_extrusion.js @@ -142,14 +142,13 @@ ExtrusionTexture.prototype.renderToMap = function() { }; function drawExtrusion(painter, source, layer, coord) { + if (painter.isOpaquePass) return; + const tile = source.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) return; - const bufferGroups = bucket.bufferGroups.fillextrusion; - if (!bufferGroups) return; - - if (painter.isOpaquePass) return; + const buffers = bucket.bufferGroups.fillextrusion; const gl = painter.gl; const image = layer.paint['fill-pattern']; @@ -165,10 +164,9 @@ function drawExtrusion(painter, source, layer, coord) { setMatrix(program, painter, coord, tile, layer); setLight(program, painter); - for (let i = 0; i < bufferGroups.length; i++) { - const group = bufferGroups[i]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, buffers.paintVertexBuffers[layer.id], segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } diff --git a/js/render/draw_fill.js b/js/render/draw_fill.js index 887a07f5586..9cc4288c2c5 100644 --- a/js/render/draw_fill.js +++ b/js/render/draw_fill.js @@ -50,9 +50,8 @@ function drawFill(painter, sourceCache, layer, coord) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) return; - const bufferGroups = bucket.bufferGroups.fill; - if (!bufferGroups) return; + const buffers = bucket.bufferGroups.fill; const gl = painter.gl; const image = layer.paint['fill-pattern']; @@ -83,10 +82,9 @@ function drawFill(painter, sourceCache, layer, coord) { painter.enableTileClippingMask(coord); - for (let i = 0; i < bufferGroups.length; i++) { - const group = bufferGroups[i]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, buffers.paintVertexBuffers[layer.id], segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } @@ -94,9 +92,8 @@ function drawStroke(painter, sourceCache, layer, coord) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) return; - const bufferGroups = bucket.bufferGroups.fill; - if (!bufferGroups) return; + const buffers = bucket.bufferGroups.fill; const gl = painter.gl; const image = layer.paint['fill-pattern']; @@ -129,9 +126,8 @@ function drawStroke(painter, sourceCache, layer, coord) { painter.enableTileClippingMask(coord); - for (let k = 0; k < bufferGroups.length; k++) { - const group = bufferGroups[k]; - group.secondVaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer2, group.paintVertexBuffers[layer.id]); - gl.drawElements(gl.LINES, group.elementBuffer2.length * 2, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments2) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer2, buffers.paintVertexBuffers[layer.id], segment.vertexOffset); + gl.drawElements(gl.LINES, segment.primitiveLength * 2, gl.UNSIGNED_SHORT, segment.primitiveOffset * 2 * 2); } } diff --git a/js/render/draw_line.js b/js/render/draw_line.js index cc64d202d2b..f8df4c23bc2 100644 --- a/js/render/draw_line.js +++ b/js/render/draw_line.js @@ -33,10 +33,10 @@ function drawLineTile(painter, sourceCache, layer, coord) { const tile = sourceCache.getTile(coord); const bucket = tile.getBucket(layer); if (!bucket) return; - const bufferGroups = bucket.bufferGroups.line; - if (!bufferGroups) return; + const buffers = bucket.bufferGroups.line; const gl = painter.gl; + const dasharray = layer.paint['line-dasharray']; const image = layer.paint['line-pattern']; @@ -120,9 +120,8 @@ function drawLineTile(painter, sourceCache, layer, coord) { gl.uniform1f(program.u_ratio, 1 / pixelsToTileUnits(tile, 1, painter.transform.zoom)); - for (let i = 0; i < bufferGroups.length; i++) { - const group = bufferGroups[i]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer, group.paintVertexBuffers[layer.id]); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, buffers.paintVertexBuffers[layer.id], segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } diff --git a/js/render/draw_symbol.js b/js/render/draw_symbol.js index 152ad5b5a5a..14c15cfd9db 100644 --- a/js/render/draw_symbol.js +++ b/js/render/draw_symbol.js @@ -84,12 +84,11 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, const tile = sourceCache.getTile(coords[j]); const bucket = tile.getBucket(layer); if (!bucket) continue; - const bothBufferGroups = bucket.bufferGroups; - const bufferGroups = isText ? bothBufferGroups.glyph : bothBufferGroups.icon; - if (!bufferGroups.length) continue; + const buffers = isText ? bucket.bufferGroups.glyph : bucket.bufferGroups.icon; + if (!buffers.segments.length) continue; painter.enableTileClippingMask(coords[j]); - drawSymbol(painter, layer, coords[j].posMatrix, tile, bucket, bufferGroups, isText, + drawSymbol(painter, layer, coords[j].posMatrix, tile, bucket, buffers, isText, isText || bucket.sdfIcons, !isText && bucket.iconsNeedLinear, isText ? bucket.adjustedTextSize : bucket.adjustedIconSize, bucket.fontstack, translate, @@ -107,7 +106,7 @@ function drawLayerSymbols(painter, sourceCache, layer, coords, isText, gl.enable(gl.DEPTH_TEST); } -function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isText, sdf, iconsNeedLinear, adjustedSize, fontstack, +function drawSymbol(painter, layer, posMatrix, tile, bucket, buffers, isText, sdf, iconsNeedLinear, adjustedSize, fontstack, translate, translateAnchor, rotationAlignment, @@ -174,8 +173,6 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex painter.frameHistory.bind(gl); gl.uniform1i(program.u_fadetexture, 1); - let group; - if (sdf) { const sdfPx = 8; const blurOffset = 1.19; @@ -189,10 +186,9 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex gl.uniform1f(program.u_opacity, opacity); gl.uniform1f(program.u_buffer, (haloOffset - haloWidth / fontScale) / sdfPx); - for (let j = 0; j < bufferGroups.length; j++) { - group = bufferGroups[j]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, null, segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } @@ -204,18 +200,16 @@ function drawSymbol(painter, layer, posMatrix, tile, bucket, bufferGroups, isTex gl.uniform1f(program.u_bearing, tr.bearing / 360 * 2 * Math.PI); gl.uniform1f(program.u_aspect_ratio, tr.width / tr.height); - for (let i = 0; i < bufferGroups.length; i++) { - group = bufferGroups[i]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, null, segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } else { gl.uniform1f(program.u_opacity, opacity); - for (let k = 0; k < bufferGroups.length; k++) { - group = bufferGroups[k]; - group.vaos[layer.id].bind(gl, program, group.layoutVertexBuffer, group.elementBuffer); - gl.drawElements(gl.TRIANGLES, group.elementBuffer.length * 3, gl.UNSIGNED_SHORT, 0); + for (const segment of buffers.segments) { + segment.vaos[layer.id].bind(gl, program, buffers.layoutVertexBuffer, buffers.elementBuffer, null, segment.vertexOffset); + gl.drawElements(gl.TRIANGLES, segment.primitiveLength * 3, gl.UNSIGNED_SHORT, segment.primitiveOffset * 3 * 2); } } } diff --git a/js/render/vertex_array_object.js b/js/render/vertex_array_object.js index e44ca57bb10..8afccb07a36 100644 --- a/js/render/vertex_array_object.js +++ b/js/render/vertex_array_object.js @@ -8,10 +8,11 @@ class VertexArrayObject { this.boundVertexBuffer = null; this.boundVertexBuffer2 = null; this.boundElementBuffer = null; + this.boundVertexOffset = null; this.vao = null; } - bind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2) { + bind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2, vertexOffset) { if (gl.extVertexArrayObject === undefined) { gl.extVertexArrayObject = gl.getExtension("OES_vertex_array_object"); @@ -22,18 +23,19 @@ class VertexArrayObject { this.boundProgram !== program || this.boundVertexBuffer !== layoutVertexBuffer || this.boundVertexBuffer2 !== vertexBuffer2 || - this.boundElementBuffer !== elementBuffer + this.boundElementBuffer !== elementBuffer || + this.boundVertexOffset !== vertexOffset ); if (!gl.extVertexArrayObject || isFreshBindRequired) { - this.freshBind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2); + this.freshBind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2, vertexOffset); this.gl = gl; } else { gl.extVertexArrayObject.bindVertexArrayOES(this.vao); } } - freshBind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2) { + freshBind(gl, program, layoutVertexBuffer, elementBuffer, vertexBuffer2, vertexOffset) { let numPrevAttributes; const numNextAttributes = program.numAttributes; @@ -48,6 +50,7 @@ class VertexArrayObject { this.boundVertexBuffer = layoutVertexBuffer; this.boundVertexBuffer2 = vertexBuffer2; this.boundElementBuffer = elementBuffer; + this.boundVertexOffset = vertexOffset; } else { numPrevAttributes = gl.currentNumAttributes || 0; @@ -68,10 +71,10 @@ class VertexArrayObject { } layoutVertexBuffer.bind(gl); - layoutVertexBuffer.setVertexAttribPointers(gl, program); + layoutVertexBuffer.setVertexAttribPointers(gl, program, vertexOffset); if (vertexBuffer2) { vertexBuffer2.bind(gl); - vertexBuffer2.setVertexAttribPointers(gl, program); + vertexBuffer2.setVertexAttribPointers(gl, program, vertexOffset); } if (elementBuffer) { elementBuffer.bind(gl); diff --git a/js/source/tile.js b/js/source/tile.js index 32f49f5e81f..fee33b42dcc 100644 --- a/js/source/tile.js +++ b/js/source/tile.js @@ -72,7 +72,7 @@ class Tile { this.symbolInstancesArray = new SymbolInstancesArray(data.symbolInstancesArray); this.symbolQuadsArray = new SymbolQuadsArray(data.symbolQuadsArray); this.featureIndex = new FeatureIndex(data.featureIndex, this.rawTileData, this.collisionTile); - this.buckets = unserializeBuckets(data.buckets, painter.style); + this.buckets = Bucket.deserialize(data.buckets, painter.style); } /** @@ -97,7 +97,7 @@ class Tile { } // Add new symbol buckets - util.extend(this.buckets, unserializeBuckets(data.buckets, style)); + util.extend(this.buckets, Bucket.deserialize(data.buckets, style)); } /** @@ -184,25 +184,4 @@ class Tile { } } -function unserializeBuckets(input, style) { - // Guard against the case where the map's style has been set to null while - // this bucket has been parsing. - if (!style) return; - - const output = {}; - for (let i = 0; i < input.length; i++) { - const layer = style.getLayer(input[i].layerId); - if (!layer) continue; - - const bucket = Bucket.create(util.extend({ - layer: layer, - childLayers: input[i].childLayerIds - .map(style.getLayer.bind(style)) - .filter((layer) => { return layer; }) - }, input[i])); - output[layer.id] = bucket; - } - return output; -} - module.exports = Tile; diff --git a/js/source/worker_tile.js b/js/source/worker_tile.js index 201c90d885b..ec43d41a6c3 100644 --- a/js/source/worker_tile.js +++ b/js/source/worker_tile.js @@ -84,9 +84,8 @@ class WorkerTile { if (layer.layout && layer.layout.visibility === 'none') continue; const bucket = buckets[layer.id] = Bucket.create({ - layer: layer, index: bucketIndex++, - childLayers: family, + layers: family, zoom: this.zoom, overscaling: this.overscaling, collisionBoxArray: this.collisionBoxArray, @@ -131,7 +130,7 @@ class WorkerTile { for (let i = layerIndex.order.length - 1; i >= 0; i--) { const id = layerIndex.order[i]; const bucket = buckets[id]; - if (bucket && bucket.layer.type === 'symbol') { + if (bucket && bucket.layers[0].type === 'symbol') { this.symbolBuckets.push(bucket); } } diff --git a/test/js/data/bucket.test.js b/test/js/data/bucket.test.js index 5b296711d10..2f73e7cb399 100644 --- a/test/js/data/bucket.test.js +++ b/test/js/data/bucket.test.js @@ -58,13 +58,12 @@ test('Bucket', (t) => { } addFeature(feature) { - const group = this.prepareArrayGroup('test', 1); + const arrays = this.arrays.test; const point = feature.loadGeometry()[0][0]; - const startIndex = group.layoutVertexArray.length; - group.layoutVertexArray.emplaceBack(point.x * 2, point.y * 2); - group.elementArray.emplaceBack(1, 2, 3); - group.elementArray2.emplaceBack(point.x, point.y); - this.populatePaintArrays('test', {}, feature.properties, group, startIndex); + arrays.layoutVertexArray.emplaceBack(point.x * 2, point.y * 2); + arrays.elementArray.emplaceBack(1, 2, 3); + arrays.elementArray2.emplaceBack(point.x, point.y); + arrays.populatePaintArrays(this.layers, {}, feature.properties); } } @@ -80,10 +79,7 @@ test('Bucket', (t) => { return styleLayer; }); - return new Class({ - layer: layers[0], - childLayers: layers - }); + return new Class({layers}); } function createOptions() { @@ -95,24 +91,24 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - const testVertex = bucket.arrayGroups.test[0].layoutVertexArray; + const testVertex = bucket.arrays.test.layoutVertexArray; t.equal(testVertex.length, 1); const v0 = testVertex.get(0); t.equal(v0.a_box0, 34); t.equal(v0.a_box1, 84); - const paintVertex = bucket.arrayGroups.test[0].paintVertexArrays.layerid; + const paintVertex = bucket.arrays.test.paintVertexArrays.layerid; t.equal(paintVertex.length, 1); const p0 = paintVertex.get(0); t.equal(p0.a_map, 17); - const testElement = bucket.arrayGroups.test[0].elementArray; + const testElement = bucket.arrays.test.elementArray; t.equal(testElement.length, 1); const e1 = testElement.get(0); t.equal(e1.vertices0, 1); t.equal(e1.vertices1, 2); t.equal(e1.vertices2, 3); - const testElement2 = bucket.arrayGroups.test[0].elementArray2; + const testElement2 = bucket.arrays.test.elementArray2; t.equal(testElement2.length, 1); const e2 = testElement2.get(0); t.equal(e2.vertices0, 17); @@ -129,9 +125,9 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - const v0 = bucket.arrayGroups.test[0].layoutVertexArray.get(0); - const a0 = bucket.arrayGroups.test[0].paintVertexArrays.one.get(0); - const b0 = bucket.arrayGroups.test[0].paintVertexArrays.two.get(0); + const v0 = bucket.arrays.test.layoutVertexArray.get(0); + const a0 = bucket.arrays.test.paintVertexArrays.one.get(0); + const b0 = bucket.arrays.test.paintVertexArrays.two.get(0); t.equal(a0.a_map, 17); t.equal(b0.a_map, 17); t.equal(v0.a_box0, 34); @@ -156,7 +152,7 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - t.equal(bucket.arrayGroups.test[0].layoutVertexArray.bytesPerElement, 0); + t.equal(bucket.arrays.test.layoutVertexArray.bytesPerElement, 0); t.deepEqual( bucket.programConfigurations.test.one.uniforms[0].getValue.call(bucket), [5] @@ -176,7 +172,7 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - const v0 = bucket.arrayGroups.test[0].layoutVertexArray.get(0); + const v0 = bucket.arrays.test.layoutVertexArray.get(0); t.equal(v0.a_map, 34); t.end(); @@ -187,9 +183,9 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - t.equal(bucket.arrayGroups.test.length, 1); + t.notEqual(bucket.arrays.test.layoutVertexArray.length, 0); bucket.createArrays(); - t.equal(bucket.arrayGroups.test.length, 0); + t.equal(bucket.arrays.test.layoutVertexArray.length, 0); t.end(); }); @@ -198,13 +194,13 @@ test('Bucket', (t) => { const bucket = create(); bucket.createArrays(); - bucket.prepareArrayGroup('test', 10); - t.equal(0, bucket.arrayGroups.test[0].layoutVertexArray.length); - t.notEqual(0, bucket.arrayGroups.test[0].layoutVertexArray.capacity); + bucket.arrays.test.prepareSegment(10); + t.equal(0, bucket.arrays.test.layoutVertexArray.length); + t.notEqual(0, bucket.arrays.test.layoutVertexArray.capacity); bucket.trimArrays(); - t.equal(0, bucket.arrayGroups.test[0].layoutVertexArray.length); - t.equal(0, bucket.arrayGroups.test[0].layoutVertexArray.capacity); + t.equal(0, bucket.arrays.test.layoutVertexArray.length); + t.equal(0, bucket.arrays.test.layoutVertexArray.capacity); t.end(); }); @@ -230,10 +226,10 @@ test('Bucket', (t) => { bucket.getTransferables(transferables); t.equal(4, transferables.length); - t.equal(bucket.arrayGroups.test[0].layoutVertexArray.arrayBuffer, transferables[0]); - t.equal(bucket.arrayGroups.test[0].elementArray.arrayBuffer, transferables[1]); - t.equal(bucket.arrayGroups.test[0].elementArray2.arrayBuffer, transferables[2]); - t.equal(bucket.arrayGroups.test[0].paintVertexArrays.layerid.arrayBuffer, transferables[3]); + t.equal(bucket.arrays.test.layoutVertexArray.arrayBuffer, transferables[0]); + t.equal(bucket.arrays.test.elementArray.arrayBuffer, transferables[1]); + t.equal(bucket.arrays.test.elementArray2.arrayBuffer, transferables[2]); + t.equal(bucket.arrays.test.paintVertexArrays.layerid.arrayBuffer, transferables[3]); t.end(); }); @@ -245,24 +241,24 @@ test('Bucket', (t) => { bucket.createArrays(); bucket.populate([createFeature(17, 42)], createOptions()); - const testVertex = bucket.arrayGroups.test[0].layoutVertexArray; + const testVertex = bucket.arrays.test.layoutVertexArray; t.equal(testVertex.length, 1); const v0 = testVertex.get(0); t.equal(v0.a_box0, 34); t.equal(v0.a_box1, 84); - const testPaintVertex = bucket.arrayGroups.test[0].paintVertexArrays.layerid; + const testPaintVertex = bucket.arrays.test.paintVertexArrays.layerid; t.equal(testPaintVertex.length, 1); const p0 = testPaintVertex.get(0); t.equal(p0.a_map, 17); - const testElement = bucket.arrayGroups.test[0].elementArray; + const testElement = bucket.arrays.test.elementArray; t.equal(testElement.length, 1); const e1 = testElement.get(0); t.equal(e1.vertices0, 1); t.equal(e1.vertices1, 2); t.equal(e1.vertices2, 3); - const testElement2 = bucket.arrayGroups.test[0].elementArray2; + const testElement2 = bucket.arrays.test.elementArray2; t.equal(testElement2.length, 1); const e2 = testElement2.get(0); t.equal(e2.vertices0, 17); @@ -273,7 +269,7 @@ test('Bucket', (t) => { t.test('layout properties', (t) => { const bucket = create(); - t.equal(bucket.layer.layout.visibility, 'visible'); + t.equal(bucket.layers[0].layout.visibility, 'visible'); t.end(); }); @@ -282,24 +278,24 @@ test('Bucket', (t) => { bucket.populate([createFeature(17, 42)], createOptions()); - const testVertex = bucket.arrayGroups.test[0].layoutVertexArray; + const testVertex = bucket.arrays.test.layoutVertexArray; t.equal(testVertex.length, 1); const v0 = testVertex.get(0); t.equal(v0.a_box0, 34); t.equal(v0.a_box1, 84); - const testPaintVertex = bucket.arrayGroups.test[0].paintVertexArrays.layerid; + const testPaintVertex = bucket.arrays.test.paintVertexArrays.layerid; t.equal(testPaintVertex.length, 1); const p0 = testPaintVertex.get(0); t.equal(p0.a_map, 17); - const testElement = bucket.arrayGroups.test[0].elementArray; + const testElement = bucket.arrays.test.elementArray; t.equal(testElement.length, 1); const e1 = testElement.get(0); t.equal(e1.vertices0, 1); t.equal(e1.vertices1, 2); t.equal(e1.vertices2, 3); - const testElement2 = bucket.arrayGroups.test[0].elementArray2; + const testElement2 = bucket.arrays.test.elementArray2; t.equal(testElement2.length, 1); const e2 = testElement2.get(0); t.equal(e2.vertices0, 17); diff --git a/test/js/data/fill_bucket.test.js b/test/js/data/fill_bucket.test.js index 113500e7c2d..c417541e7ef 100644 --- a/test/js/data/fill_bucket.test.js +++ b/test/js/data/fill_bucket.test.js @@ -2,12 +2,12 @@ const test = require('mapbox-gl-js-test').test; const fs = require('fs'); +const path = require('path'); const Protobuf = require('pbf'); const VectorTile = require('vector-tile').VectorTile; const Point = require('point-geometry'); const ArrayGroup = require('../../../js/data/array_group'); const FillBucket = require('../../../js/data/bucket/fill_bucket'); -const path = require('path'); const StyleLayer = require('../../../js/style/style_layer'); // Load a fill feature from fixture tile. @@ -22,43 +22,39 @@ function createFeature(points) { }; } -test('FillBucket', (t) => { - // Suppress console.warn output. - const warn = console.warn; - console.warn = function() {}; +function createPolygon(numPoints) { + const points = []; + for (let i = 0; i < numPoints; i++) { + points.push(new Point(i / numPoints, i / numPoints)); + } + return points; +} +test('FillBucket', (t) => { const layer = new StyleLayer({ id: 'test', type: 'fill', layout: {} }); - const bucket = new FillBucket({ - buffers: {}, - layer: layer, - childLayers: [layer] - }); + const bucket = new FillBucket({ layers: [layer] }); bucket.createArrays(); - t.equal(bucket.addFeature(createFeature([[ + bucket.addFeature(createFeature([[ new Point(0, 0), new Point(10, 10) - ]])), undefined); + ]])); - t.equal(bucket.addFeature(createFeature([[ + bucket.addFeature(createFeature([[ new Point(0, 0), new Point(10, 10), new Point(10, 20) - ]])), undefined); - - t.equal(bucket.addFeature(feature), undefined); + ]])); - // Put it back. - console.warn = warn; + bucket.addFeature(feature); t.end(); }); -test('FillBucket - feature split across array groups', (t) => { - // temporarily reduce the max array length so we can test features +test('FillBucket segmentation', (t) => { + // Stub ArrayGroup.MAX_VERTEX_ARRAY_LENGTH so we can test features // breaking across array groups without tests taking a _long_ time. - const prevMaxArrayLength = ArrayGroup.MAX_VERTEX_ARRAY_LENGTH; - ArrayGroup.MAX_VERTEX_ARRAY_LENGTH = 1023; + t.stub(ArrayGroup, 'MAX_VERTEX_ARRAY_LENGTH', 256); const layer = new StyleLayer({ id: 'test', @@ -74,67 +70,49 @@ test('FillBucket - feature split across array groups', (t) => { // populatePaintArrays iterates through each vertex layer.updatePaintTransition('fill-color', [], {}); - const bucket = new FillBucket({ - buffers: {}, - layer: layer, - childLayers: [layer] - }); + const bucket = new FillBucket({ layers: [layer] }); bucket.createArrays(); - // first add an initial, small feature to make sure the next one starts at // a non-zero offset bucket.addFeature(createFeature([createPolygon(10)])); - // add a feature that will break across the group boundary (65536) + // add a feature that will break across the group boundary bucket.addFeature(createFeature([ - ArrayGroup.MAX_VERTEX_ARRAY_LENGTH - 20, // the first polygon fits within the bucket - 20 // but the second one breaks across the boundary. - ].map(createPolygon))); - - const groups = bucket.arrayGroups.fill; - - // check array group lengths - // FillBucket#addPolygon does NOT allow a single polygon to break across - // group boundary, so we expect the first group to include the first - // feature and the first polygon of the second feature, and the second - // group to include the _entire_ second polygon of the second feature. - const expectedLengths = [ - 10 + (ArrayGroup.MAX_VERTEX_ARRAY_LENGTH - 20), - 20 - ]; - t.equal(groups[0].paintVertexArrays.test.length, expectedLengths[0], 'group 0 length, paint'); - t.equal(groups[0].layoutVertexArray.length, expectedLengths[0], 'group 0 length, layout'); - t.equal(groups[1].paintVertexArrays.test.length, expectedLengths[1], 'group 1 length, paint'); - t.equal(groups[1].layoutVertexArray.length, expectedLengths[1], 'group 1 length, layout'); - - // check that every vertex's color values match the first vertex - const expected = [0, 0, 255, 255]; - t.same(getVertexColor(0, 0), expected, 'first vertex'); - t.same(getVertexColor(0, expectedLengths[0] - 1), expected, 'last vertex of first group'); - t.same(getVertexColor(1, 0), expected, 'first vertex of second group'); - t.same(getVertexColor(1, expectedLengths[1] - 1), expected, 'last vertex'); - - function getVertexColor(g, i) { - const vertex = groups[g].paintVertexArrays.test.get(i); - return [ + createPolygon(128), + createPolygon(128) + ])); + + const arrays = bucket.arrays.fill; + + // Each polygon must fit entirely within a segment, so we expect the + // first segment to include the first feature and the first polygon + // of the second feature, and the second segment to include the + // second polygon of the second feature. + t.equal(arrays.layoutVertexArray.length, 266); + t.deepEqual(arrays.segments[0], { + vertexOffset: 0, + vertexLength: 138, + primitiveOffset: 0, + primitiveLength: 134 + }); + t.deepEqual(arrays.segments[1], { + vertexOffset: 138, + vertexLength: 128, + primitiveOffset: 134, + primitiveLength: 126 + }); + + t.equal(arrays.paintVertexArrays.test.length, 266); + for (let i = 0; i < arrays.paintVertexArrays.test.length; i++) { + const vertex = arrays.paintVertexArrays.test.get(i); + t.deepEqual([ vertex['a_color0'], vertex['a_color1'], vertex['a_color2'], vertex['a_color3'] - ]; + ], [0, 0, 255, 255]); } - // restore - ArrayGroup.MAX_VERTEX_ARRAY_LENGTH = prevMaxArrayLength; - t.end(); }); - -function createPolygon (numPoints) { - const points = []; - for (let i = 0; i < numPoints; i++) { - points.push(new Point(i / numPoints, i / numPoints)); - } - return points; -} diff --git a/test/js/data/line_bucket.test.js b/test/js/data/line_bucket.test.js index 0b3acd94696..5881d8bfff2 100644 --- a/test/js/data/line_bucket.test.js +++ b/test/js/data/line_bucket.test.js @@ -15,42 +15,38 @@ const feature = vt.layers.road.feature(0); test('LineBucket', (t) => { const layer = new StyleLayer({ id: 'test', type: 'line', layout: {} }); - const bucket = new LineBucket({ - buffers: {}, - layer: layer, - childLayers: [layer] - }); + const bucket = new LineBucket({ layers: [layer] }); bucket.createArrays(); const pointWithScale = new Point(0, 0); pointWithScale.scale = 10; // should throw in the future? - t.equal(bucket.addLine([ + bucket.addLine([ new Point(0, 0) - ], {}), undefined); + ], {}); // should also throw in the future? // this is a closed single-segment line - t.equal(bucket.addLine([ + bucket.addLine([ new Point(0, 0), new Point(0, 0) - ], {}), undefined); + ], {}); - t.equal(bucket.addLine([ + bucket.addLine([ new Point(0, 0), new Point(10, 10), new Point(10, 20) - ], {}), undefined); + ], {}); - t.equal(bucket.addLine([ + bucket.addLine([ new Point(0, 0), new Point(10, 10), new Point(10, 20), new Point(0, 0) - ], {}), undefined); + ], {}); - t.equal(bucket.addFeature(feature), undefined); + bucket.addFeature(feature); t.end(); }); diff --git a/test/js/data/symbol_bucket.test.js b/test/js/data/symbol_bucket.test.js index 2f3bb44f6a9..865a3f1e68b 100644 --- a/test/js/data/symbol_bucket.test.js +++ b/test/js/data/symbol_bucket.test.js @@ -47,8 +47,7 @@ function bucketSetup() { collisionBoxArray: collisionBoxArray, symbolInstancesArray: symbolInstancesArray, symbolQuadsArray: symbolQuadsArray, - layer: layer, - childLayers: [layer] + layers: [layer] }); }