@kitware/vtk.js 33.2.1 → 33.3.0

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.
@@ -1,4 +1,5 @@
1
1
  import { vtkAlgorithm } from './../../interfaces';
2
+ import { Nullable } from './../../types';
2
3
 
3
4
  /**
4
5
  *
@@ -15,67 +16,110 @@ export interface ITextureInitialValues {
15
16
 
16
17
  export interface vtkTexture extends vtkAlgorithm {
17
18
  /**
18
- *
19
+ * Returns the canvas used by the texture.
20
+ */
21
+ getCanvas(): Nullable<HTMLCanvasElement>;
22
+
23
+ /**
24
+ * Returns true if the texture is set to repeat at the edges.
19
25
  */
20
26
  getRepeat(): boolean;
21
27
 
22
28
  /**
23
- *
29
+ * Returns true if the texture is set to clamp at the edges.
24
30
  */
25
31
  getEdgeClamp(): boolean;
26
32
 
27
33
  /**
28
- *
34
+ * Returns true if the texture is set to interpolate between texels.
29
35
  */
30
36
  getInterpolate(): boolean;
31
37
 
32
38
  /**
33
- *
39
+ * Returns the image used by the texture.
34
40
  */
35
- getImage(): any;
41
+ getImage(): Nullable<HTMLImageElement>;
36
42
 
37
43
  /**
38
- *
44
+ * Returns an ImageBitmap object.
45
+ */
46
+ getImageBitmap(): Nullable<ImageBitmap>;
47
+
48
+ /**
49
+ * Returns true if the image is loaded.
39
50
  */
40
51
  getImageLoaded(): boolean;
41
52
 
42
53
  /**
43
- *
54
+ * Returns the input image data object.
55
+ */
56
+ getInputAsJsImageData(): Nullable<
57
+ ImageData | ImageBitmap | HTMLCanvasElement | HTMLImageElement
58
+ >;
59
+
60
+ /**
61
+ * Returns the current mip level of the texture.
44
62
  */
45
63
  getMipLevel(): number;
46
64
 
47
65
  /**
48
- *
49
- * @param repeat
50
- * @default false
66
+ * Returns true if the texture can be resized at run time.
67
+ * This is useful for dynamic textures that may change size based on user
68
+ * interaction or other factors.
51
69
  */
52
- setRepeat(repeat: boolean): boolean;
70
+ getResizable(): boolean;
71
+
72
+ /**
73
+ * Returns the canvas used by the texture.
74
+ */
75
+ setCanvas(canvas: HTMLCanvasElement): void;
53
76
 
54
77
  /**
55
- *
78
+ * Sets the texture to clamp at the edges.
56
79
  * @param edgeClamp
57
80
  * @default false
58
81
  */
59
82
  setEdgeClamp(edgeClamp: boolean): boolean;
60
83
 
61
84
  /**
62
- *
85
+ * Sets the texture to interpolate between texels.
63
86
  * @param interpolate
64
87
  * @default false
65
88
  */
66
89
  setInterpolate(interpolate: boolean): boolean;
67
90
 
68
91
  /**
69
- *
92
+ * Sets the image used by the texture.
70
93
  * @param image
71
94
  * @default null
72
95
  */
73
- setImage(image: any): void;
96
+ setImage(image: HTMLImageElement): void;
97
+
98
+ /**
99
+ * Sets the image as an ImageBitmap object.
100
+ * Supported in WebGPU only.
101
+ * @param imageBitmap
102
+ */
103
+ setImageBitmap(imageBitmap: ImageBitmap): void;
104
+
105
+ /**
106
+ * Sets the input image data as a JavaScript ImageData object.
107
+ * @param imageData
108
+ */
109
+ setJsImageData(imageData: ImageData): void;
74
110
 
75
111
  /**
112
+ * Sets the current mip level of the texture.
76
113
  * @param level
77
114
  */
78
115
  setMipLevel(level: number): boolean;
116
+
117
+ /**
118
+ * Sets the texture to repeat at the edges.
119
+ * @param repeat
120
+ * @default false
121
+ */
122
+ setRepeat(repeat: boolean): boolean;
79
123
  }
80
124
 
81
125
  /**
@@ -116,10 +160,16 @@ export function generateMipmaps(
116
160
  ): Array<Uint8ClampedArray>;
117
161
 
118
162
  /**
119
- * vtkTexture is an image algorithm that handles loading and binding of texture maps.
120
- * It obtains its data from an input image data dataset type.
121
- * Thus you can create visualization pipelines to read, process, and construct textures.
122
- * Note that textures will only work if texture coordinates are also defined, and if the rendering system supports texture.
163
+ * vtkTexture is an image algorithm that handles loading and binding of texture
164
+ * maps. It obtains its data from an input image data dataset type. Thus you can
165
+ * create visualization pipelines to read, process, and construct textures. Note
166
+ * that textures will only work if texture coordinates are also defined, and if
167
+ * the rendering system supports texture.
168
+ *
169
+ * This class is used in both WebGL and WebGPU rendering backends, but the
170
+ * implementation details may vary. In WebGL, it uses HTMLImageElement and
171
+ * HTMLCanvasElement for textures, while in WebGPU, it uses HTMLImageElement,
172
+ * HTMLCanvasElement, and ImageBitmap.
123
173
  */
124
174
  export declare const vtkTexture: {
125
175
  newInstance: typeof newInstance;
@@ -25,11 +25,29 @@ function vtkTexture(publicAPI, model) {
25
25
  publicAPI.setInputConnection(null);
26
26
  model.image = null;
27
27
  model.canvas = null;
28
+ model.imageBitmap = null;
28
29
  }
29
30
  model.jsImageData = imageData;
30
31
  model.imageLoaded = true;
31
32
  publicAPI.modified();
32
33
  };
