@kitware/vtk.js 33.2.0 → 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,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,15 +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]);
71
+
72
+ // Generate mipmaps on GPU if needed
73
+ if (publicAPI.getDimensionality() !== 3 && model.mipLevel > 0) {
74
+ vtkTexture.generateMipmaps(model.device.getHandle(), model.handle, model.mipLevel + 1);
75
+ }
70
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);
71
89
  return;
72
90
  }
73
91
  if (req.jsImageData && !req.nativeArray) {
@@ -80,43 +98,64 @@ function vtkWebGPUTexture(publicAPI, model) {
80
98
  }
81
99
  const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format);
82
100
  let bufferBytesPerRow = model.width * tDetails.stride;
83
- const fixAll = (arr, height, depth) => {
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
+ */
111
+ const alignTextureData = (arr, height, depth) => {
84
112
  // bytesPerRow must be a multiple of 256 so we might need to rebuild
85
113
  // the data here before passing to the buffer. e.g. if it is unorm8x4 then
86
114
  // we need to have width be a multiple of 64
87
- const inWidthInBytes = arr.length / (height * depth) * arr.BYTES_PER_ELEMENT;
88
-
89
- // is this a half float texture?
115
+ // Check if the texture is half float
90
116
  const halfFloat = tDetails.elementSize === 2 && tDetails.sampleType === 'float';
117
+ const bytesPerElement = arr.BYTES_PER_ELEMENT;
118
+ const inWidthInBytes = arr.length / (height * depth) * bytesPerElement;
119
+
120
+ // No changes needed if not half float and already aligned
121
+ if (!halfFloat && inWidthInBytes % 256 === 0) {
122
+ return [arr, inWidthInBytes];
123
+ }
91
124
 
92
- // if we need to copy the data
93
- if (halfFloat || inWidthInBytes % 256) {
94
- const inArray = arr;
95
- const inWidth = inWidthInBytes / inArray.BYTES_PER_ELEMENT;
96
- const outBytesPerElement = tDetails.elementSize;
97
- const outWidthInBytes = 256 * Math.floor((inWidth * outBytesPerElement + 255) / 256);
98
- const outWidth = outWidthInBytes / outBytesPerElement;
99
- const outArray = macro.newTypedArray(halfFloat ? 'Uint16Array' : inArray.constructor.name, outWidth * height * depth);
100
- for (let v = 0; v < height * depth; v++) {
101
- if (halfFloat) {
102
- for (let i = 0; i < inWidth; i++) {
103
- outArray[v * outWidth + i] = HalfFloat.toHalf(inArray[v * inWidth + i]);
104
- }
105
- } else {
106
- outArray.set(inArray.subarray(v * inWidth, (v + 1) * inWidth), v * outWidth);
125
+ // Calculate dimensions for the new buffer
126
+ const inWidth = inWidthInBytes / bytesPerElement;
127
+ const outBytesPerElement = tDetails.elementSize;
128
+ const outWidthInBytes = 256 * Math.floor((inWidth * outBytesPerElement + 255) / 256);
129
+ const outWidth = outWidthInBytes / outBytesPerElement;
130
+
131
+ // Create the output array
132
+ const outArray = macro.newTypedArray(halfFloat ? 'Uint16Array' : arr.constructor.name, outWidth * height * depth);
133
+
134
+ // Copy and convert data when needed
135
+ const totalRows = height * depth;
136
+ if (halfFloat) {
137
+ for (let v = 0; v < totalRows; v++) {
138
+ const inOffset = v * inWidth;
139
+ const outOffset = v * outWidth;
140
+ for (let i = 0; i < inWidth; i++) {
141
+ outArray[outOffset + i] = HalfFloat.toHalf(arr[inOffset + i]);
107
142
  }
108
143
  }
109
- return [outArray, outWidthInBytes];
144
+ } else if (outWidth === inWidth) {
145
+ // If the output width is the same as input, just copy
146
+ outArray.set(arr);
147
+ } else {
148
+ for (let v = 0; v < totalRows; v++) {
149
+ outArray.set(arr.subarray(v * inWidth, (v + 1) * inWidth), v * outWidth);
150
+ }
110
151
  }
111
- return [arr, inWidthInBytes];
152
+ return [outArray, outWidthInBytes];
112
153
  };
113
154
  if (req.nativeArray) {
114
155
  nativeArray = req.nativeArray;
115
156
  }
116
157
  if (req.image) {
117
- const canvas = document.createElement('canvas');
118
- canvas.width = req.image.width;
119
- canvas.height = req.image.height;
158
+ const canvas = new OffscreenCanvas(req.image.width, req.image.height);
120
159
  const ctx = canvas.getContext('2d');
121
160
  ctx.translate(0, canvas.height);
122
161
  ctx.scale(1, -1);
@@ -124,62 +163,31 @@ function vtkWebGPUTexture(publicAPI, model) {
124
163
  const imageData = ctx.getImageData(0, 0, req.image.width, req.image.height);
125
164
  nativeArray = imageData.data;
126
165
  }
127
- const cmdEnc = model.device.createCommandEncoder();
128
- if (publicAPI.getDimensionality() !== 3) {
129
- // Non-3D, supports mipmaps
130
- const mips = vtkTexture.generateMipmaps(nativeArray, model.width, model.height, model.mipLevel);
131
- let currentWidth = model.width;
132
- let currentHeight = model.height;
133
- for (let m = 0; m <= model.mipLevel; m++) {
134
- const fix = fixAll(mips[m], currentHeight, 1);
135
- bufferBytesPerRow = fix[1];
136
- const buffRequest = {
137
- dataArray: req.dataArray ? req.dataArray : null,
138
- nativeArray: fix[0],
139
- /* eslint-disable no-undef */
140
- usage: BufferUsage.Texture
141
- /* eslint-enable no-undef */
142
- };
143
-
144
- const buff = model.device.getBufferManager().getBuffer(buffRequest);
145
- cmdEnc.copyBufferToTexture({
146
- buffer: buff.getHandle(),
147
- offset: 0,
148
- bytesPerRow: bufferBytesPerRow,
149
- rowsPerImage: currentHeight
150
- }, {
151
- texture: model.handle,
152
- mipLevel: m
153
- }, [currentWidth, currentHeight, 1]);
154
- currentWidth /= 2;
155
- currentHeight /= 2;
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
156
177
  }
157
- model.device.submitCommandEncoder(cmdEnc);
158
- model.ready = true;
159
- } else {
160
- // 3D, no mipmaps
161
- const fix = fixAll(nativeArray, model.height, model.depth);
162
- bufferBytesPerRow = fix[1];
163
- const buffRequest = {
164
- dataArray: req.dataArray ? req.dataArray : null,
165
- /* eslint-disable no-undef */
166
- usage: BufferUsage.Texture
167
- /* eslint-enable no-undef */
168
- };
169
-
170
- buffRequest.nativeArray = fix[0];
171
- const buff = model.device.getBufferManager().getBuffer(buffRequest);
172
- cmdEnc.copyBufferToTexture({
173
- buffer: buff.getHandle(),
174
- offset: 0,
175
- bytesPerRow: bufferBytesPerRow,
176
- rowsPerImage: model.height
177
- }, {
178
- texture: model.handle
179
- }, [model.width, model.height, model.depth]);
180
- model.device.submitCommandEncoder(cmdEnc);
181
- 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);
182
189
  }
190
+ model.ready = true;
183
191
  };
184
192
 
185
193
  // when data is pulled out of this texture what scale must be applied to
@@ -69,6 +69,11 @@ function vtkWebGPUTextureManager(publicAPI, model) {
69
69
  req.height = req.image.height;
70
70
  req.depth = 1;
71
71
  req.format = 'rgba8unorm';
72
+ /* eslint-disable no-undef */
73
+ /* eslint-disable no-bitwise */
74
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING;
75
+ /* eslint-enable no-undef */
76
+ /* eslint-enable no-bitwise */
72
77
  }
73
78
 
74
79
  // fill in based on js imageData
@@ -79,7 +84,26 @@ function vtkWebGPUTextureManager(publicAPI, model) {
79
84
  req.format = 'rgba8unorm';
80
85
  req.flip = true;
81
86
  req.nativeArray = req.jsImageData.data;
87
+ /* eslint-disable no-undef */
88
+ /* eslint-disable no-bitwise */
89
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING;
90
+ /* eslint-enable no-undef */
91
+ /* eslint-enable no-bitwise */
82
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
+
83
107
  if (req.canvas) {
84
108
  req.width = req.canvas.width;
85
109
  req.height = req.canvas.height;
@@ -107,7 +131,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
107
131
  });
108
132
 
109
133
  // fill the texture if we have data
110
- if (req.nativeArray || req.image || req.canvas) {
134
+ if (req.nativeArray || req.image || req.canvas || req.imageBitmap) {
111
135
  newTex.writeImageData(req);
112
136
  }
113
137
  return newTex;
@@ -116,7 +140,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
116
140
  // get a texture or create it if not cached.
117
141
  // this is the main entry point
118
142
  publicAPI.getTexture = req => {
119
- // if we have a source the get/create/cache the texture
143
+ // if we have a source then get/create/cache the texture
120
144
  if (req.hash) {
121
145
  // if a matching texture already exists then return it
122
146
  return model.device.getCachedObject(req.hash, _createTexture, req);
@@ -143,6 +167,8 @@ function vtkWebGPUTextureManager(publicAPI, model) {
143
167
  treq.image = srcTexture.getImage();
144
168
  } else if (srcTexture.getJsImageData()) {
145
169
  treq.jsImageData = srcTexture.getJsImageData();
170
+ } else if (srcTexture.getImageBitmap()) {
171
+ treq.imageBitmap = srcTexture.getImageBitmap();
146
172
  } else if (srcTexture.getCanvas()) {
147
173
  treq.canvas = srcTexture.getCanvas();
148
174
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "33.2.0",
3
+ "version": "33.3.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",