@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.
- package/dist/{WorkerImageDecoder.worker-tkX9-IYo.js → WorkerImageDecoder.worker-C3ZBQ2Wk.js} +1 -1
- package/dist/{decodeImage-C8kB6T3V.js → decodeImage-CxUhz2gE.js} +14278 -2893
- package/dist/{index-P-f_cKZS.js → index-B9Zf3rrb.js} +2 -2
- package/dist/{index-BQ24Upp_.js → index-BJOK4X3d.js} +2 -2
- package/dist/{index-DMvr0-pP.js → index-BTO4og7t.js} +2 -2
- package/dist/{index-DTCT-lVn.js → index-BqTw2FSJ.js} +4 -4
- package/dist/{index-CH1TUS48.js → index-Bw0fCcF0.js} +2 -2
- package/dist/{index-DDVf76z9.js → index-CCWfhL1j.js} +2 -2
- package/dist/{index-BHXfMPMv.js → index-CFz07x1R.js} +2 -2
- package/dist/{index-Bvet1xE9.js → index-CM0J0Tip.js} +2 -2
- package/dist/{index-DtzTeqB7.js → index-D3by772J.js} +2 -2
- package/dist/{index-DzyYicoH.js → index-DMmaMUCD.js} +2813 -1608
- package/dist/{index-C-cGIa0r.js → index-DQ_hdLpb.js} +2 -2
- package/dist/{index-yfiGMPtK.js → index-DRmoIUFd.js} +2 -2
- package/dist/{index-Dcus_L6F.js → index-DWgnF3_o.js} +156 -57
- package/dist/{index-DYpNYj7G.js → index-Db42Qzy_.js} +2 -2
- package/dist/{index-C_ioBAtk.js → index-DgisSKDf.js} +2 -2
- package/dist/{index-CV57d9Tz.js → index-DjOkSXUO.js} +2 -2
- package/dist/{index-D5F-PpU5.js → index-DqqFhpKC.js} +2 -2
- package/dist/{index-RKZ-F77P.js → index-Wr3SUBO9.js} +2 -2
- package/dist/{index-DHgo3Ne_.js → index-oTzD1_p-.js} +2 -2
- package/dist/index.js +2 -2
- package/dist/lib/src/connection/foxglove-connection.d.ts +3 -1
- package/dist/lib/src/connection/foxglove-connection.d.ts.map +1 -1
- package/dist/lib/src/connection/foxglove-connection.js +16 -32
- package/dist/lib/src/connection/foxglove-connection.js.map +1 -1
- package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js +373 -247
- package/dist/lib/src/messaging/deserialization/pointcloud/pointcloudFromDepth.worker.js.map +1 -1
- package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts +30 -0
- package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.d.ts.map +1 -0
- package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js +106 -0
- package/dist/lib/src/messaging/deserialization/pointcloud/poitcloudPoolManager.js.map +1 -0
- package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts +0 -9
- package/dist/lib/src/messaging/deserialization/pointcloud/utils.d.ts.map +1 -1
- package/dist/lib/src/messaging/deserialization/pointcloud/utils.js +0 -16
- package/dist/lib/src/messaging/deserialization/pointcloud/utils.js.map +1 -1
- package/dist/lib/src/panels/PointCloudPanel.js +3 -3
- package/dist/lib/src/panels/PointCloudPanel.js.map +1 -1
- package/dist/lib/src/utils/poitcloud-sync.js +1 -1
- package/dist/lib/src/utils/poitcloud-sync.js.map +1 -1
- package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts +1 -0
- package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.d.ts.map +1 -1
- package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js +243 -154
- package/dist/packages/studio-base/src/panels/ThreeDeeRender/renderables/CameraStateSettings.js.map +1 -1
- package/dist/pointcloudFromDepth.worker-qotYPy_e.js +450 -0
- package/dist/{utils-Cmsz3FxA.js → utils-Hzt3wxhG.js} +2 -20
- package/package.json +2 -1
- 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
|
|
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
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
32
|
-
const buffer = new ArrayBuffer(pointCount * 16);
|
|
30
|
+
const buffer = new ArrayBuffer(numPixels * 16);
|
|
33
31
|
const view = new DataView(buffer);
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
view.setUint8(
|
|
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
|
-
|
|
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
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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(
|
|
58
|
-
@group(0) @binding(
|
|
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
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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(
|
|
180
|
-
const
|
|
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
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
269
|
-
result.set(new Uint8Array(mappedRange));
|
|
362
|
+
const fullResultData = new Uint8Array(mappedRange.slice(0));
|
|
270
363
|
readbackBuffer.unmap();
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
|
280
|
-
console.warn("GPU
|
|
281
|
-
const
|
|
282
|
-
|
|
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 });
|