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

visgl / luma.gl / 23968233624

04 Apr 2026 01:19AM UTC coverage: 73.637% (+0.1%) from 73.502%
23968233624

Pull #2586

github

web-flow
Merge 66ff4fb33 into 5cc1167fd
Pull Request #2586: fix(webgpu): BufferMap support

4851 of 7478 branches covered (64.87%)

Branch coverage included in aggregate %.

104 of 108 new or added lines in 9 files covered. (96.3%)

4 existing lines in 2 files now uncovered.

10975 of 14014 relevant lines covered (78.31%)

651.57 hits per line

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

92.63
/modules/core/src/adapter-utils/buffer-layout-utils.ts
1
// luma.gl
2
// SPDX-License-Identifier: MIT
3
// Copyright (c) vis.gl contributors
4

5
import {log} from '../utils/log';
6
import type {BufferLayout} from '../adapter/types/buffer-layout';
7
import type {ShaderLayout} from '../adapter/types/shader-layout';
8
import type {VertexFormat} from '../shadertypes/vertex-types/vertex-formats';
9
import {shaderTypeDecoder} from '../shadertypes/shader-types/shader-type-decoder';
10
import {vertexFormatDecoder} from '../shadertypes/vertex-types/vertex-format-decoder';
11

12
/** Backend-agnostic attribute mapping derived from a shader layout and buffer layout. */
13
export type LogicalAttributeMapping = {
14
  /** Attribute name from the shader layout. */
15
  attributeName: string;
16
  /** Logical buffer name supplying this attribute. */
17
  bufferName: string;
18
  /** Attribute location in the shader. */
19
  location: number;
20
  /** Vertex format used to read the attribute data. */
21
  vertexFormat: VertexFormat;
22
  /** Byte offset of the attribute inside the logical buffer. */
23
  byteOffset: number;
24
  /** Byte stride of the logical buffer. */
25
  byteStride: number;
26
  /** Step mode used when advancing the attribute. */
27
  stepMode: 'vertex' | 'instance';
28
};
29

30
type ResolveLogicalAttributeMappingsOptions = {
31
  /** Emit warnings when a shader attribute has no explicit buffer layout entry. */
32
  warnOnMissingBufferLayout?: boolean;
33
};
34

35
/**
36
 * Returns the attribute names referenced by a logical buffer layout.
37
 * @param bufferLayout One logical buffer layout.
38
 * @returns The shader attribute names supplied by the layout.
39
 */
40
export function getBufferLayoutAttributeNames(bufferLayout: BufferLayout): string[] {
41
  return bufferLayout.attributes
2,588✔
42
    ? bufferLayout.attributes.map(layout => layout.attribute)
17✔
43
    : [bufferLayout.name];
44
}
45

46
/**
47
 * Builds a lookup table from shader attribute name to source location.
48
 * @param shaderLayout Shader-side attribute declarations.
49
 * @returns Attribute locations keyed by attribute name.
50
 */
51
export function getShaderAttributeLocationMap(
52
  shaderLayout: ShaderLayout
53
): Record<string, number | undefined> {
54
  return Object.fromEntries(
159✔
55
    shaderLayout.attributes.map(attribute => [attribute.name, attribute.location])
484✔
56
  );
57
}
58

59
/**
60
 * Returns the lowest defined attribute location from a set of candidate locations.
61
 * @param locations Candidate attribute locations.
62
 * @returns The minimum defined location, or `Infinity` when none are defined.
63
 */
64
export function getMinimumAttributeLocation(locations: Iterable<number | undefined>): number {
65
  let minLocation = Infinity;
1,670✔
66

67
  for (const location of locations) {
1,670✔
68
    if (location !== undefined) {
1,677✔
69
      minLocation = Math.min(minLocation, location);
1,235✔
70
    }
71
  }
72

73
  return minLocation;
1,670✔
74
}
75

76
/**
77
 * Maps logical buffer names to the vertex-array slots implied by a buffer layout.
78
 * @param shaderLayout Shader-side attribute declarations.
79
 * @param bufferLayout Buffer-to-attribute mapping declarations.
80
 * @returns Vertex-array slot indexes keyed by logical buffer name.
81
 */
82
export function getLogicalBufferSlots(
83
  shaderLayout: ShaderLayout,
84
  bufferLayout: BufferLayout[]
85
): Record<string, number> {
86
  const usedAttributes = new Set<string>();
167✔
87
  const bufferSlots: Record<string, number> = {};
167✔
88
  let bufferSlot = 0;
167✔
89

90
  for (const mapping of bufferLayout) {
167✔
91
    for (const attributeName of getBufferLayoutAttributeNames(mapping)) {
655✔
92
      usedAttributes.add(attributeName);
658✔
93
    }
94
    bufferSlots[mapping.name] = bufferSlot++;
655✔
95
  }
96

97
  for (const attribute of shaderLayout.attributes) {
167✔
98
    if (!usedAttributes.has(attribute.name)) {
498✔
99
      bufferSlots[attribute.name] = bufferSlot++;
2✔
100
    }
101
  }
102

103
  return bufferSlots;
167✔
104
}
105

106
/**
107
 * Resolves backend-agnostic logical attribute mappings from a shader layout and buffer layout.
108
 * @param shaderLayout Shader-side attribute declarations.
109
 * @param bufferLayout Buffer-to-attribute mapping declarations.
110
 * @param options Optional warning controls.
111
 * @returns One logical mapping per shader attribute, ordered by shader location.
112
 */
