@myzonerocks/gosslens 0.11.0 → 0.11.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myzonerocks/gosslens",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Web SDK for the gosslens camera and AR engine, over the wasm core.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
package/src/index.ts CHANGED
@@ -527,8 +527,12 @@ export class GossEngine {
527
527
  // itself, via emscripten_webgl_create_context - passing
528
528
  // webGLContextAttributes here has no effect regardless,
529
529
  // preserveDrawingBuffer stays false. Worked around in capturePixels.
530
- const rendererStatus = await mod.ccall("goss_engine_init_renderer", "number", ["number", "number"], [this.handle, rendererDescPtr], { async: true });
531
- mod.ccall("goss_free", null, ["number", "number"], [rendererDescPtr, 12]);
530
+ let rendererStatus: number;
531
+ try {
532
+ rendererStatus = await mod.ccall("goss_engine_init_renderer", "number", ["number", "number"], [this.handle, rendererDescPtr], { async: true });
533
+ } finally {
534
+ mod.ccall("goss_free", null, ["number", "number"], [rendererDescPtr, 12]);
535
+ }
532
536
  if (rendererStatus !== GOSS_OK) throw new Error(`renderer init failed: ${rendererStatus}`);
533
537
 
534
538
  // Emscripten's C++ side just created this canvas's own rendering
@@ -1132,7 +1136,8 @@ export class GossSession {
1132
1136
  if (bytes > this.scratchCapacity) {
1133
1137
  if (this.scratchPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.scratchPtr, this.scratchCapacity]);
1134
1138
  this.scratchPtr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]) as number;
1135
- this.scratchCapacity = bytes;
1139
+ this.scratchCapacity = this.scratchPtr === 0 ? 0 : bytes;
1140
+ if (this.scratchPtr === 0) throw new Error("wasm allocation failed");
1136
1141
  }
1137
1142
  return this.scratchPtr;
1138
1143
  }
@@ -1293,7 +1298,7 @@ export class GossSession {
1293
1298
  /// muxed recording track, unused by the web preview path.
1294
1299
  submitAudio(samples: Float32Array, frameCount: number, sampleRate: number, channels: number, timestampUs: bigint = 0n): void {
1295
1300
  const bytes = samples.length * 4;
1296
- const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [bytes]) as number;
1301
+ const ptr = this.scratch(bytes);
1297
1302
  this.mod.HEAPF32.set(samples, ptr >> 2);
1298
1303
  this.mod.ccall(
1299
1304
  "goss_session_submit_audio",
@@ -1301,18 +1306,15 @@ export class GossSession {
1301
1306
  ["number", "number", "number", "number", "number", "number"],
1302
1307
  [this.handle, ptr, frameCount, sampleRate, channels, timestampUs],
1303
1308
  );
1304
- this.mod.ccall("goss_free", null, ["number", "number"], [ptr, bytes]);
1305
1309
  }
1306
1310
 
1307
1311
  /// Pulls the next block of mixed lens audio (frames interleaved s16) that
1308
1312
  /// play_sound triggers produced, for the page to feed into WebAudio.
1309
1313
  pullAudio(frames: number): Int16Array {
1310
1314
  const byteLen = frames * 2;
1311
- const ptr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLen]);
1315
+ const ptr = this.scratch(byteLen);
1312
1316
  this.mod.ccall("goss_session_pull_audio", "number", ["number", "number", "number"], [this.handle, ptr, frames]);
1313
- const out = new Int16Array(this.mod.HEAP16.buffer, ptr, frames).slice();
1314
- this.mod.ccall("goss_free", null, ["number", "number"], [ptr, byteLen]);
1315
- return out;
1317
+ return new Int16Array(this.mod.HEAP16.buffer, ptr, frames).slice();
1316
1318
  }
1317
1319
 
1318
1320
  /// Folds the active lens sound into the caller's outgoing call/live track:
