@luxonis/visualizer-protobuf 2.22.0 → 2.23.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.
Files changed (48) hide show
  1. package/dist/{WorkerImageDecoder.worker-tkX9-IYo.js → WorkerImageDecoder.worker-C3ZBQ2Wk.js} +1 -1
  2. package/dist/{decodeImage-C8kB6T3V.js → decodeImage-CxUhz2gE.js} +14278 -2893
  3. package/dist/{index-P-f_cKZS.js → index-B9Zf3rrb.js} +2 -2
  4. package/dist/{index-BQ24Upp_.js → index-BJOK4X3d.js} +2 -2
  5. package/dist/{index-DMvr0-pP.js → index-BTO4og7t.js} +2 -2
  6. package/dist/{index-DTCT-lVn.js → index-BqTw2FSJ.js} +4 -4
  7. package/dist/{index-CH1TUS48.js → index-Bw0fCcF0.js} +2 -2
  8. package/dist/{index-DDVf76z9.js → index-CCWfhL1j.js} +2 -2
  9. package/dist/{index-BHXfMPMv.js → index-CFz07x1R.js} +2 -2
  10. package/dist/{index-Bvet1xE9.js → index-CM0J0Tip.js} +2 -2
  11. package/dist/{index-DtzTeqB7.js → index-D3by772J.js} +2 -2
  12. package/dist/{index-DzyYicoH.js → index-DMmaMUCD.js} +2813 -1608
  13. package/dist/{index-C-cGIa0r.js → index-DQ_hdLpb.js} +2 -2
  14. package/dist/{index-yfiGMPtK.js → index-DRmoIUFd.js} +2 -2
  15. package/dist/{index-Dcus_L6F.js → index-DWgnF3_o.js} +156 -57
  16. package/dist/{index-DYpNYj7G.js → index-Db42Qzy_.js} +2 -2
  17. package/dist/{index-C_ioBAtk.js → index-DgisSKDf.js} +2 -2
  18. package/dist/{index-CV57d9Tz.js → index-DjOkSXUO.js} +2 -2
  19. package/dist/{index-D5F-PpU5.js → index-DqqFhpKC.js} +2 -2
  20. package/dist/{index-RKZ-F77P.js → index-Wr3SUBO9.js} +2 -2
  21. package/dist/{index-DHgo3Ne_.js → index-oTzD1_p-.js} +2 -2
  22. package/dist/index.js +2 -2
  23. package/dist/lib/src/connection/foxglove-connection.d.ts +3 -1
  24. package/dist/lib/src/connection/foxglove-connection.d.ts.map +1 -1
  25. package/dist/lib/src/connection/foxglove-connection.js +16 -32
  26. package/dist/lib/src/connection/foxglove-connection.js.map +1 -1
  27. package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js +373 -247
  28. package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js.map +1 -1
  29. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts +30 -0
  30. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts.map +1 -0
  31. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js +106 -0
  32. package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js.map +1 -0
  33. package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts +0 -9
  34. package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts.map +1 -1
  35. package/dist/lib/src/messaging/deserialization/pointcloud/utils.js +0 -16
  36. package/dist/lib/src/messaging/deserialization/pointcloud/utils.js.map +1 -1
  37. package/dist/lib/src/panels/PointCloudPanel.js +3 -3
  38. package/dist/lib/src/panels/PointCloudPanel.js.map +1 -1
  39. package/dist/lib/src/utils/poitcloud-sync.js +1 -1
  40. package/dist/lib/src/utils/poitcloud-sync.js.map +1 -1
  41. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts +1 -0
  42. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts.map +1 -1
  43. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js +243 -154
  44. package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js.map +1 -1
  45. package/dist/pointcloudFromDepth.worker-qotYPy_e.js +450 -0
  46. package/dist/{utils-Cmsz3FxA.js → utils-Hzt3wxhG.js} +2 -20
  47. package/package.json +2 -1
  48. package/dist/pointcloudFromDepth.worker-CNKyMUU-.js +0 -326
@@ -5,281 +5,407 @@
5
5
  import { expose } from "comlink";
6
6
  import { i420ToRgbaToPointcloud } from "./utils";
