@kitware/vtk.js 34.0.0-beta.1 → 34.0.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,127 +54,141 @@ 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);
71
80
  return;
72
81
  }
73
- if (req.jsImageData && !req.nativeArray) {
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 = true;
88
+ _copyImageToTexture(req.imageBitmap);
89
+ return;
90
+ }
91
+ if (req.jsImageData) {
74
92
  req.width = req.jsImageData.width;
75
93
  req.height = req.jsImageData.height;
76
94
  req.depth = 1;
77
95
  req.format = 'rgba8unorm';
78
96
  req.flip = true;
79
- req.nativeArray = req.jsImageData.data;
97
+ _copyImageToTexture(req.jsImageData);
98
+ return;
99
+ }
100
+ if (req.image) {
101
+ req.width = req.image.width;
102
+ req.height = req.image.height;
103
+ req.depth = 1;
104
+ req.format = 'rgba8unorm';
105
+ req.flip = true;
106
+ _copyImageToTexture(req.image);
107
+ return;
80
108
  }
81
109
  const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format);
82
110
  let bufferBytesPerRow = model.width * tDetails.stride;
83
- const fixAll = (arr, height, depth) => {
111
+
112
+ /**
113
+ * Align texture data to ensure bytesPerRow is a multiple of 256.
114
+ * This is necessary for WebGPU texture uploads, especially for half-float formats.
115
+ * It also handles half-float conversion if the texture format requires it.
116
+ * @param {*} arr - The input array containing texture data.
117
+ * @param {*} height - The height of the texture.
118
+ * @param {*} depth - The depth of the texture (1 for 2D textures).
119
+ * @returns
120
+ */
121
+ const alignTextureData = (arr, height, depth) => {
84
122
  // bytesPerRow must be a multiple of 256 so we might need to rebuild
85
123
  // the data here before passing to the buffer. e.g. if it is unorm8x4 then
86
124
  // 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?
125
+ // Check if the texture is half float
90
126
  const halfFloat = tDetails.elementSize === 2 && tDetails.sampleType === 'float';
127
+ const bytesPerElement = arr.BYTES_PER_ELEMENT;
128
+ const inWidthInBytes = arr.length / (height * depth) * bytesPerElement;
91
129
 
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);
130
+ // No changes needed if not half float and already aligned
131
+ if (!halfFloat && inWidthInBytes % 256 === 0) {
132
+ return [arr, inWidthInBytes];
133
+ }
134
+
135
+ // Calculate dimensions for the new buffer
136
+ const inWidth = inWidthInBytes / bytesPerElement;
137
+ const outBytesPerElement = tDetails.elementSize;
138
+ const outWidthInBytes = 256 * Math.floor((inWidth * outBytesPerElement + 255) / 256);
139
+ const outWidth = outWidthInBytes / outBytesPerElement;
140
+
141
+ // Create the output array
142
+ const outArray = macro.newTypedArray(halfFloat ? 'Uint16Array' : arr.constructor.name, outWidth * height * depth);
143
+
144
+ // Copy and convert data when needed
145
+ const totalRows = height * depth;
146
+ if (halfFloat) {
147
+ for (let v = 0; v < totalRows; v++) {
148
+ const inOffset = v * inWidth;
149
+ const outOffset = v * outWidth;
150
+ for (let i = 0; i < inWidth; i++) {
151
+ outArray[outOffset + i] = HalfFloat.toHalf(arr[inOffset + i]);
107
152
  }
108
153
  }
109
- return [outArray, outWidthInBytes];
154
+ } else if (outWidth === inWidth) {
155
+ // If the output width is the same as input, just copy
156
+ outArray.set(arr);
157
+ } else {
158
+ for (let v = 0; v < totalRows; v++) {
159
+ outArray.set(arr.subarray(v * inWidth, (v + 1) * inWidth), v * outWidth);
160
+ }
110
161
  }
111
- return [arr, inWidthInBytes];
162
+ return [outArray, outWidthInBytes];
112
163
  };
