Skip to content

Commit 2524080

Browse files
ibesoragithub-actions[bot]
authored andcommitted
More UBO optimizations
GitOrigin-RevId: f1967b624d2c8446b3851d41938b16d9cc75cb51
1 parent b78baa1 commit 2524080

File tree

3 files changed

+154
-139
lines changed

3 files changed

+154
-139
lines changed

src/data/bucket/symbol_properties_ubo.ts

Lines changed: 44 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ export class SymbolPropertiesUBO {
5555
static readonly HEADER_DWORDS = 12; // 3 uvec4s (never changes)
5656
static readonly HEADER_BYTES = 48; // HEADER_DWORDS * 4
5757

58+
// Flat evaluation buffer layout — per-property start offsets and slot counts in a Float32Array(EVAL_FLAT_TOTAL).
59+
// fill_color[0..3], halo_color[4..7], opacity[8..9], halo_width[10..11],
60+
// halo_blur[12..13], emissive_strength[14..15], occlusion_opacity[16..17],
61+
// z_offset[18..19], translate[20..23].
62+
// Colors and translate always use 4 slots; scalars always use 2 (second = 0 for non-zoom).
63+
static readonly EVAL_FLAT_OFFSETS: readonly number[] = [0, 4, 8, 10, 12, 14, 16, 18, 20];
64+
static readonly EVAL_FLAT_SIZES: readonly number[] = [4, 4, 2, 2, 2, 2, 2, 2, 4];
65+
static readonly EVAL_FLAT_TOTAL = 24;
66+
67+
// Until we support layout properties in UBOs, blockIndicesData is just
68+
// an identity mapping since deduplication happens at the vertex attribute level
69+
// (duplicate features get the same index written into the vertex buffer, so no
70+
// indirection is needed). When we move layout properties to UBO we'll use this
71+
// array to deduplicate paint properties and add a new array for layout properties
72+
// deduplication.
73+
// Until we need that, we just create the identity-mapping once and create a copy
74+
// of it
75+
private static _blockIndicesTemplate: Uint32Array | null = null;
76+
5877
propsDwords: number; // dword count for u_properties
5978
totalBytes: number; // byte size of each of properties / block-indices buffers
6079
headerData: Uint32Array; // 12 uint32s (3 uvec4s)
@@ -77,14 +96,13 @@ export class SymbolPropertiesUBO {
7796
this.headerData = new Uint32Array(SymbolPropertiesUBO.HEADER_DWORDS);
7897
this.propertiesData = new Float32Array(this.propsDwords);
7998

80-
// Block indices are write-once identity mapping: blockIndices[i] = i.
81-
// Deduplication works by assigning duplicate features the same a_feature_index
82-
// (localFeatureIndex), so the shader maps each index to itself — no indirection needed.
83-
// This buffer never changes after construction.
84-
this.blockIndicesData = new Uint32Array(this.propsDwords);
85-
for (let i = 0; i < this.propsDwords; i++) {
86-
this.blockIndicesData[i] = i;
99+
if (!SymbolPropertiesUBO._blockIndicesTemplate) {
100+
SymbolPropertiesUBO._blockIndicesTemplate = new Uint32Array(this.propsDwords);
101+
for (let i = 0; i < this.propsDwords; i++) {
102+
SymbolPropertiesUBO._blockIndicesTemplate[i] = i;
103+
}
87104
}
105+
this.blockIndicesData = new Uint32Array(SymbolPropertiesUBO._blockIndicesTemplate);
88106

89107
if (context) {
90108
this._initBuffers(context);
@@ -141,12 +159,13 @@ export class SymbolPropertiesUBO {
141159
}
142160

143161
/**
144-
* Write all data-driven properties for one feature.
162+
* Write all data-driven properties for one feature from a flat evaluation buffer.
145163
*
146164
* The feature's block starts at dword offset: featureIndex * dataDrivenBlockSizeDwords.
147165
* (No constant block — constant properties are passed as u_spp_* uniforms at draw time.)
166+
* `flat` is a Float32Array(EVAL_FLAT_TOTAL) produced by evaluateAllProperties().
148167
*/
149-
writeDataDrivenBlock(values: Array<PropertyValue | null>, featureIndex: number, header: SymbolPropertyHeader): void {
168+
writeDataDrivenBlock(flat: Float32Array, featureIndex: number, header: SymbolPropertyHeader): void {
150169
const dataDrivenBlockSizeDwords = header.dataDrivenBlockSizeVec4 * 4;
151170
if (dataDrivenBlockSizeDwords === 0) return;
152171
const base = featureIndex * dataDrivenBlockSizeDwords;
@@ -155,62 +174,42 @@ export class SymbolPropertiesUBO {
155174
}
156175
for (let i = 0; i < 9; i++) {
157176
if ((header.dataDrivenMask & (1 << i)) === 0) continue;
158-
if (values[i] === null || values[i] === undefined) continue;
159-
this._writeProperty(base + header.offsets[i], i, values[i], header.zoomDependentMask);
177+
this._copyFromFlat(base + header.offsets[i], i, flat, SymbolPropertiesUBO.EVAL_FLAT_OFFSETS[i], header.zoomDependentMask);
160178
}
161179
}
162180

163181
/**
164182
* Maximum number of features that fit in one UBO batch given a header.
165183
* Returns Infinity when dataDrivenBlockSizeVec4 is 0 (all properties constant).
166184
*/
167-
static getMaxFeatureCount(header: SymbolPropertyHeader, propsDwords: number = 4096 - SymbolPropertiesUBO.HEADER_DWORDS): number {
185+
static getMaxFeatureCount(header: SymbolPropertyHeader, propsDwords: number = 4096): number {
168186
const dataDrivenBlockSizeDwords = header.dataDrivenBlockSizeVec4 * 4;
169187
if (dataDrivenBlockSizeDwords === 0) return Infinity;
170188
return Math.floor(propsDwords / dataDrivenBlockSizeDwords);
171189
}
172190

173191
/**
174-
* Write a single property value at the given dword offset in propertiesData.
192+
* Copy one property's values from the flat evaluation buffer into propertiesData.
175193
*
176-
* Colors (propIdx < 2) always occupy 4 dwords — non-premultiplied, packed:
177-
* non-zoom → [packed0, packed1, 0, 0]
178-
* zoom-dep → [packMin[0], packMin[1], packMax[0], packMax[1]]
179-
* Floats occupy 1 dword (non-zoom) or 2 dwords (zoom-dep, [min, max]).
194+
* Colors (propIdx < 2) and zoom-dep translate always copy 4 dwords.
195+
* Non-zoom translate and zoom-dep scalars copy 2 dwords. Non-zoom scalars copy 1 dword.
180196
*/
181-
private _writeProperty(dwordOffset: number, propIdx: number, value: PropertyValue, zoomDependentMask: number): void {
197+
private _copyFromFlat(dwordOffset: number, propIdx: number, flat: Float32Array, flatOffset: number, zoomDependentMask: number): void {
182198
const pd = this.propertiesData;
183-
// Property order is fixed by the GL Native contract: 0=fill_color, 1=halo_color (colors),
184-
// 2-7=floats, 8=translate (vec2).
185-
// This must stay in sync with _getPropDefs() in symbol_property_binder_ubo.ts.
186199
const isColor = propIdx < 2;
187-
const isVec2 = propIdx === 8; // translate: [tx, ty] non-zoom or [tx_min, ty_min, tx_max, ty_max] zoom-dep
200+
const isVec2 = propIdx === 8;
188201
const isZoomDep = (zoomDependentMask & (1 << propIdx)) !== 0;
189202

190-
if (isColor) {
191-
const v = value as [number, number, number, number];
192-
pd[dwordOffset] = v[0];
193-
pd[dwordOffset + 1] = v[1];
194-
pd[dwordOffset + 2] = v[2];
195-
pd[dwordOffset + 3] = v[3];
196-
} else if (isVec2 && isZoomDep) {
197-
// zoom-dep translate: [tx_min, ty_min, tx_max, ty_max], vec4-aligned
198-
const v = value as [number, number, number, number];
199-
pd[dwordOffset] = v[0]; // tx_min
200-
pd[dwordOffset + 1] = v[1]; // ty_min
201-
pd[dwordOffset + 2] = v[2]; // tx_max
202-
pd[dwordOffset + 3] = v[3]; // ty_max
203-
} else if (isVec2) {
204-
// non-zoom translate: [tx, ty], 2-aligned (both within same vec4)
205-
const v = value as [number, number];
206-
pd[dwordOffset] = v[0]; // tx
207-
pd[dwordOffset + 1] = v[1]; // ty
208-
} else if (isZoomDep) {
209-
const v = value as [number, number];
210-
pd[dwordOffset] = v[0]; // min
211-
pd[dwordOffset + 1] = v[1]; // max
203+
if (isColor || (isVec2 && isZoomDep)) {
204+
pd[dwordOffset] = flat[flatOffset];
205+
pd[dwordOffset + 1] = flat[flatOffset + 1];
206+
pd[dwordOffset + 2] = flat[flatOffset + 2];
207+
pd[dwordOffset + 3] = flat[flatOffset + 3];
208+
} else if (isVec2 || isZoomDep) {
209+
pd[dwordOffset] = flat[flatOffset];
210+
pd[dwordOffset + 1] = flat[flatOffset + 1];
212211
} else {
213-
pd[dwordOffset] = value as number;
212+
pd[dwordOffset] = flat[flatOffset];
214213
}
215214
}
216215

0 commit comments

Comments
 (0)