7
7
  function depthToPointCloudBuffer(uint16Array, width, height, fx, fy, cx, cy) {
8
- const depthData = Array.from({ length: height }, () => new Array(width));
8
+ const numPixels = width * height;
9
+ const points = new Float32Array(numPixels * 3);
9
10
  for (let v = 0; v < height; v++) {
10
- depthData[v] = depthData[v] ?? [];
11
11
  for (let u = 0; u < width; u++) {
12
- const d = uint16Array[v * width + u] ?? -1;
13
- // biome-ignore lint/style/noNonNullAssertion: <explanation>
14
- depthData[v][u] = d > 0 ? d : -1;
15
- }
16
- }
17
- const points = [];
18
- // Accumulate the x, y, z values for each pixel.
19
- for (let v = 0; v < height; v++) {
20
- const row = depthData[v];
21
- if (!row) {
22
- continue;
23
- }
24
- for (let u = 0; u < width; u++) {
25
- const z = row[u] ?? 0;
26
- const x = (cx - u) * z / fx;
27
- const y = (cy - v) * z / fy;
28
- points.push(x, y, z);
12
+ const pixelIndex = v * width + u;
13
+ const depthValue = uint16Array[pixelIndex] ?? 0;
14
+ let x = 0.0;
15
+ let y = 0.0;
16
+ let z = 0.0;
17
+ const depthScale = 1.0;
18
+ const scaledDepth = depthValue * depthScale;
19
+ if (scaledDepth > 0 && fx !== 0 && fy !== 0) {
20
+ x = (cx - u) * scaledDepth / fx;
21
+ y = (cy - v) * scaledDepth / fy;
22
+ z = scaledDepth;
23
+ }
24
+ const pointOffset = pixelIndex * 3;
25
+ points[pointOffset] = x;
26
+ points[pointOffset + 1] = y;
27
+ points[pointOffset + 2] = z;
29
28
  }
30
29
  }
31
- const pointCount = points.length / 3;
32
- const buffer = new ArrayBuffer(pointCount * 16);
30
+ const buffer = new ArrayBuffer(numPixels * 16);
33
31
  const view = new DataView(buffer);
34
- for (let i = 0; i < pointCount; i++) {
35
- const offset = i * 16;
36
- view.setFloat32(offset, points[i * 3] ?? 0, true); //X
37
- view.setFloat32(offset + 4, points[i * 3 + 1] ?? 0, true); //Y
38
- view.setFloat32(offset + 8, points[i * 3 + 2] ?? 0, true); //Z
39
- view.setUint8(offset + 12, 0); // R
40
- view.setUint8(offset + 13, 0); // G
41
- view.setUint8(offset + 14, 0); // B
42
- view.setUint8(offset + 15, 255); // A
32
+ const pointsU8 = new Uint8Array(points.buffer);
33
+ for (let i = 0; i < numPixels; i++) {
34
+ const xyzOffsetBytes = i * 12;
35
+ const outputOffsetBytes = i * 16;
36
+ for (let b = 0; b < 12; ++b) {
37
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
38
+ view.setUint8(outputOffsetBytes + b, pointsU8[xyzOffsetBytes + b]);
39
+ }
40
+ view.setUint8(outputOffsetBytes + 12, 0); // R
41
+ view.setUint8(outputOffsetBytes + 13, 0); // G
42
+ view.setUint8(outputOffsetBytes + 14, 0); // B
43
+ view.setUint8(outputOffsetBytes + 15, 255); // A (Opaque)
43
44
  }
44
45
  return new Uint8Array(buffer);
45
46
  }
47
+ const WORKGROUP_SIZE_X = 128;
48
+ const POINT_STRIDE_BYTES = 16;
46
49
  const shader = `
47
- @group(0) @binding(1) var<storage, read> intrinsics: array<f32>;
50
+ struct Intrinsics {
51
+ fx: f32,
52
+ fy: f32,
53
+ cx: f32,
54
+ cy: f32,
55
+ depthScale: f32,
56
+ depthWidth: f32,
57
+ depthHeight: f32,
58
+ i420Width: f32,
59
+ i420Height: f32,
60
+ numFrames_f: f32,
61
+ bytesPerI420Frame_f: f32,
62
+ };
48
63
 
49
- struct PointOutput {
50
- x: f32,
51
- y: f32,
52
- z: f32,
53
- color: u32,
64
+ struct OutputPointData {
65
+ x: f32,
66
+ y: f32,
67
+ z: f32,
68
+ color: u32,
54
69
  };
55
70
 
56
71
  @group(0) @binding(0) var<storage, read> depthBuffer: array<u32>;
57
- @group(0) @binding(2) var<storage, read_write> xyzColorBuffer: array<PointOutput>;
58
- @group(0) @binding(3) var<storage, read> i420Buffer: array<u32>; // Correct type
59
-
60
- @compute @workgroup_size(256, 1, 1)
61
- fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
62
- let i = global_id.x;
72
+ @group(0) @binding(1) var<storage, read> intrinsics: Intrinsics;
73
+ @group(0) @binding(2) var<storage, read_write> xyzColorBuffer: array<OutputPointData>;
74
+ @group(0) @binding(3) var<storage, read> i420Buffer: array<u32>;
63
75
 
64
- // Read dimensions
65
- let depthWidth = intrinsics[5];
66
- let depthHeight = intrinsics[6];
67
- let numPixels = u32(depthWidth * depthHeight);
68
-
69
- // Bounds check
70
- if (i >= numPixels || depthWidth <= 0.0 || depthHeight <= 0.0 ) { return; }
71
-
72
- // Read other intrinsics
73
- let fx = intrinsics[0];
74
- let fy = intrinsics[1];
75
- let cx = intrinsics[2];
76
- let cy = intrinsics[3];
77
- let depthScale = intrinsics[4]; // Make sure this is correct (e.g., 0.001)
78
- let i420Width = intrinsics[7];
79
- let i420Height = intrinsics[8];
80
-
81
- let u = f32(i % u32(depthWidth));
82
- let v = f32(i / u32(depthWidth));
76
+ fn getI420Byte(byteIndexWithinFrame: u32, frameOffsetElements: u32, bufferLengthElements: u32) -> u32 {
77
+ let globalByteIndex = (frameOffsetElements * 4u) + byteIndexWithinFrame;
78
+ let elementIndex = globalByteIndex / 4u;
79
+ let offsetInElement = globalByteIndex % 4u;
80
+ if (elementIndex >= bufferLengthElements) {
81
+ return 0u;
82
+ }
83
+ let packedValue = i420Buffer[elementIndex];
84
+ return (packedValue >> (offsetInElement * 8u)) & 0xFFu;
85
+ }
83
86
 
84
- // --- Extract u16 depth from u32 buffer ---
85
- let depthIndex32 = i / 2u;
86
- // Add bounds check for safety, though numPixels check should cover it
87
- if (depthIndex32 >= arrayLength(&depthBuffer)) { return; }
88
- let packedDepthU32 = depthBuffer[depthIndex32];
89
- var rawDepthU32 : u32;
90
- if (i % 2u == 0u) {
91
- rawDepthU32 = packedDepthU32 & 0xFFFFu; // Lower 16 bits
92
- } else {
93
- rawDepthU32 = (packedDepthU32 >> 16) & 0xFFFFu; // Upper 16 bits
87
+ @compute @workgroup_size(${WORKGROUP_SIZE_X}, 1, 1)
88
+ fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
89
+ let pixel_idx_in_frame = global_id.x;
90
+ let frame_idx = global_id.y;
91
+ let depthWidth = u32(intrinsics.depthWidth);
92
+ let depthHeight = u32(intrinsics.depthHeight);
93
+ let i420WidthU = u32(intrinsics.i420Width);
94
+ let i420HeightU = u32(intrinsics.i420Height);
95
+ let numFrames = u32(intrinsics.numFrames_f);
96
+ let bytesPerI420Frame = u32(intrinsics.bytesPerI420Frame_f);
97
+ let numPixelsPerFrame = depthWidth * depthHeight;
98
+ if (pixel_idx_in_frame >= numPixelsPerFrame || frame_idx >= numFrames || depthWidth == 0u || depthHeight == 0u) {
99
+ return;
100
+ }
101
+ let global_output_idx = (frame_idx * numPixelsPerFrame) + pixel_idx_in_frame;
102
+ let depth_elements_per_frame = (numPixelsPerFrame + 1u) / 2u;
103
+ let depth_frame_offset = frame_idx * depth_elements_per_frame;
104
+ let i420AlignedStrideBytes = (bytesPerI420Frame + 3u) / 4u * 4u;
105
+ let i420_elements_per_frame_aligned = i420AlignedStrideBytes / 4u;
106
+ let i420_frame_offset = frame_idx * i420_elements_per_frame_aligned;
107
+ let depthIndex32_within_frame = pixel_idx_in_frame / 2u;
108
+ let packedDepthU32_idx = depth_frame_offset + depthIndex32_within_frame;
109
+ if (packedDepthU32_idx >= arrayLength(&depthBuffer)) {
110
+ return;
111
+ }
112
+ let packedDepthU32 = depthBuffer[packedDepthU32_idx];
113
+ var rawDepthU16: u32;
114
+ if (pixel_idx_in_frame % 2u == 0u) {
115
+ rawDepthU16 = packedDepthU32 & 0xFFFFu;
116
+ } else {
117
+ rawDepthU16 = (packedDepthU32 >> 16u) & 0xFFFFu;
118
+ }
119
+ let u = f32(pixel_idx_in_frame % depthWidth);
120
+ let v = f32(pixel_idx_in_frame / depthWidth);
121
+ var outputPoint: OutputPointData;
122
+ outputPoint.x = 0.0;
123
+ outputPoint.y = 0.0;
124
+ outputPoint.z = 0.0;
125
+ outputPoint.color = 0xFF000000u;
126
+ let fx = intrinsics.fx;
127
+ let fy = intrinsics.fy;
128
+ let cx = intrinsics.cx;
129
+ let cy = intrinsics.cy;
130
+ let depthScale = intrinsics.depthScale;
131
+ if (rawDepthU16 > 0u && fx != 0.0 && fy != 0.0 && depthScale > 0.0) {
132
+ let z = f32(rawDepthU16) * depthScale;
133
+ outputPoint.x = (cx - u) * z / fx;
134
+ outputPoint.y = (cy - v) * z / fy;
135
+ outputPoint.z = z;
136
+ if (i420WidthU > 0u && i420HeightU > 0u && bytesPerI420Frame > 0u) {
137
+ let i420X = min(i420WidthU - 1u, u32(floor(u * (intrinsics.i420Width / intrinsics.depthWidth))));
138
+ let i420Y = min(i420HeightU - 1u, u32(floor(v * (intrinsics.i420Height / intrinsics.depthHeight))));
139
+ let frameSizeY = i420WidthU * i420HeightU;
140
+ let uvWidth = (i420WidthU + 1u) / 2u;
141
+ let uvHeight = (i420HeightU + 1u) / 2u;
142
+ let frameSizeUV = uvWidth * uvHeight;
143
+ let uOffsetBytes = frameSizeY;
144
+ let vOffsetBytes = frameSizeY + frameSizeUV;
145
+ let yIndexBytes = i420Y * i420WidthU + i420X;
146
+ let uvX = i420X / 2u;
147
+ let uvY = i420Y / 2u;
148
+ let uvIndexBytes = uvY * uvWidth + uvX;
149
+ let uIndexBytes = uOffsetBytes + uvIndexBytes;
150
+ let vIndexBytes = vOffsetBytes + uvIndexBytes;
151
+ let i420BufferTotalElements = arrayLength(&i420Buffer);
152
+ if (yIndexBytes < bytesPerI420Frame && uIndexBytes < bytesPerI420Frame && vIndexBytes < bytesPerI420Frame && (i420_frame_offset + (vIndexBytes / 4u)) < i420BufferTotalElements) {
153
+ let y_byte = getI420Byte(yIndexBytes, i420_frame_offset, i420BufferTotalElements);
154
+ let u_byte = getI420Byte(uIndexBytes, i420_frame_offset, i420BufferTotalElements);
155
+ let v_byte = getI420Byte(vIndexBytes, i420_frame_offset, i420BufferTotalElements);
156
+ let y_f = f32(y_byte);
157
+ let u_f = f32(u_byte) - 128.0;
158
+ let v_f = f32(v_byte) - 128.0;
159
+ var r_f = y_f + 1.402 * v_f;
160
+ var g_f = y_f - 0.344136 * u_f - 0.714136 * v_f;
161
+ var b_f = y_f + 1.772 * u_f;
162
+ let r = u32(clamp(r_f, 0.0, 255.0)) & 0xFFu;
163
+ let g = u32(clamp(g_f, 0.0, 255.0)) & 0xFFu;
164
+ let b = u32(clamp(b_f, 0.0, 255.0)) & 0xFFu;
165
+ let a = 255u;
166
+ outputPoint.color = (r) | (g << 8u) | (b << 16u) | (a << 24u);
167
+ }
94
168
  }
95
- // --- End depth extraction ---
96
-
97
- // Default point output (black, opaque)
98
- var outputPoint: PointOutput;
99
- outputPoint.x = 0.0;
100
- outputPoint.y = 0.0;
101
- outputPoint.z = 0.0;
102
- outputPoint.color = 0u | (0u << 8) | (0u << 16) | (255u << 24);
103
-
104
- // Process only valid depth pixels and valid intrinsics
105
- if (rawDepthU32 > 0u && fx != 0.0 && fy != 0.0 && depthScale > 0.0) {
106
- let z = f32(rawDepthU32) * depthScale;
107
- outputPoint.x = (cx- u) * z / fx;
108
- outputPoint.y = (cy- v) * z / fy;
109
- outputPoint.z = z;
110
-
111
- // --- I420 Color Calculation with u8 extraction from u32 buffer ---
112
- if (i420Width > 0.0 && i420Height > 0.0) {
113
- let i420WidthU = u32(i420Width);
114
- let i420HeightU = u32(i420Height);
115
-
116
- let i420X = min(i420WidthU - 1u, u32( floor(u * (i420Width / depthWidth)) ));
117
- let i420Y = min(i420HeightU - 1u, u32( floor(v * (i420Height / depthHeight)) ));
118
-
119
- // Calculate original byte indices
120
- let frameSize = i420WidthU * i420HeightU;
121
- let uvWidth = (i420WidthU + 1u) / 2u;
122
- let uvHeight = (i420HeightU + 1u) / 2u;
123
- let uOffset = frameSize;
124
- let vOffset = frameSize + uvWidth * uvHeight;
125
-
126
- let yIndex = i420Y * i420WidthU + i420X;
127
- let uvX = i420X / 2u;
128
- let uvY = i420Y / 2u;
129
- let uvIndex = uvY * uvWidth + uvX;
130
- let uIndex = uOffset + uvIndex;
131
- let vIndex = vOffset + uvIndex;
132
-
133
- // --- Extract Y byte ---
134
- let yIndex32 = yIndex / 4u;
135
- let yOffsetIn32 = yIndex % 4u;
136
- // Bounds check (use original byte length concept for check)
137
- let bufferByteLength = arrayLength(&i420Buffer) * 4u; // Conceptual byte length
138
- if (yIndex32 < arrayLength(&i420Buffer) && yIndex < bufferByteLength) { // Check index and conceptual byte index
139
- let packedY = i420Buffer[yIndex32];
140
- let y_byte = (packedY >> (yOffsetIn32 * 8u)) & 0xFFu;
141
- let y_f = f32(y_byte);
142
-
143
- // --- Extract U byte ---
144
- let uIndex32 = uIndex / 4u;
145
- let uOffsetIn32 = uIndex % 4u;
146
- if (uIndex32 < arrayLength(&i420Buffer) && uIndex < bufferByteLength) { // Check index and conceptual byte index
147
- let packedU = i420Buffer[uIndex32];
148
- let u_byte = (packedU >> (uOffsetIn32 * 8u)) & 0xFFu;
149
- let u_f = f32(u_byte) - 128.0;
150
-
151
- // --- Extract V byte ---
152
- let vIndex32 = vIndex / 4u;
153
- let vOffsetIn32 = vIndex % 4u;
154
- if (vIndex32 < arrayLength(&i420Buffer) && vIndex < bufferByteLength) { // Check index and conceptual byte index
155
- let packedV = i420Buffer[vIndex32];
156
- let v_byte = (packedV >> (vOffsetIn32 * 8u)) & 0xFFu;
157
- let v_f = f32(v_byte) - 128.0;
158
-
159
- // --- YUV to RGB ---
160
- var r_f = y_f + 1.402 * v_f;
161
- var g_f = y_f - 0.344136 * u_f - 0.714136 * v_f;
162
- var b_f = y_f + 1.772 * u_f;
163
-
164
- let r = u32(clamp(r_f, 0.0, 255.0));
165
- let g = u32(clamp(g_f, 0.0, 255.0));
166
- let b = u32(clamp(b_f, 0.0, 255.0));
167
- let a = 255u;
168
-
169
- outputPoint.color = (r & 0xFFu) | ((g & 0xFFu) << 8) | ((b & 0xFFu) << 16) | (a << 24);
170
- } // End V valid
171
- } // End U valid
172
- } // End Y valid
173
- } // End if i420 dimensions valid
174
- } // End if rawDepth valid
175
-
176
- xyzColorBuffer[i] = outputPoint;
169
+ }
170
+ if (global_output_idx < arrayLength(&xyzColorBuffer)) {
171
+ xyzColorBuffer[global_output_idx] = outputPoint;
172
+ }
177
173
  }
174
+
178
175
  `;
179
- async function depthToPointcloudGPU(depthArray, depthWidth, depthHeight, fx, fy, cx, cy, i420Data, i420Width, i420Height) {
180
- const depthScale = 1;
176
+ async function depthToPointcloudGPU(depthFrames, depthWidth, depthHeight, fx, fy, cx, cy, i420Frames, i420Width, i420Height, { hasGPU }) {
177
+ const numFrames = depthFrames.length;
178
+ if (numFrames === 0 || numFrames !== i420Frames.length) {
179
+ console.warn("GPU: Input frame arrays are empty or mismatched.");
180
+ return [];
181
+ }
181
182
  if (depthWidth <= 0 || depthHeight <= 0) {
182
- throw new Error("Invalid depth dimensions!");
183
+ throw new Error("GPU: Invalid depth dimensions!");
183
184
  }
184
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
185
- if (!navigator.gpu) {
186
- const xyzData = depthToPointCloudBuffer(depthArray, depthWidth, depthHeight, fx, fy, cx, cy);
187
- return i420ToRgbaToPointcloud(i420Data, i420Width, i420Height, depthWidth, depthHeight, xyzData);
185
+ if (depthFrames[0] && !(depthFrames[0] instanceof Uint16Array)) {
186
+ throw new Error("GPU: depthFrames[0].data is NOT a Uint16Array!");
188
187
  }
189
- const adapter = await navigator.gpu.requestAdapter();
190
- if (!adapter) {
191
- const xyzData = depthToPointCloudBuffer(depthArray, depthWidth, depthHeight, fx, fy, cx, cy);
192
- return i420ToRgbaToPointcloud(i420Data, i420Width, i420Height, depthWidth, depthHeight, xyzData);
188
+ if (i420Frames[0] && !(i420Frames[0] instanceof Uint8Array)) {
189
+ throw new Error("GPU: i420Frames[0].data is NOT a Uint8Array!");
193
190
  }
194
- const device = await adapter.requestDevice();
195
- const queue = device.queue;
196
- const numPixels = depthWidth * depthHeight;
197
- const outputBufferSize = numPixels * 16;
198
- const depthBuffer = device.createBuffer({
199
- size: depthArray.byteLength,
200
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
201
- mappedAtCreation: true,
202
- });
203
- new Uint16Array(depthBuffer.getMappedRange()).set(depthArray);
204
- depthBuffer.unmap();
205
- const intrinsicsData = new Float32Array([
206
- fx, fy, cx, cy,
207
- depthScale,
208
- depthWidth, depthHeight,
209
- i420Width, i420Height
210
- ]);
211
- const intrinsicsBufferSize = Math.max(intrinsicsData.byteLength, 40);
212
- const intrinsicsBuffer = device.createBuffer({
213
- size: intrinsicsBufferSize,
214
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
215
- });
216
- queue.writeBuffer(intrinsicsBuffer, 0, intrinsicsData);
217
- const i420BufferSize = i420Data.byteLength;
218
- const i420GpuBuffer = device.createBuffer({
219
- size: i420BufferSize,
220
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
221
- mappedAtCreation: true,
222
- });
223
- new Uint8Array(i420GpuBuffer.getMappedRange()).set(i420Data);
224
- i420GpuBuffer.unmap();
225
- const xyzColorBuffer = device.createBuffer({
226
- size: outputBufferSize,
227
- usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
228
- });
229
- const bindGroupLayout = device.createBindGroupLayout({
230
- entries: [
231
- { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
232
- { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
233
- { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
234
- { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
235
- ],
236
- });
237
- const bindGroup = device.createBindGroup({
238
- layout: bindGroupLayout,
239
- entries: [
240
- { binding: 0, resource: { buffer: depthBuffer } },
241
- { binding: 1, resource: { buffer: intrinsicsBuffer } },
242
- { binding: 2, resource: { buffer: xyzColorBuffer } },
243
- { binding: 3, resource: { buffer: i420GpuBuffer } },
244
- ],
245
- });
246
- const shaderModule = device.createShaderModule({ code: shader });
247
- const pipeline = device.createComputePipeline({
248
- layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
249
- compute: { module: shaderModule, entryPoint: "main" },
250
- });
251
- const commandEncoder = device.createCommandEncoder();
252
- const passEncoder = commandEncoder.beginComputePass();
253
- passEncoder.setPipeline(pipeline);
254
- passEncoder.setBindGroup(0, bindGroup);
255
- const workgroupSize = 256;
256
- const numWorkgroups = Math.ceil(numPixels / workgroupSize);
257
- passEncoder.dispatchWorkgroups(numWorkgroups);
258
- passEncoder.end();
259
- const readbackBuffer = device.createBuffer({
260
- size: outputBufferSize,
261
- usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
262
- });
263
- commandEncoder.copyBufferToBuffer(xyzColorBuffer, 0, readbackBuffer, 0, outputBufferSize);
264
- queue.submit([commandEncoder.finish()]);
191
+ const depthScale = 1.0;
192
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
193
+ if (!hasGPU) {
194
+ const results = [];
195
+ for (let i = 0; i < numFrames; i++) {
196
+ const depthFrame = depthFrames[i];
197
+ const colorFrame = i420Frames[i];
198
+ if (!depthFrame || !colorFrame) {
199
+ console.warn(`CPU Fallback: Skipping frame ${i} due to missing data.`);
200
+ results.push(new Uint8Array(0));
201
+ continue;
202
+ }
203
+ try {
204
+ const xyzData = depthToPointCloudBuffer(depthFrame, depthWidth, depthHeight, fx, fy, cx, cy);
205
+ results.push(i420ToRgbaToPointcloud(colorFrame, i420Width, i420Height, depthWidth, depthHeight, xyzData));
206
+ }
207
+ catch (cpuError) {
208
+ console.error(`CPU Fallback: Error processing frame ${i}:`, cpuError);
209
+ results.push(new Uint8Array(0));
210
+ }
211
+ }
212
+ return results;
213
+ }
214
+ let device = null;
215
+ let depthGpuBuffer = null;
216
+ let intrinsicsBuffer = null;
217
+ let i420GpuBuffer = null;
218
+ let xyzColorBuffer = null;
219
+ let readbackBuffer = null;
265
220
  try {
221
+ const adapter = await navigator.gpu.requestAdapter();
222
+ if (!adapter) {
223
+ throw new Error("Failed to get GPU adapter.");
224
+ }
225
+ device = await adapter.requestDevice();
226
+ const queue = device.queue;
227
+ const numPixelsPerFrame = depthWidth * depthHeight;
228
+ const outputFrameSize = numPixelsPerFrame * POINT_STRIDE_BYTES;
229
+ const totalOutputBufferSize = outputFrameSize * numFrames;
230
+ const depthBytesPerFrame = numPixelsPerFrame * 2;
231
+ const depthElementsPerFrame = Math.ceil(depthBytesPerFrame / 4);
232
+ const totalDepthBufferSize = depthElementsPerFrame * numFrames * 4;
233
+ const i420BytesPerFrame = i420Frames[0]?.byteLength ?? 0;
234
+ if (i420BytesPerFrame === 0 && numFrames > 0) {
235
+ throw new Error("GPU: I420 frame data is empty or invalid for size calculation.");
236
+ }
237
+ const i420AlignedStrideBytes = Math.ceil(i420BytesPerFrame / 4) * 4;
238
+ const totalI420BufferSize = i420AlignedStrideBytes * numFrames;
239
+ const intrinsicsData = new Float32Array([
240
+ fx, fy, cx, cy,
241
+ depthScale,
242
+ depthWidth, depthHeight,
243
+ i420Width, i420Height,
244
+ numFrames,
245
+ i420BytesPerFrame,
246
+ ]);
247
+ const intrinsicsBufferSize = Math.max(intrinsicsData.byteLength, 64);
248
+ intrinsicsBuffer = device.createBuffer({
249
+ label: "Intrinsics Buffer",
250
+ size: intrinsicsBufferSize,
251
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
252
+ });
253
+ queue.writeBuffer(intrinsicsBuffer, 0, intrinsicsData.buffer, intrinsicsData.byteOffset, intrinsicsData.byteLength);
254
+ depthGpuBuffer = device.createBuffer({
255
+ label: "Depth Buffer (Combined)",
256
+ size: totalDepthBufferSize,
257
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
258
+ mappedAtCreation: true,
259
+ });
260
+ const mappedDepthRange = depthGpuBuffer.getMappedRange();
261
+ const fullDepthArrayViewU32 = new Uint32Array(mappedDepthRange);
262
+ for (let i = 0; i < numFrames; ++i) {
263
+ const frameDataU16 = depthFrames[i];
264
+ if (!frameDataU16 || !(frameDataU16 instanceof Uint16Array)) {
265
+ console.warn(`GPU: Skipping depth frame ${i} due to missing/invalid data.`);
266
+ const frameOffsetElements = i * depthElementsPerFrame;
267
+ const elementsToWrite = depthElementsPerFrame;
268
+ fullDepthArrayViewU32.fill(0, frameOffsetElements, frameOffsetElements + elementsToWrite);
269
+ continue;
270
+ }
271
+ const frameOffsetElements = i * depthElementsPerFrame;
272
+ for (let p = 0; p < numPixelsPerFrame; p++) {
273
+ const u16_val = frameDataU16[p] ?? 0;
274
+ const elementIdx = frameOffsetElements + Math.floor(p / 2);
275
+ if (elementIdx >= fullDepthArrayViewU32.length) {
276
+ continue;
277
+ }
278
+ const isLowerHalf = (p % 2) === 0;
279
+ if (isLowerHalf) {
280
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
281
+ fullDepthArrayViewU32[elementIdx] = (fullDepthArrayViewU32[elementIdx] & 0xFFFF0000) | u16_val;
282
+ }
283
+ else {
284
+ // biome-ignore lint/style/noNonNullAssertion: <explanation>
285
+ fullDepthArrayViewU32[elementIdx] = (fullDepthArrayViewU32[elementIdx] & 0x0000FFFF) | (u16_val << 16);
286
+ }
287
+ }
288
+ }
289
+ depthGpuBuffer.unmap();
290
+ i420GpuBuffer = device.createBuffer({
291
+ label: "I420 Buffer (Combined, Aligned)",
292
+ size: totalI420BufferSize,
293
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
294
+ mappedAtCreation: true,
295
+ });
296
+ const mappedI420Range = i420GpuBuffer.getMappedRange();
297
+ const fullI420ArrayViewU8 = new Uint8Array(mappedI420Range);
298
+ for (let i = 0; i < numFrames; ++i) {
299
+ const frameData = i420Frames[i];
300
+ const byteOffset = i * i420AlignedStrideBytes;
301
+ if (!frameData || !(frameData instanceof Uint8Array) || frameData.byteLength !== i420BytesPerFrame) {
302
+ console.warn(`GPU: Skipping I420 frame ${i} due to missing/invalid data or size mismatch.`);
303
+ fullI420ArrayViewU8.fill(0, byteOffset, byteOffset + i420AlignedStrideBytes);
304
+ continue;
305
+ }
306
+ const frameTargetView = new Uint8Array(mappedI420Range, byteOffset, frameData.byteLength);
307
+ frameTargetView.set(frameData);
308
+ if (i420AlignedStrideBytes > frameData.byteLength) {
309
+ const paddingStart = byteOffset + frameData.byteLength;
310
+ const paddingLength = i420AlignedStrideBytes - frameData.byteLength;
311
+ fullI420ArrayViewU8.fill(0, paddingStart, paddingStart + paddingLength);
312
+ }
313
+ }
314
+ i420GpuBuffer.unmap();
315
+ xyzColorBuffer = device.createBuffer({
316
+ label: "Output XYZColor Buffer (Combined)",
317
+ size: totalOutputBufferSize,
318
+ usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC,
319
+ });
320
+ readbackBuffer = device.createBuffer({
321
+ label: "Readback Buffer",
322
+ size: totalOutputBufferSize,
323
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
324
+ });
325
+ const bindGroupLayout = device.createBindGroupLayout({
326
+ label: "Compute Bind Group Layout",
327
+ entries: [
328
+ { binding: 0, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
329
+ { binding: 1, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
330
+ { binding: 2, visibility: GPUShaderStage.COMPUTE, buffer: { type: "storage" } },
331
+ { binding: 3, visibility: GPUShaderStage.COMPUTE, buffer: { type: "read-only-storage" } },
332
+ ],
333
+ });
334
+ const bindGroup = device.createBindGroup({
335
+ label: "Compute Bind Group",
336
+ layout: bindGroupLayout,
337
+ entries: [
338
+ { binding: 0, resource: { buffer: depthGpuBuffer } },
339
+ { binding: 1, resource: { buffer: intrinsicsBuffer } },
340
+ { binding: 2, resource: { buffer: xyzColorBuffer } },
341
+ { binding: 3, resource: { buffer: i420GpuBuffer } },
342
+ ],
343
+ });
344
+ const shaderModule = device.createShaderModule({ label: "Compute Shader Module", code: shader });
345
+ const pipeline = device.createComputePipeline({
346
+ label: "Compute Pipeline",
347
+ layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
348
+ compute: { module: shaderModule, entryPoint: "main" },
349
+ });
350
+ const commandEncoder = device.createCommandEncoder({ label: "Compute Command Encoder" });
351
+ const passEncoder = commandEncoder.beginComputePass({ label: "Compute Pass" });
352
+ passEncoder.setPipeline(pipeline);
353
+ passEncoder.setBindGroup(0, bindGroup);
354
+ const numWorkgroupsX = Math.ceil(numPixelsPerFrame / WORKGROUP_SIZE_X);
355
+ const numWorkgroupsY = numFrames;
356
+ passEncoder.dispatchWorkgroups(numWorkgroupsX, numWorkgroupsY, 1);
357
+ passEncoder.end();
358
+ commandEncoder.copyBufferToBuffer(xyzColorBuffer, 0, readbackBuffer, 0, totalOutputBufferSize);
359
+ queue.submit([commandEncoder.finish()]);
266
360
  await readbackBuffer.mapAsync(GPUMapMode.READ);
267
361
  const mappedRange = readbackBuffer.getMappedRange();
268
- const result = new Uint8Array(mappedRange.byteLength);
269
- result.set(new Uint8Array(mappedRange));
362
+ const fullResultData = new Uint8Array(mappedRange.slice(0));
270
363
  readbackBuffer.unmap();
271
- depthBuffer.destroy();
272
- intrinsicsBuffer.destroy();
273
- i420GpuBuffer.destroy();
274
- xyzColorBuffer.destroy();
275
- readbackBuffer.destroy();
276
- return result;
364
+ readbackBuffer = null;
365
+ const results = [];
366
+ for (let i = 0; i < numFrames; i++) {
367
+ const frameOffset = i * outputFrameSize;
368
+ const frameEnd = Math.min(frameOffset + outputFrameSize, fullResultData.byteLength);
369
+ const frameData = fullResultData.slice(frameOffset, frameEnd);
370
+ if (frameData.byteLength !== outputFrameSize) {
371
+ console.warn(`GPU: Result frame ${i} has incorrect size (${frameData.byteLength} vs ${outputFrameSize}). Padding or error occurred.`);
372
+ results.push(new Uint8Array(outputFrameSize));
373
+ }
374
+ else {
375
+ results.push(frameData);
376
+ }
377
+ }
378
+ return results;
277
379
  }
278
380
  catch (error) {
279
- console.error("Error mapping readback buffer:", error);
280
- console.warn("GPU processing failed during readback. Falling back to CPU.");
281
- const xyzData = depthToPointCloudBuffer(depthArray, depthWidth, depthHeight, fx, fy, cx, cy);
282
- return i420ToRgbaToPointcloud(i420Data, i420Width, i420Height, depthWidth, depthHeight, xyzData);
381
+ console.error("GPU: Error during WebGPU processing:", error);
382
+ console.warn("GPU: Falling back to CPU due to error.");
383
+ const results = [];
384
+ for (let i = 0; i < numFrames; i++) {
385
+ const depthFrame = depthFrames[i];
386
+ const colorFrame = i420Frames[i];
387
+ if (!depthFrame || !colorFrame) {
388
+ console.warn(`CPU Fallback (error path): Skipping frame ${i} due to missing data.`);
389
+ results.push(new Uint8Array(0));
390
+ continue;
391
+ }
392
+ try {
393
+ const xyzData = depthToPointCloudBuffer(depthFrame, depthWidth, depthHeight, fx, fy, cx, cy);
394
+ results.push(i420ToRgbaToPointcloud(colorFrame, i420Width, i420Height, depthWidth, depthHeight, xyzData));
395
+ }
396
+ catch (cpuError) {
397
+ console.error(`CPU Fallback (error path): Error processing frame ${i}:`, cpuError);
398
+ results.push(new Uint8Array(0));
399
+ }
400
+ }
401
+ return results;
402
+ }
403
+ finally {
404
+ depthGpuBuffer?.destroy();
405
+ intrinsicsBuffer?.destroy();
406
+ i420GpuBuffer?.destroy();
407
+ xyzColorBuffer?.destroy();
408
+ readbackBuffer?.destroy();
283
409
  }
284
410
  }
285
411
  expose({ depthToPointcloudGPU });