@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.
- package/Rendering/Core/Texture.d.ts +81 -29
- package/Rendering/Core/Texture.js +171 -86
- package/Rendering/WebGPU/CellArrayMapper.js +136 -196
- package/Rendering/WebGPU/Texture.js +95 -87
- package/Rendering/WebGPU/TextureManager.js +28 -2
- package/package.json +1 -1
|
@@ -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
|
-
|
|
57
|
+
const _copyImageToTexture = source => {
|
|
63
58
|
model.device.getHandle().queue.copyExternalImageToTexture({
|
|
64
|
-
source
|
|
59
|
+
source,
|
|
65
60
|
flipY: req.flip
|
|
66
61
|
}, {
|
|
67
62
|
texture: model.handle,
|
|
68
|
-
premultipliedAlpha: true
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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 [
|
|
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 =
|
|
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
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
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
|
}
|