113
164
  if (req.nativeArray) {
114
165
  nativeArray = req.nativeArray;
115
166
  }
116
- if (req.image) {
117
- const canvas = document.createElement('canvas');
118
- canvas.width = req.image.width;
119
- canvas.height = req.image.height;
120
- const ctx = canvas.getContext('2d');
121
- ctx.translate(0, canvas.height);
122
- ctx.scale(1, -1);
123
- ctx.drawImage(req.image, 0, 0, req.image.width, req.image.height, 0, 0, canvas.width, canvas.height);
124
- const imageData = ctx.getImageData(0, 0, req.image.width, req.image.height);
125
- nativeArray = imageData.data;
126
- }
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;
167
+ const is3D = publicAPI.getDimensionality() === 3;
168
+ const alignedTextureData = alignTextureData(nativeArray, model.height, is3D ? model.depth : 1);
169
+ bufferBytesPerRow = alignedTextureData[1];
170
+ const data = alignedTextureData[0];
171
+ model.device.getHandle().queue.writeTexture({
172
+ texture: model.handle,
173
+ mipLevel: 0,
174
+ origin: {
175
+ x: 0,
176
+ y: 0,
177
+ z: 0
156
178
  }
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;
179
+ }, data, {
180
+ offset: 0,
181
+ bytesPerRow: bufferBytesPerRow,
182
+ rowsPerImage: model.height
183
+ }, {
184
+ width: model.width,
185
+ height: model.height,
186
+ depthOrArrayLayers: is3D ? model.depth : 1
187
+ });
188
+ if (!is3D && model.mipLevel > 0) {
189
+ vtkTexture.generateMipmaps(model.device.getHandle(), model.handle, model.mipLevel + 1);
182
190
  }
191
+ model.ready = true;
183
192
  };
184
193
 
185
194
  // when data is pulled out of this texture what scale must be applied to
@@ -69,6 +69,12 @@ function vtkWebGPUTextureManager(publicAPI, model) {
69
69
  req.height = req.image.height;
70
70
  req.depth = 1;
71
71
  req.format = 'rgba8unorm';
72
+ req.flip = true;
73
+ /* eslint-disable no-undef */
74
+ /* eslint-disable no-bitwise */
75
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
76
+ /* eslint-enable no-undef */
77
+ /* eslint-enable no-bitwise */
72
78
  }
73
79
 
74
80
  // fill in based on js imageData
@@ -79,7 +85,26 @@ function vtkWebGPUTextureManager(publicAPI, model) {
79
85
  req.format = 'rgba8unorm';
80
86
  req.flip = true;
81
87
  req.nativeArray = req.jsImageData.data;
88
+ /* eslint-disable no-undef */
89
+ /* eslint-disable no-bitwise */
90
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
91
+ /* eslint-enable no-undef */
92
+ /* eslint-enable no-bitwise */
93
+ }
94
+
95
+ if (req.imageBitmap) {
96
+ req.width = req.imageBitmap.width;
97
+ req.height = req.imageBitmap.height;
98
+ req.depth = 1;
99
+ req.format = 'rgba8unorm';
100
+ req.flip = true;
101
+ /* eslint-disable no-undef */
102
+ /* eslint-disable no-bitwise */
103
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
104
+ /* eslint-enable no-undef */
105
+ /* eslint-enable no-bitwise */
82
106
  }
107
+
83
108
  if (req.canvas) {
84
109
  req.width = req.canvas.width;
85
110
  req.height = req.canvas.height;
@@ -88,7 +113,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
88
113
  req.flip = true;
89
114
  /* eslint-disable no-undef */
90
115
  /* eslint-disable no-bitwise */
91
- req.usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT;
116
+ req.usage = GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT;
92
117
  /* eslint-enable no-undef */
93
118
  /* eslint-enable no-bitwise */
94
119
  }