@@ -1321,12 +1323,12 @@ export class GossSession {
1321
1323
  /// mixed interleaved s16. Advances the mixer once, replacing `pullAudio`.
1322
1324
  mixOutputAudio(mic: Float32Array | null, frameCount: number, sampleRate: number, channels: number): Int16Array {
1323
1325
  const outLen = frameCount * channels;
1324
- const outBytes = outLen * 2;
1325
- const outPtr = this.mod.ccall("goss_alloc", "number", ["number"], [outBytes]);
1326
- let micPtr = 0;
1326
+ const outBytes = (outLen * 2 + 3) & ~3;
1327
1327
  const micBytes = mic ? mic.length * 4 : 0;
1328
+ const outPtr = this.scratch(outBytes + micBytes);
1329
+ let micPtr = 0;
1328
1330
  if (mic) {
1329
- micPtr = this.mod.ccall("goss_alloc", "number", ["number"], [micBytes]);
1331
+ micPtr = outPtr + outBytes;
1330
1332
  this.mod.HEAPF32.set(mic, micPtr >> 2);
1331
1333
  }
1332
1334
  this.mod.ccall(
@@ -1335,10 +1337,7 @@ export class GossSession {
1335
1337
  ["number", "number", "number", "number", "number", "number"],
1336
1338
  [this.handle, micPtr, outPtr, frameCount, sampleRate, channels],
1337
1339
  );
1338
- const out = new Int16Array(this.mod.HEAP16.buffer, outPtr, outLen).slice();
1339
- this.mod.ccall("goss_free", null, ["number", "number"], [outPtr, outBytes]);
1340
- if (mic) this.mod.ccall("goss_free", null, ["number", "number"], [micPtr, micBytes]);
1341
- return out;
1340
+ return new Int16Array(this.mod.HEAP16.buffer, outPtr, outLen).slice();
1342
1341
  }
1343
1342
 
1344
1343
  /// Stores validated camera-hardware intent; the engine normalizes it. Read it
@@ -1483,7 +1482,7 @@ export class GossSession {
1483
1482
  /// Uploads one RGBA/BGRA frame into a named source (pixelFormat 3 BGRA, 4 RGBA).
1484
1483
  submitSourceFrameRgba(name: string, rgba: Uint8Array, width: number, height: number, stride: number, pixelFormat: GossPixelFormat = GossPixelFormat.Rgba8): void {
1485
1484
  const byteLen = stride * height;
1486
- const rgbaPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLen]) as number;
1485
+ const rgbaPtr = this.scratch(byteLen);
1487
1486
  this.mod.HEAPU8.set(rgba.subarray(0, byteLen), rgbaPtr);
1488
1487
  this.mod.setValue(this.frameDescPtr, width, "i32");
1489
1488
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -1495,7 +1494,6 @@ export class GossSession {
1495
1494
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
1496
1495
  this.withName(name, (ptr, len) =>
1497
1496
  this.mod.ccall("goss_session_submit_source_frame_rgba_copy", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, ptr, len, this.frameDescPtr, rgbaPtr, stride]));
1498
- this.mod.ccall("goss_free", null, ["number", "number"], [rgbaPtr, byteLen]);
1499
1497
  }
1500
1498
 
1501
1499
  /// Arranges the camera and named sources: 0 custom, 1 side-by-side, 2 top-bottom, 3 pip, 4 grid.
@@ -1798,7 +1796,7 @@ export class GossSession {
1798
1796
  }
1799
1797
  const scaleX = this.frameWidth / sourceWidth;
1800
1798
  const scaleY = this.frameHeight / sourceHeight;
1801
- const pointCount = landmarks.length / 3;
1799
+ const pointCount = Math.min(landmarks.length / 3, GOSS_FACE_LANDMARK_COUNT);
1802
1800
  const base = this.landmarksPtr >> 2;
1803
1801
  for (let at = 0; at < pointCount; at += 1) {
1804
1802
  this.mod.HEAPF32[base + at * 3] = landmarks[at * 3]! * scaleX;
@@ -1829,15 +1827,15 @@ export class GossSession {
1829
1827
  const off = i * FACE_RESULT_BYTES;
1830
1828
  const base = ptr + off;
1831
1829
  const f = faces[i]!;
1832
- const count = f.landmarks.length / 3;
1830
+ const count = Math.min(f.landmarks.length / 3, GOSS_FACE_LANDMARK_COUNT);
1833
1831
  dv.setBigUint64(off, BigInt(f.frameSerial ?? 0), true);
1834
1832
  dv.setBigInt64(off + 8, BigInt(f.timestampUs ?? 0), true);
1835
1833
  dv.setFloat32(off + 16, f.presence ?? 1, true);
1836
1834
  dv.setUint32(off + 20, count, true);
1837
- this.mod.HEAPF32.set(f.landmarks, (base + 24) >> 2);
1835
+ this.mod.HEAPF32.set(f.landmarks.subarray(0, count * 3), (base + 24) >> 2);
1838
1836
  const bsStart = (base + 24 + GOSS_FACE_LANDMARK_COUNT * 3 * 4) >> 2;
1839
1837
  this.mod.HEAPF32.fill(0, bsStart, bsStart + GOSS_FACE_BLENDSHAPE_COUNT);
1840
- if (f.blendshapes) this.mod.HEAPF32.set(f.blendshapes, bsStart);
1838
+ if (f.blendshapes) this.mod.HEAPF32.set(f.blendshapes.subarray(0, GOSS_FACE_BLENDSHAPE_COUNT), bsStart);
1841
1839
  }
1842
1840
  this.mod.ccall("goss_session_submit_faces", "number", ["number", "number", "number"], [this.handle, ptr, faces.length]);
1843
1841
  }
@@ -1894,16 +1892,16 @@ export class GossSession {
1894
1892
  const off = i * POSE_RESULT_BYTES;
1895
1893
  const base = ptr + off;
1896
1894
  const b = bodies[i]!;
1897
- const count = b.landmarks.length / 3;
1895
+ const count = Math.min(b.landmarks.length / 3, GOSS_POSE_LANDMARK_COUNT);
1898
1896
  dv.setBigUint64(off, BigInt(b.frameSerial ?? 0), true);
1899
1897
  dv.setBigInt64(off + 8, BigInt(b.timestampUs ?? 0), true);
1900
1898
  dv.setFloat32(off + 16, b.presence ?? 1, true);
1901
1899
  dv.setUint32(off + 20, count, true);
1902
- this.mod.HEAPF32.set(b.landmarks, (base + 24) >> 2);
1900
+ this.mod.HEAPF32.set(b.landmarks.subarray(0, count * 3), (base + 24) >> 2);
1903
1901
  const visStart = (base + 24 + GOSS_POSE_LANDMARK_COUNT * 3 * 4) >> 2;
1904
1902
  this.mod.HEAPF32.fill(0, visStart, visStart + GOSS_POSE_LANDMARK_COUNT * 2);
1905
- if (b.visibilities) this.mod.HEAPF32.set(b.visibilities, visStart);
1906
- if (b.presences) this.mod.HEAPF32.set(b.presences, visStart + GOSS_POSE_LANDMARK_COUNT);
1903
+ if (b.visibilities) this.mod.HEAPF32.set(b.visibilities.subarray(0, GOSS_POSE_LANDMARK_COUNT), visStart);
1904
+ if (b.presences) this.mod.HEAPF32.set(b.presences.subarray(0, GOSS_POSE_LANDMARK_COUNT), visStart + GOSS_POSE_LANDMARK_COUNT);
1907
1905
  }
1908
1906
  this.mod.ccall("goss_session_submit_bodies", "number", ["number", "number", "number"], [this.handle, ptr, bodies.length]);
1909
1907
  }