113
export function resolveLogicalAttributeMappings(
114
  shaderLayout: ShaderLayout,
115
  bufferLayout: BufferLayout[],
116
  options?: ResolveLogicalAttributeMappingsOptions
117
): LogicalAttributeMapping[] {
118
  validateBufferLayouts(bufferLayout);
184✔
119

120
  const bufferMappingsByAttribute = new Map<
184✔
121
    string,
122
    {
123
      bufferName: string;
124
      stepMode?: 'vertex' | 'instance';
125
      vertexFormat: VertexFormat;
126
      byteOffset: number;
127
      byteStride: number;
128
    }
129
  >();
130

131
  for (const layout of bufferLayout) {
184✔
132
    const byteStride = getBufferLayoutByteStride(layout);
428✔
133

134
    if (layout.attributes) {
428✔
135
      for (const attributeMapping of layout.attributes) {
10✔
136
        if (!bufferMappingsByAttribute.has(attributeMapping.attribute)) {
26!
137
          bufferMappingsByAttribute.set(attributeMapping.attribute, {
26✔
138
            bufferName: layout.name,
139
            stepMode: layout.stepMode,
140
            vertexFormat: attributeMapping.format,
141
            byteOffset: attributeMapping.byteOffset,
142
            byteStride
143
          });
144
        }
145
      }
146
    } else if (layout.format && !bufferMappingsByAttribute.has(layout.name)) {
418!
147
      bufferMappingsByAttribute.set(layout.name, {
418✔
148
        bufferName: layout.name,
149
        stepMode: layout.stepMode,
150
        vertexFormat: layout.format,
151
        byteOffset: 0,
152
        byteStride: layout.byteStride || 0
836✔
153
      });
154
    }
155
  }
156

157
  return shaderLayout.attributes
184✔
158
    .map(attribute => {
159
      const bufferMapping = bufferMappingsByAttribute.get(attribute.name);
355✔
160
      if (!bufferMapping && options?.warnOnMissingBufferLayout) {
355!
NEW
161
        log.warn(`layout for attribute "${attribute.name}" not present in buffer layout`)();
×
162
      }
163

164
      const attributeTypeInfo = shaderTypeDecoder.getAttributeShaderTypeInfo(attribute.type);
355✔
165
      const vertexFormat =
166
        bufferMapping?.vertexFormat ||
355✔
167
        vertexFormatDecoder.getCompatibleVertexFormat(attributeTypeInfo);
168

169
      return {
355✔
170
        attributeName: attribute.name,
171
        bufferName: bufferMapping?.bufferName || attribute.name,
356✔
172
        location: attribute.location,
173
        vertexFormat,
174
        byteOffset: bufferMapping?.byteOffset || 0,
694✔
175
        byteStride: bufferMapping?.byteStride || 0,
684✔
176
        stepMode:
177
          bufferMapping?.stepMode ||
558✔
178
          attribute.stepMode ||
179
          (attribute.name.startsWith('instance') ? 'instance' : 'vertex')
8!
180
      };
181
    })
182
    .sort((mappingA, mappingB) => mappingA.location - mappingB.location);
323✔
183
}
184

185
/**
186
 * Returns the minimum attribute location referenced by a logical buffer layout.
187
 * @param bufferLayout One logical buffer layout.
188
 * @param shaderLayout Shader-side attribute declarations.
189
 * @returns The lowest shader location referenced by the layout.
190
 */
191
export function getBufferLayoutMinAttributeLocation(
192
  bufferLayout: BufferLayout,
193
  shaderLayout: ShaderLayout
194
): number {
195
  const shaderLocationMap = getShaderAttributeLocationMap(shaderLayout);
1✔
196
  return getMinimumAttributeLocation(
1✔
197
    getBufferLayoutAttributeNames(bufferLayout).map(
198
      attributeName => shaderLocationMap[attributeName]
2✔
199
    )
200
  );
201
}
202

203
/**
204
 * Validates that each logical buffer layout declares either shorthand or interleaved attributes.
205
 * @param bufferLayouts Buffer-to-attribute mapping declarations.
206
 */
207
function validateBufferLayouts(bufferLayouts: BufferLayout[]): void {
208
  for (const bufferLayout of bufferLayouts) {
184✔
209
    if (
428!
210
      (bufferLayout.attributes && bufferLayout.format) ||
1,284✔
211
      (!bufferLayout.attributes && !bufferLayout.format)
212
    ) {
NEW
213
      log.warn(
×
214
        `BufferLayout ${bufferLayout.name} must have either 'attributes' or 'format' field`
215
      )();
216
    }
217
  }
218
}
219

220
/**
221
 * Returns the effective byte stride for a logical buffer layout.
222
 * @param bufferLayout One logical buffer layout.
223
 * @returns The explicit stride, or the packed stride implied by the mapped formats.
224
 */
225
function getBufferLayoutByteStride(bufferLayout: BufferLayout): number {
226
  if (typeof bufferLayout.byteStride === 'number') {
428✔
227
    return bufferLayout.byteStride;
4✔
228
  }
229

230
  if (bufferLayout.attributes) {
424✔
231
    let packedByteStride = 0;
6✔
232
    for (const attributeMapping of bufferLayout.attributes) {
6✔
233
      packedByteStride += vertexFormatDecoder.getVertexFormatInfo(
8✔
234
        attributeMapping.format
235
      ).byteLength;
236
    }
237
    return packedByteStride;
6✔
238
  }
239

240
  return vertexFormatDecoder.getVertexFormatInfo(bufferLayout.format!).byteLength;
418✔
241
}
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