Skip to content

Commit b2815ae

Browse files
dakersankhesh
authored andcommitted
perf(WebGPU): improve texture upload using ImageBitmap
This commit refactors writeImageData to support texture uploads from ImageBitmap and OffscreenCanvas sources. It also simplifies the code path for 2D and 3D uploads by using `queue.writeTexture`, removing buffer usage.
1 parent 5a72ad8 commit b2815ae

File tree

2 files changed

+94
-72
lines changed

2 files changed

+94
-72
lines changed

Sources/Rendering/WebGPU/Texture/index.js

Lines changed: 66 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
import macro from 'vtk.js/Sources/macros';
22
import HalfFloat from 'vtk.js/Sources/Common/Core/HalfFloat';
3-
import vtkWebGPUBufferManager from 'vtk.js/Sources/Rendering/WebGPU/BufferManager';
43
import vtkWebGPUTextureView from 'vtk.js/Sources/Rendering/WebGPU/TextureView';
54
import vtkWebGPUTypes from 'vtk.js/Sources/Rendering/WebGPU/Types';
65
import vtkTexture from 'vtk.js/Sources/Rendering/Core/Texture';
76

8-
const { BufferUsage } = vtkWebGPUBufferManager;
9-
107
// ----------------------------------------------------------------------------
118
// Global methods
129
// ----------------------------------------------------------------------------
@@ -62,14 +59,19 @@ function vtkWebGPUTexture(publicAPI, model) {
6259

6360
publicAPI.writeImageData = (req) => {
6461
let nativeArray = [];
65-
if (req.canvas) {
62+
const _copyImageToTexture = (source) => {
6663
model.device.getHandle().queue.copyExternalImageToTexture(
6764
{
68-
source: req.canvas,
65+
source,
6966
flipY: req.flip,
7067
},
71-
{ texture: model.handle, premultipliedAlpha: true },
72-
[model.width, model.height, model.depth]
68+
{
69+
texture: model.handle,
70+
premultipliedAlpha: true,
71+
mipLevel: 0,
72+
origin: { x: 0, y: 0, z: 0 },
73+
},
74+
[source.width, source.height, model.depth]
7375
);
7476

7577
// Generate mipmaps on GPU if needed
@@ -82,6 +84,20 @@ function vtkWebGPUTexture(publicAPI, model) {
8284
}
8385

8486
model.ready = true;
87+
};
88+
89+
if (req.canvas) {
90+
_copyImageToTexture(req.canvas);
91+
return;
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 = false;
100+
_copyImageToTexture(req.imageBitmap);
85101
return;
86102
}
87103

@@ -97,6 +113,15 @@ function vtkWebGPUTexture(publicAPI, model) {
97113
const tDetails = vtkWebGPUTypes.getDetailsFromTextureFormat(model.format);
98114
let bufferBytesPerRow = model.width * tDetails.stride;
99115

116+
/**
117+
* Align texture data to ensure bytesPerRow is a multiple of 256.
118+
* This is necessary for WebGPU texture uploads, especially for half-float formats.
119+
* It also handles half-float conversion if the texture format requires it.
120+
* @param {*} arr - The input array containing texture data.
121+
* @param {*} height - The height of the texture.
122+
* @param {*} depth - The depth of the texture (1 for 2D textures).
123+
* @returns
124+
*/
100125
const alignTextureData = (arr, height, depth) => {
101126
// bytesPerRow must be a multiple of 256 so we might need to rebuild
102127
// the data here before passing to the buffer. e.g. if it is unorm8x4 then
@@ -156,9 +181,7 @@ function vtkWebGPUTexture(publicAPI, model) {
156181
}
157182

158183
if (req.image) {
159-
const canvas = document.createElement('canvas');
160-
canvas.width = req.image.width;
161-
canvas.height = req.image.height;
184+
const canvas = new OffscreenCanvas(req.image.width, req.image.height);
162185
const ctx = canvas.getContext('2d');
163186
ctx.translate(0, canvas.height);
164187
ctx.scale(1, -1);
@@ -183,68 +206,42 @@ function vtkWebGPUTexture(publicAPI, model) {
183206
nativeArray = imageData.data;
184207
}
185208

186-
const cmdEnc = model.device.createCommandEncoder();
187-
188-
if (publicAPI.getDimensionality() !== 3) {
189-
// Non-3D
190-
// First, upload the base mip level (level 0)
191-
const ret = alignTextureData(nativeArray, model.height, 1);
192-
bufferBytesPerRow = ret[1];
193-
const buffRequest = {
194-
dataArray: req.dataArray ? req.dataArray : null,
195-
nativeArray: ret[0],
196-
usage: BufferUsage.Texture,
197-
};
198-
const buff = model.device.getBufferManager().getBuffer(buffRequest);
199-
cmdEnc.copyBufferToTexture(
200-
{
201-
buffer: buff.getHandle(),
202-
offset: 0,
203-
bytesPerRow: bufferBytesPerRow,
204-
rowsPerImage: model.height,
205-
},
206-
{
207-
texture: model.handle,
208-
mipLevel: 0,
209-
},
210-
[model.width, model.height, 1]
211-
);
212-
213-
// Submit the base level upload
214-
model.device.submitCommandEncoder(cmdEnc);
215-
216-
// Generate remaining mip levels on GPU
217-
if (model.mipLevel > 0) {
218-
vtkTexture.generateMipmaps(
219-
model.device.getHandle(),
220-
model.handle,
221-
model.mipLevel + 1
222-
);
209+
const is3D = publicAPI.getDimensionality() === 3;
210+
const alignedTextureData = alignTextureData(
211+
nativeArray,
212+
model.height,
213+
is3D ? model.depth : 1
214+
);
215+
bufferBytesPerRow = alignedTextureData[1];
216+
const data = alignedTextureData[0];
217+
218+
model.device.getHandle().queue.writeTexture(
219+
{
220+
texture: model.handle,
221+
mipLevel: 0,
222+
origin: { x: 0, y: 0, z: 0 },
223+
},
224+
data,
225+
{
226+
offset: 0,
227+
bytesPerRow: bufferBytesPerRow,
228+
rowsPerImage: model.height,
229+
},
230+
{
231+
width: model.width,
232+
height: model.height,
233+
depthOrArrayLayers: is3D ? model.depth : 1,
223234
}
224-
model.ready = true;
225-
} else {
226-
// 3D, no mipmaps
227-
const ret = alignTextureData(nativeArray, model.height, model.depth);
228-
bufferBytesPerRow = ret[1];
229-
const buffRequest = {
230-
dataArray: req.dataArray ? req.dataArray : null,
231-
usage: BufferUsage.Texture,
232-
};
233-
buffRequest.nativeArray = ret[0];
234-
const buff = model.device.getBufferManager().getBuffer(buffRequest);
235-
cmdEnc.copyBufferToTexture(
236-
{
237-
buffer: buff.getHandle(),
238-
offset: 0,
239-
bytesPerRow: bufferBytesPerRow,
240-
rowsPerImage: model.height,
241-
},
242-
{ texture: model.handle },
243-
[model.width, model.height, model.depth]
235+
);
236+
237+
if (!is3D && model.mipLevel > 0) {
238+
vtkTexture.generateMipmaps(
239+
model.device.getHandle(),
240+
model.handle,
241+
model.mipLevel + 1
244242
);
245-
model.device.submitCommandEncoder(cmdEnc);
246-
model.ready = true;
247243
}
244+
model.ready = true;
248245
};
249246

250247
// when data is pulled out of this texture what scale must be applied to

Sources/Rendering/WebGPU/TextureManager/index.js

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
/* eslint-disable no-bitwise */
2-
/* eslint-disable no-undef */
31
import macro from 'vtk.js/Sources/macros';
42
import vtkDataArray from 'vtk.js/Sources/Common/Core/DataArray';
53
import vtkWebGPUTexture from 'vtk.js/Sources/Rendering/WebGPU/Texture';
@@ -70,10 +68,14 @@ function vtkWebGPUTextureManager(publicAPI, model) {
7068
req.height = req.image.height;
7169
req.depth = 1;
7270
req.format = 'rgba8unorm';
71+
/* eslint-disable no-undef */
72+
/* eslint-disable no-bitwise */
7373
req.usage =
7474
GPUTextureUsage.STORAGE_BINDING |
7575
GPUTextureUsage.COPY_DST |
7676
GPUTextureUsage.TEXTURE_BINDING;
77+
/* eslint-enable no-undef */
78+
/* eslint-enable no-bitwise */
7779
}
7880

7981
// fill in based on js imageData
@@ -84,10 +86,31 @@ function vtkWebGPUTextureManager(publicAPI, model) {
8486
req.format = 'rgba8unorm';
8587
req.flip = true;
8688
req.nativeArray = req.jsImageData.data;
89+
/* eslint-disable no-undef */
90+
/* eslint-disable no-bitwise */
8791
req.usage =
8892
GPUTextureUsage.STORAGE_BINDING |
8993
GPUTextureUsage.COPY_DST |
9094
GPUTextureUsage.TEXTURE_BINDING;
95+
/* eslint-enable no-undef */
96+
/* eslint-enable no-bitwise */
97+
}
98+
99+
if (req.imageBitmap) {
100+
req.width = req.imageBitmap.width;
101+
req.height = req.imageBitmap.height;
102+
req.depth = 1;
103+
req.format = 'rgba8unorm';
104+
req.flip = true;
105+
/* eslint-disable no-undef */
106+
/* eslint-disable no-bitwise */
107+
req.usage =
108+
GPUTextureUsage.TEXTURE_BINDING |
109+
GPUTextureUsage.COPY_DST |
110+
GPUTextureUsage.RENDER_ATTACHMENT |
111+
GPUTextureUsage.COPY_SRC;
112+
/* eslint-enable no-undef */
113+
/* eslint-enable no-bitwise */
91114
}
92115

93116
if (req.canvas) {
@@ -121,7 +144,7 @@ function vtkWebGPUTextureManager(publicAPI, model) {
121144
});
122145

123146
// fill the texture if we have data
124-
if (req.nativeArray || req.image || req.canvas) {
147+
if (req.nativeArray || req.image || req.canvas || req.imageBitmap) {
125148
newTex.writeImageData(req);
126149
}
127150
return newTex;
@@ -156,6 +179,8 @@ function vtkWebGPUTextureManager(publicAPI, model) {
156179
treq.image = srcTexture.getImage();
157180
} else if (srcTexture.getJsImageData()) {
158181
treq.jsImageData = srcTexture.getJsImageData();
182+
} else if (srcTexture.getImageBitmap()) {
183+
treq.imageBitmap = srcTexture.getImageBitmap();
159184
} else if (srcTexture.getCanvas()) {
160185
treq.canvas = srcTexture.getCanvas();
161186
}

0 commit comments

Comments
 (0)