@deck.gl-community/infovis-layers 9.3.5 → 9.3.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +2998 -114
- package/dist/index.cjs.map +4 -4
- package/dist/index.d.ts +11 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/layers/animation-layer/animation-layer.d.ts +30 -0
- package/dist/layers/animation-layer/animation-layer.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation-layer.js +53 -0
- package/dist/layers/animation-layer/animation-layer.js.map +1 -0
- package/dist/layers/animation-layer/animation.d.ts +86 -0
- package/dist/layers/animation-layer/animation.d.ts.map +1 -0
- package/dist/layers/animation-layer/animation.js +214 -0
- package/dist/layers/animation-layer/animation.js.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js +41 -0
- package/dist/layers/block-layer/block-layer-fragment.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts +27 -0
- package/dist/layers/block-layer/block-layer-uniforms.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-uniforms.js +26 -0
- package/dist/layers/block-layer/block-layer-uniforms.js.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts +3 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js +67 -0
- package/dist/layers/block-layer/block-layer-vertex.glsl.js.map +1 -0
- package/dist/layers/block-layer/block-layer.d.ts +71 -0
- package/dist/layers/block-layer/block-layer.d.ts.map +1 -0
- package/dist/layers/block-layer/block-layer.js +108 -0
- package/dist/layers/block-layer/block-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts +148 -0
- package/dist/layers/fast-text-layer/fast-text-layer.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js +862 -0
- package/dist/layers/fast-text-layer/fast-text-layer.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts +2 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js +300 -0
- package/dist/layers/fast-text-layer/fast-text-layer.test.js.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts +179 -0
- package/dist/layers/fast-text-layer/fast-text-layout.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js +665 -0
- package/dist/layers/fast-text-layer/fast-text-layout.js.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts +98 -0
- package/dist/layers/fast-text-layer/font-atlas.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/font-atlas.js +337 -0
- package/dist/layers/fast-text-layer/font-atlas.js.map +1 -0
- package/dist/layers/fast-text-layer/index.d.ts +4 -0
- package/dist/layers/fast-text-layer/index.d.ts.map +1 -0
- package/dist/layers/fast-text-layer/index.js +4 -0
- package/dist/layers/fast-text-layer/index.js.map +1 -0
- package/dist/layers/time-delta-layer.d.ts +22 -60
- package/dist/layers/time-delta-layer.d.ts.map +1 -1
- package/dist/layers/time-delta-layer.js +23 -45
- package/dist/layers/time-delta-layer.js.map +1 -1
- package/dist/synchronized-views/synchronized-views.js +1 -1
- package/dist/synchronized-views/synchronized-views.js.map +1 -1
- package/dist/utils/format-utils.d.ts +27 -3
- package/dist/utils/format-utils.d.ts.map +1 -1
- package/dist/utils/format-utils.js +72 -40
- package/dist/utils/format-utils.js.map +1 -1
- package/dist/utils/utf8-string-view.d.ts +53 -0
- package/dist/utils/utf8-string-view.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.js +169 -0
- package/dist/utils/utf8-string-view.js.map +1 -0
- package/dist/utils/utf8-string-view.test.d.ts +2 -0
- package/dist/utils/utf8-string-view.test.d.ts.map +1 -0
- package/dist/utils/utf8-string-view.test.js +88 -0
- package/dist/utils/utf8-string-view.test.js.map +1 -0
- package/dist/views/bounds-utils.d.ts +23 -0
- package/dist/views/bounds-utils.d.ts.map +1 -0
- package/dist/views/bounds-utils.js +65 -0
- package/dist/views/bounds-utils.js.map +1 -0
- package/dist/views/layer-bounds-filter.d.ts +48 -0
- package/dist/views/layer-bounds-filter.d.ts.map +1 -0
- package/dist/views/layer-bounds-filter.js +99 -0
- package/dist/views/layer-bounds-filter.js.map +1 -0
- package/dist/views/orthographic-utils.d.ts +30 -2
- package/dist/views/orthographic-utils.d.ts.map +1 -1
- package/dist/views/orthographic-utils.js +33 -14
- package/dist/views/orthographic-utils.js.map +1 -1
- package/dist/views/view-layout/build-views-from-view-layout.d.ts +37 -0
- package/dist/views/view-layout/build-views-from-view-layout.d.ts.map +1 -0
- package/dist/views/view-layout/build-views-from-view-layout.js +413 -0
- package/dist/views/view-layout/build-views-from-view-layout.js.map +1 -0
- package/dist/views/view-layout/index.d.ts +3 -0
- package/dist/views/view-layout/index.d.ts.map +1 -0
- package/dist/views/view-layout/index.js +3 -0
- package/dist/views/view-layout/index.js.map +1 -0
- package/dist/views/view-layout/view-layout-item.d.ts +74 -0
- package/dist/views/view-layout/view-layout-item.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout-item.js +48 -0
- package/dist/views/view-layout/view-layout-item.js.map +1 -0
- package/dist/views/view-layout/view-layout.test.d.ts +2 -0
- package/dist/views/view-layout/view-layout.test.d.ts.map +1 -0
- package/dist/views/view-layout/view-layout.test.js +112 -0
- package/dist/views/view-layout/view-layout.test.js.map +1 -0
- package/dist/views/viewport-bounds-utils.d.ts +55 -0
- package/dist/views/viewport-bounds-utils.d.ts.map +1 -0
- package/dist/views/viewport-bounds-utils.js +70 -0
- package/dist/views/viewport-bounds-utils.js.map +1 -0
- package/package.json +11 -4
- package/src/index.ts +91 -2
- package/src/layers/animation-layer/animation-layer.ts +92 -0
- package/src/layers/animation-layer/animation.ts +328 -0
- package/src/layers/block-layer/block-layer-fragment.glsl.ts +41 -0
- package/src/layers/block-layer/block-layer-uniforms.ts +43 -0
- package/src/layers/block-layer/block-layer-vertex.glsl.ts +67 -0
- package/src/layers/block-layer/block-layer.ts +199 -0
- package/src/layers/fast-text-layer/fast-text-layer.test.ts +359 -0
- package/src/layers/fast-text-layer/fast-text-layer.ts +1208 -0
- package/src/layers/fast-text-layer/fast-text-layout.ts +1124 -0
- package/src/layers/fast-text-layer/font-atlas.ts +574 -0
- package/src/layers/fast-text-layer/index.ts +37 -0
- package/src/layers/time-delta-layer.ts +47 -62
- package/src/synchronized-views/synchronized-views.ts +1 -1
- package/src/utils/format-utils.ts +90 -40
- package/src/utils/utf8-string-view.test.ts +119 -0
- package/src/utils/utf8-string-view.ts +273 -0
- package/src/views/bounds-utils.ts +88 -0
- package/src/views/layer-bounds-filter.ts +160 -0
- package/src/views/orthographic-utils.ts +65 -18
- package/src/views/view-layout/build-views-from-view-layout.ts +547 -0
- package/src/views/view-layout/index.ts +18 -0
- package/src/views/view-layout/view-layout-item.ts +125 -0
- package/src/views/view-layout/view-layout.test.ts +129 -0
- package/src/views/viewport-bounds-utils.ts +104 -0
|
@@ -0,0 +1,1208 @@
|
|
|
1
|
+
// Minimal WebGL attribute text layer ported from WIP luma.gl text utilities.
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
|
|
4
|
+
import {color, createIterable, Layer, project32, UNIT} from '@deck.gl/core';
|
|
5
|
+
import {Buffer, Texture} from '@luma.gl/core';
|
|
6
|
+
import {Geometry, Model} from '@luma.gl/engine';
|
|
7
|
+
import {Log} from '@probe.gl/log';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
buildFastTextGlyphData,
|
|
11
|
+
buildFastTextUtf8ColumnSource,
|
|
12
|
+
collectFastTextCharacterSet,
|
|
13
|
+
FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE,
|
|
14
|
+
updateFastTextDynamicGlyphAttributes
|
|
15
|
+
} from './fast-text-layout';
|
|
16
|
+
import {DEFAULT_FAST_TEXT_FONT_SETTINGS, FastTextFontAtlasManager} from './font-atlas';
|
|
17
|
+
|
|
18
|
+
import type {
|
|
19
|
+
FastTextAlignmentBaseline,
|
|
20
|
+
FastTextAnchor,
|
|
21
|
+
FastTextClipRect,
|
|
22
|
+
FastTextContentAlign,
|
|
23
|
+
FastTextDynamicGlyphUpdateStats,
|
|
24
|
+
FastTextGlyphBuildStats,
|
|
25
|
+
FastTextGlyphData,
|
|
26
|
+
FastTextUtf8Column,
|
|
27
|
+
FastTextUtf8ColumnSource,
|
|
28
|
+
FastTextUtf8ViewAccessor
|
|
29
|
+
} from './fast-text-layout';
|
|
30
|
+
import type {FastTextCharacterMapping, FastTextFontAtlas, FastTextFontSettings} from './font-atlas';
|
|
31
|
+
import type {
|
|
32
|
+
Accessor,
|
|
33
|
+
Color,
|
|
34
|
+
DefaultProps,
|
|
35
|
+
LayerContext,
|
|
36
|
+
LayerDataSource,
|
|
37
|
+
LayerProps,
|
|
38
|
+
Position,
|
|
39
|
+
Unit,
|
|
40
|
+
UpdateParameters
|
|
41
|
+
} from '@deck.gl/core';
|
|
42
|
+
import type {ShaderModule} from '@luma.gl/shadertools';
|
|
43
|
+
|
|
44
|
+
/** Fast text layer props that intentionally cover dense Tracevis-style span labels. */
|
|
45
|
+
export type _FastTextLayerProps<DataT> = {
|
|
46
|
+
/** Source label rows. */
|
|
47
|
+
data: LayerDataSource<DataT>;
|
|
48
|
+
/** If true, labels face the camera in screen space. */
|
|
49
|
+
billboard?: boolean;
|
|
50
|
+
/** Text size multiplier. */
|
|
51
|
+
sizeScale?: number;
|
|
52
|
+
/** Units for `size`, matching deck.gl size units. */
|
|
53
|
+
sizeUnits?: Unit;
|
|
54
|
+
/** Minimum rendered text size in screen pixels. */
|
|
55
|
+
sizeMinPixels?: number;
|
|
56
|
+
/** Maximum rendered text size in screen pixels. */
|
|
57
|
+
sizeMaxPixels?: number;
|
|
58
|
+
/** Fragment alpha cutoff below which a glyph pixel is discarded. */
|
|
59
|
+
alphaCutoff?: number;
|
|
60
|
+
/** Character set to place in the generated atlas, or `auto` to scan `getText`. */
|
|
61
|
+
characterSet?: FastTextFontSettings['characterSet'] | 'auto';
|
|
62
|
+
/** CSS font family used for generated atlases. */
|
|
63
|
+
fontFamily?: FastTextFontSettings['fontFamily'];
|
|
64
|
+
/** CSS font weight used for generated atlases. */
|
|
65
|
+
fontWeight?: FastTextFontSettings['fontWeight'];
|
|
66
|
+
/** Unitless line-height multiplier. */
|
|
67
|
+
lineHeight?: number;
|
|
68
|
+
/** Layer-wide text size. */
|
|
69
|
+
size?: number;
|
|
70
|
+
/** Layer-wide pixel offset applied after glyph layout. */
|
|
71
|
+
pixelOffset?: readonly [number, number];
|
|
72
|
+
/** Compatibility alias for callers that still inspect TextLayer-style pixel offset props. */
|
|
73
|
+
getPixelOffset?: readonly [number, number];
|
|
74
|
+
/** Additional atlas rasterization settings. */
|
|
75
|
+
fontSettings?: FastTextFontSettings;
|
|
76
|
+
/** Optional prebuilt mapping, supplied together with `fontAtlas`. */
|
|
77
|
+
characterMapping?: FastTextCharacterMapping | null;
|
|
78
|
+
/** Optional prebuilt atlas, supplied together with `characterMapping`. */
|
|
79
|
+
fontAtlas?: FastTextFontAtlas | null;
|
|
80
|
+
/** Label text accessor. */
|
|
81
|
+
getText?: Accessor<DataT, string>;
|
|
82
|
+
/** Optional UTF-8 text column used instead of `getText`. */
|
|
83
|
+
textUtf8Column?: FastTextUtf8Column | null;
|
|
84
|
+
/** Row accessor into `textUtf8Column`. Defaults to the source row index. */
|
|
85
|
+
getTextUtf8Row?: Accessor<DataT, number | null>;
|
|
86
|
+
/** Optional accessor that fills a reused UTF-8 byte view instead of calling `getText`. */
|
|
87
|
+
getTextUtf8?: FastTextUtf8ViewAccessor<DataT> | null;
|
|
88
|
+
/** Whether labels are expected to contain one line with no newline handling. */
|
|
89
|
+
singleLine?: boolean;
|
|
90
|
+
/** Label anchor position accessor. */
|
|
91
|
+
getPosition?: Accessor<DataT, Position>;
|
|
92
|
+
/** Label color accessor. */
|
|
93
|
+
getColor?: Accessor<DataT, Color>;
|
|
94
|
+
/** Layer-wide horizontal alignment. */
|
|
95
|
+
textAnchor?: FastTextAnchor;
|
|
96
|
+
/** Layer-wide vertical alignment. */
|
|
97
|
+
alignmentBaseline?: FastTextAlignmentBaseline;
|
|
98
|
+
/** Per-row content clip rectangle accessor. */
|
|
99
|
+
getClipRect?: Accessor<DataT, FastTextClipRect>;
|
|
100
|
+
/** Compatibility alias for callers that still inspect TextLayer-style content boxes. */
|
|
101
|
+
getContentBox?: Accessor<DataT, FastTextClipRect>;
|
|
102
|
+
/** Minimum visible content box dimensions in screen pixels. */
|
|
103
|
+
contentCutoffPixels?: readonly [width: number, height: number];
|
|
104
|
+
/** Horizontal alignment mode inside the visible content box. */
|
|
105
|
+
contentAlignHorizontal?: FastTextContentAlign;
|
|
106
|
+
/** Vertical alignment mode inside the visible content box. */
|
|
107
|
+
contentAlignVertical?: FastTextContentAlign;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/** Props accepted by {@link FastTextLayer}. */
|
|
111
|
+
export type FastTextLayerProps<DataT = unknown> = _FastTextLayerProps<DataT> & LayerProps;
|
|
112
|
+
|
|
113
|
+
const defaultProps: DefaultProps<FastTextLayerProps> = {
|
|
114
|
+
billboard: true,
|
|
115
|
+
sizeScale: {type: 'number', value: 1, min: 0},
|
|
116
|
+
sizeUnits: 'pixels',
|
|
117
|
+
sizeMinPixels: {type: 'number', value: 0, min: 0},
|
|
118
|
+
sizeMaxPixels: {type: 'number', value: Number.MAX_SAFE_INTEGER, min: 0},
|
|
119
|
+
alphaCutoff: {type: 'number', value: 0.001, min: 0, max: 1},
|
|
120
|
+
characterSet: {type: 'object', value: DEFAULT_FAST_TEXT_FONT_SETTINGS.characterSet},
|
|
121
|
+
fontFamily: DEFAULT_FAST_TEXT_FONT_SETTINGS.fontFamily,
|
|
122
|
+
fontWeight: DEFAULT_FAST_TEXT_FONT_SETTINGS.fontWeight,
|
|
123
|
+
lineHeight: {type: 'number', value: 1, min: 0},
|
|
124
|
+
size: {type: 'number', value: 32, min: 0},
|
|
125
|
+
pixelOffset: {type: 'array', value: [0, 0]},
|
|
126
|
+
getPixelOffset: {type: 'array', value: [0, 0]},
|
|
127
|
+
fontSettings: {type: 'object', value: {}, compare: 1},
|
|
128
|
+
characterMapping: {type: 'object', value: null, optional: true},
|
|
129
|
+
fontAtlas: {type: 'object', value: null, optional: true},
|
|
130
|
+
getText: {type: 'accessor', value: (datum: any) => datum.text},
|
|
131
|
+
textUtf8Column: {type: 'object', value: null, optional: true},
|
|
132
|
+
getTextUtf8Row: {
|
|
133
|
+
type: 'accessor',
|
|
134
|
+
value: (_datum: any, objectInfo: {index: number}) => objectInfo.index
|
|
135
|
+
},
|
|
136
|
+
getTextUtf8: {type: 'accessor', value: null},
|
|
137
|
+
singleLine: true,
|
|
138
|
+
getPosition: {type: 'accessor', value: (datum: any) => datum.position},
|
|
139
|
+
getColor: {type: 'accessor', value: [0, 0, 0, 255]},
|
|
140
|
+
textAnchor: 'middle',
|
|
141
|
+
alignmentBaseline: 'center',
|
|
142
|
+
getClipRect: {type: 'accessor', value: [0, 0, -1, -1]},
|
|
143
|
+
getContentBox: {type: 'accessor', value: [0, 0, -1, -1]},
|
|
144
|
+
contentCutoffPixels: {type: 'array', value: [0, 0]},
|
|
145
|
+
contentAlignHorizontal: 'none',
|
|
146
|
+
contentAlignVertical: 'none'
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
/** Lightweight bitmap text layer backed by one typed-array glyph instance stream. */
|
|
150
|
+
export class FastTextLayer<DataT = any, ExtraPropsT extends {} = {}> extends Layer<
|
|
151
|
+
ExtraPropsT & Required<_FastTextLayerProps<DataT>>
|
|
152
|
+
> {
|
|
153
|
+
static defaultProps = defaultProps;
|
|
154
|
+
static layerName = 'FastTextLayer';
|
|
155
|
+
|
|
156
|
+
declare state: FastTextLayerState;
|
|
157
|
+
|
|
158
|
+
/** Return the shader set used by the direct glyph instance model. */
|
|
159
|
+
override getShaders() {
|
|
160
|
+
return super.getShaders({
|
|
161
|
+
vs: FAST_TEXT_VS,
|
|
162
|
+
fs: FAST_TEXT_FS,
|
|
163
|
+
modules: [project32, color, fastTextUniforms]
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Initialize the model and atlas manager. */
|
|
168
|
+
override initializeState(): void {
|
|
169
|
+
this.state = {
|
|
170
|
+
fontAtlasManager: new FastTextFontAtlasManager()
|
|
171
|
+
};
|
|
172
|
+
this.state.model = this.getModel();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Rebuild text layout or update dynamic glyph attributes when inputs change. */
|
|
176
|
+
override updateState(params: UpdateParameters<this>): void {
|
|
177
|
+
super.updateState(params);
|
|
178
|
+
|
|
179
|
+
if (params.changeFlags.extensionsChanged) {
|
|
180
|
+
this.state.model?.destroy();
|
|
181
|
+
this.state.model = this.getModel();
|
|
182
|
+
if (this.state.buffers) {
|
|
183
|
+
this.state.model.setAttributes(this.state.buffers);
|
|
184
|
+
this.state.model.setInstanceCount(this.state.glyphData?.length ?? 0);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (shouldRebuildFastText(params) || this.hasMutableLayoutSourceChanged()) {
|
|
189
|
+
this.rebuildGlyphState();
|
|
190
|
+
} else {
|
|
191
|
+
const dynamicUpdate = getFastTextDynamicUpdate(params);
|
|
192
|
+
if (dynamicUpdate) {
|
|
193
|
+
this.updateDynamicGlyphState(dynamicUpdate);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Draw the glyph model when atlas texture and glyph buffers are ready. */
|
|
199
|
+
override draw(): void {
|
|
200
|
+
const {atlasTexture, glyphData, model, fontSize} = this.state;
|
|
201
|
+
if (!model || !atlasTexture || !glyphData || glyphData.length === 0 || !fontSize) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
model.shaderInputs.setProps({
|
|
206
|
+
fastText: {
|
|
207
|
+
fontAtlasTexture: atlasTexture,
|
|
208
|
+
fontAtlasSize: [atlasTexture.width, atlasTexture.height],
|
|
209
|
+
fontSize,
|
|
210
|
+
size: this.props.size,
|
|
211
|
+
sizeScale: this.props.sizeScale,
|
|
212
|
+
sizeMinPixels: this.props.sizeMinPixels,
|
|
213
|
+
sizeMaxPixels: this.props.sizeMaxPixels,
|
|
214
|
+
pixelOffset: this.props.pixelOffset,
|
|
215
|
+
billboard: this.props.billboard,
|
|
216
|
+
sizeUnits: UNIT[this.props.sizeUnits],
|
|
217
|
+
alphaCutoff: this.props.alphaCutoff,
|
|
218
|
+
sdfEnabled: this.resolveSdfEnabled(),
|
|
219
|
+
sdfBuffer: FAST_TEXT_SDF_BUFFER,
|
|
220
|
+
sdfGamma: this.resolveSdfSmoothing(),
|
|
221
|
+
contentCutoffPixels: this.props.contentCutoffPixels,
|
|
222
|
+
contentAlign: [
|
|
223
|
+
getFastTextContentAlignUniform(this.props.contentAlignHorizontal),
|
|
224
|
+
getFastTextContentAlignUniform(this.props.contentAlignVertical)
|
|
225
|
+
],
|
|
226
|
+
flipY: Boolean((this.context.viewport as {flipY?: boolean}).flipY)
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
model.draw(this.context.renderPass);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** Release GPU buffers, atlas texture, and the luma model. */
|
|
233
|
+
override finalizeState(context: LayerContext): void {
|
|
234
|
+
destroyFastTextBuffers(this.state.buffers);
|
|
235
|
+
this.state.atlasTexture?.destroy();
|
|
236
|
+
this.state.model?.destroy();
|
|
237
|
+
super.finalizeState(context);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Build a luma model for instanced glyph quads. */
|
|
241
|
+
private getModel(): Model {
|
|
242
|
+
return new Model(this.context.device, {
|
|
243
|
+
...this.getShaders(),
|
|
244
|
+
id: this.props.id,
|
|
245
|
+
bufferLayout: FAST_TEXT_BUFFER_LAYOUT,
|
|
246
|
+
geometry: new Geometry({
|
|
247
|
+
topology: 'triangle-list',
|
|
248
|
+
attributes: {
|
|
249
|
+
positions: {
|
|
250
|
+
size: 2,
|
|
251
|
+
value: new Float32Array([0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1])
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}),
|
|
255
|
+
isInstanced: true
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/** Rebuild CPU glyph data, GPU vertex buffers, and the font atlas texture. */
|
|
260
|
+
private rebuildGlyphState(): void {
|
|
261
|
+
const rebuildStartTime = performance.now();
|
|
262
|
+
const atlasStartTime = performance.now();
|
|
263
|
+
const atlas = this.resolveFontAtlas();
|
|
264
|
+
const atlasDurationMs = performance.now() - atlasStartTime;
|
|
265
|
+
const mapping = this.props.characterMapping ?? atlas.mapping;
|
|
266
|
+
const glyphDataStartTime = performance.now();
|
|
267
|
+
const glyphData = buildFastTextGlyphData({
|
|
268
|
+
data: this.props.data,
|
|
269
|
+
getText: this.props.getText,
|
|
270
|
+
textUtf8Column: this.props.textUtf8Column,
|
|
271
|
+
getTextUtf8Row: this.props.getTextUtf8Row,
|
|
272
|
+
getTextUtf8: this.props.getTextUtf8,
|
|
273
|
+
singleLine: this.props.singleLine,
|
|
274
|
+
getPosition: this.props.getPosition,
|
|
275
|
+
getColor: this.props.getColor,
|
|
276
|
+
textAnchor: this.props.textAnchor,
|
|
277
|
+
alignmentBaseline: this.props.alignmentBaseline,
|
|
278
|
+
getClipRect: this.props.getClipRect,
|
|
279
|
+
mapping,
|
|
280
|
+
baselineOffset: atlas.baselineOffset,
|
|
281
|
+
fontSize: this.resolveFontSize(),
|
|
282
|
+
lineHeight: this.props.lineHeight
|
|
283
|
+
});
|
|
284
|
+
const glyphDataDurationMs = performance.now() - glyphDataStartTime;
|
|
285
|
+
const bufferStartTime = performance.now();
|
|
286
|
+
const buffers = createFastTextBuffers(this.context.device, glyphData);
|
|
287
|
+
const bufferCreateDurationMs = performance.now() - bufferStartTime;
|
|
288
|
+
|
|
289
|
+
destroyFastTextBuffers(this.state.buffers);
|
|
290
|
+
this.state.model?.setAttributes(buffers);
|
|
291
|
+
this.state.model?.setInstanceCount(glyphData.length);
|
|
292
|
+
this.setState({
|
|
293
|
+
glyphData,
|
|
294
|
+
buffers,
|
|
295
|
+
layoutSourceSignature: getFastTextLayoutSourceSignature(this.props)
|
|
296
|
+
});
|
|
297
|
+
logFastTextAttributeBuildProbe({
|
|
298
|
+
atlasDurationMs,
|
|
299
|
+
bufferCreateDurationMs,
|
|
300
|
+
glyphDataDurationMs,
|
|
301
|
+
glyphStats: glyphData.buildStats,
|
|
302
|
+
layerId: this.props.id,
|
|
303
|
+
totalDurationMs: performance.now() - rebuildStartTime
|
|
304
|
+
});
|
|
305
|
+
this.setNeedsRedraw();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Rewrite dynamic glyph attributes in place without rebuilding text layout. */
|
|
309
|
+
private updateDynamicGlyphState(update: FastTextDynamicUpdate): void {
|
|
310
|
+
const {glyphData, buffers} = this.state;
|
|
311
|
+
if (!glyphData || !buffers) {
|
|
312
|
+
this.rebuildGlyphState();
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const updateStartTime = performance.now();
|
|
317
|
+
const dynamicStats = updateFastTextDynamicGlyphAttributes({
|
|
318
|
+
data: this.props.data,
|
|
319
|
+
glyphData,
|
|
320
|
+
getPosition: this.props.getPosition,
|
|
321
|
+
getColor: this.props.getColor,
|
|
322
|
+
getClipRect: this.props.getClipRect,
|
|
323
|
+
updatePositions: update.positions,
|
|
324
|
+
updateColors: update.colors,
|
|
325
|
+
updateClipRects: update.clipRects
|
|
326
|
+
});
|
|
327
|
+
if (dynamicStats.rowCount !== glyphData.startIndices.length - 1) {
|
|
328
|
+
this.rebuildGlyphState();
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
const bufferUploadStartTime = performance.now();
|
|
332
|
+
if (update.positions) {
|
|
333
|
+
buffers.instancePositions.write(glyphData.attributes.instancePositions);
|
|
334
|
+
}
|
|
335
|
+
if (update.colors || update.clipRects) {
|
|
336
|
+
buffers.instanceGlyphData.write(glyphData.attributes.instanceGlyphData);
|
|
337
|
+
}
|
|
338
|
+
const bufferUploadDurationMs = performance.now() - bufferUploadStartTime;
|
|
339
|
+
logFastTextDynamicAttributeUpdateProbe({
|
|
340
|
+
bufferUploadDurationMs,
|
|
341
|
+
dynamicStats,
|
|
342
|
+
layerId: this.props.id,
|
|
343
|
+
totalDurationMs: performance.now() - updateStartTime,
|
|
344
|
+
update
|
|
345
|
+
});
|
|
346
|
+
this.setNeedsRedraw();
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/** Return whether source data or UTF-8 column growth invalidated the current glyph layout. */
|
|
350
|
+
private hasMutableLayoutSourceChanged(): boolean {
|
|
351
|
+
const {glyphData, layoutSourceSignature} = this.state;
|
|
352
|
+
if (!glyphData || !layoutSourceSignature) {
|
|
353
|
+
return true;
|
|
354
|
+
}
|
|
355
|
+
return !areFastTextLayoutSourceSignaturesEqual(
|
|
356
|
+
getFastTextLayoutSourceSignature(this.props),
|
|
357
|
+
layoutSourceSignature
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/** Resolve a caller-supplied atlas or rebuild the local generated atlas. */
|
|
362
|
+
private resolveFontAtlas(): FastTextFontAtlas {
|
|
363
|
+
const {characterMapping, fontAtlas} = this.props;
|
|
364
|
+
if (
|
|
365
|
+
(this.props.textUtf8Column || this.props.getTextUtf8) &&
|
|
366
|
+
this.props.characterSet === 'auto'
|
|
367
|
+
) {
|
|
368
|
+
throw new Error('FastTextLayer characterSet="auto" is not supported with UTF-8 text sources');
|
|
369
|
+
}
|
|
370
|
+
if (characterMapping || fontAtlas) {
|
|
371
|
+
if (!characterMapping || !fontAtlas) {
|
|
372
|
+
throw new Error(
|
|
373
|
+
'FastTextLayer requires characterMapping and fontAtlas to be supplied together'
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
this.syncAtlasTexture(fontAtlas);
|
|
377
|
+
return fontAtlas;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const characterSet =
|
|
381
|
+
this.props.characterSet === 'auto'
|
|
382
|
+
? collectFastTextCharacterSet({
|
|
383
|
+
data: this.props.data,
|
|
384
|
+
getText: this.props.getText
|
|
385
|
+
})
|
|
386
|
+
: this.props.characterSet;
|
|
387
|
+
this.state.fontAtlasManager.setProps({
|
|
388
|
+
...this.props.fontSettings,
|
|
389
|
+
fontFamily: this.props.fontFamily,
|
|
390
|
+
fontWeight: this.props.fontWeight,
|
|
391
|
+
characterSet
|
|
392
|
+
});
|
|
393
|
+
const atlas = this.state.fontAtlasManager.atlas;
|
|
394
|
+
if (!atlas) {
|
|
395
|
+
throw new Error('FastTextLayer failed to build a font atlas');
|
|
396
|
+
}
|
|
397
|
+
this.syncAtlasTexture(atlas);
|
|
398
|
+
return atlas;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
/** Resolve the atlas pixel size used by glyph offsets. */
|
|
402
|
+
private resolveFontSize(): number {
|
|
403
|
+
return this.props.fontAtlas
|
|
404
|
+
? (this.props.fontSettings.fontSize ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.fontSize)
|
|
405
|
+
: this.state.fontAtlasManager.props.fontSize;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/** Resolve whether the current atlas should be sampled as SDF data. */
|
|
409
|
+
private resolveSdfEnabled(): boolean {
|
|
410
|
+
return this.props.fontAtlas
|
|
411
|
+
? (this.props.fontSettings.sdf ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.sdf)
|
|
412
|
+
: this.state.fontAtlasManager.props.sdf;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Resolve the SDF smoothing width for the current atlas. */
|
|
416
|
+
private resolveSdfSmoothing(): number {
|
|
417
|
+
return this.props.fontAtlas
|
|
418
|
+
? (this.props.fontSettings.smoothing ?? DEFAULT_FAST_TEXT_FONT_SETTINGS.smoothing)
|
|
419
|
+
: this.state.fontAtlasManager.props.smoothing;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Upload a new atlas texture when the atlas object changes. */
|
|
423
|
+
private syncAtlasTexture(atlas: FastTextFontAtlas): void {
|
|
424
|
+
if (this.state.textureAtlas === atlas && this.state.atlasTexture) {
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
this.state.atlasTexture?.destroy();
|
|
428
|
+
const texture = createFastTextAtlasTexture(this.context.device, atlas);
|
|
429
|
+
this.setState({
|
|
430
|
+
textureAtlas: atlas,
|
|
431
|
+
atlasTexture: texture,
|
|
432
|
+
fontSize: this.resolveFontSize()
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
type FastTextLayerState = {
|
|
438
|
+
/** Atlas manager owned by this layer when no external atlas is supplied. */
|
|
439
|
+
fontAtlasManager: FastTextFontAtlasManager;
|
|
440
|
+
/** Luma model used to draw glyph quads. */
|
|
441
|
+
model?: Model;
|
|
442
|
+
/** Last expanded CPU glyph data. */
|
|
443
|
+
glyphData?: FastTextGlyphData;
|
|
444
|
+
/** Primitive source-shape values captured when `glyphData` was built. */
|
|
445
|
+
layoutSourceSignature?: FastTextLayoutSourceSignature;
|
|
446
|
+
/** GPU buffers for the current glyph data. */
|
|
447
|
+
buffers?: FastTextLayerBuffers;
|
|
448
|
+
/** Atlas object currently uploaded to `atlasTexture`. */
|
|
449
|
+
textureAtlas?: FastTextFontAtlas;
|
|
450
|
+
/** GPU texture for the current font atlas. */
|
|
451
|
+
atlasTexture?: Texture;
|
|
452
|
+
/** Atlas font size in pixels for the current glyph offsets. */
|
|
453
|
+
fontSize?: number;
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
type FastTextLayerBuffers = {
|
|
457
|
+
/** Packed glyph offsets, atlas frames, clip rectangles, and colors. */
|
|
458
|
+
instanceGlyphData: Buffer;
|
|
459
|
+
/** Repeated per-glyph anchor positions. */
|
|
460
|
+
instancePositions: Buffer;
|
|
461
|
+
};
|
|
462
|
+
|
|
463
|
+
type FastTextDynamicUpdate = {
|
|
464
|
+
/** Whether per-glyph positions need to be rewritten. */
|
|
465
|
+
positions: boolean;
|
|
466
|
+
/** Whether per-glyph colors need to be rewritten. */
|
|
467
|
+
colors: boolean;
|
|
468
|
+
/** Whether per-glyph clip rectangles need to be rewritten. */
|
|
469
|
+
clipRects: boolean;
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
type FastTextLayoutSourceSignature = {
|
|
473
|
+
/** Number of source rows visible through the data iterable. */
|
|
474
|
+
dataRowCount: number;
|
|
475
|
+
/** Whether source text comes from a row-level UTF-8 view accessor. */
|
|
476
|
+
hasTextUtf8Accessor: boolean;
|
|
477
|
+
/** Number of rows in the optional UTF-8 text column. */
|
|
478
|
+
textUtf8ColumnRowCount: number;
|
|
479
|
+
/** Number of chunks in the optional UTF-8 text column. */
|
|
480
|
+
textUtf8ColumnChunkCount: number;
|
|
481
|
+
/** Total entries across UTF-8 offset buffers. */
|
|
482
|
+
textUtf8ColumnOffsetCount: number;
|
|
483
|
+
/** Total bytes across UTF-8 value buffers. */
|
|
484
|
+
textUtf8ColumnValueByteLength: number;
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
type FastTextUniformProps = {
|
|
488
|
+
/** Font atlas texture binding. */
|
|
489
|
+
fontAtlasTexture: Texture;
|
|
490
|
+
/** Font atlas dimensions in pixels. */
|
|
491
|
+
fontAtlasSize: [number, number];
|
|
492
|
+
/** Atlas font size in pixels. */
|
|
493
|
+
fontSize: number;
|
|
494
|
+
/** Layer-wide text size. */
|
|
495
|
+
size: number;
|
|
496
|
+
/** Text size multiplier. */
|
|
497
|
+
sizeScale: number;
|
|
498
|
+
/** Minimum rendered text size in screen pixels. */
|
|
499
|
+
sizeMinPixels: number;
|
|
500
|
+
/** Maximum rendered text size in screen pixels. */
|
|
501
|
+
sizeMaxPixels: number;
|
|
502
|
+
/** Layer-wide pixel offset applied after glyph layout. */
|
|
503
|
+
pixelOffset: readonly [number, number];
|
|
504
|
+
/** Whether text should face the camera in screen space. */
|
|
505
|
+
billboard: boolean;
|
|
506
|
+
/** Deck.gl unit enum for text size projection. */
|
|
507
|
+
sizeUnits: number;
|
|
508
|
+
/** Fragment alpha cutoff below which a glyph pixel is discarded. */
|
|
509
|
+
alphaCutoff: number;
|
|
510
|
+
/** Whether the atlas alpha channel contains signed distance values. */
|
|
511
|
+
sdfEnabled: boolean;
|
|
512
|
+
/** SDF edge threshold used for fill rendering. */
|
|
513
|
+
sdfBuffer: number;
|
|
514
|
+
/** SDF smoothing width around `sdfBuffer`. */
|
|
515
|
+
sdfGamma: number;
|
|
516
|
+
/** Minimum visible content-box dimensions in screen pixels. */
|
|
517
|
+
contentCutoffPixels: readonly [number, number];
|
|
518
|
+
/** Horizontal and vertical content alignment enum values. */
|
|
519
|
+
contentAlign: readonly [number, number];
|
|
520
|
+
/** Whether the active viewport flips the Y axis for content clipping. */
|
|
521
|
+
flipY: boolean;
|
|
522
|
+
};
|
|
523
|
+
|
|
524
|
+
const FAST_TEXT_CONTENT_ALIGN: Record<FastTextContentAlign, number> = {
|
|
525
|
+
none: 0,
|
|
526
|
+
start: 1,
|
|
527
|
+
center: 2,
|
|
528
|
+
end: 3
|
|
529
|
+
};
|
|
530
|
+
const FAST_TEXT_ALIGN_START = FAST_TEXT_CONTENT_ALIGN.start;
|
|
531
|
+
const FAST_TEXT_ALIGN_CENTER = FAST_TEXT_CONTENT_ALIGN.center;
|
|
532
|
+
const FAST_TEXT_ALIGN_END = FAST_TEXT_CONTENT_ALIGN.end;
|
|
533
|
+
const FAST_TEXT_SDF_BUFFER = 192.0 / 256.0;
|
|
534
|
+
const fastTextLog = new Log({id: 'trace-layers'});
|
|
535
|
+
const FAST_TEXT_PROBE_LABEL_STYLE =
|
|
536
|
+
'background:#f59e0b;color:#111827;font-weight:700;padding:2px 8px;border-radius:6px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;';
|
|
537
|
+
|
|
538
|
+
const fastTextUniforms = {
|
|
539
|
+
name: 'fastText',
|
|
540
|
+
vs: `\
|
|
541
|
+
layout(std140) uniform fastTextUniforms {
|
|
542
|
+
vec2 fontAtlasSize;
|
|
543
|
+
float fontSize;
|
|
544
|
+
float size;
|
|
545
|
+
float sizeScale;
|
|
546
|
+
float sizeMinPixels;
|
|
547
|
+
float sizeMaxPixels;
|
|
548
|
+
vec2 pixelOffset;
|
|
549
|
+
bool billboard;
|
|
550
|
+
highp int sizeUnits;
|
|
551
|
+
float alphaCutoff;
|
|
552
|
+
bool sdfEnabled;
|
|
553
|
+
float sdfBuffer;
|
|
554
|
+
float sdfGamma;
|
|
555
|
+
vec2 contentCutoffPixels;
|
|
556
|
+
highp ivec2 contentAlign;
|
|
557
|
+
bool flipY;
|
|
558
|
+
} fastText;
|
|
559
|
+
|
|
560
|
+
#define FAST_TEXT_ALIGN_MODE_START ${FAST_TEXT_ALIGN_START}
|
|
561
|
+
#define FAST_TEXT_ALIGN_MODE_CENTER ${FAST_TEXT_ALIGN_CENTER}
|
|
562
|
+
#define FAST_TEXT_ALIGN_MODE_END ${FAST_TEXT_ALIGN_END}
|
|
563
|
+
`,
|
|
564
|
+
fs: `\
|
|
565
|
+
layout(std140) uniform fastTextUniforms {
|
|
566
|
+
vec2 fontAtlasSize;
|
|
567
|
+
float fontSize;
|
|
568
|
+
float size;
|
|
569
|
+
float sizeScale;
|
|
570
|
+
float sizeMinPixels;
|
|
571
|
+
float sizeMaxPixels;
|
|
572
|
+
vec2 pixelOffset;
|
|
573
|
+
bool billboard;
|
|
574
|
+
highp int sizeUnits;
|
|
575
|
+
float alphaCutoff;
|
|
576
|
+
bool sdfEnabled;
|
|
577
|
+
float sdfBuffer;
|
|
578
|
+
float sdfGamma;
|
|
579
|
+
vec2 contentCutoffPixels;
|
|
580
|
+
highp ivec2 contentAlign;
|
|
581
|
+
bool flipY;
|
|
582
|
+
} fastText;
|
|
583
|
+
`,
|
|
584
|
+
uniformTypes: {
|
|
585
|
+
fontAtlasSize: 'vec2<f32>',
|
|
586
|
+
fontSize: 'f32',
|
|
587
|
+
size: 'f32',
|
|
588
|
+
sizeScale: 'f32',
|
|
589
|
+
sizeMinPixels: 'f32',
|
|
590
|
+
sizeMaxPixels: 'f32',
|
|
591
|
+
pixelOffset: 'vec2<f32>',
|
|
592
|
+
billboard: 'f32',
|
|
593
|
+
sizeUnits: 'i32',
|
|
594
|
+
alphaCutoff: 'f32',
|
|
595
|
+
sdfEnabled: 'f32',
|
|
596
|
+
sdfBuffer: 'f32',
|
|
597
|
+
sdfGamma: 'f32',
|
|
598
|
+
contentCutoffPixels: 'vec2<f32>',
|
|
599
|
+
contentAlign: 'vec2<i32>',
|
|
600
|
+
flipY: 'f32'
|
|
601
|
+
}
|
|
602
|
+
} as const satisfies ShaderModule<FastTextUniformProps>;
|
|
603
|
+
|
|
604
|
+
const FAST_TEXT_BUFFER_LAYOUT = [
|
|
605
|
+
{name: 'instancePositions', stepMode: 'instance', format: 'float32x2'},
|
|
606
|
+
{
|
|
607
|
+
name: 'instanceGlyphData',
|
|
608
|
+
stepMode: 'instance',
|
|
609
|
+
byteStride: FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE,
|
|
610
|
+
attributes: [
|
|
611
|
+
{attribute: 'instanceGlyphOffsets', format: 'sint16x2', byteOffset: 0},
|
|
612
|
+
{
|
|
613
|
+
attribute: 'instanceGlyphFrames',
|
|
614
|
+
format: 'uint16x4',
|
|
615
|
+
byteOffset: Int16Array.BYTES_PER_ELEMENT * 2
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
attribute: 'instanceClipRects',
|
|
619
|
+
format: 'sint16x4',
|
|
620
|
+
byteOffset: Int16Array.BYTES_PER_ELEMENT * 6
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
attribute: 'instanceColors',
|
|
624
|
+
format: 'unorm8x4',
|
|
625
|
+
byteOffset: Int16Array.BYTES_PER_ELEMENT * 10
|
|
626
|
+
}
|
|
627
|
+
]
|
|
628
|
+
}
|
|
629
|
+
] as const;
|
|
630
|
+
|
|
631
|
+
const DEFAULT_SAMPLER_PARAMETERS = {
|
|
632
|
+
minFilter: 'linear',
|
|
633
|
+
mipmapFilter: 'linear',
|
|
634
|
+
magFilter: 'linear',
|
|
635
|
+
addressModeU: 'clamp-to-edge',
|
|
636
|
+
addressModeV: 'clamp-to-edge'
|
|
637
|
+
} as const;
|
|
638
|
+
|
|
639
|
+
const FAST_TEXT_VS = `\
|
|
640
|
+
#version 300 es
|
|
641
|
+
#define SHADER_NAME fast-text-layer-vertex-shader
|
|
642
|
+
|
|
643
|
+
in vec2 positions;
|
|
644
|
+
in vec2 instancePositions;
|
|
645
|
+
in ivec2 instanceGlyphOffsets;
|
|
646
|
+
in uvec4 instanceGlyphFrames;
|
|
647
|
+
in ivec4 instanceClipRects;
|
|
648
|
+
in vec4 instanceColors;
|
|
649
|
+
|
|
650
|
+
out vec4 vColor;
|
|
651
|
+
out vec2 vTextureCoords;
|
|
652
|
+
out vec2 uv;
|
|
653
|
+
|
|
654
|
+
float fastTextGetAlignmentPixelOffset(float anchor, float extent, float clipStart, float clipEnd, int mode) {
|
|
655
|
+
if (clipEnd < clipStart) {
|
|
656
|
+
return 0.0;
|
|
657
|
+
}
|
|
658
|
+
if (mode == FAST_TEXT_ALIGN_MODE_START) {
|
|
659
|
+
return max(-(anchor + clipStart), 0.0);
|
|
660
|
+
}
|
|
661
|
+
if (mode == FAST_TEXT_ALIGN_MODE_CENTER) {
|
|
662
|
+
float visibleMin = max(0.0, anchor + clipStart);
|
|
663
|
+
float visibleMax = min(extent, anchor + clipEnd);
|
|
664
|
+
return visibleMin < visibleMax ? (visibleMin + visibleMax) / 2.0 - anchor : 0.0;
|
|
665
|
+
}
|
|
666
|
+
if (mode == FAST_TEXT_ALIGN_MODE_END) {
|
|
667
|
+
return min(extent - (anchor + clipEnd), 0.0);
|
|
668
|
+
}
|
|
669
|
+
return 0.0;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
void fastTextClipGlyphVertex(vec2 pixelOffset, vec2 anchorPosScreen) {
|
|
673
|
+
vec4 clipRect = vec4(instanceClipRects);
|
|
674
|
+
vec2 clipXY = project_size_to_pixel(clipRect.xy);
|
|
675
|
+
vec2 clipWH = project_size_to_pixel(clipRect.zw);
|
|
676
|
+
if (fastText.flipY) {
|
|
677
|
+
clipXY.y = -clipXY.y - clipWH.y;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
if (fastText.contentAlign.x > 0 || fastText.contentAlign.y > 0) {
|
|
681
|
+
vec2 viewportPixels = project.viewportSize / project.devicePixelRatio;
|
|
682
|
+
vec2 scrollPixels = vec2(
|
|
683
|
+
fastTextGetAlignmentPixelOffset(
|
|
684
|
+
anchorPosScreen.x,
|
|
685
|
+
viewportPixels.x,
|
|
686
|
+
clipXY.x,
|
|
687
|
+
clipXY.x + clipWH.x,
|
|
688
|
+
fastText.contentAlign.x
|
|
689
|
+
),
|
|
690
|
+
-fastTextGetAlignmentPixelOffset(
|
|
691
|
+
anchorPosScreen.y,
|
|
692
|
+
viewportPixels.y,
|
|
693
|
+
-clipXY.y - clipWH.y,
|
|
694
|
+
-clipXY.y,
|
|
695
|
+
fastText.contentAlign.y
|
|
696
|
+
)
|
|
697
|
+
);
|
|
698
|
+
pixelOffset += scrollPixels;
|
|
699
|
+
gl_Position.xy += project_pixel_size_to_clipspace(scrollPixels);
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
if (clipRect.z >= 0.0) {
|
|
703
|
+
if (pixelOffset.x < clipXY.x || pixelOffset.x > clipXY.x + clipWH.x) {
|
|
704
|
+
gl_Position = vec4(0.0);
|
|
705
|
+
} else if (fastText.contentCutoffPixels.x > 0.0) {
|
|
706
|
+
float viewportWidth = project.viewportSize.x / project.devicePixelRatio;
|
|
707
|
+
float left = max(anchorPosScreen.x + clipXY.x, 0.0);
|
|
708
|
+
float right = min(anchorPosScreen.x + clipXY.x + clipWH.x, viewportWidth);
|
|
709
|
+
if (right - left < fastText.contentCutoffPixels.x) {
|
|
710
|
+
gl_Position = vec4(0.0);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (clipRect.w >= 0.0) {
|
|
715
|
+
if (pixelOffset.y < clipXY.y || pixelOffset.y > clipXY.y + clipWH.y) {
|
|
716
|
+
gl_Position = vec4(0.0);
|
|
717
|
+
} else if (fastText.contentCutoffPixels.y > 0.0) {
|
|
718
|
+
float viewportHeight = project.viewportSize.y / project.devicePixelRatio;
|
|
719
|
+
float top = max(anchorPosScreen.y - clipXY.y - clipWH.y, 0.0);
|
|
720
|
+
float bottom = min(anchorPosScreen.y - clipXY.y, viewportHeight);
|
|
721
|
+
if (bottom - top < fastText.contentCutoffPixels.y) {
|
|
722
|
+
gl_Position = vec4(0.0);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
void main(void) {
|
|
729
|
+
vec3 worldPosition = vec3(instancePositions, 0.0);
|
|
730
|
+
geometry.worldPosition = worldPosition;
|
|
731
|
+
geometry.uv = positions;
|
|
732
|
+
uv = positions;
|
|
733
|
+
|
|
734
|
+
vec4 glyphFrame = vec4(instanceGlyphFrames);
|
|
735
|
+
vec2 glyphSize = glyphFrame.zw;
|
|
736
|
+
float sizePixels = clamp(
|
|
737
|
+
project_size_to_pixel(fastText.size * fastText.sizeScale, fastText.sizeUnits),
|
|
738
|
+
fastText.sizeMinPixels,
|
|
739
|
+
fastText.sizeMaxPixels
|
|
740
|
+
);
|
|
741
|
+
float instanceScale = fastText.fontSize == 0.0 ? 0.0 : sizePixels / fastText.fontSize;
|
|
742
|
+
vec2 pixelOffset = vec2(instanceGlyphOffsets) + positions * glyphSize;
|
|
743
|
+
pixelOffset = pixelOffset * instanceScale + fastText.pixelOffset;
|
|
744
|
+
pixelOffset.y *= -1.0;
|
|
745
|
+
|
|
746
|
+
vec2 anchorPosScreen;
|
|
747
|
+
if (fastText.billboard) {
|
|
748
|
+
gl_Position = project_position_to_clipspace(
|
|
749
|
+
worldPosition,
|
|
750
|
+
vec3(0.0),
|
|
751
|
+
vec3(0.0),
|
|
752
|
+
geometry.position
|
|
753
|
+
);
|
|
754
|
+
anchorPosScreen = gl_Position.xy / gl_Position.w;
|
|
755
|
+
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
|
|
756
|
+
vec3 offset = vec3(pixelOffset, 0.0);
|
|
757
|
+
DECKGL_FILTER_SIZE(offset, geometry);
|
|
758
|
+
gl_Position.xy += project_pixel_size_to_clipspace(offset.xy);
|
|
759
|
+
} else {
|
|
760
|
+
vec3 offsetCommon = vec3(project_pixel_size(pixelOffset), 0.0);
|
|
761
|
+
if (fastText.flipY) {
|
|
762
|
+
offsetCommon.y *= -1.0;
|
|
763
|
+
}
|
|
764
|
+
DECKGL_FILTER_SIZE(offsetCommon, geometry);
|
|
765
|
+
vec4 anchorPosition = project_position_to_clipspace(worldPosition, vec3(0.0), vec3(0.0));
|
|
766
|
+
anchorPosScreen = anchorPosition.xy / anchorPosition.w;
|
|
767
|
+
gl_Position = project_position_to_clipspace(
|
|
768
|
+
worldPosition,
|
|
769
|
+
vec3(0.0),
|
|
770
|
+
offsetCommon,
|
|
771
|
+
geometry.position
|
|
772
|
+
);
|
|
773
|
+
DECKGL_FILTER_GL_POSITION(gl_Position, geometry);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
anchorPosScreen = vec2(anchorPosScreen.x + 1.0, 1.0 - anchorPosScreen.y) / 2.0 *
|
|
777
|
+
project.viewportSize / project.devicePixelRatio;
|
|
778
|
+
fastTextClipGlyphVertex(pixelOffset, anchorPosScreen);
|
|
779
|
+
|
|
780
|
+
vTextureCoords = (glyphFrame.xy + positions * glyphSize) / fastText.fontAtlasSize;
|
|
781
|
+
vColor = instanceColors;
|
|
782
|
+
DECKGL_FILTER_COLOR(vColor, geometry);
|
|
783
|
+
}
|
|
784
|
+
`;
|
|
785
|
+
|
|
786
|
+
const FAST_TEXT_FS = `\
|
|
787
|
+
#version 300 es
|
|
788
|
+
#define SHADER_NAME fast-text-layer-fragment-shader
|
|
789
|
+
|
|
790
|
+
precision highp float;
|
|
791
|
+
|
|
792
|
+
uniform sampler2D fontAtlasTexture;
|
|
793
|
+
|
|
794
|
+
in vec4 vColor;
|
|
795
|
+
in vec2 vTextureCoords;
|
|
796
|
+
in vec2 uv;
|
|
797
|
+
|
|
798
|
+
out vec4 fragColor;
|
|
799
|
+
|
|
800
|
+
void main(void) {
|
|
801
|
+
geometry.uv = uv;
|
|
802
|
+
|
|
803
|
+
float alpha = texture(fontAtlasTexture, vTextureCoords).a;
|
|
804
|
+
if (fastText.sdfEnabled) {
|
|
805
|
+
alpha = smoothstep(fastText.sdfBuffer - fastText.sdfGamma, fastText.sdfBuffer + fastText.sdfGamma, alpha);
|
|
806
|
+
}
|
|
807
|
+
float outputAlpha = alpha * vColor.a;
|
|
808
|
+
|
|
809
|
+
if (outputAlpha < fastText.alphaCutoff) {
|
|
810
|
+
discard;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
fragColor = vec4(vColor.rgb, outputAlpha * layer.opacity);
|
|
814
|
+
|
|
815
|
+
DECKGL_FILTER_COLOR(fragColor, geometry);
|
|
816
|
+
}
|
|
817
|
+
`;
|
|
818
|
+
|
|
819
|
+
/** Capture primitive source-shape values that can change without prop identity changes. */
|
|
820
|
+
function getFastTextLayoutSourceSignature(
|
|
821
|
+
props: Required<_FastTextLayerProps<any>>
|
|
822
|
+
): FastTextLayoutSourceSignature {
|
|
823
|
+
const textUtf8ColumnSignature = getFastTextUtf8ColumnSourceSignature(props.textUtf8Column);
|
|
824
|
+
return {
|
|
825
|
+
dataRowCount: getFastTextDataRowCount(props.data),
|
|
826
|
+
hasTextUtf8Accessor: Boolean(props.getTextUtf8),
|
|
827
|
+
textUtf8ColumnRowCount: textUtf8ColumnSignature.rowCount,
|
|
828
|
+
textUtf8ColumnChunkCount: textUtf8ColumnSignature.chunkCount,
|
|
829
|
+
textUtf8ColumnOffsetCount: textUtf8ColumnSignature.offsetCount,
|
|
830
|
+
textUtf8ColumnValueByteLength: textUtf8ColumnSignature.valueByteLength
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** Return whether two captured source-shape signatures match. */
|
|
835
|
+
function areFastTextLayoutSourceSignaturesEqual(
|
|
836
|
+
signature: FastTextLayoutSourceSignature,
|
|
837
|
+
oldSignature: FastTextLayoutSourceSignature
|
|
838
|
+
): boolean {
|
|
839
|
+
return (
|
|
840
|
+
signature.dataRowCount === oldSignature.dataRowCount &&
|
|
841
|
+
signature.hasTextUtf8Accessor === oldSignature.hasTextUtf8Accessor &&
|
|
842
|
+
signature.textUtf8ColumnRowCount === oldSignature.textUtf8ColumnRowCount &&
|
|
843
|
+
signature.textUtf8ColumnChunkCount === oldSignature.textUtf8ColumnChunkCount &&
|
|
844
|
+
signature.textUtf8ColumnOffsetCount === oldSignature.textUtf8ColumnOffsetCount &&
|
|
845
|
+
signature.textUtf8ColumnValueByteLength === oldSignature.textUtf8ColumnValueByteLength
|
|
846
|
+
);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/** Count source data rows without relying on deck.gl's identity-based data change flag. */
|
|
850
|
+
function getFastTextDataRowCount(data: LayerDataSource<any>): number {
|
|
851
|
+
const arrayLikeLength = (data as {length?: unknown}).length;
|
|
852
|
+
if (typeof arrayLikeLength === 'number') {
|
|
853
|
+
return arrayLikeLength;
|
|
854
|
+
}
|
|
855
|
+
const tableLikeNumRows = (data as {numRows?: unknown}).numRows;
|
|
856
|
+
if (typeof tableLikeNumRows === 'number') {
|
|
857
|
+
return tableLikeNumRows;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const {iterable} = createIterable(data);
|
|
861
|
+
let rowCount = 0;
|
|
862
|
+
for (const _object of iterable) {
|
|
863
|
+
rowCount++;
|
|
864
|
+
}
|
|
865
|
+
return rowCount;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/** Capture primitive shape values for the optional direct UTF-8 text source. */
|
|
869
|
+
function getFastTextUtf8ColumnSourceSignature(
|
|
870
|
+
textUtf8Column: FastTextUtf8Column | null | undefined
|
|
871
|
+
): {
|
|
872
|
+
/** Number of rows in the normalized source. */
|
|
873
|
+
readonly rowCount: number;
|
|
874
|
+
/** Number of chunks in the normalized source. */
|
|
875
|
+
readonly chunkCount: number;
|
|
876
|
+
/** Total entries across UTF-8 offset buffers. */
|
|
877
|
+
readonly offsetCount: number;
|
|
878
|
+
/** Total bytes across UTF-8 value buffers. */
|
|
879
|
+
readonly valueByteLength: number;
|
|
880
|
+
} {
|
|
881
|
+
if (!textUtf8Column) {
|
|
882
|
+
return {rowCount: 0, chunkCount: 0, offsetCount: 0, valueByteLength: 0};
|
|
883
|
+
}
|
|
884
|
+
const source = normalizeFastTextUtf8ColumnForComparison(textUtf8Column);
|
|
885
|
+
if (!source) {
|
|
886
|
+
return {rowCount: -1, chunkCount: -1, offsetCount: -1, valueByteLength: -1};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
let offsetCount = 0;
|
|
890
|
+
let valueByteLength = 0;
|
|
891
|
+
for (const chunk of source.chunks) {
|
|
892
|
+
offsetCount += chunk.valueOffsets.length;
|
|
893
|
+
valueByteLength += chunk.values.byteLength;
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
rowCount: source.rowCount,
|
|
897
|
+
chunkCount: source.chunks.length,
|
|
898
|
+
offsetCount,
|
|
899
|
+
valueByteLength
|
|
900
|
+
};
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
/** Decide whether a deck update requires glyph data regeneration. */
|
|
904
|
+
function shouldRebuildFastText(layerParams: UpdateParameters<FastTextLayer<any, any>>): boolean {
|
|
905
|
+
const {props, oldProps, changeFlags} = layerParams;
|
|
906
|
+
const updateTriggersChanged = changeFlags.updateTriggersChanged;
|
|
907
|
+
return (
|
|
908
|
+
Boolean(changeFlags.dataChanged) ||
|
|
909
|
+
didFastTextAccessorPropChange(props.getText, oldProps.getText) ||
|
|
910
|
+
!areFastTextUtf8ColumnsEqual(props.textUtf8Column, oldProps.textUtf8Column) ||
|
|
911
|
+
didFastTextAccessorPropChange(props.getTextUtf8Row, oldProps.getTextUtf8Row) ||
|
|
912
|
+
didFastTextAccessorPropChange(props.getTextUtf8, oldProps.getTextUtf8) ||
|
|
913
|
+
props.singleLine !== oldProps.singleLine ||
|
|
914
|
+
props.textAnchor !== oldProps.textAnchor ||
|
|
915
|
+
props.alignmentBaseline !== oldProps.alignmentBaseline ||
|
|
916
|
+
props.characterSet !== oldProps.characterSet ||
|
|
917
|
+
props.fontFamily !== oldProps.fontFamily ||
|
|
918
|
+
props.fontWeight !== oldProps.fontWeight ||
|
|
919
|
+
props.lineHeight !== oldProps.lineHeight ||
|
|
920
|
+
props.fontSettings !== oldProps.fontSettings ||
|
|
921
|
+
props.characterMapping !== oldProps.characterMapping ||
|
|
922
|
+
props.fontAtlas !== oldProps.fontAtlas ||
|
|
923
|
+
Boolean(
|
|
924
|
+
updateTriggersChanged &&
|
|
925
|
+
(updateTriggersChanged.all ||
|
|
926
|
+
updateTriggersChanged.getText ||
|
|
927
|
+
updateTriggersChanged.getTextUtf8 ||
|
|
928
|
+
updateTriggersChanged.getTextUtf8Row)
|
|
929
|
+
)
|
|
930
|
+
);
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
/** Return which dynamic FastText attributes can be updated without rebuilding text layout. */
|
|
934
|
+
function getFastTextDynamicUpdate(
|
|
935
|
+
layerParams: UpdateParameters<FastTextLayer<any, any>>
|
|
936
|
+
): FastTextDynamicUpdate | null {
|
|
937
|
+
const {props, oldProps, changeFlags} = layerParams;
|
|
938
|
+
const updateTriggersChanged = changeFlags.updateTriggersChanged;
|
|
939
|
+
const positions =
|
|
940
|
+
didFastTextAccessorPropChange(props.getPosition, oldProps.getPosition) ||
|
|
941
|
+
Boolean(updateTriggersChanged && updateTriggersChanged.getPosition);
|
|
942
|
+
const colors =
|
|
943
|
+
didFastTextAccessorPropChange(props.getColor, oldProps.getColor) ||
|
|
944
|
+
Boolean(updateTriggersChanged && updateTriggersChanged.getColor);
|
|
945
|
+
const clipRects =
|
|
946
|
+
didFastTextAccessorPropChange(props.getClipRect, oldProps.getClipRect) ||
|
|
947
|
+
Boolean(updateTriggersChanged && updateTriggersChanged.getClipRect);
|
|
948
|
+
if (!positions && !colors && !clipRects) {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
return {positions, colors, clipRects};
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
/** Return whether two UTF-8 column props read from the same underlying Arrow buffers. */
|
|
955
|
+
function areFastTextUtf8ColumnsEqual(
|
|
956
|
+
textUtf8Column: FastTextUtf8Column | null | undefined,
|
|
957
|
+
oldTextUtf8Column: FastTextUtf8Column | null | undefined
|
|
958
|
+
): boolean {
|
|
959
|
+
if (textUtf8Column === oldTextUtf8Column) {
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
if (!textUtf8Column || !oldTextUtf8Column) {
|
|
963
|
+
return false;
|
|
964
|
+
}
|
|
965
|
+
const source = normalizeFastTextUtf8ColumnForComparison(textUtf8Column);
|
|
966
|
+
const oldSource = normalizeFastTextUtf8ColumnForComparison(oldTextUtf8Column);
|
|
967
|
+
if (!source || !oldSource || source.rowCount !== oldSource.rowCount) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
if (source.chunks.length !== oldSource.chunks.length) {
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
for (let index = 0; index < source.chunks.length; index += 1) {
|
|
974
|
+
const chunk = source.chunks[index]!;
|
|
975
|
+
const oldChunk = oldSource.chunks[index]!;
|
|
976
|
+
if (
|
|
977
|
+
chunk.rowOffset !== oldChunk.rowOffset ||
|
|
978
|
+
chunk.rowCount !== oldChunk.rowCount ||
|
|
979
|
+
chunk.valueOffsetIndex !== oldChunk.valueOffsetIndex ||
|
|
980
|
+
chunk.valueOffsets !== oldChunk.valueOffsets ||
|
|
981
|
+
chunk.values !== oldChunk.values
|
|
982
|
+
) {
|
|
983
|
+
return false;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return true;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
/** Normalize a UTF-8 column prop for same-buffer comparison. */
|
|
990
|
+
function normalizeFastTextUtf8ColumnForComparison(
|
|
991
|
+
textUtf8Column: FastTextUtf8Column
|
|
992
|
+
): FastTextUtf8ColumnSource | null {
|
|
993
|
+
return isFastTextUtf8ColumnSource(textUtf8Column)
|
|
994
|
+
? textUtf8Column
|
|
995
|
+
: buildFastTextUtf8ColumnSource(textUtf8Column);
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
/** Return whether an object has the normalized FastText UTF-8 source shape. */
|
|
999
|
+
function isFastTextUtf8ColumnSource(
|
|
1000
|
+
textUtf8Column: FastTextUtf8Column
|
|
1001
|
+
): textUtf8Column is FastTextUtf8ColumnSource {
|
|
1002
|
+
return 'rowCount' in textUtf8Column && 'chunks' in textUtf8Column;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
/** Return whether an accessor prop changed in a way deck.gl expects attributes to observe. */
|
|
1006
|
+
function didFastTextAccessorPropChange<ValueT>(
|
|
1007
|
+
accessor: Accessor<any, ValueT>,
|
|
1008
|
+
oldAccessor: Accessor<any, ValueT>
|
|
1009
|
+
): boolean {
|
|
1010
|
+
if (typeof oldAccessor === 'function') {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
if (typeof accessor === 'function') {
|
|
1014
|
+
return true;
|
|
1015
|
+
}
|
|
1016
|
+
return !areFastTextAccessorConstantsEqual(accessor, oldAccessor);
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
/** Return whether two non-function accessor constants are shallowly equivalent. */
|
|
1020
|
+
function areFastTextAccessorConstantsEqual<ValueT>(
|
|
1021
|
+
accessor: Accessor<any, ValueT>,
|
|
1022
|
+
oldAccessor: Accessor<any, ValueT>
|
|
1023
|
+
): boolean {
|
|
1024
|
+
if (accessor === oldAccessor) {
|
|
1025
|
+
return true;
|
|
1026
|
+
}
|
|
1027
|
+
if (!Array.isArray(accessor) || !Array.isArray(oldAccessor)) {
|
|
1028
|
+
return false;
|
|
1029
|
+
}
|
|
1030
|
+
if (accessor.length !== oldAccessor.length) {
|
|
1031
|
+
return false;
|
|
1032
|
+
}
|
|
1033
|
+
for (let index = 0; index < accessor.length; index += 1) {
|
|
1034
|
+
if (accessor[index] !== oldAccessor[index]) {
|
|
1035
|
+
return false;
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
/** Emit one compact timing probe for a completed FastText attribute rebuild. */
|
|
1042
|
+
function logFastTextAttributeBuildProbe(params: {
|
|
1043
|
+
/** Time spent resolving or generating the font atlas. */
|
|
1044
|
+
atlasDurationMs: number;
|
|
1045
|
+
/** Time spent creating GPU vertex buffers from generated attributes. */
|
|
1046
|
+
bufferCreateDurationMs: number;
|
|
1047
|
+
/** Time spent building CPU glyph attribute arrays. */
|
|
1048
|
+
glyphDataDurationMs: number;
|
|
1049
|
+
/** Detailed CPU glyph attribute build counters. */
|
|
1050
|
+
glyphStats: FastTextGlyphBuildStats;
|
|
1051
|
+
/** Deck layer id that rebuilt text attributes. */
|
|
1052
|
+
layerId: string;
|
|
1053
|
+
/** End-to-end time spent in the layer attribute rebuild. */
|
|
1054
|
+
totalDurationMs: number;
|
|
1055
|
+
}): void {
|
|
1056
|
+
getFastTextProbeLog().probe(
|
|
1057
|
+
0,
|
|
1058
|
+
`%cFastTextLayer%c ${params.glyphStats.rowCount} rows in ${formatFastTextDurationMs(params.totalDurationMs)} ${params.layerId}`,
|
|
1059
|
+
FAST_TEXT_PROBE_LABEL_STYLE,
|
|
1060
|
+
'',
|
|
1061
|
+
{
|
|
1062
|
+
layerId: params.layerId,
|
|
1063
|
+
updateMode: 'full',
|
|
1064
|
+
sourceMode: params.glyphStats.sourceMode,
|
|
1065
|
+
layoutMode: params.glyphStats.layoutMode,
|
|
1066
|
+
rowCount: params.glyphStats.rowCount,
|
|
1067
|
+
glyphCount: params.glyphStats.glyphCount,
|
|
1068
|
+
attributeByteLength: params.glyphStats.attributeByteLength,
|
|
1069
|
+
atlasDurationMs: params.atlasDurationMs,
|
|
1070
|
+
glyphDataDurationMs: params.glyphDataDurationMs,
|
|
1071
|
+
columnNormalizeDurationMs: params.glyphStats.columnNormalizeDurationMs,
|
|
1072
|
+
countDurationMs: params.glyphStats.countDurationMs,
|
|
1073
|
+
allocateDurationMs: params.glyphStats.allocateDurationMs,
|
|
1074
|
+
writeDurationMs: params.glyphStats.writeDurationMs,
|
|
1075
|
+
textResolveDurationMs: params.glyphStats.textResolveDurationMs,
|
|
1076
|
+
styleAccessorDurationMs: params.glyphStats.styleAccessorDurationMs,
|
|
1077
|
+
layoutDurationMs: params.glyphStats.layoutDurationMs,
|
|
1078
|
+
glyphWriteDurationMs: params.glyphStats.glyphWriteDurationMs,
|
|
1079
|
+
bufferCreateDurationMs: params.bufferCreateDurationMs,
|
|
1080
|
+
totalDurationMs: params.totalDurationMs
|
|
1081
|
+
}
|
|
1082
|
+
)();
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/** Emit one compact timing probe for a dynamic FastText attribute update. */
|
|
1086
|
+
function logFastTextDynamicAttributeUpdateProbe(params: {
|
|
1087
|
+
/** Time spent uploading rewritten dynamic CPU arrays to GPU buffers. */
|
|
1088
|
+
bufferUploadDurationMs: number;
|
|
1089
|
+
/** Detailed dynamic glyph attribute update counters. */
|
|
1090
|
+
dynamicStats: FastTextDynamicGlyphUpdateStats;
|
|
1091
|
+
/** Deck layer id that updated text attributes. */
|
|
1092
|
+
layerId: string;
|
|
1093
|
+
/** End-to-end time spent in the layer dynamic attribute update. */
|
|
1094
|
+
totalDurationMs: number;
|
|
1095
|
+
/** Dynamic attribute groups that changed. */
|
|
1096
|
+
update: FastTextDynamicUpdate;
|
|
1097
|
+
}): void {
|
|
1098
|
+
getFastTextProbeLog().probe(
|
|
1099
|
+
0,
|
|
1100
|
+
`%cFastTextLayer%c dynamic ${params.dynamicStats.rowCount} rows in ${formatFastTextDurationMs(params.totalDurationMs)} ${params.layerId}`,
|
|
1101
|
+
FAST_TEXT_PROBE_LABEL_STYLE,
|
|
1102
|
+
'',
|
|
1103
|
+
{
|
|
1104
|
+
layerId: params.layerId,
|
|
1105
|
+
updateMode: 'dynamic',
|
|
1106
|
+
updatePositions: params.update.positions,
|
|
1107
|
+
updateColors: params.update.colors,
|
|
1108
|
+
updateClipRects: params.update.clipRects,
|
|
1109
|
+
rowCount: params.dynamicStats.rowCount,
|
|
1110
|
+
glyphCount: params.dynamicStats.glyphCount,
|
|
1111
|
+
attributeByteLength: params.dynamicStats.attributeByteLength,
|
|
1112
|
+
positionAccessorDurationMs: params.dynamicStats.positionAccessorDurationMs,
|
|
1113
|
+
colorAccessorDurationMs: params.dynamicStats.colorAccessorDurationMs,
|
|
1114
|
+
clipRectAccessorDurationMs: params.dynamicStats.clipRectAccessorDurationMs,
|
|
1115
|
+
writeDurationMs: params.dynamicStats.writeDurationMs,
|
|
1116
|
+
bufferUploadDurationMs: params.bufferUploadDurationMs,
|
|
1117
|
+
totalDurationMs: params.totalDurationMs
|
|
1118
|
+
}
|
|
1119
|
+
)();
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
/** Format a duration for compact inline probe messages. */
|
|
1123
|
+
function formatFastTextDurationMs(durationMs: number): string {
|
|
1124
|
+
return `${durationMs.toFixed(durationMs < 10 ? 2 : 1)}ms`;
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
/** Return the shared Tracevis debug log when it is available in the page. */
|
|
1128
|
+
function getFastTextProbeLog(): {
|
|
1129
|
+
/** Probe.gl-compatible timing probe method. */
|
|
1130
|
+
probe: (logLevel: unknown, message?: unknown, ...args: unknown[]) => () => void;
|
|
1131
|
+
} {
|
|
1132
|
+
const globalLog = (globalThis as {traceLayers?: {log?: typeof fastTextLog}}).traceLayers?.log;
|
|
1133
|
+
return globalLog ?? fastTextLog;
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/** Create luma buffers for every per-glyph typed array. */
|
|
1137
|
+
function createFastTextBuffers(
|
|
1138
|
+
device: LayerContext['device'],
|
|
1139
|
+
glyphData: FastTextGlyphData
|
|
1140
|
+
): FastTextLayerBuffers {
|
|
1141
|
+
const {attributes} = glyphData;
|
|
1142
|
+
return {
|
|
1143
|
+
instanceGlyphData: createVertexBuffer(
|
|
1144
|
+
device,
|
|
1145
|
+
attributes.instanceGlyphData,
|
|
1146
|
+
new Uint8Array(FAST_TEXT_GLYPH_VERTEX_BYTE_STRIDE)
|
|
1147
|
+
),
|
|
1148
|
+
instancePositions: createVertexBuffer(device, attributes.instancePositions, new Float32Array(2))
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
/** Create one non-empty luma vertex buffer for a typed array. */
|
|
1153
|
+
function createVertexBuffer<T extends ArrayBufferView>(
|
|
1154
|
+
device: LayerContext['device'],
|
|
1155
|
+
data: T,
|
|
1156
|
+
fallback: T
|
|
1157
|
+
): Buffer {
|
|
1158
|
+
return device.createBuffer({
|
|
1159
|
+
data: data.byteLength > 0 ? data : fallback,
|
|
1160
|
+
usage: Buffer.VERTEX | Buffer.COPY_DST
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/** Destroy all owned glyph vertex buffers. */
|
|
1165
|
+
function destroyFastTextBuffers(buffers?: FastTextLayerBuffers): void {
|
|
1166
|
+
if (!buffers) {
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
for (const buffer of Object.values(buffers)) {
|
|
1170
|
+
buffer.destroy();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
/** Upload one canvas-backed font atlas into a luma texture. */
|
|
1175
|
+
function createFastTextAtlasTexture(
|
|
1176
|
+
device: LayerContext['device'],
|
|
1177
|
+
atlas: FastTextFontAtlas
|
|
1178
|
+
): Texture {
|
|
1179
|
+
const texture = device.createTexture({
|
|
1180
|
+
format: 'rgba8unorm',
|
|
1181
|
+
data: null,
|
|
1182
|
+
width: atlas.width,
|
|
1183
|
+
height: atlas.height,
|
|
1184
|
+
sampler: DEFAULT_SAMPLER_PARAMETERS,
|
|
1185
|
+
mipLevels: device.getMipLevelCount(atlas.width, atlas.height)
|
|
1186
|
+
});
|
|
1187
|
+
texture.copyExternalImage({
|
|
1188
|
+
image: atlas.data,
|
|
1189
|
+
width: atlas.width,
|
|
1190
|
+
height: atlas.height
|
|
1191
|
+
});
|
|
1192
|
+
regenerateMipmaps(texture);
|
|
1193
|
+
return texture;
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/** Regenerate texture mipmaps for WebGL or WebGPU devices. */
|
|
1197
|
+
function regenerateMipmaps(texture: Texture): void {
|
|
1198
|
+
if (texture.device.type === 'webgl') {
|
|
1199
|
+
texture.generateMipmapsWebGL();
|
|
1200
|
+
} else if (texture.device.type === 'webgpu') {
|
|
1201
|
+
texture.device.generateMipmapsWebGPU(texture);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
/** Return the shader enum value for one fast-text content alignment mode. */
|
|
1206
|
+
function getFastTextContentAlignUniform(mode: FastTextContentAlign): number {
|
|
1207
|
+
return FAST_TEXT_CONTENT_ALIGN[mode];
|
|
1208
|
+
}
|