@@ -96,7 +121,9 @@ function vtkWebGPUTextureManager(publicAPI, model) {
96
121
 
97
122
  // create a texture (used by getTexture)
98
123
  function _createTexture(req) {
99
- const newTex = vtkWebGPUTexture.newInstance();
124
+ const newTex = vtkWebGPUTexture.newInstance({
125
+ label: req.label
126
+ });
100
127
  newTex.create(model.device, {
101
128
  width: req.width,
102
129
  height: req.height,
@@ -107,7 +134,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
107
134
  });
108
135
 
109
136
  // fill the texture if we have data
110
- if (req.nativeArray || req.image || req.canvas) {
137
+ if (req.nativeArray || req.image || req.canvas || req.imageBitmap) {
111
138
  newTex.writeImageData(req);
112
139
  }
113
140
  return newTex;
@@ -116,7 +143,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
116
143
  // get a texture or create it if not cached.
117
144
  // this is the main entry point
118
145
  publicAPI.getTexture = req => {
119
- // if we have a source the get/create/cache the texture
146
+ // if we have a source then get/create/cache the texture
120
147
  if (req.hash) {
121
148
  // if a matching texture already exists then return it
122
149
  return model.device.getCachedObject(req.hash, _createTexture, req);
@@ -133,9 +160,11 @@ function vtkWebGPUTextureManager(publicAPI, model) {
133
160
  treq.hash = treq.time + treq.format + treq.mipLevel;
134
161
  return model.device.getTextureManager().getTexture(treq);
135
162
  };
136
- publicAPI.getTextureForVTKTexture = srcTexture => {
163
+ publicAPI.getTextureForVTKTexture = function (srcTexture) {
164
+ let label = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
137
165
  const treq = {
138
- time: srcTexture.getMTime()
166
+ time: srcTexture.getMTime(),
167
+ label
139
168
  };
140
169
  if (srcTexture.getInputData()) {
141
170
  treq.imageData = srcTexture.getInputData();
@@ -143,6 +172,8 @@ function vtkWebGPUTextureManager(publicAPI, model) {
143
172
  treq.image = srcTexture.getImage();
144
173
  } else if (srcTexture.getJsImageData()) {
145
174
  treq.jsImageData = srcTexture.getJsImageData();
175
+ } else if (srcTexture.getImageBitmap()) {
176
+ treq.imageBitmap = srcTexture.getImageBitmap();
146
177
  } else if (srcTexture.getCanvas()) {
147
178
  treq.canvas = srcTexture.getCanvas();
148
179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kitware/vtk.js",
3
- "version": "34.0.0-beta.1",
3
+ "version": "34.0.0",
4
4
  "description": "Visualization Toolkit for the Web",
5
5
  "keywords": [
6
6
  "3d",
@@ -1,42 +0,0 @@
1
- import registerWebworker from 'webworker-promise/lib/register';
2
-
3
- /**
4
- *
5
- * @param {ArrayBuffer} imageBuffer
6
- * @param {string} mimeType
7
- * @param {string} channel
8
- * @returns {Promise<ImageData>}
9
- */
10
- registerWebworker(async _ref => {
11
- let {
12
- imageBuffer,
13
- mimeType,
14
- channel
15
- } = _ref;
16
- const channelsMap = {
17
- r: 0,
18
- g: 1,
19
- b: 2
20
- };
21
- const blob = new Blob([imageBuffer], {
22
- type: mimeType
23
- });
24
- const img = await createImageBitmap(blob);
25
- const canvas = new OffscreenCanvas(img.width, img.height);
26
- const ctx = canvas.getContext('2d');
27
- ctx.drawImage(img, 0, 0, img.width, img.height);
28
- const bitmap = ctx.getImageData(0, 0, img.width, img.height);
29
- if (channel) {
30
- const idx = channelsMap[channel];
31
- for (let i = 0; i < bitmap.data.length; i += 4) {
32
- const channelValue = bitmap.data[i + idx];
33
- bitmap.data[i] = channelValue; // red channel
34
- bitmap.data[i + 1] = channelValue; // green channel
35
- bitmap.data[i + 2] = channelValue; // blue channel
36
- }
37
- }
38
-
39
- return {
40
- bitmap
41
- };
42
- });