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

visgl / luma.gl / 17192422902

24 Aug 2025 06:41PM UTC coverage: 76.322% (+1.1%) from 75.234%
17192422902

push

github

web-flow
test(engine): add ShaderPassRenderer test (#2437)

2264 of 2952 branches covered (76.69%)

Branch coverage included in aggregate %.

520 of 666 new or added lines in 7 files covered. (78.08%)

14 existing lines in 1 file now uncovered.

28544 of 37414 relevant lines covered (76.29%)

65.14 hits per line

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

81.92
/modules/engine/src/dynamic-texture/texture-data.ts
1
import type {TypedArray, TextureFormat, ExternalImage} from '@luma.gl/core';
1✔
2
import {isExternalImage, getExternalImageSize} from '@luma.gl/core';
1✔
3

1✔
4
export type TextureImageSource = ExternalImage;
1✔
5

1✔
6
/**
1✔
7
 * One mip level
1✔
8
 * Basic data structure is similar to `ImageData`
1✔
9
 * additional optional fields can describe compressed texture data.
1✔
10
 */
1✔
11
export type TextureImageData = {
1✔
12
  /** WebGPU style format string. Defaults to 'rgba8unorm' */
1✔
13
  format?: TextureFormat;
1✔
14
  /** Typed Array with the bytes of the image. @note beware row byte alignment requirements */
1✔
15
  data: TypedArray;
1✔
16
  /** Width of the image, in pixels, @note beware row byte alignment requirements */
1✔
17
  width: number;
1✔
18
  /** Height of the image, in rows */
1✔
19
  height: number;
1✔
20
};
1✔
21

1✔
22
/**
1✔
23
 * A single mip-level can be initialized by data or an ImageBitmap etc
1✔
24
 * @note in the WebGPU spec a mip-level is called a subresource
1✔
25
 */
1✔
26
export type TextureMipLevelData = TextureImageData | TextureImageSource;
1✔
27

1✔
28
/**
1✔
29
 * Texture data for one image "slice" (which can consist of multiple miplevels)
1✔
30
 * Thus data for one slice be a single mip level or an array of miplevels
1✔
31
 * @note in the WebGPU spec each cross-section image in a 3D texture is called a "slice",
1✔
32
 * in a array texture each image in the array is called an array "layer"
1✔
33
 * luma.gl calls one image in a GPU texture a "slice" regardless of context.
1✔
34
 */
1✔
35
export type TextureSliceData = TextureMipLevelData | TextureMipLevelData[];
1✔
36

1✔
37
/** Names of cube texture faces */
1✔
38
export type TextureCubeFace = '+X' | '-X' | '+Y' | '-Y' | '+Z' | '-Z';
1✔
39

1✔
40
/** Array of cube texture faces. @note: index in array is the face index */
1✔
41
// prettier-ignore
1✔
42
export const TEXTURE_CUBE_FACES = ['+X', '-X', '+Y', '-Y', '+Z', '-Z'] as const satisfies readonly TextureCubeFace[];
1✔
43

1✔
44
/** Map of cube texture face names to face indexes */
1✔
45
// prettier-ignore
1✔
46
export const TEXTURE_CUBE_FACE_MAP = {'+X': 0, '-X': 1, '+Y': 2, '-Y': 3, '+Z': 4, '-Z': 5} as const satisfies Record<TextureCubeFace, number>;
1✔
47

1✔
48
/** @todo - Define what data type is supported for 1D textures. TextureImageData with height = 1 */
1✔
49
export type Texture1DData = TextureSliceData;
1✔
50

1✔
51
/** Texture data can be one or more mip levels */
1✔
52
export type Texture2DData = TextureSliceData;
1✔
53

1✔
54
/** 6 face textures */
1✔
55
export type TextureCubeData = Record<TextureCubeFace, TextureSliceData>;
1✔
56

1✔
57
/** Array of textures */
1✔
58
export type Texture3DData = TextureSliceData[];
1✔
59

1✔
60
/** Array of textures */
1✔
61
export type TextureArrayData = TextureSliceData[];
1✔
62

1✔
63
/** Array of 6 face textures */
1✔
64
export type TextureCubeArrayData = Record<TextureCubeFace, TextureSliceData>[];
1✔
65

1✔
66
type TextureData =
1✔
67
  | Texture1DData
1✔
68
  | Texture3DData
1✔
69
  | TextureArrayData
1✔
70
  | TextureCubeArrayData
1✔
71
  | TextureCubeData;
1✔
72

1✔
73
/** Sync data props */
1✔
74
export type TextureDataProps =
1✔
75
  | {dimension: '1d'; data: Texture1DData | null}
1✔
76
  | {dimension?: '2d'; data: Texture2DData | null}
1✔
77
  | {dimension: '3d'; data: Texture3DData | null}
1✔
78
  | {dimension: '2d-array'; data: TextureArrayData | null}
1✔
79
  | {dimension: 'cube'; data: TextureCubeData | null}
1✔
80
  | {dimension: 'cube-array'; data: TextureCubeArrayData | null};
1✔
81

1✔
82
/** Async data props */
1✔
83
export type TextureDataAsyncProps =
1✔
84
  | {dimension: '1d'; data?: Promise<Texture1DData> | Texture1DData | null}
1✔
85
  | {dimension?: '2d'; data?: Promise<Texture2DData> | Texture2DData | null}
1✔
86
  | {dimension: '3d'; data?: Promise<Texture3DData> | Texture3DData | null}
1✔
87
  | {dimension: '2d-array'; data?: Promise<TextureArrayData> | TextureArrayData | null}
1✔
88
  | {dimension: 'cube'; data?: Promise<TextureCubeData> | TextureCubeData | null}
1✔
89
  | {dimension: 'cube-array'; data?: Promise<TextureCubeArrayData> | TextureCubeArrayData | null};
1✔
90

1✔
91
/** Describes data for one sub resource (one mip level of one slice (depth or array layer)) */
1✔
92
export type TextureSubresource = {
1✔
93
  /** slice (depth or array layer)) */
1✔
94
  z: number;
1✔
95
  /** mip level (0 - max mip levels) */
1✔
96
  mipLevel: number;
1✔
97
} & (
1✔
98
  | {
1✔
99
      type: 'external-image';
1✔
100
      image: ExternalImage;
1✔
101
      /** @deprecated is this an appropriate place for this flag? */
1✔
102
      flipY?: boolean;
1✔
103
    }
1✔
104
  | {
1✔
105
      type: 'texture-data';
1✔
106
      data: TextureImageData;
1✔
107
    }
1✔
108
);
1✔
109

1✔
110
/** Check if texture data is a typed array */
1✔
111
export function isTextureSliceData(data: TextureData): data is TextureImageData {
1✔
112
  const typedArray = (data as TextureImageData)?.data;
5✔
113
  return ArrayBuffer.isView(typedArray);
5✔
114
}
5✔
115

1✔
116
export function getFirstMipLevel(layer: TextureSliceData | null): TextureMipLevelData | null {
1✔
117
  if (!layer) return null;
15✔
118
  return Array.isArray(layer) ? (layer[0] ?? null) : layer;
15✔
119
}
15✔
120

1✔
121
export function getTextureSizeFromData(
1✔
122
  props: TextureDataProps
15✔
123
): {width: number; height: number} | null {
15✔
124
  const {dimension, data} = props;
15✔
125
  if (!data) {
15!
NEW
126
    return null;
×
NEW
127
  }
×
128

15✔
129
  switch (dimension) {
15✔
130
    case '1d': {
15✔
131
      const mipLevel = getFirstMipLevel(data);
1✔
132
      if (!mipLevel) return null;
1!
133
      const {width} = getTextureMipLevelSize(mipLevel);
1✔
134
      return {width, height: 1};
1✔
135
    }
1✔
136
    case '2d': {
15✔
137
      const mipLevel = getFirstMipLevel(data);
6✔
138
      return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
6!
139
    }
6✔
140
    case '3d':
15✔
141
    case '2d-array': {
15✔
142
      if (!Array.isArray(data) || data.length === 0) return null;
4✔
143
      const mipLevel = getFirstMipLevel(data[0]);
2✔
144
      return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
4!
145
    }
4✔
146
    case 'cube': {
15✔
147
      const face = (Object.keys(data)[0] as TextureCubeFace) ?? null;
2✔
148
      if (!face) return null;
2✔
149
      const faceData = (data as Record<TextureCubeFace, TextureSliceData>)[face];
1✔
150
      const mipLevel = getFirstMipLevel(faceData);
1✔
151
      return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
2!
152
    }
2✔
153
    case 'cube-array': {
15✔
154
      if (!Array.isArray(data) || data.length === 0) return null;
2✔
155
      const firstCube = data[0];
1✔
156
      const face = (Object.keys(firstCube)[0] as TextureCubeFace) ?? null;
2!
157
      if (!face) return null;
2!
158
      const mipLevel = getFirstMipLevel(firstCube[face]);
1✔
159
      return mipLevel ? getTextureMipLevelSize(mipLevel) : null;
2!
160
    }
2✔
161
    default:
15!
NEW
162
      return null;
×
163
  }
15✔
164
}
15✔
165

1✔
166
function getTextureMipLevelSize(data: TextureMipLevelData): {width: number; height: number} {
11✔
167
  if (isExternalImage(data)) {
11✔
168
    return getExternalImageSize(data);
1✔
169
  }
1✔
170
  if (typeof data === 'object' && 'width' in data && 'height' in data) {
11✔
171
    return {width: data.width, height: data.height};
9✔
172
  }
9✔
173
  throw new Error('Unsupported mip-level data');
1✔
174
}
1✔
175

1✔
176
/** Type guard: is a mip-level `TextureImageData` (vs ExternalImage) */
1✔
177
function isTextureImageData(data: TextureMipLevelData): data is TextureImageData {
2✔
178
  return (
2✔
179
    typeof data === 'object' &&
2✔
180
    data !== null &&
2✔
181
    'data' in data &&
2✔
182
    'width' in data &&
2✔
183
    'height' in data
2✔
184
  );
2✔
185
}
2✔
186

1✔
187
/** Resolve size for a single mip-level datum */
1✔
188
// function getTextureMipLevelSizeFromData(data: TextureMipLevelData): {
1✔
189
//   width: number;
1✔
190
//   height: number;
1✔
191
// } {
1✔
192
//   if (this.device.isExternalImage(data)) {
1✔
193
//     return this.device.getExternalImageSize(data);
1✔
194
//   }
1✔
195
//   if (this.isTextureImageData(data)) {
1✔
196
//     return {width: data.width, height: data.height};
1✔
197
//   }
1✔
198
//   // Fallback (should not happen with current types)
1✔
199
//   throw new Error('Unsupported mip-level data');
1✔
200
// }
1✔
201

1✔
202
/** Convert cube face label to depth index */
1✔
203
export function getCubeFaceIndex(face: TextureCubeFace): number {
1✔
NEW
204
  const idx = TEXTURE_CUBE_FACE_MAP[face];
×
NEW
205
  if (idx === undefined) throw new Error(`Invalid cube face: ${face}`);
×
NEW
206
  return idx;
×
NEW
207
}
×
208

1✔
209
/** Convert cube face label to texture slice index. Index can be used with `setTexture2DData()`. */
1✔
210
export function getCubeArrayFaceIndex(cubeIndex: number, face: TextureCubeFace): number {
1✔
NEW
211
  return 6 * cubeIndex + getCubeFaceIndex(face);
×
NEW
212
}
×
213

1✔
214
// ------------------ Upload helpers ------------------
1✔
215

1✔
216
/** Experimental: Set multiple mip levels (1D) */
1✔
217
export function getTexture1DSubresources(data: Texture1DData): TextureSubresource[] {
1✔
NEW
218
  // Not supported in WebGL; left explicit
×
NEW
219
  throw new Error('setTexture1DData not supported in WebGL.');
×
NEW
220
  // const subresources: TextureSubresource[] = [];
×
NEW
221
  // return subresources;
×
NEW
222
}
×
223

1✔
224
/** Normalize 2D layer payload into an array of mip-level items */
1✔
225
function _normalizeTexture2DData(data: Texture2DData): (TextureImageData | ExternalImage)[] {
2✔
226
  return Array.isArray(data) ? data : [data];
2!
227
}
2✔
228

1✔
229
/** Experimental: Set multiple mip levels (2D), optionally at `z` (depth/array index) */
1✔
230
export function getTexture2DSubresources(
1✔
231
  slice: number,
2✔
232
  lodData: Texture2DData
2✔
233
): TextureSubresource[] {
2✔
234
  const lodArray = _normalizeTexture2DData(lodData);
2✔
235
  const z = slice;
2✔
236

2✔
237
  const subresources: TextureSubresource[] = [];
2✔
238

2✔
239
  for (let mipLevel = 0; mipLevel < lodArray.length; mipLevel++) {
2✔
240
    const imageData = lodArray[mipLevel];
2✔
241
    if (isExternalImage(imageData)) {
2!
NEW
242
      subresources.push({
×
NEW
243
        type: 'external-image',
×
NEW
244
        image: imageData,
×
NEW
245
        z,
×
NEW
246
        mipLevel
×
NEW
247
      });
×
248
    } else if (isTextureImageData(imageData)) {
2✔
249
      subresources.push({
2✔
250
        type: 'texture-data',
2✔
251
        data: imageData,
2✔
252
        z,
2✔
253
        mipLevel
2✔
254
      });
2✔
255
    } else {
2!
NEW
256
      throw new Error('Unsupported 2D mip-level payload');
×
NEW
257
    }
×
258
  }
2✔
259

2✔
260
  return subresources;
2✔
261
}
2✔
262

1✔
263
/** 3D: multiple depth slices, each may carry multiple mip levels */
1✔
264
export function getTexture3DSubresources(data: Texture3DData): TextureSubresource[] {
1✔
NEW
265
  const subresources: TextureSubresource[] = [];
×
NEW
266
  for (let depth = 0; depth < data.length; depth++) {
×
NEW
267
    subresources.push(...getTexture2DSubresources(depth, data[depth]));
×
NEW
268
  }
×
NEW
269
  return subresources;
×
NEW
270
}
×
271

1✔
272
/** 2D array: multiple layers, each may carry multiple mip levels */
1✔
273
export function getTextureArraySubresources(data: TextureArrayData): TextureSubresource[] {
1✔
NEW
274
  const subresources: TextureSubresource[] = [];
×
NEW
275
  for (let layer = 0; layer < data.length; layer++) {
×
NEW
276
    subresources.push(...getTexture2DSubresources(layer, data[layer]));
×
NEW
277
  }
×
NEW
278
  return subresources;
×
NEW
279
}
×
280

1✔
281
/** Cube: 6 faces, each may carry multiple mip levels */
1✔
282
export function getTextureCubeSubresources(data: TextureCubeData): TextureSubresource[] {
1✔
NEW
283
  const subresources: TextureSubresource[] = [];
×
NEW
284
  for (const [face, faceData] of Object.entries(data) as [TextureCubeFace, TextureSliceData][]) {
×
NEW
285
    const faceDepth = getCubeFaceIndex(face);
×
NEW
286
    subresources.push(...getTexture2DSubresources(faceDepth, faceData));
×
NEW
287
  }
×
NEW
288
  return subresources;
×
NEW
289
}
×
290

1✔
291
/** Cube array: multiple cubes (faces×layers), each face may carry multiple mips */
1✔
292
export function getTextureCubeArraySubresources(data: TextureCubeArrayData): TextureSubresource[] {
1✔
NEW
293
  const subresources: TextureSubresource[] = [];
×
NEW
294
  data.forEach((cubeData, cubeIndex) => {
×
NEW
295
    for (const [face, faceData] of Object.entries(cubeData)) {
×
NEW
296
      const faceDepth = getCubeArrayFaceIndex(cubeIndex, face as TextureCubeFace);
×
NEW
297
      getTexture2DSubresources(faceDepth, faceData);
×
NEW
298
    }
×
NEW
299
  });
×
NEW
300
  return subresources;
×
NEW
301
}
×
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