• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / luma.gl / 28700361014

04 Jul 2026 08:20AM UTC coverage: 72.161% (-0.3%) from 72.426%
28700361014

push

github

web-flow
feat(arrow) Bring deck.gl Arrow examples to v10 input parity (#2710)

11247 of 17538 branches covered (64.13%)

Branch coverage included in aggregate %.

329 of 546 new or added lines in 18 files covered. (60.26%)

27 existing lines in 10 files now uncovered.

21673 of 28082 relevant lines covered (77.18%)

5643.18 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

66.95
/modules/tables/src/models/path/path-attribute-model.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {
6
  type Buffer,
7
  type BufferLayout,
8
  type Device,
9
  type RenderPass,
10
  type ShaderLayout
11
} from '@luma.gl/core';
12
import {GPUTableModel, type GPUTableModelProps} from '../../engine/gpu-table-model';
13
import type {GPUTable} from '../../table/gpu-table';
14
import type {GPUVector} from '../../table/gpu-vector';
15
import {getGPUDataBuffersForLayout} from '../../table/gpu-vector-utils';
16
import {isVertexListGPUVectorFormat, type VertexList} from '../../table/gpu-vector-format';
17
import type {GeneratedBufferBatch} from '../../utils/generated-buffer-batches';
18
import {type GPUInputSchema, validateGPUInputVectors} from '../../engine/gpu-input-schema';
19

20
const SEGMENT_START_POSITIONS_COLUMN = 'segmentStartPositions';
62✔
21
const SEGMENT_END_POSITIONS_COLUMN = 'segmentEndPositions';
62✔
22
const SEGMENT_PREVIOUS_POSITIONS_COLUMN = 'segmentPreviousPositions';
62✔
23
const SEGMENT_NEXT_POSITIONS_COLUMN = 'segmentNextPositions';
62✔
24
const SEGMENT_START_COLORS_COLUMN = 'segmentStartColors';
62✔
25
const SEGMENT_END_COLORS_COLUMN = 'segmentEndColors';
62✔
26
const SEGMENT_FLAGS_COLUMN = 'segmentFlags';
62✔
27
const ROW_INDICES_COLUMN = 'rowIndices';
62✔
28
const EXPANDED_PATH_VERTEX_DATA = 'expandedPathVertexData';
62✔
29
const PATH_VIEW_ORIGINS_COLUMN = 'pathViewOrigins';
62✔
30
const PATH_VIEW_ORIGIN_DATA = 'pathViewOriginData';
62✔
31

32
const EXPANDED_PATH_VERTEX_BYTE_STRIDE =
33
  Float32Array.BYTES_PER_ELEMENT * 16 + Uint32Array.BYTES_PER_ELEMENT * 4;
62✔
34
const SEGMENT_END_POSITIONS_BYTE_OFFSET = Float32Array.BYTES_PER_ELEMENT * 4;
62✔
35
const SEGMENT_PREVIOUS_POSITIONS_BYTE_OFFSET = Float32Array.BYTES_PER_ELEMENT * 8;
62✔
36
const SEGMENT_NEXT_POSITIONS_BYTE_OFFSET = Float32Array.BYTES_PER_ELEMENT * 12;
62✔
37
const SEGMENT_FLAGS_BYTE_OFFSET = Float32Array.BYTES_PER_ELEMENT * 16;
62✔
38
const ROW_INDICES_BYTE_OFFSET = SEGMENT_FLAGS_BYTE_OFFSET + Uint32Array.BYTES_PER_ELEMENT;
62✔
39
const SEGMENT_START_COLORS_BYTE_OFFSET = ROW_INDICES_BYTE_OFFSET + Uint32Array.BYTES_PER_ELEMENT;
62✔
40
const SEGMENT_END_COLORS_BYTE_OFFSET =
41
  SEGMENT_START_COLORS_BYTE_OFFSET + Uint32Array.BYTES_PER_ELEMENT;
62✔
42

43
/** Prepared GPU inputs consumed by the attribute-backed path model. */
44
export const PATH_ATTRIBUTE_GPU_INPUT_SCHEMA = [
62✔
45
  {
46
    columnName: 'paths',
47
    kind: 'positions',
48
    required: true,
49
    formats: ['vertex-list<float32x2>', 'vertex-list<float32x3>', 'vertex-list<float32x4>']
50
  },
51
  {
52
    columnName: 'colors',
53
    kind: 'colors',
54
    required: false,
55
    formats: ['unorm8x4', 'vertex-list<unorm8x4>']
56
  },
57
  {
58
    columnName: 'widths',
59
    kind: 'scalars',
60
    required: false,
61
    formats: ['float32']
62
  },
63
  {
64
    columnName: 'viewOrigins',
65
    kind: 'positions',
66
    required: false,
67
    formats: ['float32x4'],
68
    internal: true
69
  }
70
] as const satisfies GPUInputSchema;
71

72
const DEFAULT_PATH_SHADER_LAYOUT: ShaderLayout = {
62✔
73
  attributes: [
74
    {name: SEGMENT_START_POSITIONS_COLUMN, location: 0, type: 'vec4<f32>', stepMode: 'instance'},
75
    {name: SEGMENT_END_POSITIONS_COLUMN, location: 1, type: 'vec4<f32>', stepMode: 'instance'},
76
    {name: SEGMENT_START_COLORS_COLUMN, location: 2, type: 'u32', stepMode: 'instance'},
77
    {name: SEGMENT_END_COLORS_COLUMN, location: 3, type: 'u32', stepMode: 'instance'},
78
    {name: PATH_VIEW_ORIGINS_COLUMN, location: 4, type: 'vec4<f32>', stepMode: 'instance'}
79
  ],
80
  bindings: []
81
};
82

83
const DEFAULT_PATH_VS = `#version 300 es
62✔
84
precision highp float;
85

86
in vec4 segmentStartPositions;
87
in vec4 segmentEndPositions;
88
in uint segmentStartColors;
89
in uint segmentEndColors;
90
in vec4 pathViewOrigins;
91

92
void main() {
93
  vec4 pathDelta = gl_VertexID % 2 == 0 ? segmentStartPositions : segmentEndPositions;
94
  vec4 pathPosition = pathViewOrigins + pathDelta;
95
  gl_Position = vec4(pathPosition.xyz, 1.0);
96
}
97
`;
98

99
const DEFAULT_PATH_FS = `#version 300 es
62✔
100
precision highp float;
101

102
out vec4 fragColor;
103

104
void main() {
105
  fragColor = vec4(1.0);
106
}
107
`;
108

109
/** CPU-generated segment geometry diagnostics retained by prepared path render state. */
110
export type PathSegmentLayout = {
111
  /** Cumulative segment offsets, length = source path rows + 1. */
112
  startIndices: number[];
113
  /** Number of generated segment records. */
114
  segmentCount: number;
115
  /** Generated segment starts, padded to four Float32 lanes per segment. */
116
  segmentStartPositions: Float32Array;
117
  /** Generated segment ends, padded to four Float32 lanes per segment. */
118
  segmentEndPositions: Float32Array;
119
  /** Previous coordinate used for join/cap decisions, padded to four Float32 lanes. */
120
  segmentPreviousPositions: Float32Array;
121
  /** Next coordinate used for join/cap decisions, padded to four Float32 lanes. */
122
  segmentNextPositions: Float32Array;
123
  /** Per-segment view-space path origin, padded to four Float32 lanes. */
124
  segmentViewOrigins: Float32Array;
125
  /** Packed first/last/closed flags for each generated segment. */
126
  segmentFlags: Uint32Array;
127
  /** Packed RGBA8 color at each generated segment start point. */
128
  segmentStartColors: Uint32Array;
129
  /** Packed RGBA8 color at each generated segment end point. */
130
  segmentEndColors: Uint32Array;
131
};
132

133
/** Generated render-batch state consumed by {@link PathAttributeModel}. */
134
export type PathRenderBatchState = {
135
  /** First source path row included in this generated render batch. */
136
  rowStart: number;
137
  /** Source path row after the last row included in this generated render batch. */
138
  rowEnd: number;
139
  /** Generated segment records drawn by this render batch. */
140
  segmentCount: number;
141
  /** Generated packed segment vertex attribute buffer. */
142
  expandedPathVertexData: Buffer;
143
  /** Generated per-segment Float32 view-origin attribute buffer. */
144
  pathViewOriginData: Buffer;
145
};
146

147
/** GPU-only path render state prepared before constructing {@link PathAttributeModel}. */
148
export type PathAttributeModelState = {
149
  /** Generated path segment layout diagnostics. */
150
  segmentLayout: PathSegmentLayout;
151
  /** Generated render batches preserved for device buffer-size limits. */
152
  renderBatches: PathRenderBatchState[];
153
  /** Planning metadata for generated render batches. */
154
  generatedBufferBatches: GeneratedBufferBatch[];
155
  /** First generated packed segment vertex attribute buffer. */
156
  expandedPathVertexData: Buffer;
157
  /** First generated per-segment Float32 view-origin attribute buffer. */
158
  pathViewOriginData: Buffer;
159
};
160

161
/** Props for the GPU-only attribute-backed path renderer. */
162
export type PathAttributeModelProps = Omit<GPUTableModelProps, 'table' | 'tableCount'> & {
163
  /** GPU table supplying already prepared segment-compatible row attributes. */
164
  table: GPUTable;
165
  /** Whether the model should destroy its prepared GPU table. Defaults to `false`. */
166
  ownsTable?: boolean;
167
  /** Variable-length Float32 XY, XYZ, or XYZM path coordinates, one GPU row per path. */
168
  paths: GPUVector<VertexList<'float32x2' | 'float32x3' | 'float32x4'>>;
169
  /** Optional packed RGBA8 path colors, either one per path row or one per path vertex. */
170
  colors?: GPUVector<'unorm8x4' | VertexList<'unorm8x4'>>;
171
  /** Optional per-path widths, one GPU row per path. */
172
  widths?: GPUVector<'float32'>;
173
  /** Optional per-path view-space origins, one GPU row per path. */
174
  viewOrigins?: GPUVector<'float32x4'>;
175
  /** Prepared GPU-only path expansion state. */
176
  pathState: PathAttributeModelState;
177
};
178

179
type PreparedPathAttributeModel = {
180
  modelProps: GPUTableModelProps;
181
  segmentLayout: PathSegmentLayout;
182
  expandedPathVertexData: Buffer;
183
  pathViewOriginData: Buffer;
184
  renderBatches: PathRenderBatchState[];
185
};
186

187
/** GPU-only path renderer that consumes already prepared path GPU vectors and render state. */
188
export class PathAttributeModel extends GPUTableModel {
189
  /** Prepared GPU vectors consumed by the attribute-backed path model. */
190
  static readonly gpuInputSchema = PATH_ATTRIBUTE_GPU_INPUT_SCHEMA;
62✔
191

192
  /** Generated path segment layout diagnostics. */
193
  segmentLayout: PathSegmentLayout;
194
  /** First generated packed segment vertex attribute buffer. */
195
  expandedPathVertexData: Buffer;
196
  /** First generated per-segment Float32 view-origin attribute buffer. */
197
  pathViewOriginData: Buffer;
198
  /** Generated render batches preserved for device buffer-size limits. */
199
  renderBatches: PathRenderBatchState[];
200
  private pathProps: PathAttributeModelProps;
201
  private pathShaderLayout: ShaderLayout;
202
  private pathTable: GPUTable;
203
  private ownsPathTable: boolean;
204

205
  /** Creates an attribute-backed path model from prepared GPU props. */
206
  constructor(device: Device, props: PathAttributeModelProps) {
207
    const prepared = preparePathAttributeModel(props);
8✔
208
    super(device, prepared.modelProps);
6✔
209
    this.pathTable = props.table;
6✔
210
    this.ownsPathTable = props.ownsTable ?? false;
6!
211
    this.pathProps = props;
6✔
212
    this.pathShaderLayout = prepared.modelProps.shaderLayout!;
6✔
213
    this.segmentLayout = prepared.segmentLayout;
6✔
214
    this.expandedPathVertexData = prepared.expandedPathVertexData;
6✔
215
    this.pathViewOriginData = prepared.pathViewOriginData;
6✔
216
    this.renderBatches = prepared.renderBatches;
6✔
217
  }
218

219
  /** Replace generated segment records with new prepared GPU path state. */
220
  override setProps(props: Partial<PathAttributeModelProps>): void {
221
    const nextProps = {...this.pathProps, ...props};
×
222
    const shouldRebuild =
223
      props.pathState !== undefined || props.table !== undefined || props.ownsTable !== undefined;
×
224

225
    this.pathProps = nextProps;
×
226
    if (!shouldRebuild) {
×
227
      super.setProps({});
×
228
      return;
×
229
    }
230

231
    const prepared = preparePathAttributeModel(nextProps);
×
232
    this.segmentLayout = prepared.segmentLayout;
×
233
    this.expandedPathVertexData = prepared.expandedPathVertexData;
×
234
    this.pathViewOriginData = prepared.pathViewOriginData;
×
235
    this.renderBatches = prepared.renderBatches;
×
236
    this.pathShaderLayout = prepared.modelProps.shaderLayout!;
×
237

238
    const previousPathTable = this.pathTable;
×
239
    const previousOwnsPathTable = this.ownsPathTable;
×
240
    this.pathTable = nextProps.table;
×
241
    this.ownsPathTable = nextProps.ownsTable ?? false;
×
242
    super.setProps({table: this.pathTable});
×
243
    if (previousOwnsPathTable && previousPathTable !== this.pathTable) {
×
244
      previousPathTable.destroy();
×
245
    }
246
    this.setAttributes(
×
247
      getPathAttributeModelAttributes(prepared.modelProps.shaderLayout!, prepared)
248
    );
249
    this.setInstanceCount(prepared.segmentLayout.segmentCount);
×
250
    this.setNeedsRedraw('Path segment state updated');
×
251
  }
252

253
  /** Draws each generated path render batch against the supplied render pass. */
254
  override draw(renderPass: RenderPass): boolean {
255
    const tableBatches = this.table?.batches || [];
8!
256
    if (tableBatches.length > 0 && tableBatches.length !== this.renderBatches.length) {
8!
257
      throw new Error(
×
258
        'PathAttributeModel draw batches must align with generated path render batches'
259
      );
260
    }
261

262
    let drawSuccess = true;
8✔
263
    try {
8✔
264
      for (const [batchIndex, renderBatch] of this.renderBatches.entries()) {
8✔
265
        const tableBatch = tableBatches[batchIndex];
8✔
266
        this.setAttributes({
8✔
267
          ...(tableBatch
8!
268
            ? getGPUDataBuffersForLayout(tableBatch.bufferLayout, tableBatch.gpuData)
269
            : {}),
270
          ...getPathAttributeModelBatchAttributes(this.pathShaderLayout, renderBatch)
271
        });
272
        this.setInstanceCount(renderBatch.segmentCount);
8✔
273
        drawSuccess = super.draw(renderPass) && drawSuccess;
8✔
274
      }
275
    } finally {
276
      this.setAttributes({
8✔
277
        ...getPathTableAttributes(this.table),
278
        ...getPathAttributeModelAttributes(this.pathShaderLayout, {
279
          expandedPathVertexData: this.expandedPathVertexData,
280
          pathViewOriginData: this.pathViewOriginData
281
        })
282
      });
283
      this.setInstanceCount(this.segmentLayout.segmentCount);
8✔
284
    }
285

286
    return drawSuccess;
8✔
287
  }
288

289
  /** Releases inherited model resources. Prepared GPU path state remains caller-owned. */
290
  override destroy(): void {
291
    super.destroy();
6✔
292
    if (this.ownsPathTable) {
6!
293
      this.pathTable.destroy();
6✔
294
      this.ownsPathTable = false;
6✔
295
    }
296
  }
297
}
298

299
function getPathTableAttributes(table?: GPUTable): ReturnType<typeof getGPUDataBuffersForLayout> {
300
  const firstBatch = table?.batches[0];
8✔
301
  if (!firstBatch) {
8!
302
    return {};
×
303
  }
304
  return getGPUDataBuffersForLayout(firstBatch.bufferLayout, firstBatch.gpuData);
8✔
305
}
306

307
function preparePathAttributeModel(props: PathAttributeModelProps): PreparedPathAttributeModel {
308
  assertArrowPathVectorTypes(props);
8✔
309
  assertArrowPathVectorRowAlignment(props);
7✔
310
  assertPathPreparedStateAlignment(props);
7✔
311
  const shaderLayout = props.shaderLayout ?? DEFAULT_PATH_SHADER_LAYOUT;
6!
312
  const firstRenderBatch = props.pathState.renderBatches[0];
6✔
313
  if (!firstRenderBatch) {
6!
314
    throw new Error('PathAttributeModel requires at least one prepared path render batch');
×
315
  }
316
  const explicitShaderAttributeNames = getExplicitShaderAttributeNames(props);
6✔
317

318
  return {
6✔
319
    modelProps: {
320
      ...props,
321
      vs: props.vs ?? DEFAULT_PATH_VS,
8✔
322
      fs: props.fs ?? DEFAULT_PATH_FS,
8✔
323
      shaderLayout,
324
      attributes: {
325
        ...(props.attributes || {}),
8✔
326
        ...getPathAttributeModelBatchAttributes(shaderLayout, firstRenderBatch)
327
      },
328
      bufferLayout: [
329
        ...(props.bufferLayout || []),
8✔
330
        ...createArrowPathBufferLayouts(shaderLayout, explicitShaderAttributeNames)
331
      ],
332
      topology: props.topology ?? 'line-list',
8✔
333
      vertexCount: props.vertexCount ?? 2,
8✔
334
      instanceCount: props.pathState.segmentLayout.segmentCount,
335
      table: props.table,
336
      tableCount: 'none'
337
    },
338
    segmentLayout: props.pathState.segmentLayout,
339
    expandedPathVertexData: firstRenderBatch.expandedPathVertexData,
340
    pathViewOriginData: firstRenderBatch.pathViewOriginData,
341
    renderBatches: props.pathState.renderBatches
342
  };
343
}
344

345
function assertArrowPathVectorTypes(props: PathAttributeModelProps): void {
346
  validateGPUInputVectors('PathAttributeModel', PathAttributeModel.gpuInputSchema, {
8✔
347
    paths: props.paths,
348
    colors: props.colors,
349
    widths: props.widths,
350
    viewOrigins: props.viewOrigins
351
  });
352
}
353

354
function assertArrowPathVectorRowAlignment(props: PathAttributeModelProps): void {
355
  const rowInputs = getArrowPathRowInputs(props);
7✔
356
  const [referenceName, referenceVector] = rowInputs[0];
7✔
357
  for (const [name, vector] of rowInputs.slice(1)) {
7✔
358
    if (vector.length !== referenceVector.length) {
5!
359
      throw new Error(
×
360
        `PathAttributeModel ${name} rows must match ${referenceName} rows (${vector.length} !== ${referenceVector.length})`
361
      );
362
    }
363
    if (vector.data.length !== referenceVector.data.length) {
5!
364
      throw new Error(
×
365
        `PathAttributeModel ${name} batch count must match ${referenceName} batch count`
366
      );
367
    }
368
    for (let batchIndex = 0; batchIndex < vector.data.length; batchIndex++) {
5✔
369
      if (vector.data[batchIndex].length !== referenceVector.data[batchIndex].length) {
5!
370
        throw new Error(
×
371
          `PathAttributeModel ${name} batch ${batchIndex} rows must match ${referenceName}`
372
        );
373
      }
374
    }
375
  }
376
  if (props.colors && isPathVertexColorGPUVector(props.colors)) {
7!
377
    assertPathVertexColorGPUVectorAlignment(props.paths, props.colors);
×
378
  }
379
}
380

381
function getArrowPathRowInputs(props: PathAttributeModelProps): Array<[string, GPUVector]> {
382
  return [
7✔
383
    ['paths', props.paths],
384
    ['colors', props.colors],
385
    ['widths', props.widths],
386
    ['viewOrigins', props.viewOrigins]
387
  ].filter(([, vector]) => vector !== undefined) as Array<[string, GPUVector]>;
28✔
388
}
389

390
function isPathVertexColorGPUVector(
391
  colors: NonNullable<PathAttributeModelProps['colors']>
392
): boolean {
393
  return colors.format !== undefined && isVertexListGPUVectorFormat(colors.format);
3✔
394
}
395

396
function assertPathPreparedStateAlignment(props: PathAttributeModelProps): void {
397
  if (!props.pathState) {
7!
398
    throw new Error('PathAttributeModel requires prepared pathState');
×
399
  }
400
  const preparedRowCount = props.pathState.segmentLayout.startIndices.length - 1;
7✔
401
  if (preparedRowCount !== props.paths.length) {
7✔
402
    throw new Error(
1✔
403
      `PathAttributeModel prepared path rows must match path GPU rows (${preparedRowCount} !== ${props.paths.length})`
404
    );
405
  }
406
}
407

408
function assertPathVertexColorGPUVectorAlignment(paths: GPUVector, colors: GPUVector): void {
409
  for (let batchIndex = 0; batchIndex < paths.data.length; batchIndex++) {
×
410
    const pathOffsets = paths.data[batchIndex]?.valueOffsets;
×
411
    const colorOffsets = colors.data[batchIndex]?.valueOffsets;
×
412
    if (!pathOffsets || !colorOffsets || !arePathOffsetsEqual(pathOffsets, colorOffsets)) {
×
413
      throw new Error('PathAttributeModel vertex colors must align with path vertex offsets');
×
414
    }
415
  }
416
}
417

418
function arePathOffsetsEqual(left: Int32Array, right: Int32Array): boolean {
419
  return left.length === right.length && left.every((value, index) => value === right[index]);
×
420
}
421

422
function getExplicitShaderAttributeNames(props: PathAttributeModelProps): Set<string> {
423
  const attributeNames = new Set(Object.keys(props.constantAttributes || {}));
6✔
424
  for (const layout of props.bufferLayout || []) {
6✔
425
    if (layout.attributes) {
11!
426
      for (const attribute of layout.attributes) {
11✔
427
        attributeNames.add(attribute.attribute);
22✔
428
      }
429
    } else {
NEW
430
      attributeNames.add(layout.name);
×
431
    }
432
  }
433
  return attributeNames;
6✔
434
}
435

436
function createArrowPathBufferLayouts(
437
  shaderLayout: ShaderLayout,
438
  excludedAttributeNames: Set<string>
439
): BufferLayout[] {
440
  const shaderAttributeNames = new Set(
6✔
441
    shaderLayout.attributes
442
      .map(attribute => attribute.name)
50✔
443
      .filter(attributeName => !excludedAttributeNames.has(attributeName))
50✔
444
  );
445
  const expandedAttributes: NonNullable<BufferLayout['attributes']> = [];
6✔
446
  if (shaderAttributeNames.has(SEGMENT_START_POSITIONS_COLUMN)) {
6!
447
    expandedAttributes.push({
6✔
448
      attribute: SEGMENT_START_POSITIONS_COLUMN,
449
      format: 'float32x4',
450
      byteOffset: 0
451
    });
452
  }
453
  if (shaderAttributeNames.has(SEGMENT_END_POSITIONS_COLUMN)) {
6!
454
    expandedAttributes.push({
6✔
455
      attribute: SEGMENT_END_POSITIONS_COLUMN,
456
      format: 'float32x4',
457
      byteOffset: SEGMENT_END_POSITIONS_BYTE_OFFSET
458
    });
459
  }
460
  if (shaderAttributeNames.has(SEGMENT_PREVIOUS_POSITIONS_COLUMN)) {
6!
461
    expandedAttributes.push({
×
462
      attribute: SEGMENT_PREVIOUS_POSITIONS_COLUMN,
463
      format: 'float32x4',
464
      byteOffset: SEGMENT_PREVIOUS_POSITIONS_BYTE_OFFSET
465
    });
466
  }
467
  if (shaderAttributeNames.has(SEGMENT_NEXT_POSITIONS_COLUMN)) {
6!
468
    expandedAttributes.push({
×
469
      attribute: SEGMENT_NEXT_POSITIONS_COLUMN,
470
      format: 'float32x4',
471
      byteOffset: SEGMENT_NEXT_POSITIONS_BYTE_OFFSET
472
    });
473
  }
474
  if (shaderAttributeNames.has(SEGMENT_FLAGS_COLUMN)) {
6!
475
    expandedAttributes.push({
×
476
      attribute: SEGMENT_FLAGS_COLUMN,
477
      format: 'uint32',
478
      byteOffset: SEGMENT_FLAGS_BYTE_OFFSET
479
    });
480
  }
481
  if (shaderAttributeNames.has(ROW_INDICES_COLUMN)) {
6✔
482
    expandedAttributes.push({
4✔
483
      attribute: ROW_INDICES_COLUMN,
484
      format: 'uint32',
485
      byteOffset: ROW_INDICES_BYTE_OFFSET
486
    });
487
  }
488
  if (shaderAttributeNames.has(SEGMENT_START_COLORS_COLUMN)) {
6✔
489
    expandedAttributes.push({
3✔
490
      attribute: SEGMENT_START_COLORS_COLUMN,
491
      format: 'uint32',
492
      byteOffset: SEGMENT_START_COLORS_BYTE_OFFSET
493
    });
494
  }
495
  if (shaderAttributeNames.has(SEGMENT_END_COLORS_COLUMN)) {
6✔
496
    expandedAttributes.push({
3✔
497
      attribute: SEGMENT_END_COLORS_COLUMN,
498
      format: 'uint32',
499
      byteOffset: SEGMENT_END_COLORS_BYTE_OFFSET
500
    });
501
  }
502

503
  const bufferLayouts: BufferLayout[] = [
6✔
504
    {
505
      name: EXPANDED_PATH_VERTEX_DATA,
506
      byteStride: EXPANDED_PATH_VERTEX_BYTE_STRIDE,
507
      stepMode: 'instance',
508
      attributes: expandedAttributes
509
    }
510
  ];
511

512
  if (shaderAttributeNames.has(PATH_VIEW_ORIGINS_COLUMN)) {
6!
513
    bufferLayouts.push({
6✔
514
      name: PATH_VIEW_ORIGIN_DATA,
515
      byteStride: Float32Array.BYTES_PER_ELEMENT * 4,
516
      stepMode: 'instance',
517
      attributes: [
518
        {
519
          attribute: PATH_VIEW_ORIGINS_COLUMN,
520
          format: 'float32x4',
521
          byteOffset: 0
522
        }
523
      ]
524
    });
525
  }
526

527
  return bufferLayouts;
6✔
528
}
529

530
function getPathAttributeModelAttributes(
531
  shaderLayout: ShaderLayout,
532
  state: Pick<PathAttributeModelState, 'expandedPathVertexData' | 'pathViewOriginData'>
533
): Record<string, Buffer> {
534
  const shaderAttributeNames = new Set(shaderLayout.attributes.map(attribute => attribute.name));
210✔
535
  return {
22✔
536
    [EXPANDED_PATH_VERTEX_DATA]: state.expandedPathVertexData,
537
    ...(shaderAttributeNames.has(PATH_VIEW_ORIGINS_COLUMN)
22!
538
      ? {[PATH_VIEW_ORIGIN_DATA]: state.pathViewOriginData}
539
      : {})
540
  };
541
}
542

543
function getPathAttributeModelBatchAttributes(
544
  shaderLayout: ShaderLayout,
545
  renderBatch: PathRenderBatchState
546
): Record<string, Buffer> {
547
  return getPathAttributeModelAttributes(shaderLayout, renderBatch);
14✔
548
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc