@@ -9,7 +9,10 @@ import type Context from '../../gl/context';
99 * the shader's SymbolPropertyHeader struct). These constants name its dword slots:
1010 *
1111 * [HEADER_DATA_DRIVEN_MASK] bitmask: 1 = property goes in the per-feature data-driven block
12- * [HEADER_ZOOM_DEPENDENT_MASK] bitmask: 1 = property uses zoom interpolation (composite kind)
12+ * [HEADER_ZOOM_DEPENDENT_MASK] low 16 bits: 1 = property uses zoom interpolation (composite kind).
13+ * high 16 bits (>> HEADER_APPEARANCE_ZOOM_STOPS_SHIFT): 1 = appearances
14+ * override this property with differing zoom stops, so its zoom range
15+ * [zm, zM] is stored per feature in the data-driven block
1316 * [HEADER_BLOCK_SIZE_VEC4] size of the data-driven block in vec4 units (0 when no DD props)
1417 * [HEADER_OFFSETS + i] dword offset of property i within the data-driven block
1518 * (only meaningful when property i's data-driven bit is set)
@@ -22,6 +25,9 @@ export const HEADER_ZOOM_DEPENDENT_MASK = 1;
2225export const HEADER_BLOCK_SIZE_VEC4 = 2 ;
2326export const HEADER_OFFSETS = 3 ;
2427
28+ // Bit shift for the appearance-zoom-stops flags packed into the high half of the HEADER_ZOOM_DEPENDENT_MASK dword.
29+ export const HEADER_APPEARANCE_ZOOM_STOPS_SHIFT = 16 ;
30+
2531/**
2632 * A packed value for writing into the UBO's data-driven properties array.
2733 *
@@ -56,10 +62,13 @@ export class SymbolPropertiesUBO {
5662 // Flat evaluation buffer layout — per-property start offset in a Float32Array(EVAL_FLAT_TOTAL).
5763 // fill_color[0..3], halo_color[4..7], opacity[8..9], halo_width[10..11],
5864 // halo_blur[12..13], emissive_strength[14..15], occlusion_opacity[16..17],
59- // z_offset[18..19], translate[20..23].
65+ // z_offset[18..19], translate[20..23]
66+ // Each property also gets a 2-slot [zm, zM] zoom range starting at EVAL_FLAT_ZOOM_OFFSETS[i]
67+ // ([24,25] … [40,41]); only written for appearance-zoom-stops properties.
6068 // Colors and translate always use 4 slots; scalars always use 2 (second = 0 for non-zoom).
6169 static readonly EVAL_FLAT_OFFSETS : readonly number [ ] = [ 0 , 4 , 8 , 10 , 12 , 14 , 16 , 18 , 20 ] ;
62- static readonly EVAL_FLAT_TOTAL = 24 ;
70+ static readonly EVAL_FLAT_ZOOM_OFFSETS : readonly number [ ] = [ 24 , 26 , 28 , 30 , 32 , 34 , 36 , 38 , 40 ] ;
71+ static readonly EVAL_FLAT_TOTAL = 42 ;
6372
6473 // The block-indices buffer is a pure identity mapping (blockIndices[i] = i): dedup currently
6574 // happens at the vertex-attribute level (duplicate features get the same index written into the
@@ -167,9 +176,10 @@ export class SymbolPropertiesUBO {
167176 }
168177 const dataDrivenMask = h [ HEADER_DATA_DRIVEN_MASK ] ;
169178 const zoomDependentMask = h [ HEADER_ZOOM_DEPENDENT_MASK ] ;
179+ const appearanceZoomStopsMask = zoomDependentMask >>> HEADER_APPEARANCE_ZOOM_STOPS_SHIFT ;
170180 for ( let i = 0 ; i < 9 ; i ++ ) {
171181 if ( ( dataDrivenMask & ( 1 << i ) ) === 0 ) continue ;
172- this . _copyFromFlat ( base + h [ HEADER_OFFSETS + i ] , i , flat , SymbolPropertiesUBO . EVAL_FLAT_OFFSETS [ i ] , zoomDependentMask ) ;
182+ this . _copyFromFlat ( base + h [ HEADER_OFFSETS + i ] , i , flat , SymbolPropertiesUBO . EVAL_FLAT_OFFSETS [ i ] , zoomDependentMask , appearanceZoomStopsMask ) ;
173183 }
174184 // Track dword range touched so upload() can do a partial bufferSubData.
175185 if ( this . _propsDirtyMin === - 1 || base < this . _propsDirtyMin ) this . _propsDirtyMin = base ;
@@ -201,14 +211,25 @@ export class SymbolPropertiesUBO {
201211 *
202212 * Colors (propIdx < 2) and zoom-dep translate always copy 4 dwords.
203213 * Non-zoom translate and zoom-dep scalars copy 2 dwords. Non-zoom scalars copy 1 dword.
214+ *
215+ * Appearance-zoom-stops properties additionally carry a [zm, zM] pair, written after
216+ * the value data: at relative dwords +4/+5 for colors/translate (the next vec4) and +2/+3 for
217+ * scalars (the third/fourth dword of the value's vec4).
204218 */
205- private _copyFromFlat ( dwordOffset : number , propIdx : number , flat : Float32Array , flatOffset : number , zoomDependentMask : number ) : void {
219+ private _copyFromFlat ( dwordOffset : number , propIdx : number , flat : Float32Array , flatOffset : number , zoomDependentMask : number , appearanceZoomStopsMask : number ) : void {
206220 const isColor = propIdx < 2 ;
207221 const isVec2 = propIdx === 8 ;
208222 const isZoomDep = ( zoomDependentMask & ( 1 << propIdx ) ) !== 0 ;
209223 const count = isColor || ( isVec2 && isZoomDep ) ? 4 : ( isVec2 || isZoomDep ) ? 2 : 1 ;
210224
211- for ( let k = 0 ; k < count ; k ++ ) this . propertiesData [ dwordOffset + k ] = flat [ flatOffset + k ] ;
225+ this . propertiesData . set ( flat . subarray ( flatOffset , flatOffset + count ) , dwordOffset ) ;
226+
227+ if ( ( appearanceZoomStopsMask & ( 1 << propIdx ) ) !== 0 ) {
228+ const zoomRel = isColor || isVec2 ? 4 : 2 ;
229+ const zStart = SymbolPropertiesUBO . EVAL_FLAT_ZOOM_OFFSETS [ propIdx ] ;
230+ this . propertiesData [ dwordOffset + zoomRel ] = flat [ zStart ] ;
231+ this . propertiesData [ dwordOffset + zoomRel + 1 ] = flat [ zStart + 1 ] ;
232+ }
212233 }
213234
214235 /**
0 commit comments