@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():
|
|
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
|
-
*
|
|
50
|
-
*
|
|
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
|
-
|
|
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:
|
|
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
|
|
120
|
-
* It obtains its data from an input image data dataset type.
|
|
121
|
-
*
|
|
122
|
-
*
|
|
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
|
|
106
|
-
|
|
107
|
-
canvas
|
|
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,
|
|
136
|
+
context.translate(0, height);
|
|
110
137
|
context.scale(1, -1);
|
|
111
|
-
context.drawImage(model.image, 0, 0,
|
|
112
|
-
const imageData = context.getImageData(0, 0,
|
|
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()
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
//
|
|
725
|
+
// --- Index Buffer ---
|
|
726
|
+
let indexBuffer = null;
|
|
728
727
|
if (cells) {
|
|
729
|
-
|
|
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
|
-
|
|
757
|
-
hash: `${points.getMTime()}I${indexBuffer
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
817
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
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 (
|
|
924
|
-
newTextures.push(
|
|
861
|
+
if (tex.getImage() && tex.getImageLoaded()) {
|
|
862
|
+
newTextures.push([name, tex]);
|
|
925
863
|
}
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
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
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
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
|
-
|
|
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]);
|
|
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 =
|
|
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
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
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
|
}
|