From 0fd76c1d7e3cdf1f1e43b9e022aae1b2268aecb3 Mon Sep 17 00:00:00 2001 From: Chris Loer Date: Thu, 16 Aug 2018 16:44:54 -0700 Subject: [PATCH] Make symbolInstances array transferrable. This change decreases worker transfer time which should decrease jankiness during tile loading. However, there is some extra overhead in accessing the StructArray of symbolInstances during render-time symbol placement. If we identify hotspots, the most direct optimization is to used indexed accessors into the underlying TypedArray, instead of using the `get(i).property` approach. --- build/generate-struct-arrays.js | 5 +- src/data/array_types.js | 141 ++++++++++++++++++++ src/data/bucket/symbol_attributes.js | 17 +++ src/data/bucket/symbol_bucket.js | 117 ++++++++-------- src/symbol/cross_tile_symbol_index.js | 26 ++-- src/symbol/placement.js | 75 ++++++----- src/symbol/symbol_layout.js | 19 ++- test/unit/symbol/cross_tile_symbol_index.js | 9 +- 8 files changed, 284 insertions(+), 125 deletions(-) diff --git a/build/generate-struct-arrays.js b/build/generate-struct-arrays.js index 071f41e738c..e60bb10369f 100644 --- a/build/generate-struct-arrays.js +++ b/build/generate-struct-arrays.js @@ -125,7 +125,7 @@ createStructArrayType('raster_bounds', rasterBoundsAttributes); const circleAttributes = require('../src/data/bucket/circle_attributes').default; const fillAttributes = require('../src/data/bucket/fill_attributes').default; -const fillExtrusionAttributes = require('../src/data/bucket/fill_extrusion_attributes').default ; +const fillExtrusionAttributes = require('../src/data/bucket/fill_extrusion_attributes').default; const lineAttributes = require('../src/data/bucket/line_attributes').default; // layout vertex arrays @@ -150,6 +150,7 @@ const { collisionCircleLayout, collisionVertexAttributes, placement, + symbolInstance, glyphOffset, lineVertex } = require('../src/data/bucket/symbol_attributes'); @@ -162,6 +163,7 @@ createStructArrayType(`collision_box_layout`, collisionBoxLayout); createStructArrayType(`collision_circle_layout`, collisionCircleLayout); createStructArrayType(`collision_vertex`, collisionVertexAttributes); createStructArrayType('placed_symbol', placement, true); +createStructArrayType('symbol_instance', symbolInstance, true); createStructArrayType('glyph_offset', glyphOffset, true); createStructArrayType('symbol_line_vertex', lineVertex, true); @@ -233,4 +235,3 @@ export { ${[...arrayTypeEntries].join(',\n ')} }; `); - diff --git a/src/data/array_types.js b/src/data/array_types.js index 635c0f83f10..c50a3c72285 100644 --- a/src/data/array_types.js +++ b/src/data/array_types.js @@ -520,6 +520,74 @@ StructArrayLayout2i2ui3ul3ui2f2ub40.prototype.bytesPerElement = 40; register('StructArrayLayout2i2ui3ul3ui2f2ub40', StructArrayLayout2i2ui3ul3ui2f2ub40); +/** + * Implementation of the StructArray layout: + * [0]: Int16[4] + * [8]: Uint16[9] + * [28]: Uint32[1] + * + * @private + */ +class StructArrayLayout4i9ui1ul32 extends StructArray { + uint8: Uint8Array; + int16: Int16Array; + uint16: Uint16Array; + uint32: Uint32Array; + + _refreshViews() { + this.uint8 = new Uint8Array(this.arrayBuffer); + this.int16 = new Int16Array(this.arrayBuffer); + this.uint16 = new Uint16Array(this.arrayBuffer); + this.uint32 = new Uint32Array(this.arrayBuffer); + } + + emplaceBack(v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number) { + const i = this.length; + this.resize(i + 1); + const o2 = i * 16; + const o4 = i * 8; + this.int16[o2 + 0] = v0; + this.int16[o2 + 1] = v1; + this.int16[o2 + 2] = v2; + this.int16[o2 + 3] = v3; + this.uint16[o2 + 4] = v4; + this.uint16[o2 + 5] = v5; + this.uint16[o2 + 6] = v6; + this.uint16[o2 + 7] = v7; + this.uint16[o2 + 8] = v8; + this.uint16[o2 + 9] = v9; + this.uint16[o2 + 10] = v10; + this.uint16[o2 + 11] = v11; + this.uint16[o2 + 12] = v12; + this.uint32[o4 + 7] = v13; + return i; + } + + emplace(i: number, v0: number, v1: number, v2: number, v3: number, v4: number, v5: number, v6: number, v7: number, v8: number, v9: number, v10: number, v11: number, v12: number, v13: number) { + const o2 = i * 16; + const o4 = i * 8; + this.int16[o2 + 0] = v0; + this.int16[o2 + 1] = v1; + this.int16[o2 + 2] = v2; + this.int16[o2 + 3] = v3; + this.uint16[o2 + 4] = v4; + this.uint16[o2 + 5] = v5; + this.uint16[o2 + 6] = v6; + this.uint16[o2 + 7] = v7; + this.uint16[o2 + 8] = v8; + this.uint16[o2 + 9] = v9; + this.uint16[o2 + 10] = v10; + this.uint16[o2 + 11] = v11; + this.uint16[o2 + 12] = v12; + this.uint32[o4 + 7] = v13; + return i; + } +} + +StructArrayLayout4i9ui1ul32.prototype.bytesPerElement = 32; +register('StructArrayLayout4i9ui1ul32', StructArrayLayout4i9ui1ul32); + + /** * Implementation of the StructArray layout: * [0]: Float32[1] @@ -732,6 +800,11 @@ class StructArrayLayout1ui2 extends StructArray { return i; } + emplace(i: number, v0: number) { + const o2 = i * 1; + this.uint16[o2 + 0] = v0; + return i; + } } StructArrayLayout1ui2.prototype.bytesPerElement = 2; @@ -941,6 +1014,73 @@ export class PlacedSymbolArray extends StructArrayLayout2i2ui3ul3ui2f2ub40 { register('PlacedSymbolArray', PlacedSymbolArray); +class SymbolInstanceStruct extends Struct { + _structArray: SymbolInstanceArray; + anchorX: number; + anchorY: number; + horizontalPlacedTextSymbolIndex: number; + verticalPlacedTextSymbolIndex: number; + key: number; + textBoxStartIndex: number; + textBoxEndIndex: number; + iconBoxStartIndex: number; + iconBoxEndIndex: number; + featureIndex: number; + numGlyphVertices: number; + numVerticalGlyphVertices: number; + numIconVertices: number; + crossTileID: number; + get anchorX() { return this._structArray.int16[this._pos2 + 0]; } + set anchorX(x) { this._structArray.int16[this._pos2 + 0] = x; } + get anchorY() { return this._structArray.int16[this._pos2 + 1]; } + set anchorY(x) { this._structArray.int16[this._pos2 + 1] = x; } + get horizontalPlacedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 2]; } + set horizontalPlacedTextSymbolIndex(x) { this._structArray.int16[this._pos2 + 2] = x; } + get verticalPlacedTextSymbolIndex() { return this._structArray.int16[this._pos2 + 3]; } + set verticalPlacedTextSymbolIndex(x) { this._structArray.int16[this._pos2 + 3] = x; } + get key() { return this._structArray.uint16[this._pos2 + 4]; } + set key(x) { this._structArray.uint16[this._pos2 + 4] = x; } + get textBoxStartIndex() { return this._structArray.uint16[this._pos2 + 5]; } + set textBoxStartIndex(x) { this._structArray.uint16[this._pos2 + 5] = x; } + get textBoxEndIndex() { return this._structArray.uint16[this._pos2 + 6]; } + set textBoxEndIndex(x) { this._structArray.uint16[this._pos2 + 6] = x; } + get iconBoxStartIndex() { return this._structArray.uint16[this._pos2 + 7]; } + set iconBoxStartIndex(x) { this._structArray.uint16[this._pos2 + 7] = x; } + get iconBoxEndIndex() { return this._structArray.uint16[this._pos2 + 8]; } + set iconBoxEndIndex(x) { this._structArray.uint16[this._pos2 + 8] = x; } + get featureIndex() { return this._structArray.uint16[this._pos2 + 9]; } + set featureIndex(x) { this._structArray.uint16[this._pos2 + 9] = x; } + get numGlyphVertices() { return this._structArray.uint16[this._pos2 + 10]; } + set numGlyphVertices(x) { this._structArray.uint16[this._pos2 + 10] = x; } + get numVerticalGlyphVertices() { return this._structArray.uint16[this._pos2 + 11]; } + set numVerticalGlyphVertices(x) { this._structArray.uint16[this._pos2 + 11] = x; } + get numIconVertices() { return this._structArray.uint16[this._pos2 + 12]; } + set numIconVertices(x) { this._structArray.uint16[this._pos2 + 12] = x; } + get crossTileID() { return this._structArray.uint32[this._pos4 + 7]; } + set crossTileID(x) { this._structArray.uint32[this._pos4 + 7] = x; } +} + +SymbolInstanceStruct.prototype.size = 32; + +export type SymbolInstance = SymbolInstanceStruct; + + +/** + * @private + */ +export class SymbolInstanceArray extends StructArrayLayout4i9ui1ul32 { + /** + * Return the SymbolInstanceStruct at the given location in the array. + * @param {number} index The index of the element. + */ + get(index: number): SymbolInstanceStruct { + assert(!this.isTransferred); + return new SymbolInstanceStruct(this, index); + } +} + +register('SymbolInstanceArray', SymbolInstanceArray); + class GlyphOffsetStruct extends Struct { _structArray: GlyphOffsetArray; offsetX: number; @@ -1054,6 +1194,7 @@ export { StructArrayLayout2i2i2i12, StructArrayLayout2ub4, StructArrayLayout2i2ui3ul3ui2f2ub40, + StructArrayLayout4i9ui1ul32, StructArrayLayout1f4, StructArrayLayout3i6, StructArrayLayout1ul2ui8, diff --git a/src/data/bucket/symbol_attributes.js b/src/data/bucket/symbol_attributes.js index e76fd544175..ea07cbd61cb 100644 --- a/src/data/bucket/symbol_attributes.js +++ b/src/data/bucket/symbol_attributes.js @@ -73,6 +73,23 @@ export const placement = createLayout([ { type: 'Uint8', name: 'hidden' } ]); +export const symbolInstance = createLayout([ + { type: 'Int16', name: 'anchorX' }, + { type: 'Int16', name: 'anchorY' }, + { type: 'Int16', name: 'horizontalPlacedTextSymbolIndex' }, + { type: 'Int16', name: 'verticalPlacedTextSymbolIndex' }, + { type: 'Uint16', name: 'key' }, + { type: 'Uint16', name: 'textBoxStartIndex' }, + { type: 'Uint16', name: 'textBoxEndIndex' }, + { type: 'Uint16', name: 'iconBoxStartIndex' }, + { type: 'Uint16', name: 'iconBoxEndIndex' }, + { type: 'Uint16', name: 'featureIndex' }, + { type: 'Uint16', name: 'numGlyphVertices' }, + { type: 'Uint16', name: 'numVerticalGlyphVertices' }, + { type: 'Uint16', name: 'numIconVertices' }, + { type: 'Uint32', name: 'crossTileID' } +]); + export const glyphOffset = createLayout([ { type: 'Float32', name: 'offsetX' } ]); diff --git a/src/data/bucket/symbol_bucket.js b/src/data/bucket/symbol_bucket.js index cabedd1e85c..21152f93710 100644 --- a/src/data/bucket/symbol_bucket.js +++ b/src/data/bucket/symbol_bucket.js @@ -2,7 +2,7 @@ import { symbolLayoutAttributes, collisionVertexAttributes, collisionBoxLayout, collisionCircleLayout, dynamicLayoutAttributes } from './symbol_attributes'; -import { SymbolLayoutArray, SymbolDynamicLayoutArray, SymbolOpacityArray, CollisionBoxLayoutArray, CollisionCircleLayoutArray, CollisionVertexArray, PlacedSymbolArray, GlyphOffsetArray, SymbolLineVertexArray } from '../array_types'; +import { SymbolLayoutArray, SymbolDynamicLayoutArray, SymbolOpacityArray, CollisionBoxLayoutArray, CollisionCircleLayoutArray, CollisionVertexArray, PlacedSymbolArray, SymbolInstanceArray, GlyphOffsetArray, SymbolLineVertexArray } from '../array_types'; import Point from '@mapbox/point-geometry'; import SegmentVector from '../segment'; import { ProgramConfigurationSet } from '../program_configuration'; @@ -26,7 +26,7 @@ import type { IndexedFeature, PopulateParameters } from '../bucket'; -import type {CollisionBoxArray, CollisionBox} from '../array_types'; +import type {CollisionBoxArray, CollisionBox, SymbolInstance} from '../array_types'; import type { StructArray, StructArrayMember } from '../../util/struct_array'; import type SymbolStyleLayer from '../../style/style_layer/symbol_style_layer'; import type Context from '../../gl/context'; @@ -64,29 +64,6 @@ export type SymbolFeature = {| id?: any |}; -export type SymbolInstance = { - key: number, - textBoxStartIndex: number, - textBoxEndIndex: number, - iconBoxStartIndex: number, - iconBoxEndIndex: number, - anchor: Anchor, - featureIndex: number, - textCollisionFeature?: {boxStartIndex: number, boxEndIndex: number}, - iconCollisionFeature?: {boxStartIndex: number, boxEndIndex: number}, - horizontalPlacedTextSymbolIndex: number; - verticalPlacedTextSymbolIndex: number; - numGlyphVertices: number; - numVerticalGlyphVertices: number; - numIconVertices: number; - // Populated/modified on foreground during placement - crossTileID: number; - collisionArrays?: CollisionArrays; - placedText?: boolean; - placedIcon?: boolean; - hidden?: boolean; -}; - // Opacity arrays are frequently updated but don't contain a lot of information, so we pack them // tight. Each Uint32 is actually four duplicate Uint8s for the four corners of a glyph // 7 bits are for the current opacity, and the lowest bit is the target opacity @@ -275,7 +252,8 @@ class SymbolBucket implements Bucket { glyphOffsetArray: GlyphOffsetArray; lineVertexArray: SymbolLineVertexArray; features: Array; - symbolInstances: Array; + symbolInstances: SymbolInstanceArray; + collisionArrays: Array; pixelRatio: number; tilePixelRatio: number; compareText: {[string]: Array}; @@ -324,6 +302,7 @@ class SymbolBucket implements Bucket { this.glyphOffsetArray = new GlyphOffsetArray(); this.lineVertexArray = new SymbolLineVertexArray(); + this.symbolInstances = new SymbolInstanceArray(); } calculateGlyphDependencies(text: string, stack: {[number]: boolean}, textAlongLine: boolean, doesAllowVerticalWritingMode: boolean) { @@ -539,15 +518,15 @@ class SymbolBucket implements Bucket { arrays.programConfigurations.populatePaintArrays(arrays.layoutVertexArray.length, feature, feature.index); } - _addCollisionDebugVertex(layoutVertexArray: StructArray, collisionVertexArray: StructArray, point: Point, anchor: Point, extrude: Point) { + _addCollisionDebugVertex(layoutVertexArray: StructArray, collisionVertexArray: StructArray, point: Point, anchorX: number, anchorY: number, extrude: Point) { collisionVertexArray.emplaceBack(0, 0); return layoutVertexArray.emplaceBack( // pos point.x, point.y, // a_anchor_pos - anchor.x, - anchor.y, + anchorX, + anchorY, // extrude Math.round(extrude.x), Math.round(extrude.y)); @@ -561,10 +540,13 @@ class SymbolBucket implements Bucket { const layoutVertexArray = arrays.layoutVertexArray; const collisionVertexArray = arrays.collisionVertexArray; - this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, symbolInstance.anchor, new Point(x1, y1)); - this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, symbolInstance.anchor, new Point(x2, y1)); - this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, symbolInstance.anchor, new Point(x2, y2)); - this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, symbolInstance.anchor, new Point(x1, y2)); + const anchorX = symbolInstance.anchorX; + const anchorY = symbolInstance.anchorY; + + this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, anchorX, anchorY, new Point(x1, y1)); + this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, anchorX, anchorY, new Point(x2, y1)); + this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, anchorX, anchorY, new Point(x2, y2)); + this._addCollisionDebugVertex(layoutVertexArray, collisionVertexArray, boxAnchorPoint, anchorX, anchorY, new Point(x1, y2)); segment.vertexLength += 4; if (isCircle) { @@ -584,34 +566,32 @@ class SymbolBucket implements Bucket { } } + addDebugCollisionBoxes(startIndex: number, endIndex: number, symbolInstance: SymbolInstance) { + for (let b = startIndex; b < endIndex; b++) { + const box: CollisionBox = (this.collisionBoxArray.get(b): any); + const x1 = box.x1; + const y1 = box.y1; + const x2 = box.x2; + const y2 = box.y2; + + // If the radius > 0, this collision box is actually a circle + // The data we add to the buffers is exactly the same, but we'll render with a different shader. + const isCircle = box.radius > 0; + this.addCollisionDebugVertices(x1, y1, x2, y2, isCircle ? this.collisionCircle : this.collisionBox, box.anchorPoint, symbolInstance, isCircle); + } + } + generateCollisionDebugBuffers() { - for (const symbolInstance of this.symbolInstances) { - symbolInstance.textCollisionFeature = {boxStartIndex: symbolInstance.textBoxStartIndex, boxEndIndex: symbolInstance.textBoxEndIndex}; - symbolInstance.iconCollisionFeature = {boxStartIndex: symbolInstance.iconBoxStartIndex, boxEndIndex: symbolInstance.iconBoxEndIndex}; - - for (let i = 0; i < 2; i++) { - const feature = symbolInstance[i === 0 ? 'textCollisionFeature' : 'iconCollisionFeature']; - if (!feature) continue; - - for (let b = feature.boxStartIndex; b < feature.boxEndIndex; b++) { - const box: CollisionBox = (this.collisionBoxArray.get(b): any); - const x1 = box.x1; - const y1 = box.y1; - const x2 = box.x2; - const y2 = box.y2; - - // If the radius > 0, this collision box is actually a circle - // The data we add to the buffers is exactly the same, but we'll render with a different shader. - const isCircle = box.radius > 0; - this.addCollisionDebugVertices(x1, y1, x2, y2, isCircle ? this.collisionCircle : this.collisionBox, box.anchorPoint, symbolInstance, isCircle); - } - } + for (let i = 0; i < this.symbolInstances.length; i++) { + const symbolInstance = this.symbolInstances.get(i); + this.addDebugCollisionBoxes(symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance); + this.addDebugCollisionBoxes(symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex, symbolInstance); } } // These flat arrays are meant to be quicker to iterate over than the source // CollisionBoxArray - deserializeCollisionBoxes(collisionBoxArray: CollisionBoxArray, textStartIndex: number, textEndIndex: number, iconStartIndex: number, iconEndIndex: number): CollisionArrays { + _deserializeCollisionBoxesForSymbol(collisionBoxArray: CollisionBoxArray, textStartIndex: number, textEndIndex: number, iconStartIndex: number, iconEndIndex: number): CollisionArrays { const collisionArrays = {}; for (let k = textStartIndex; k < textEndIndex; k++) { const box: CollisionBox = (collisionBoxArray.get(k): any); @@ -640,6 +620,20 @@ class SymbolBucket implements Bucket { return collisionArrays; } + deserializeCollisionBoxes(collisionBoxArray: CollisionBoxArray) { + this.collisionArrays = []; + for (let i = 0; i < this.symbolInstances.length; i++) { + const symbolInstance = this.symbolInstances.get(i); + this.collisionArrays.push(this._deserializeCollisionBoxesForSymbol( + collisionBoxArray, + symbolInstance.textBoxStartIndex, + symbolInstance.textBoxEndIndex, + symbolInstance.iconBoxStartIndex, + symbolInstance.iconBoxEndIndex + )); + } + } + hasTextData() { return this.text.segments.get().length > 0; } @@ -690,10 +684,10 @@ class SymbolBucket implements Bucket { cos = Math.cos(angle); symbolInstanceIndexes.sort((aIndex, bIndex) => { - const a = this.symbolInstances[aIndex]; - const b = this.symbolInstances[bIndex]; - const aRotated = Math.round(sin * a.anchor.x + cos * a.anchor.y) | 0; - const bRotated = Math.round(sin * b.anchor.x + cos * b.anchor.y) | 0; + const a = this.symbolInstances.get(aIndex); + const b = this.symbolInstances.get(bIndex); + const aRotated = Math.round(sin * a.anchorX + cos * a.anchorY) | 0; + const bRotated = Math.round(sin * b.anchorX + cos * b.anchorY) | 0; return (aRotated - bRotated) || (b.featureIndex - a.featureIndex); }); @@ -703,7 +697,7 @@ class SymbolBucket implements Bucket { this.featureSortOrder = []; for (const i of symbolInstanceIndexes) { - const symbolInstance = this.symbolInstances[i]; + const symbolInstance = this.symbolInstances.get(i); this.featureSortOrder.push(symbolInstance.featureIndex); if (symbolInstance.horizontalPlacedTextSymbolIndex >= 0) { @@ -727,8 +721,7 @@ class SymbolBucket implements Bucket { } register('SymbolBucket', SymbolBucket, { - omit: ['layers', 'collisionBoxArray', 'features', 'compareText'], - shallow: ['symbolInstances'] + omit: ['layers', 'collisionBoxArray', 'features', 'compareText'] }); // this constant is based on the size of StructArray indexes used in a symbol diff --git a/src/symbol/cross_tile_symbol_index.js b/src/symbol/cross_tile_symbol_index.js index 17fde69712e..ade2eacdbc3 100644 --- a/src/symbol/cross_tile_symbol_index.js +++ b/src/symbol/cross_tile_symbol_index.js @@ -2,8 +2,11 @@ import EXTENT from '../data/extent'; +import { SymbolInstanceArray } from '../data/array_types'; + +import type { SymbolInstance } from '../data/array_types'; import type {OverscaledTileID} from '../source/tile_id'; -import type SymbolBucket, {SymbolInstance} from '../data/bucket/symbol_bucket'; +import type SymbolBucket from '../data/bucket/symbol_bucket'; import type StyleLayer from '../style/style_layer'; import type Tile from '../source/tile'; @@ -35,12 +38,13 @@ class TileLayerIndex { }>}; bucketInstanceId: number; - constructor(tileID: OverscaledTileID, symbolInstances: Array, bucketInstanceId: number) { + constructor(tileID: OverscaledTileID, symbolInstances: SymbolInstanceArray, bucketInstanceId: number) { this.tileID = tileID; this.indexedSymbolInstances = {}; this.bucketInstanceId = bucketInstanceId; - for (const symbolInstance of symbolInstances) { + for (let i = 0; i < symbolInstances.length; i++) { + const symbolInstance = symbolInstances.get(i); const key = symbolInstance.key; if (!this.indexedSymbolInstances[key]) { this.indexedSymbolInstances[key] = []; @@ -63,17 +67,17 @@ class TileLayerIndex { getScaledCoordinates(symbolInstance: SymbolInstance, childTileID: OverscaledTileID) { const zDifference = childTileID.canonical.z - this.tileID.canonical.z; const scale = roundingFactor / Math.pow(2, zDifference); - const anchor = symbolInstance.anchor; return { - x: Math.floor((childTileID.canonical.x * EXTENT + anchor.x) * scale), - y: Math.floor((childTileID.canonical.y * EXTENT + anchor.y) * scale) + x: Math.floor((childTileID.canonical.x * EXTENT + symbolInstance.anchorX) * scale), + y: Math.floor((childTileID.canonical.y * EXTENT + symbolInstance.anchorY) * scale) }; } - findMatches(symbolInstances: Array, newTileID: OverscaledTileID, zoomCrossTileIDs: {[crossTileID: number]: boolean}) { + findMatches(symbolInstances: SymbolInstanceArray, newTileID: OverscaledTileID, zoomCrossTileIDs: {[crossTileID: number]: boolean}) { const tolerance = this.tileID.canonical.z < newTileID.canonical.z ? 1 : Math.pow(2, this.tileID.canonical.z - newTileID.canonical.z); - for (const symbolInstance of symbolInstances) { + for (let i = 0; i < symbolInstances.length; i++) { + const symbolInstance = symbolInstances.get(i); if (symbolInstance.crossTileID) { // already has a match, skip continue; @@ -166,7 +170,8 @@ class CrossTileSymbolLayerIndex { } } - for (const symbolInstance of bucket.symbolInstances) { + for (let i = 0; i < bucket.symbolInstances.length; i++) { + const symbolInstance = bucket.symbolInstances.get(i); symbolInstance.crossTileID = 0; } @@ -193,7 +198,8 @@ class CrossTileSymbolLayerIndex { } } - for (const symbolInstance of bucket.symbolInstances) { + for (let i = 0; i < bucket.symbolInstances.length; i++) { + const symbolInstance = bucket.symbolInstances.get(i); if (!symbolInstance.crossTileID) { // symbol did not match any known symbol, assign a new id symbolInstance.crossTileID = crossTileIDs.generate(); diff --git a/src/symbol/placement.js b/src/symbol/placement.js index 9476434b97f..607f5316844 100644 --- a/src/symbol/placement.js +++ b/src/symbol/placement.js @@ -190,7 +190,12 @@ export class Placement { const collisionGroup = this.collisionGroups.get(bucket.sourceID); - for (const symbolInstance of bucket.symbolInstances) { + if (!bucket.collisionArrays && collisionBoxArray) { + bucket.deserializeCollisionBoxes(collisionBoxArray); + } + + for (let i = 0; i < bucket.symbolInstances.length; i++) { + const symbolInstance = bucket.symbolInstances.get(i); if (!seenCrossTileIDs[symbolInstance.crossTileID]) { if (holdingForFade) { // Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't @@ -210,22 +215,18 @@ export class Placement { let textFeatureIndex = 0; let iconFeatureIndex = 0; - if (!symbolInstance.collisionArrays) { - symbolInstance.collisionArrays = bucket.deserializeCollisionBoxes( - ((collisionBoxArray: any): CollisionBoxArray), - symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex); - } + const collisionArrays = bucket.collisionArrays[i]; - if (symbolInstance.collisionArrays.textFeatureIndex) { - textFeatureIndex = symbolInstance.collisionArrays.textFeatureIndex; + if (collisionArrays.textFeatureIndex) { + textFeatureIndex = collisionArrays.textFeatureIndex; } - if (symbolInstance.collisionArrays.textBox) { - placedGlyphBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.textBox, + if (collisionArrays.textBox) { + placedGlyphBoxes = this.collisionIndex.placeCollisionBox(collisionArrays.textBox, layout.get('text-allow-overlap'), textPixelRatio, posMatrix, collisionGroup.predicate); placeText = placedGlyphBoxes.box.length > 0; offscreen = offscreen && placedGlyphBoxes.offscreen; } - const textCircles = symbolInstance.collisionArrays.textCircles; + const textCircles = collisionArrays.textCircles; if (textCircles) { const placedSymbol = bucket.text.placedSymbolArray.get(symbolInstance.horizontalPlacedTextSymbolIndex); const fontSize = symbolSize.evaluateSizeForFeature(bucket.textSizeData, partiallyEvaluatedTextSize, placedSymbol); @@ -250,11 +251,11 @@ export class Placement { offscreen = offscreen && placedGlyphCircles.offscreen; } - if (symbolInstance.collisionArrays.iconFeatureIndex) { - iconFeatureIndex = symbolInstance.collisionArrays.iconFeatureIndex; + if (collisionArrays.iconFeatureIndex) { + iconFeatureIndex = collisionArrays.iconFeatureIndex; } - if (symbolInstance.collisionArrays.iconBox) { - placedIconBoxes = this.collisionIndex.placeCollisionBox(symbolInstance.collisionArrays.iconBox, + if (collisionArrays.iconBox) { + placedIconBoxes = this.collisionIndex.placeCollisionBox(collisionArrays.iconBox, layout.get('icon-allow-overlap'), textPixelRatio, posMatrix, collisionGroup.predicate); placeIcon = placedIconBoxes.box.length > 0; offscreen = offscreen && placedIconBoxes.offscreen; @@ -375,8 +376,12 @@ export class Placement { iconAllowOverlap && (textAllowOverlap || !bucket.hasTextData() || layout.get('text-optional')), true); + if (!bucket.collisionArrays && collisionBoxArray && (bucket.hasCollisionBoxData() || bucket.hasCollisionCircleData())) { + bucket.deserializeCollisionBoxes(collisionBoxArray); + } + for (let s = 0; s < bucket.symbolInstances.length; s++) { - const symbolInstance = bucket.symbolInstances[s]; + const symbolInstance = bucket.symbolInstances.get(s); const isDuplicate = seenCrossTileIDs[symbolInstance.crossTileID]; let opacityState = this.opacities[symbolInstance.crossTileID]; @@ -417,31 +422,27 @@ export class Placement { for (let i = 0; i < symbolInstance.numIconVertices / 4; i++) { bucket.icon.opacityVertexArray.emplaceBack(packedOpacity); } - const placedSymbol = bucket.icon.placedSymbolArray.get(s); - placedSymbol.hidden = (opacityState.icon.isHidden(): any); + bucket.icon.placedSymbolArray.get(s).hidden = + (opacityState.icon.isHidden(): any); } - if (!symbolInstance.collisionArrays) { - symbolInstance.collisionArrays = bucket.deserializeCollisionBoxes( - ((collisionBoxArray: any): CollisionBoxArray), - symbolInstance.textBoxStartIndex, symbolInstance.textBoxEndIndex, symbolInstance.iconBoxStartIndex, symbolInstance.iconBoxEndIndex); - } - - const collisionArrays = symbolInstance.collisionArrays; - if (collisionArrays) { - if (collisionArrays.textBox && bucket.hasCollisionBoxData()) { - updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.text.placed, false); - } + if (bucket.hasCollisionBoxData() || bucket.hasCollisionCircleData()) { + const collisionArrays = bucket.collisionArrays[s]; + if (collisionArrays) { + if (collisionArrays.textBox) { + updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.text.placed, false); + } - if (collisionArrays.iconBox && bucket.hasCollisionBoxData()) { - updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.icon.placed, false); - } + if (collisionArrays.iconBox) { + updateCollisionVertices(bucket.collisionBox.collisionVertexArray, opacityState.icon.placed, false); + } - const textCircles = collisionArrays.textCircles; - if (textCircles && bucket.hasCollisionCircleData()) { - for (let k = 0; k < textCircles.length; k += 5) { - const notUsed = isDuplicate || textCircles[k + 4] === 0; - updateCollisionVertices(bucket.collisionCircle.collisionVertexArray, opacityState.text.placed, notUsed); + const textCircles = collisionArrays.textCircles; + if (textCircles && bucket.hasCollisionCircleData()) { + for (let k = 0; k < textCircles.length; k += 5) { + const notUsed = isDuplicate || textCircles[k + 4] === 0; + updateCollisionVertices(bucket.collisionCircle.collisionVertexArray, opacityState.text.placed, notUsed); + } } } } diff --git a/src/symbol/symbol_layout.js b/src/symbol/symbol_layout.js index ec6ccb95969..8aef10f1228 100644 --- a/src/symbol/symbol_layout.js +++ b/src/symbol/symbol_layout.js @@ -62,7 +62,6 @@ export function performSymbolLayout(bucket: SymbolBucket, imagePositions: {[string]: ImagePosition}, showCollisionBoxes: boolean) { bucket.createArrays(); - bucket.symbolInstances = []; const tileSize = 512 * bucket.overscaling; bucket.tilePixelRatio = EXTENT / tileSize; @@ -206,11 +205,11 @@ function addFeature(bucket: SymbolBucket, return; } - bucket.symbolInstances.push(addSymbol(bucket, anchor, line, shapedTextOrientations, shapedIcon, bucket.layers[0], + addSymbol(bucket, anchor, line, shapedTextOrientations, shapedIcon, bucket.layers[0], bucket.collisionBoxArray, feature.index, feature.sourceLayerIndex, bucket.index, textBoxScale, textPadding, textAlongLine, textOffset, iconBoxScale, iconPadding, iconAlongLine, iconOffset, - feature, glyphPositionMap, sizes)); + feature, glyphPositionMap, sizes); }; if (symbolPlacement === 'line') { @@ -426,23 +425,21 @@ function addSymbol(bucket: SymbolBucket, "Too many glyphs being rendered in a tile. See https://github.com/mapbox/mapbox-gl-js/issues/2907" ); - return { + bucket.symbolInstances.emplaceBack( + anchor.x, + anchor.y, + placedTextSymbolIndices.length > 0 ? placedTextSymbolIndices[0] : -1, + placedTextSymbolIndices.length > 1 ? placedTextSymbolIndices[1] : -1, key, textBoxStartIndex, textBoxEndIndex, iconBoxStartIndex, iconBoxEndIndex, - anchor, featureIndex, numGlyphVertices, numVerticalGlyphVertices, numIconVertices, - horizontalPlacedTextSymbolIndex: - placedTextSymbolIndices.length > 0 ? placedTextSymbolIndices[0] : -1, - verticalPlacedTextSymbolIndex: - placedTextSymbolIndices.length > 1 ? placedTextSymbolIndices[1] : -1, - crossTileID: 0 - }; + 0); } function anchorIsTooClose(bucket: any, text: string, repeatDistance: number, anchor: Point) { diff --git a/test/unit/symbol/cross_tile_symbol_index.js b/test/unit/symbol/cross_tile_symbol_index.js index 0e42dabb76b..659a02a6db9 100644 --- a/test/unit/symbol/cross_tile_symbol_index.js +++ b/test/unit/symbol/cross_tile_symbol_index.js @@ -1,5 +1,4 @@ import { test } from 'mapbox-gl-js-test'; -import Anchor from '../../../src/symbol/anchor'; import CrossTileSymbolIndex from '../../../src/symbol/cross_tile_symbol_index'; import { OverscaledTileID } from '../../../src/source/tile_id'; @@ -9,14 +8,18 @@ const styleLayer = { function makeSymbolInstance(x, y, key) { return { - anchor: new Anchor(x, y), + anchorX: x, + anchorY: y, key: key }; } function makeTile(tileID, symbolInstances) { const bucket = { - symbolInstances: symbolInstances, + symbolInstances: { + get: function(i) { return symbolInstances[i]; }, + length: symbolInstances.length + }, layerIds: ['test'] }; return {