@@ -2215,9 +2213,9 @@ export class GossSession {
2215
2213
  /// Submits one NV12 exposure of an HDR bracket, fed to bracket-source
2216
2214
  /// temporal.fuse nodes. y and uv are the plane bytes as submitFrameCopy takes.
2217
2215
  submitFrameBracket(y: Uint8Array, yStride: number, uv: Uint8Array, uvStride: number, width: number, height: number, colorStandard = 1, colorRange = 0): void {
2218
- const yPtr = this.mod.ccall("goss_alloc", "number", ["number"], [y.length]) as number;
2216
+ const yPtr = this.scratch(y.length + uv.length);
2219
2217
  this.mod.HEAPU8.set(y, yPtr);
2220
- const uvPtr = this.mod.ccall("goss_alloc", "number", ["number"], [uv.length]) as number;
2218
+ const uvPtr = yPtr + y.length;
2221
2219
  this.mod.HEAPU8.set(uv, uvPtr);
2222
2220
  this.mod.setValue(this.frameDescPtr, width, "i32");
2223
2221
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -2228,14 +2226,12 @@ export class GossSession {
2228
2226
  this.mod.setValue(this.frameDescPtr + 24, 0, "i32");
2229
2227
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
2230
2228
  this.mod.ccall("goss_session_submit_frame_bracket", "number", ["number", "number", "number", "number", "number", "number"], [this.handle, this.frameDescPtr, yPtr, yStride, uvPtr, uvStride]);
2231
- this.mod.ccall("goss_free", null, ["number", "number"], [yPtr, y.length]);
2232
- this.mod.ccall("goss_free", null, ["number", "number"], [uvPtr, uv.length]);
2233
2229
  }
2234
2230
 
2235
2231
  private submitPlanes(sym: string, y: Uint8Array, yStride: number, uv: Uint8Array, uvStride: number, width: number, height: number, colorStandard: number, colorRange: number): void {
2236
- const yPtr = this.mod.ccall("goss_alloc", "number", ["number"], [y.length]) as number;
2232
+ const yPtr = this.scratch(y.length + uv.length);
2237
2233
  this.mod.HEAPU8.set(y, yPtr);
2238
- const uvPtr = this.mod.ccall("goss_alloc", "number", ["number"], [uv.length]) as number;
2234
+ const uvPtr = yPtr + y.length;
2239
2235
  this.mod.HEAPU8.set(uv, uvPtr);
2240
2236
  this.mod.setValue(this.frameDescPtr, width, "i32");
2241
2237
  this.mod.setValue(this.frameDescPtr + 4, height, "i32");
@@ -2246,8 +2242,6 @@ export class GossSession {
2246
2242
  this.mod.setValue(this.frameDescPtr + 24, 0, "i32");
2247
2243
  this.mod.setValue(this.frameDescPtr + 28, 0, "i32");
2248
2244
  this.mod.ccall(sym, "number", ["number", "number", "number", "number", "number", "number"], [this.handle, this.frameDescPtr, yPtr, yStride, uvPtr, uvStride]);
2249
- this.mod.ccall("goss_free", null, ["number", "number"], [yPtr, y.length]);
2250
- this.mod.ccall("goss_free", null, ["number", "number"], [uvPtr, uv.length]);
2251
2245
  }
2252
2246
 
2253
2247
  /// Feeds one NV12 frame to the in-engine trackers only (no render), for a host
@@ -2626,7 +2620,8 @@ export class GossSession {
2626
2620
  if (this.framePixelsCapacity >= byteLength) return;
2627
2621
  if (this.framePixelsPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.framePixelsPtr, this.framePixelsCapacity]);
2628
2622
  this.framePixelsPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLength]);
2629
- this.framePixelsCapacity = byteLength;
2623
+ this.framePixelsCapacity = this.framePixelsPtr === 0 ? 0 : byteLength;
2624
+ if (this.framePixelsPtr === 0) throw new Error("wasm allocation failed");
2630
2625
  }