34
+ publicAPI.setImageBitmap = imageBitmap => {
35
+ if (model.imageBitmap === imageBitmap) {
36
+ return;
37
+ }
38
+
39
+ // clear other entries
40
+ if (imageBitmap !== null) {
41
+ publicAPI.setInputData(null);
42
+ publicAPI.setInputConnection(null);
43
+ model.image = null;
44
+ model.canvas = null;
45
+ model.jsImageData = null;
46
+ }
47
+ model.imageBitmap = imageBitmap;
48
+ model.imageLoaded = true;
49
+ publicAPI.modified();
50
+ };
33
51
  publicAPI.setCanvas = canvas => {
34
52
  if (model.canvas === canvas) {
35
53
  return;
@@ -40,6 +58,7 @@ function vtkTexture(publicAPI, model) {
40
58
  publicAPI.setInputData(null);
41
59
  publicAPI.setInputConnection(null);
42
60
  model.image = null;
61
+ model.imageBitmap = null;
43
62
  model.jsImageData = null;
44
63
  }
45
64
  model.canvas = canvas;
@@ -56,6 +75,7 @@ function vtkTexture(publicAPI, model) {
56
75
  publicAPI.setInputConnection(null);
57
76
  model.canvas = null;
58
77
  model.jsImageData = null;
78
+ model.imageBitmap = null;
59
79
  }
60
80
  model.image = image;
61
81
  model.imageLoaded = false;
@@ -88,13 +108,20 @@ function vtkTexture(publicAPI, model) {
88
108
  width = model.image.width;
89
109
  height = model.image.height;
90
110
  }
111
+ if (model.imageBitmap) {
112
+ width = model.imageBitmap.width;
113
+ height = model.imageBitmap.height;
114
+ }
91
115
  const dimensionality = (width > 1) + (height > 1) + (depth > 1);
92
116
  return dimensionality;
93
117
  };
94
118
  publicAPI.getInputAsJsImageData = () => {
95
119
  if (!model.imageLoaded || publicAPI.getInputData()) return null;
96
120
  if (model.jsImageData) {
97
- return model.jsImageData();
121
+ return model.jsImageData;
122
+ }
123
+ if (model.imageBitmap) {
124
+ return model.imageBitmap;
98
125
  }
99
126
  if (model.canvas) {
100
127
  const context = model.canvas.getContext('2d');
@@ -102,14 +129,14 @@ function vtkTexture(publicAPI, model) {
102
129
  return imageData;
103
130
  }
104
131
  if (model.image) {
105
- const canvas = document.createElement('canvas');
106
- canvas.width = model.image.width;
107
- canvas.height = model.image.height;
132
+ const width = model.image.width;
133
+ const height = model.image.height;
134
+ const canvas = new OffscreenCanvas(width, height);
108
135
  const context = canvas.getContext('2d');
109
- context.translate(0, canvas.height);
136
+ context.translate(0, height);
110
137
  context.scale(1, -1);
111
- context.drawImage(model.image, 0, 0, model.image.width, model.image.height);
112
- const imageData = context.getImageData(0, 0, canvas.width, canvas.height);
138
+ context.drawImage(model.image, 0, 0, width, height);
139
+ const imageData = context.getImageData(0, 0, width, height);
113
140
  return imageData;
114
141
  }
115
142
  return null;
@@ -206,6 +233,7 @@ const generateMipmaps = (device, texture, mipLevelCount) => {
206
233
  bindGroupLayouts: [bindGroupLayout]
207
234
  });
208
235
  const pipeline = device.createComputePipeline({
236
+ label: 'ComputeMipmapPipeline',
209
237
  layout: pipelineLayout,
210
238
  compute: {
211
239
  module: computeShader,
@@ -240,7 +268,9 @@ const generateMipmaps = (device, texture, mipLevelCount) => {
240
268
  resource: sampler
241
269
  }]
242
270
  });
243
- const commandEncoder = device.createCommandEncoder();
271
+ const commandEncoder = device.createCommandEncoder({
272
+ label: `MipmapGenerateCommandEncoder`
273
+ });
244
274
  const computePass = commandEncoder.beginComputePass();
245
275
  computePass.setPipeline(pipeline);
246
276
  computePass.setBindGroup(0, bindGroup);
@@ -262,6 +292,7 @@ const DEFAULT_VALUES = {
262
292
  image: null,
263
293
  canvas: null,
264
294
  jsImageData: null,
295
+ imageBitmap: null,
265
296
  imageLoaded: false,
266
297
  repeat: false,
267
298
  interpolate: false,
@@ -279,7 +310,7 @@ function extend(publicAPI, model) {
279
310
  // Build VTK API
280
311
  macro.obj(publicAPI, model);
281
312
  macro.algo(publicAPI, model, 6, 0);
282
- macro.get(publicAPI, model, ['canvas', 'image', 'jsImageData', 'imageLoaded', 'resizable']);
313
+ macro.get(publicAPI, model, ['canvas', 'image', 'jsImageData', 'imageBitmap', 'imageLoaded', 'resizable']);
283
314
  macro.setGet(publicAPI, model, ['repeat', 'edgeClamp', 'interpolate', 'mipLevel']);
284
315
  vtkTexture(publicAPI, model);
285
316
  }
@@ -71,6 +71,8 @@ struct PBRData {
71
71
  specular: vec3<f32>,
72
72
  }
73
73
 
74
+ const pi: f32 = 3.14159265359;
75
+
74
76
  // Dot product with the max already in it
75
77
  fn mdot(a: vec3<f32>, b: vec3<f32>) -> f32 {
76
78
  return max(0.0, dot(a, b));
@@ -89,7 +91,6 @@ fn cdot(a: vec3<f32>, b: vec3<f32>) -> f32 {
89
91
 
90
92
  // Lambertian diffuse model
91
93
  fn lambertDiffuse(base: vec3<f32>, N: vec3<f32>, L: vec3<f32>) -> vec3<f32> {
92
- var pi: f32 = 3.14159265359;
93
94
  var NdotL: f32 = mdot(N, L);
94
95
  NdotL = pow(NdotL, 1.5);
95
96
  return (base/pi)*NdotL;
@@ -135,12 +136,10 @@ fn schlickFresnelRGB(V: vec3<f32>, N: vec3<f32>, F0: vec3<f32>) -> vec3<f32> {
135
136
  // https://learnopengl.com/PBR/Theory
136
137
  // Trowbridge-Reitz GGX functions: normal, halfway, roughness^2
137
138
  fn trGGX(N: vec3<f32>, H: vec3<f32>, a: f32) -> f32 {
138
- var pi: f32 = 3.14159265359;
139
-
140
139
  var a2: f32 = a*a;
141
140
  var NdotH = mdot(N, H);
142
141
  var NdotH2 = NdotH*NdotH;
143
-
142
+
144
143
  var denom: f32 = NdotH2 * (a2 - 1.0) + 1.0;
145
144
 
146
145
  return a2 / max((pi*denom*denom), 0.000001);
@@ -176,7 +175,7 @@ fn cookTorrance(D: f32, F: f32, G: f32, N: vec3<f32>, V: vec3<f32>, L: vec3<f32>
176
175
  }
177
176
 
178
177
  // Different lighting calculations for different light sources
179
- fn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, metallic: f32, direction: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData {
178
+ fn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, metallic: f32, direction: vec3<f32>, color: vec3<f32>, base: vec3<f32>) -> PBRData {
180
179
  var L: vec3<f32> = normalize(direction); // Light Vector
181
180
  var H: vec3<f32> = normalize(L + V); // Halfway Vector
182
181
 
@@ -195,10 +194,10 @@ fn calcDirectionalLight(N: vec3<f32>, V: vec3<f32>, ior: f32, roughness: f32, me
195
194
  var specular: vec3<f32> = brdf*incoming*angle;
196
195
  // Oren-Nayar gives a clay-like effect when fully rough which some people may not want, so it might be better to give a separate
197
196
  // control property for the diffuse vs specular roughness
198
- var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);
197
+ var diffuse: vec3<f32> = incoming*fujiiOrenNayar(base, roughness, N, L, V);
199
198
  // Stores the specular and diffuse separately to allow for finer post processing
200
199
  var out = PBRData(diffuse, specular);
201
-
200
+
202
201
  return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)
203
202
  }
204
203
 
@@ -226,7 +225,7 @@ fn calcPointLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, roug
226
225
  // Stores the specular and diffuse separately to allow for finer post processing
227
226
  // Could also be done (propably more properly) with a struct
228
227
  var out = PBRData(diffuse, specular);
229
-
228
+
230
229
  return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)
231
230
  }
232
231
 
@@ -244,7 +243,7 @@ fn calcSpotLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, rough
244
243
  var G: f32 = smithSurfaceRoughness(N, V, L, k); // Geometry
245
244
 
246
245
  var brdf: f32 = cookTorrance(D, 1.0, G, N, V, L);
247
-
246
+
248
247
  // Cones.x is the inner phi and cones.y is the outer phi
249
248
  var theta: f32 = mdot(normalize(direction), L);
250
249
  var epsilon: f32 = cones.x - cones.y;
@@ -263,7 +262,7 @@ fn calcSpotLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, rough
263
262
  // Stores the specular and diffuse separately to allow for finer post processing
264
263
  // Could also be done (propably more properly) with a struct
265
264
  var out = PBRData(diffuse, specular);
266
-
265
+
267
266
  return out; // Returns angle along with color of light so the final color can be multiplied by angle as well (creates black areas)
268
267
  }
269
268
 
@@ -271,7 +270,6 @@ fn calcSpotLight(N: vec3<f32>, V: vec3<f32>, fragPos: vec3<f32>, ior: f32, rough
271
270
  // Takes in a vector and converts it to an equivalent coordinate in a rectilinear texture. Should be replaced with cubemaps at some point
272
271
  fn vecToRectCoord(dir: vec3<f32>) -> vec2<f32> {
273
272
  var tau: f32 = 6.28318530718;
274
- var pi: f32 = 3.14159265359;
275
273
  var out: vec2<f32> = vec2<f32>(0.0);
276
274
 
277
275
  out.x = atan2(dir.z, dir.x) / tau;
@@ -385,56 +383,59 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
385
383
  }
386
384
  };
387
385
  publicAPI.updateUBO = () => {
388
- // make sure the data is up to date
389
386
  const actor = model.WebGPUActor.getRenderable();
390
387
  const ppty = actor.getProperty();
391
388
  const utime = model.UBO.getSendTime();
392
- if (publicAPI.getMTime() > utime || ppty.getMTime() > utime || model.renderable.getMTime() > utime) {
393
- // Matricies
394
- const keyMats = model.WebGPUActor.getKeyMatrices(model.WebGPURenderer);
395
- model.UBO.setArray('BCWCMatrix', keyMats.bcwc);
396
- model.UBO.setArray('BCSCMatrix', keyMats.bcsc);
397
- model.UBO.setArray('MCWCNormals', keyMats.normalMatrix);
398
- if (model.is2D) {
399
- model.UBO.setValue('ZValue', model.WebGPUActor.getRenderable().getProperty().getDisplayLocation() === DisplayLocation.FOREGROUND ? 1.0 : 0.0);
400
- const aColor = ppty.getColorByReference();
401
- model.UBO.setValue('AmbientIntensity', 1.0);
402
- model.UBO.setArray('DiffuseColor', [aColor[0], aColor[1], aColor[2], 1.0]);
403
- model.UBO.setValue('DiffuseIntensity', 0.0);
404
- model.UBO.setValue('SpecularIntensity', 0.0);
405
- } else {
406
- // Base Colors
407
- let aColor = ppty.getAmbientColorByReference();
408
- model.UBO.setValue('AmbientIntensity', ppty.getAmbient());
409
- model.UBO.setArray('AmbientColor', [aColor[0], aColor[1], aColor[2], 1.0]);
410
- model.UBO.setValue('DiffuseIntensity', ppty.getDiffuse());
411
- aColor = ppty.getDiffuseColorByReference();
412
- model.UBO.setArray('DiffuseColor', [aColor[0], aColor[1], aColor[2], 1.0]);
413
- // Roughness
414
- model.UBO.setValue('Roughness', ppty.getRoughness());
415
- model.UBO.setValue('BaseIOR', ppty.getBaseIOR());
416
- // Metallic
417
- model.UBO.setValue('Metallic', ppty.getMetallic());
418
- // Normal
419
- model.UBO.setValue('NormalStrength', ppty.getNormalStrength());
420
- // Emission
421
- model.UBO.setValue('Emission', ppty.getEmission());
422
- // Specular
423
- model.UBO.setValue('SpecularIntensity', ppty.getSpecular());
424
- aColor = ppty.getSpecularColorByReference();
425
- model.UBO.setArray('SpecularColor', [aColor[0], aColor[1], aColor[2], 1.0]);
426
- }
427
- // Edge and line rendering
428
- const aColor = ppty.getEdgeColorByReference?.();
429
- if (aColor) {
430
- model.UBO.setArray('EdgeColor', [aColor[0], aColor[1], aColor[2], 1.0]);
389
+ if (publicAPI.getMTime() <= utime && ppty.getMTime() <= utime && model.renderable.getMTime() <= utime) {
390
+ return;
391
+ }
392
+
393
+ // --- Matrix Updates ---
394
+ const keyMats = model.WebGPUActor.getKeyMatrices(model.WebGPURenderer);
395
+ model.UBO.setArray('BCWCMatrix', keyMats.bcwc);
396
+ model.UBO.setArray('BCSCMatrix', keyMats.bcsc);
397
+ model.UBO.setArray('MCWCNormals', keyMats.normalMatrix);
398
+
399
+ // --- 2D or 3D ---
400
+ if (model.is2D) {
401
+ const displayLoc = ppty.getDisplayLocation?.() ?? DisplayLocation.BACKGROUND;
402
+ model.UBO.setValue('ZValue', displayLoc === DisplayLocation.FOREGROUND ? 1.0 : 0.0);
403
+ const aColor = ppty.getColorByReference();
404
+ model.UBO.setValue('AmbientIntensity', 1.0);
405
+ model.UBO.setArray('DiffuseColor', [...aColor, 1.0]);
406
+ model.UBO.setValue('DiffuseIntensity', 0.0);
407
+ model.UBO.setValue('SpecularIntensity', 0.0);
408
+ } else {
409
+ // Base Colors
410
+ model.UBO.setValue('AmbientIntensity', ppty.getAmbient());
411
+ model.UBO.setArray('AmbientColor', [...ppty.getAmbientColorByReference(), 1.0]);
412
+ model.UBO.setValue('DiffuseIntensity', ppty.getDiffuse());
413
+ model.UBO.setArray('DiffuseColor', [...ppty.getDiffuseColorByReference(), 1.0]);
414
+ // Roughness
415
+ model.UBO.setValue('Roughness', ppty.getRoughness());
416
+ model.UBO.setValue('BaseIOR', ppty.getBaseIOR());
417
+ // Metallic
418
+ model.UBO.setValue('Metallic', ppty.getMetallic());
419
+ // Normal
420
+ model.UBO.setValue('NormalStrength', ppty.getNormalStrength());
421
+ // Emission
422
+ model.UBO.setValue('Emission', ppty.getEmission());
423
+ // Specular
424
+ model.UBO.setValue('SpecularIntensity', ppty.getSpecular());
425
+ if (ppty.getSpecularColorByReference()) {
426
+ model.UBO.setArray('SpecularColor', [...ppty.getSpecularColorByReference(), 1.0]);
431
427
  }
432
- model.UBO.setValue('LineWidth', ppty.getLineWidth());
433
- model.UBO.setValue('Opacity', ppty.getOpacity());
434
- model.UBO.setValue('PropID', model.WebGPUActor.getPropID());
435
- const device = model.WebGPURenderWindow.getDevice();
436
- model.UBO.sendIfNeeded(device);
437
428
  }
429
+
430
+ // --- Edge and Misc ---
431
+ const edgeColor = ppty.getEdgeColorByReference?.();
432
+ if (edgeColor) model.UBO.setArray('EdgeColor', [...edgeColor, 1.0]);
433
+ model.UBO.setValue('LineWidth', ppty.getLineWidth());
434
+ model.UBO.setValue('Opacity', ppty.getOpacity());
435
+ model.UBO.setValue('PropID', model.WebGPUActor.getPropID());
436
+
437
+ // Only send if needed
438
+ model.UBO.sendIfNeeded(model.WebGPURenderWindow.getDevice());
438
439
  };
439
440
  publicAPI.haveWideLines = () => {
440
441
  const actor = model.WebGPUActor.getRenderable();
@@ -523,8 +524,6 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
523
524
  // Code that runs if the fragment shader includes normals
524
525
  if (code.includes('var normal:') && model.useRendererMatrix && !isEdges(hash) && !model.is2D && !hash.includes('sel')) {
525
526
  const lightingCode = [
526
- // Constants
527
- ' var pi: f32 = 3.14159265359;',
528
527
  // Vectors needed for light calculations
529
528
  ' var fragPos: vec3<f32> = vec3<f32>(input.vertexVC.xyz);', ' var V: vec3<f32> = mix(normalize(-fragPos), vec3<f32>(0, 0, 1), f32(rendererUBO.cameraParallel)); // View Vector',
530
529
  // Values needed for light calculations
@@ -640,7 +639,7 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
640
639
  }
641
640
  if (ambientOcclusionTexture?.getImageLoaded()) {
642
641
  if (checkDims(ambientOcclusionTexture)) {
643
- usedTextures.push('_ambientOcclusionMap = textureSample(AmbientOcclusionTexture, AmbientOcclusionTextureSampler, input.tcoordVS);');
642
+ usedTextures.push('_ambientOcclusionMap = textureSample(AmbientOcclusionTexture, AmbientOcclusionTextureSampler, input.tcoordVS).rrra;');
644
643
  }
645
644
  }
646
645
  }
@@ -722,19 +721,18 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
722
721
  }
723
722
  const vertexInput = model.vertexInput;
724
723
  const points = pd.getPoints();
725
- let indexBuffer;
726
724
 
727
- // get the flat mapping indexBuffer for the cells
725
+ // --- Index Buffer ---
726
+ let indexBuffer = null;
728
727
  if (cells) {
729
- const buffRequest = {
728
+ indexBuffer = device.getBufferManager().getBuffer({
730
729
  hash: `R${representation}P${primType}${cells.getMTime()}`,
731
730
  usage: BufferUsage.Index,
732
731
  cells,
733
732
  numberOfPoints: points.getNumberOfPoints(),
734
733
  primitiveType: primType,
735
734
  representation
736
- };
737
- indexBuffer = device.getBufferManager().getBuffer(buffRequest);
735
+ });
738
736
  vertexInput.setIndexBuffer(indexBuffer);
739
737
  } else {
740
738
  vertexInput.setIndexBuffer(null);
@@ -749,26 +747,23 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
749
747
  // - format
750
748
  // - usage
751
749
  // - packExtra - covered by format
752
-
753
- // points
750
+ // --- Points Buffer ---
754
751
  if (points) {
755
752
  const shift = model.WebGPUActor.getBufferShift(model.WebGPURenderer);
756
- const buffRequest = {
757
- hash: `${points.getMTime()}I${indexBuffer.getMTime()}${shift.join()}float32x4`,
753
+ vertexInput.addBuffer(device.getBufferManager().getBuffer({
754
+ hash: `${points.getMTime()}I${indexBuffer?.getMTime?.() ?? 0}${shift.join()}float32x4`,
758
755
  usage: BufferUsage.PointArray,
759
756
  format: 'float32x4',
760
757
  dataArray: points,
761
758
  indexBuffer,
762
759
  shift,
763
760
  packExtra: true
764
- };
765
- const buff = device.getBufferManager().getBuffer(buffRequest);
766
- vertexInput.addBuffer(buff, ['vertexBC']);
761
+ }), ['vertexBC']);
767
762
  } else {
768
763
  vertexInput.removeBufferIfPresent('vertexBC');
769
764
  }
770
765
 
771
- // normals, only used for surface rendering
766
+ // --- Normals ---
772
767
  const usage = publicAPI.getUsage(representation, primType);
773
768
  model._usesCellNormals = false;
774
769
  if (!model.is2D && (
@@ -788,16 +783,14 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
788
783
  buffRequest.hash = `${normals.getMTime()}I${indexBuffer.getMTime()}snorm8x4`;
789
784
  buffRequest.dataArray = normals;
790
785
  buffRequest.usage = BufferUsage.PointArray;
791
- const buff = device.getBufferManager().getBuffer(buffRequest);
792
- vertexInput.addBuffer(buff, ['normalMC']);
786
+ vertexInput.addBuffer(device.getBufferManager().getBuffer(buffRequest), ['normalMC']);
793
787
  } else if (primType === PrimitiveTypes.Triangles) {
794
788
  model._usesCellNormals = true;
795
789
  buffRequest.hash = `PFN${points.getMTime()}I${indexBuffer.getMTime()}snorm8x4`;
796
790
  buffRequest.dataArray = points;
797
791
  buffRequest.cells = cells;
798
792
  buffRequest.usage = BufferUsage.NormalsFromPoints;
799
- const buff = device.getBufferManager().getBuffer(buffRequest);
800
- vertexInput.addBuffer(buff, ['normalMC']);
793
+ vertexInput.addBuffer(device.getBufferManager().getBuffer(buffRequest), ['normalMC']);
801
794
  } else {
802
795
  vertexInput.removeBufferIfPresent('normalMC');
803
796
  }
@@ -805,18 +798,15 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
805
798
  vertexInput.removeBufferIfPresent('normalMC');
806
799
  }
807
800
 
808
- // deal with colors but only if modified
801
+ // --- Colors ---
809
802
  let haveColors = false;
810
803
  if (model.renderable.getScalarVisibility()) {
811
804
  const c = model.renderable.getColorMapColors();
812
805
  if (c && !edges) {
813
806
  const scalarMode = model.renderable.getScalarMode();
814
- let haveCellScalars = false;
815
807
  // We must figure out how the scalars should be mapped to the polydata.
816
- if ((scalarMode === ScalarMode.USE_CELL_DATA || scalarMode === ScalarMode.USE_CELL_FIELD_DATA || scalarMode === ScalarMode.USE_FIELD_DATA || !pd.getPointData().getScalars()) && scalarMode !== ScalarMode.USE_POINT_FIELD_DATA && c) {
817
- haveCellScalars = true;
818
- }
819
- const buffRequest = {
808
+ const haveCellScalars = (scalarMode === ScalarMode.USE_CELL_DATA || scalarMode === ScalarMode.USE_CELL_FIELD_DATA || scalarMode === ScalarMode.USE_FIELD_DATA || !pd.getPointData().getScalars()) && scalarMode !== ScalarMode.USE_POINT_FIELD_DATA && c;
809
+ vertexInput.addBuffer(device.getBufferManager().getBuffer({
820
810
  usage: BufferUsage.PointArray,
821
811
  format: 'unorm8x4',
822
812
  hash: `${haveCellScalars}${c.getMTime()}I${indexBuffer.getMTime()}unorm8x4`,
@@ -824,15 +814,13 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
824
814
  indexBuffer,
825
815
  cellData: haveCellScalars,
826
816
  cellOffset: 0
827
- };
828
- const buff = device.getBufferManager().getBuffer(buffRequest);
829
- vertexInput.addBuffer(buff, ['colorVI']);
817
+ }), ['colorVI']);
830
818
  haveColors = true;
831
819
  }
832
820
  }
833
- if (!haveColors) {
834
- vertexInput.removeBufferIfPresent('colorVI');
835
- }
821
+ if (!haveColors) vertexInput.removeBufferIfPresent('colorVI');
822
+
823
+ // --- Texture Coordinates ---
836
824
  let tcoords = null;
837
825
  if (model.renderable.getInterpolateScalarsBeforeMapping?.() && model.renderable.getColorCoordinates()) {
838
826
  tcoords = model.renderable.getColorCoordinates();
@@ -840,134 +828,86 @@ function vtkWebGPUCellArrayMapper(publicAPI, model) {
840
828
  tcoords = pd.getPointData().getTCoords();
841
829
  }
842
830
  if (tcoords && !edges) {
843
- const buff = device.getBufferManager().getBufferForPointArray(tcoords, vertexInput.getIndexBuffer());
844
- vertexInput.addBuffer(buff, ['tcoord']);
831
+ vertexInput.addBuffer(device.getBufferManager().getBufferForPointArray(tcoords, vertexInput.getIndexBuffer()), ['tcoord']);
845
832
  } else {
846
833
  vertexInput.removeBufferIfPresent('tcoord');
847
834
  }
848
835
  };
849
836
  publicAPI.updateTextures = () => {
850
- // we keep track of new and used textures so
851
- // that we can clean up any unused textures so we don't hold onto them
837
+ // Track textures in-use and new
852
838
  const usedTextures = [];
853
839
  const newTextures = [];
854
840
 
855
- // do we have a scalar color texture
841
+ // Add scalar color texture if available
856
842
  const idata = model.renderable.getColorTextureMap?.();
843
+ if (idata && !model.colorTexture) {
844
+ model.colorTexture = vtkTexture.newInstance({
845
+ label: 'polyDataColor'
846
+ });
847
+ }
857
848
  if (idata) {
858
- if (!model.colorTexture) {
859
- model.colorTexture = vtkTexture.newInstance({
860
- label: 'polyDataColor'
861
- });
862
- }
863
849
  model.colorTexture.setInputData(idata);
864
850
  newTextures.push(['Diffuse', model.colorTexture]);
865
851
  }
866
-
867
- // actor textures?
868
852
  const actor = model.WebGPUActor.getRenderable();
869
853
  const renderer = model.WebGPURenderer.getRenderable();
870
-
871
- // Reusing the old code for new and old textures, just loading in from properties instead of actor.getTextures()
872
- const textures = [];
873
-
874
- // Feels like there should be a better way than individually adding all
875
- if (actor.getProperty().getDiffuseTexture?.()) {
876
- const pair = ['Diffuse', actor.getProperty().getDiffuseTexture()];
877
- textures.push(pair);
878
- }
879
- if (actor.getTextures()[0]) {
880
- const pair = ['Diffuse', actor.getTextures()[0]];
881
- textures.push(pair);
882
- }
883
- if (model.colorTexture) {
884
- const pair = ['Diffuse', model.colorTexture];
885
- textures.push(pair);
886
- }
887
- if (actor.getProperty().getORMTexture?.()) {
888
- const pair = ['ORM', actor.getProperty().getORMTexture()];
889
- textures.push(pair);
890
- }
891
- if (actor.getProperty().getRMTexture?.()) {
892
- const pair = ['RM', actor.getProperty().getRMTexture()];
893
- textures.push(pair);
894
- }
895
- if (actor.getProperty().getRoughnessTexture?.()) {
896
- const pair = ['Roughness', actor.getProperty().getRoughnessTexture()];
897
- textures.push(pair);
898
- }
899
- if (actor.getProperty().getMetallicTexture?.()) {
900
- const pair = ['Metallic', actor.getProperty().getMetallicTexture()];
901
- textures.push(pair);
902
- }
903
- if (actor.getProperty().getNormalTexture?.()) {
904
- const pair = ['Normal', actor.getProperty().getNormalTexture()];
905
- textures.push(pair);
906
- }
907
- if (actor.getProperty().getAmbientOcclusionTexture?.()) {
908
- const pair = ['AmbientOcclusion', actor.getProperty().getAmbientOcclusionTexture()];
909
- textures.push(pair);
910
- }
911
- if (actor.getProperty().getEmissionTexture?.()) {
912
- const pair = ['Emission', actor.getProperty().getEmissionTexture()];
913
- textures.push(pair);
914
- }
915
- if (renderer.getEnvironmentTexture?.()) {
916
- const pair = ['Environment', renderer.getEnvironmentTexture()];
917
- textures.push(pair);
918
- }
919
- for (let i = 0; i < textures.length; i++) {
920
- if (textures[i][1].getInputData() || textures[i][1].getJsImageData() || textures[i][1].getCanvas()) {
921
- newTextures.push(textures[i]);
854
+ const textures = [['Diffuse', actor.getProperty().getDiffuseTexture?.()], ['Diffuse', actor.getTextures()[0]], ['Diffuse', model.colorTexture], ['ORM', actor.getProperty().getORMTexture?.()], ['RM', actor.getProperty().getRMTexture?.()], ['Roughness', actor.getProperty().getRoughnessTexture?.()], ['Metallic', actor.getProperty().getMetallicTexture?.()], ['Normal', actor.getProperty().getNormalTexture?.()], ['AmbientOcclusion', actor.getProperty().getAmbientOcclusionTexture?.()], ['Emission', actor.getProperty().getEmissionTexture?.()], ['Environment', renderer.getEnvironmentTexture?.()]];
855
+ textures.forEach(_ref => {
856
+ let [name, tex] = _ref;
857
+ if (!tex) return;
858
+ if (tex.getInputData() || tex.getJsImageData() || tex.getCanvas() || tex.getImageBitmap()) {
859
+ newTextures.push([name, tex]);
922
860
  }
923
- if (textures[i][1].getImage() && textures[i][1].getImageLoaded()) {
924
- newTextures.push(textures[i]);
861
+ if (tex.getImage() && tex.getImageLoaded()) {
862
+ newTextures.push([name, tex]);
925
863
  }
926
- }
927
- for (let i = 0; i < newTextures.length; i++) {
928
- const srcTexture = newTextures[i][1];
929
- const textureName = newTextures[i][0];
930
- const newTex = model.device.getTextureManager().getTextureForVTKTexture(srcTexture); // Generates hash
931
- if (newTex.getReady()) {
932
- // is this a new texture
933
- let found = false;
934
- for (let t = 0; t < model.textures.length; t++) {
935
- if (model.textures[t] === newTex) {
936
- found = true;
937
- usedTextures[t] = true;
938
- }
864
+ });
865
+
866
+ // Add textures to manager only if not present
867
+ newTextures.forEach(_ref2 => {
868
+ let [textureName, srcTexture] = _ref2;
869
+ const newTex = model.device.getTextureManager().getTextureForVTKTexture(srcTexture);
870
+ if (!newTex.getReady()) return;
871
+ let found = false;
872
+ for (let t = 0; t < model.textures.length; ++t) {
873
+ if (model.textures[t] === newTex) {
874
+ found = true;
875
+ usedTextures[t] = true;
876
+ break;
939
877
  }
940
- if (!found) {
941
- usedTextures[model.textures.length] = true;
942
- const tview = newTex.createView(`${textureName}Texture`);
943
- model.textures.push(newTex);
944
- model.textureViews.push(tview);
945
- const interpolate = srcTexture.getInterpolate() ? 'linear' : 'nearest';
946
- let addressMode = null;
947
- if (!addressMode && srcTexture.getEdgeClamp() && srcTexture.getRepeat()) addressMode = 'mirror-repeat';
948
- if (!addressMode && srcTexture.getEdgeClamp()) addressMode = 'clamp-to-edge';
949
- if (!addressMode && srcTexture.getRepeat()) addressMode = 'repeat';
950
- if (textureName !== 'Environment') {
951
- tview.addSampler(model.device, {
952
- addressModeU: addressMode,
953
- addressModeV: addressMode,
954
- addressModeW: addressMode,
955
- minFilter: interpolate,
956
- magFilter: interpolate
957
- });
958
- } else {
959
- tview.addSampler(model.device, {
960
- addressModeU: 'repeat',
961
- addressModeV: 'clamp-to-edge',
962
- addressModeW: 'repeat',
963
- minFilter: interpolate,
964
- magFilter: interpolate,
965
- mipmapFilter: 'linear'
966
- });
967
- }
878
+ }
879
+ if (!found) {
880
+ usedTextures[model.textures.length] = true;
881
+ const tview = newTex.createView(`${textureName}Texture`);
882
+ model.textures.push(newTex);
883
+ model.textureViews.push(tview);
884
+
885
+ // Sampler setup
886
+ const interpolate = srcTexture.getInterpolate() ? 'linear' : 'nearest';
887
+ let addressMode = null;
888
+ if (srcTexture.getEdgeClamp() && srcTexture.getRepeat()) addressMode = 'mirror-repeat';else if (srcTexture.getEdgeClamp()) addressMode = 'clamp-to-edge';else if (srcTexture.getRepeat()) addressMode = 'repeat';
889
+
890
+ // Handle environment texture separately
891
+ if (textureName !== 'Environment') {
892
+ tview.addSampler(model.device, {
893
+ addressModeU: addressMode,
894
+ addressModeV: addressMode,
895
+ addressModeW: addressMode,
896
+ minFilter: interpolate,
897
+ magFilter: interpolate
898
+ });
899
+ } else {
900
+ tview.addSampler(model.device, {
901
+ addressModeU: 'repeat',
902
+ addressModeV: 'clamp-to-edge',
903
+ addressModeW: 'repeat',
904
+ minFilter: interpolate,
905
+ magFilter: interpolate,
906
+ mipmapFilter: 'linear'
907
+ });
968
908
  }
969
909
  }
970
- }
910
+ });
971
911
 
972
912
  // remove unused textures
973
913
  for (let i = model.textures.length - 1; i >= 0; i--) {
@@ -1,14 +1,9 @@
1
1
  import { m as macro } from '../../macros2.js';
2
2
  import HalfFloat from '../../Common/Core/HalfFloat.js';
3
- import vtkWebGPUBufferManager from './BufferManager.js';
4
3
  import vtkWebGPUTextureView from './TextureView.js';
5
4
  import vtkWebGPUTypes from './Types.js';
6
5
  import vtkTexture from '../Core/Texture.js';
7
6
 
8
- const {
9
- BufferUsage
10
- } = vtkWebGPUBufferManager;
11
-
12
7
  // ----------------------------------------------------------------------------
13
8
  // Global methods
14
9
  // ----------------------------------------------------------------------------
@@ -59,20 +54,38 @@ function vtkWebGPUTexture(publicAPI, model) {
59
54
 
60
55
  publicAPI.writeImageData = req => {
61
56
  let nativeArray = [];
62
- if (req.canvas) {
57
+ const _copyImageToTexture = source => {
63
58
  model.device.getHandle().queue.copyExternalImageToTexture({
64
- source: req.canvas,
59
+ source,
65
60
  flipY: req.flip
66
61
  }, {
67
62
  texture: model.handle,
68
- premultipliedAlpha: true
69
- }, [model.width, model.height, model.depth]);
63
+ premultipliedAlpha: true,
64
+ mipLevel: 0,
65
+ origin: {
66
+ x: 0,
67
+ y: 0,
68
+ z: 0
69
+ }
70
+ }, [source.width, source.height, model.depth]);
70
71
 
71
72
  // Generate mipmaps on GPU if needed
72
73
  if (publicAPI.getDimensionality() !== 3 && model.mipLevel > 0) {
73
74
  vtkTexture.generateMipmaps(model.device.getHandle(), model.handle, model.mipLevel + 1);
74
75
  }
75
76
  model.ready = true;
77
+ };
78
+ if (req.canvas) {
79
+ _copyImageToTexture(req.canvas);
80
+ return;
81
+ }
82
+ if (req.imageBitmap) {
83
+ req.width = req.imageBitmap.width;
84
+ req.height = req.imageBitmap.height;
85
+ req.depth = 1;
86
+ req.format = 'rgba8unorm';
87
+ req.flip = false;
88
+ _copyImageToTexture(req.imageBitmap);
76
89
  return;
77
90
  }
78
91
  if (req.jsImageData && !req.nativeArray) {
@@ -85,6 +98,16 @@ function vtkWebGPUTexture(publicAPI, model) {
85
98
  }
86
99
  const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format);
87
100
  let bufferBytesPerRow = model.width * tDetails.stride;
101
+
102
+ /**
103
+ * Align texture data to ensure bytesPerRow is a multiple of 256.
104
+ * This is necessary for WebGPU texture uploads, especially for half-float formats.
105
+ * It also handles half-float conversion if the texture format requires it.
106
+ * @param {*} arr - The input array containing texture data.
107
+ * @param {*} height - The height of the texture.
108
+ * @param {*} depth - The depth of the texture (1 for 2D textures).
109
+ * @returns
110
+ */
88
111
  const alignTextureData = (arr, height, depth) => {
89
112
  // bytesPerRow must be a multiple of 256 so we might need to rebuild
90
113
  // the data here before passing to the buffer. e.g. if it is unorm8x4 then
@@ -132,9 +155,7 @@ function vtkWebGPUTexture(publicAPI, model) {
132
155
  nativeArray = req.nativeArray;
133
156
  }
134
157
  if (req.image) {
135
- const canvas = document.createElement('canvas');
136
- canvas.width = req.image.width;
137
- canvas.height = req.image.height;
158
+ const canvas = new OffscreenCanvas(req.image.width, req.image.height);
138
159
  const ctx = canvas.getContext('2d');
139
160
  ctx.translate(0, canvas.height);
140
161
  ctx.scale(1, -1);
@@ -142,57 +163,31 @@ function vtkWebGPUTexture(publicAPI, model) {
142
163
  const imageData = ctx.getImageData(0, 0, req.image.width, req.image.height);
143
164
  nativeArray = imageData.data;
144
165
  }
145
- const cmdEnc = model.device.createCommandEncoder();
146
- if (publicAPI.getDimensionality() !== 3) {
147
- // Non-3D
148
- // First, upload the base mip level (level 0)
149
- const ret = alignTextureData(nativeArray, model.height, 1);
150
- bufferBytesPerRow = ret[1];
151
- const buffRequest = {
152
- dataArray: req.dataArray ? req.dataArray : null,
153
- nativeArray: ret[0],
154
- usage: BufferUsage.Texture
155
- };
156
- const buff = model.device.getBufferManager().getBuffer(buffRequest);
157
- cmdEnc.copyBufferToTexture({
158
- buffer: buff.getHandle(),
159
- offset: 0,
160
- bytesPerRow: bufferBytesPerRow,
161
- rowsPerImage: model.height
162
- }, {
163
- texture: model.handle,
164
- mipLevel: 0
165
- }, [model.width, model.height, 1]);
166
-
167
- // Submit the base level upload
168
- model.device.submitCommandEncoder(cmdEnc);
169
-
170
- // Generate remaining mip levels on GPU
171
- if (model.mipLevel > 0) {
172
- vtkTexture.generateMipmaps(model.device.getHandle(), model.handle, model.mipLevel + 1);
166
+ const is3D = publicAPI.getDimensionality() === 3;
167
+ const alignedTextureData = alignTextureData(nativeArray, model.height, is3D ? model.depth : 1);
168
+ bufferBytesPerRow = alignedTextureData[1];
169
+ const data = alignedTextureData[0];
170
+ model.device.getHandle().queue.writeTexture({
171
+ texture: model.handle,
172
+ mipLevel: 0,
173
+ origin: {
174
+ x: 0,
175
+ y: 0,
176
+ z: 0
173
177
  }
174
- model.ready = true;
175
- } else {
176
- // 3D, no mipmaps
177
- const ret = alignTextureData(nativeArray, model.height, model.depth);
178
- bufferBytesPerRow = ret[1];
179
- const buffRequest = {
180
- dataArray: req.dataArray ? req.dataArray : null,
181
- usage: BufferUsage.Texture
182
- };
183
- buffRequest.nativeArray = ret[0];
184
- const buff = model.device.getBufferManager().getBuffer(buffRequest);
185
- cmdEnc.copyBufferToTexture({
186
- buffer: buff.getHandle(),
187
- offset: 0,
188
- bytesPerRow: bufferBytesPerRow,
189
- rowsPerImage: model.height
190
- }, {
191
- texture: model.handle
192
- }, [model.width, model.height, model.depth]);
193
- model.device.submitCommandEncoder(cmdEnc);
194
- model.ready = true;
178
+ }, data, {
179
+ offset: 0,
180
+ bytesPerRow: bufferBytesPerRow,
181
+ rowsPerImage: model.height
182
+ }, {
183
+ width: model.width,
184
+ height: model.height,
185
+ depthOrArrayLayers: is3D ? model.depth : 1
186
+ });
187
+ if (!is3D && model.mipLevel > 0) {
188
+ vtkTexture.generateMipmaps(model.device.getHandle(), model.handle, model.mipLevel + 1);
195
189
  }
190
+ model.ready = true;
196
191
  };
197
192
 
198
193
  // when data is pulled out of this texture what scale must be applied to
@@ -2,7 +2,6 @@ import { m as macro } from '../../macros2.js';
2
2
  import vtkDataArray from '../../Common/Core/DataArray.js';
3
3
  import vtkWebGPUTexture from './Texture.js';
4
4
 
5
- /* eslint-disable no-bitwise */
6
5
  const {
7
6
  VtkDataTypes
8
7
  } = vtkDataArray;
@@ -70,7 +69,11 @@ function vtkWebGPUTextureManager(publicAPI, model) {
70
69
  req.height = req.image.height;
71
70
  req.depth = 1;
72
71
  req.format = 'rgba8unorm';
72
+ /* eslint-disable no-undef */
73
+ /* eslint-disable no-bitwise */
73
74
  req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING;
75
+ /* eslint-enable no-undef */
76
+ /* eslint-enable no-bitwise */
74
77
  }
75
78
 
76
79
  // fill in based on js imageData
@@ -81,8 +84,26 @@ function vtkWebGPUTextureManager(publicAPI, model) {
81
84
  req.format = 'rgba8unorm';
82
85
  req.flip = true;
83
86
  req.nativeArray = req.jsImageData.data;
87
+ /* eslint-disable no-undef */
88
+ /* eslint-disable no-bitwise */
84
89
  req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING;
90
+ /* eslint-enable no-undef */
91
+ /* eslint-enable no-bitwise */
85
92
  }
93
+
94
+ if (req.imageBitmap) {
95
+ req.width = req.imageBitmap.width;
96
+ req.height = req.imageBitmap.height;
97
+ req.depth = 1;
98
+ req.format = 'rgba8unorm';
99
+ req.flip = true;
100
+ /* eslint-disable no-undef */
101
+ /* eslint-disable no-bitwise */
102
+ req.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
103
+ /* eslint-enable no-undef */
104
+ /* eslint-enable no-bitwise */
105
+ }
106
+
86
107
  if (req.canvas) {
87
108
  req.width = req.canvas.width;
88
109
  req.height = req.canvas.height;
@@ -110,7 +131,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
110
131
  });
111
132
 
112
133
  // fill the texture if we have data
113
- if (req.nativeArray || req.image || req.canvas) {
134
+ if (req.nativeArray || req.image || req.canvas || req.imageBitmap) {
114
135
  newTex.writeImageData(req);
115
136
  }
116
137
  return newTex;
@@ -146,6 +167,8 @@ function vtkWebGPUTextureManager(publicAPI, model) {
146
167
  treq.image = srcTexture.getImage();
147
168
  } else if (srcTexture.getJsImageData()) {
148
169
  treq.jsImageData = srcTexture.getJsImageData();
170
+ } else if (srcTexture.getImageBitmap()) {
171
+ treq.imageBitmap = srcTexture.getImageBitmap();
149
172
  } else if (srcTexture.getCanvas()) {
150
173
  treq.canvas = srcTexture.getCanvas();
151
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "33.2.1",
3
+ "version": "33.3.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",