2631
2626
 
2632
2627
  /// rotationDegrees omitted means the setVideoFlip state decides (a
@@ -2729,7 +2724,8 @@ export class GossSession {
2729
2724
  if (this.worldScratchLen >= byteLength) return;
2730
2725
  if (this.worldScratchPtr !== 0) this.mod.ccall("goss_free", null, ["number", "number"], [this.worldScratchPtr, this.worldScratchLen]);
2731
2726
  this.worldScratchPtr = this.mod.ccall("goss_alloc", "number", ["number"], [byteLength]);
2732
- this.worldScratchLen = byteLength;
2727
+ this.worldScratchLen = this.worldScratchPtr === 0 ? 0 : byteLength;
2728
+ if (this.worldScratchPtr === 0) throw new Error("wasm allocation failed");
2733
2729
  }
2734
2730
 
2735
2731
  /// Reports one finished frame: measured whole-pipeline time plus
@@ -2805,9 +2801,14 @@ export class GossPreviewSession {
2805
2801
  static async create(canvas: HTMLCanvasElement, wasmJsUrl: string | URL, events: GossSessionEvents = {}): Promise<GossPreviewSession> {
2806
2802
  const gosslens = await Gosslens.load(canvas, wasmJsUrl);
2807
2803
  const engine = GossEngine.create(gosslens);
2808
- await engine.initRenderer(canvas);
2809
- const session = GossSession.create(engine);
2810
- return new GossPreviewSession(gosslens, engine, session, events);
2804
+ try {
2805
+ await engine.initRenderer(canvas);
2806
+ const session = GossSession.create(engine);
2807
+ return new GossPreviewSession(gosslens, engine, session, events);
2808
+ } catch (err) {
2809
+ engine.destroy();
2810
+ throw err;
2811
+ }
2811
2812
  }
2812
2813
 
2813
2814
  abiVersion(): number {
@@ -2936,8 +2937,10 @@ export class GossPreviewSession {
2936
2937
  this.cameraFrames += 1;
2937
2938
  const width = this.video.videoWidth;
2938
2939
  const height = this.video.videoHeight;
2939
- this.scratchCanvas.width = width;
2940
- this.scratchCanvas.height = height;
2940
+ if (this.scratchCanvas.width !== width || this.scratchCanvas.height !== height) {
2941
+ this.scratchCanvas.width = width;
2942
+ this.scratchCanvas.height = height;
2943
+ }
2941
2944
  this.scratchCtx.drawImage(this.video, 0, 0, width, height);
2942
2945
  const pixels = this.scratchCtx.getImageData(0, 0, width, height);
2943
2946
  // Not mirrored here - the demo page's own CSS mirrors the canvas