serve-sim 0.1.40-beta.66.1 → 0.1.40

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.
@@ -20,6 +20,7 @@ xcrun --sdk macosx clang \
20
20
  -framework CoreImage \
21
21
  -framework CoreText \
22
22
  -framework ImageIO \
23
+ -framework IOSurface \
23
24
  -framework Accelerate \
24
25
  -O2 \
25
26
  -o "$BIN" \
@@ -29,6 +29,7 @@
29
29
  #import <CoreImage/CoreImage.h>
30
30
  #import <Accelerate/Accelerate.h>
31
31
  #import <ImageIO/ImageIO.h>
32
+ #import <IOSurface/IOSurface.h>
32
33
 
33
34
  #include <fcntl.h>
34
35
  #include <signal.h>
@@ -44,7 +45,9 @@
44
45
  #pragma mark - Globals (shm + writer)
45
46
 
46
47
  static SimCamShmHeader *gHeader = NULL;
47
- static uint8_t *gPixels = NULL;
48
+ static SimCamSurfaceTable *gSurfaceTable = NULL;
49
+ static IOSurfaceRef gSurfaces[SIMCAM_SURFACE_RING];
50
+ static uint32_t gWriteIndex = 0; // last ring slot rendered into
48
51
  static uint32_t gWidth = SIMCAM_DEFAULT_WIDTH;
49
52
  static uint32_t gHeight = SIMCAM_DEFAULT_HEIGHT;
50
53
  static const char *gShmName = NULL;
@@ -65,15 +68,48 @@ static uint8_t ParseMirrorCode(NSString *mode);
65
68
  static NSString *MirrorName(uint8_t code);
66
69
 
67
70
  // Publish a fully-prepared BGRA frame (gWidth x gHeight, packed at gWidth*4
68
- // bytes per row) to the shm region. Writers MUST go through this so seq/ts
69
- // stay coherent for the dylib's tear-detection check.
71
+ // bytes per row) into the next free ring surface. Writers MUST go through this
72
+ // so latestIndex/frameSeq stay coherent for the dylib's tear-detection check.
70
73
  static void PublishFrame(const uint8_t *bgra) {
71
- if (!gHeader || !gPixels || !bgra) return;
72
- memcpy(gPixels, bgra, (size_t)gWidth * gHeight * 4);
74
+ if (!gHeader || !gSurfaceTable || !bgra) return;
75
+ uint32_t count = gSurfaceTable->surfaceCount;
76
+ if (count == 0) return;
77
+
78
+ // Render into a surface the reader isn't holding and isn't the one it last
79
+ // published, so an in-flight frame is never overwritten mid-read.
80
+ uint32_t latest = gSurfaceTable->latestIndex;
81
+ uint32_t idx = gWriteIndex;
82
+ BOOL found = NO;
83
+ for (uint32_t tries = 0; tries < count; tries++) {
84
+ idx = (idx + 1) % count;
85
+ if (idx == latest) continue;
86
+ if (!IOSurfaceIsInUse(gSurfaces[idx])) {
87
+ found = YES;
88
+ break;
89
+ }
90
+ }
91
+ if (!found) return;
92
+ gWriteIndex = idx;
93
+
94
+ IOSurfaceRef surface = gSurfaces[idx];
95
+ IOSurfaceLock(surface, 0, NULL);
96
+ uint8_t *dst = (uint8_t *)IOSurfaceGetBaseAddress(surface);
97
+ size_t dstStride = IOSurfaceGetBytesPerRow(surface);
98
+ size_t srcStride = (size_t)gWidth * 4;
99
+ if (dstStride == srcStride) {
100
+ memcpy(dst, bgra, srcStride * gHeight);
101
+ } else {
102
+ for (uint32_t y = 0; y < gHeight; y++) {
103
+ memcpy(dst + (size_t)y * dstStride, bgra + (size_t)y * srcStride, srcStride);
104
+ }
105
+ }
106
+ IOSurfaceUnlock(surface, 0, NULL);
107
+
108
+ gSurfaceTable->latestIndex = idx;
73
109
  gHeader->timestampNs = MachAbsToNs(mach_absolute_time());
74
110
  atomic_thread_fence(memory_order_release);
75
111
  uint64_t next = atomic_fetch_add(&gFrameSeq, 1) + 1;
76
- gHeader->frameSeq = next;
112
+ atomic_store_explicit(&gHeader->frameSeq, next, memory_order_release);
77
113
  }
78
114
 
79
115
  #pragma mark - Source pipeline (start / stop / switch)
@@ -754,7 +790,41 @@ static void ListDevices(void) {
754
790
  }
755
791
  }
756
792
 
757
- static int OpenShm(const char *name, size_t size) {
793
+ // Allocate the IOSurface ring and record their global IDs in the table.
794
+ static BOOL CreateSurfaces(void) {
795
+ #pragma clang diagnostic push
796
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
797
+ NSDictionary *props = @{
798
+ (id)kIOSurfaceWidth: @(gWidth),
799
+ (id)kIOSurfaceHeight: @(gHeight),
800
+ (id)kIOSurfaceBytesPerElement: @4,
801
+ (id)kIOSurfacePixelFormat: @((uint32_t)kCVPixelFormatType_32BGRA),
802
+ // Global so the simulator process can resolve the surface by ID.
803
+ (id)kIOSurfaceIsGlobal: @YES,
804
+ };
805
+ #pragma clang diagnostic pop
806
+ for (uint32_t i = 0; i < SIMCAM_SURFACE_RING; i++) {
807
+ IOSurfaceRef s = IOSurfaceCreate((__bridge CFDictionaryRef)props);
808
+ if (!s) {
809
+ fprintf(stderr, "[serve-sim-camera] IOSurfaceCreate failed at %u\n", i);
810
+ return NO;
811
+ }
812
+ gSurfaces[i] = s;
813
+ gSurfaceTable->ids[i] = IOSurfaceGetID(s);
814
+ }
815
+ gSurfaceTable->surfaceCount = SIMCAM_SURFACE_RING;
816
+ gSurfaceTable->latestIndex = 0;
817
+ return YES;
818
+ }
819
+
820
+ static void ReleaseSurfaces(void) {
821
+ for (uint32_t i = 0; i < SIMCAM_SURFACE_RING; i++) {
822
+ if (gSurfaces[i]) { CFRelease(gSurfaces[i]); gSurfaces[i] = NULL; }
823
+ }
824
+ }
825
+
826
+ static int OpenShm(const char *name) {
827
+ size_t size = (size_t)SimCamControlSize();
758
828
  shm_unlink(name);
759
829
  int fd = shm_open(name, O_CREAT | O_RDWR, 0644);
760
830
  if (fd < 0) { perror("shm_open"); return -1; }
@@ -762,14 +832,15 @@ static int OpenShm(const char *name, size_t size) {
762
832
  void *map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
763
833
  if (map == MAP_FAILED) { perror("mmap"); close(fd); return -1; }
764
834
  gHeader = (SimCamShmHeader *)map;
765
- gPixels = (uint8_t *)map + sizeof(SimCamShmHeader);
766
- memset(gHeader, 0, sizeof(*gHeader));
835
+ gSurfaceTable = (SimCamSurfaceTable *)((uint8_t *)map + sizeof(SimCamShmHeader));
836
+ memset(map, 0, size);
837
+ if (!CreateSurfaces()) { close(fd); return -1; }
767
838
  gHeader->magic = SIMCAM_SHM_MAGIC;
768
- gHeader->version = 1;
839
+ gHeader->version = 2;
769
840
  gHeader->width = gWidth;
770
841
  gHeader->height = gHeight;
771
842
  gHeader->pixelFormat = SIMCAM_PIXEL_BGRA;
772
- gHeader->bytesPerRow = gWidth * 4;
843
+ gHeader->bytesPerRow = (uint32_t)IOSurfaceGetBytesPerRow(gSurfaces[0]);
773
844
  gHeader->pixelByteSize = (uint64_t)gWidth * gHeight * 4;
774
845
  gHeader->mirrorMode = SIMCAM_MIRROR_UNSET; // dylib falls back to env
775
846
  return fd;
@@ -824,10 +895,9 @@ int main(int argc, const char *argv[]) {
824
895
  // (no-op marker; --device implies webcam below if user intended it)
825
896
  }
826
897
 
827
- size_t shmSize = (size_t)SimCamShmSizeFor(gWidth, gHeight);
828
- if (OpenShm(gShmName, shmSize) < 0) return 1;
829
- fprintf(stderr, "[serve-sim-camera] shm \"%s\" %zu bytes (%ux%u BGRA)\n",
830
- gShmName, shmSize, gWidth, gHeight);
898
+ if (OpenShm(gShmName) < 0) return 1;
899
+ fprintf(stderr, "[serve-sim-camera] shm \"%s\" + %u IOSurfaces (%ux%u BGRA)\n",
900
+ gShmName, SIMCAM_SURFACE_RING, gWidth, gHeight);
831
901
 
832
902
  gSourceQueue = dispatch_queue_create("simcam.helper.source", DISPATCH_QUEUE_SERIAL);
833
903
 
@@ -863,6 +933,8 @@ int main(int argc, const char *argv[]) {
863
933
  if (gControlListenFd >= 0) { close(gControlListenFd); if (socketPath) unlink(socketPath); }
864
934
  StopPlaceholderSource();
865
935
  StopWebcamSource();
936
+ StopVideoSource();
937
+ ReleaseSurfaces();
866
938
  if (gShmName) shm_unlink(gShmName);
867
939
  fprintf(stderr, "[serve-sim-camera] stopped\n");
868
940
  return 0;
@@ -6,6 +6,7 @@
6
6
  #import <CoreImage/CoreImage.h>
7
7
  #import <CoreMedia/CoreMedia.h>
8
8
  #import <CoreVideo/CoreVideo.h>
9
+ #import <IOSurface/IOSurfaceRef.h>
9
10
  #import <UIKit/UIKit.h>
10
11
  #import <QuartzCore/QuartzCore.h>
11
12
  #import <objc/runtime.h>
@@ -26,8 +27,8 @@ static size_t kFrameHeight = 720;
26
27
  static const double kFrameRate = 30.0;
27
28
 
28
29
  static SimCamShmHeader *gShmHeader = NULL;
29
- static const uint8_t *gShmPixels = NULL;
30
- static size_t gShmTotalSize = 0;
30
+ static SimCamSurfaceTable *gSurfaceTable = NULL;
31
+ static IOSurfaceRef gSurfaces[SIMCAM_SURFACE_RING]; // resolved from global IDs
31
32
  static uint64_t gLastSeenSeq = 0;
32
33
 
33
34
  #pragma mark - Last-frame cache
@@ -236,38 +237,37 @@ static CGImageRef SimCamAcquireCachedCGImage(void) CF_RETURNS_RETAINED {
236
237
  });
237
238
  }
238
239
 
239
- - (CVPixelBufferRef)newPixelBufferFromShm CF_RETURNS_RETAINED {
240
- return [self newPixelBufferFromShmForceFresh:NO];
240
+ - (CVPixelBufferRef)newPixelBufferFromSurface CF_RETURNS_RETAINED {
241
+ return [self newPixelBufferFromSurfaceForceFresh:NO];
241
242
  }
242
243
 
243
- - (CVPixelBufferRef)newPixelBufferFromShmForceFresh:(BOOL)force CF_RETURNS_RETAINED {
244
- if (!gShmHeader || !gShmPixels) return NULL;
244
+ // Wrap the latest shared IOSurface as a CVPixelBuffer — zero copy. Holding the
245
+ // pixel buffer keeps the surface in use, so the host writer renders into a
246
+ // different ring slot until we release it.
247
+ - (CVPixelBufferRef)newPixelBufferFromSurfaceForceFresh:(BOOL)force CF_RETURNS_RETAINED {
248
+ if (!gShmHeader || !gSurfaceTable) return NULL;
245
249
  if (gShmHeader->magic != SIMCAM_SHM_MAGIC) return NULL;
246
- uint64_t seqA = gShmHeader->frameSeq;
250
+ uint64_t seqA = atomic_load_explicit(&gShmHeader->frameSeq, memory_order_acquire);
247
251
  if (seqA == 0) return NULL;
248
252
  if (!force && seqA == gLastSeenSeq) return NULL;
249
- uint32_t w = gShmHeader->width;
250
- uint32_t h = gShmHeader->height;
251
- uint32_t bpr = gShmHeader->bytesPerRow;
252
- if (!w || !h || bpr < w * 4) return NULL;
253
- if (sizeof(SimCamShmHeader) + (size_t)bpr * h > gShmTotalSize) return NULL;
253
+
254
+ uint32_t count = gSurfaceTable->surfaceCount;
255
+ if (count == 0 || count > SIMCAM_SURFACE_RING) return NULL;
256
+ uint32_t idx = gSurfaceTable->latestIndex;
257
+ if (idx >= count) return NULL;
258
+ IOSurfaceRef surface = gSurfaces[idx];
259
+ if (!surface) {
260
+ simcam_log(@"missing IOSurface at latest index %u/%u", idx, count);
261
+ return NULL;
262
+ }
254
263
 
255
264
  CVPixelBufferRef pb = NULL;
256
265
  NSDictionary *attrs = @{ (id)kCVPixelBufferIOSurfacePropertiesKey: @{} };
257
- CVReturn r = CVPixelBufferCreate(kCFAllocatorDefault, w, h,
258
- kCVPixelFormatType_32BGRA, (__bridge CFDictionaryRef)attrs, &pb);
266
+ CVReturn r = CVPixelBufferCreateWithIOSurface(kCFAllocatorDefault, surface,
267
+ (__bridge CFDictionaryRef)attrs, &pb);
259
268
  if (r != kCVReturnSuccess || !pb) return NULL;
260
- CVPixelBufferLockBaseAddress(pb, 0);
261
- uint8_t *dst = (uint8_t *)CVPixelBufferGetBaseAddress(pb);
262
- size_t dstBpr = CVPixelBufferGetBytesPerRow(pb);
263
- size_t copyBpr = MIN((size_t)bpr, dstBpr);
264
- for (uint32_t y = 0; y < h; y++) {
265
- memcpy(dst + y * dstBpr, gShmPixels + y * bpr, copyBpr);
266
- }
267
- CVPixelBufferUnlockBaseAddress(pb, 0);
268
269
 
269
- atomic_thread_fence(memory_order_acquire);
270
- uint64_t seqB = gShmHeader->frameSeq;
270
+ uint64_t seqB = atomic_load_explicit(&gShmHeader->frameSeq, memory_order_acquire);
271
271
  if (!force && seqA != seqB) {
272
272
  CVPixelBufferRelease(pb);
273
273
  return NULL;
@@ -277,7 +277,7 @@ static CGImageRef SimCamAcquireCachedCGImage(void) CF_RETURNS_RETAINED {
277
277
  }
278
278
 
279
279
  - (CVPixelBufferRef)currentPixelBuffer CF_RETURNS_RETAINED {
280
- CVPixelBufferRef pb = [self newPixelBufferFromShmForceFresh:YES];
280
+ CVPixelBufferRef pb = [self newPixelBufferFromSurfaceForceFresh:YES];
281
281
  if (!pb) pb = [self newPixelBufferFromImage];
282
282
  return pb;
283
283
  }
@@ -350,7 +350,7 @@ static CGImageRef SimCamAcquireCachedCGImage(void) CF_RETURNS_RETAINED {
350
350
  }
351
351
 
352
352
  - (CMSampleBufferRef)newSampleBufferAtTime:(CMTime)pts CF_RETURNS_RETAINED {
353
- CVPixelBufferRef pb = [self newPixelBufferFromShm];
353
+ CVPixelBufferRef pb = [self newPixelBufferFromSurface];
354
354
  if (!pb) pb = [self newPixelBufferFromImage];
355
355
  if (pb) {
356
356
  SimCamCacheFrame(pb);
@@ -362,7 +362,7 @@ static CGImageRef SimCamAcquireCachedCGImage(void) CF_RETURNS_RETAINED {
362
362
  dispatch_once(&logOnce, ^{
363
363
  simcam_log(@"no-signal fallback: shm=%@ frameSeq=%llu cache=empty",
364
364
  gShmHeader ? @"attached" : @"unattached",
365
- (unsigned long long)(gShmHeader ? gShmHeader->frameSeq : 0));
365
+ (unsigned long long)(gShmHeader ? atomic_load_explicit(&gShmHeader->frameSeq, memory_order_acquire) : 0));
366
366
  });
367
367
  pb = [self newPixelBufferNoSignal];
368
368
  }
@@ -518,13 +518,14 @@ void SimCamFrameSourceOpenShmIfRequested(void) {
518
518
  simcam_log(@"shm_open(%s) failed: %s", shmName, strerror(errno));
519
519
  return;
520
520
  }
521
+ size_t size = (size_t)SimCamControlSize();
521
522
  struct stat st;
522
- if (fstat(fd, &st) < 0 || st.st_size < (off_t)sizeof(SimCamShmHeader)) {
523
+ if (fstat(fd, &st) < 0 || (size_t)st.st_size < size) {
523
524
  simcam_log(@"shm fstat failed or too small");
524
525
  close(fd);
525
526
  return;
526
527
  }
527
- void *map = mmap(NULL, (size_t)st.st_size, PROT_READ, MAP_SHARED, fd, 0);
528
+ void *map = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
528
529
  close(fd);
529
530
  if (map == MAP_FAILED) {
530
531
  simcam_log(@"shm mmap failed: %s", strerror(errno));
@@ -533,14 +534,44 @@ void SimCamFrameSourceOpenShmIfRequested(void) {
533
534
  SimCamShmHeader *hdr = (SimCamShmHeader *)map;
534
535
  if (hdr->magic != SIMCAM_SHM_MAGIC) {
535
536
  simcam_log(@"shm magic mismatch: 0x%x", hdr->magic);
536
- munmap(map, (size_t)st.st_size);
537
+ munmap(map, size);
538
+ return;
539
+ }
540
+ SimCamSurfaceTable *table =
541
+ (SimCamSurfaceTable *)((uint8_t *)map + sizeof(SimCamShmHeader));
542
+ uint32_t count = table->surfaceCount;
543
+ if (count == 0 || count > SIMCAM_SURFACE_RING) {
544
+ simcam_log(@"shm surface table invalid (count=%u)", count);
545
+ munmap(map, size);
546
+ return;
547
+ }
548
+
549
+ // Resolve each global IOSurface ID to a local reference.
550
+ uint32_t resolved = 0;
551
+ #pragma clang diagnostic push
552
+ #pragma clang diagnostic ignored "-Wdeprecated-declarations"
553
+ for (uint32_t i = 0; i < count; i++) {
554
+ IOSurfaceRef s = IOSurfaceLookup(table->ids[i]);
555
+ gSurfaces[i] = s;
556
+ if (s) resolved++;
557
+ }
558
+ #pragma clang diagnostic pop
559
+ if (resolved != count) {
560
+ for (uint32_t i = 0; i < count; i++) {
561
+ if (gSurfaces[i]) {
562
+ CFRelease(gSurfaces[i]);
563
+ gSurfaces[i] = NULL;
564
+ }
565
+ }
566
+ simcam_log(@"only %u/%u IOSurfaces resolved from shm \"%s\"", resolved, count, shmName);
567
+ munmap(map, size);
537
568
  return;
538
569
  }
570
+
539
571
  gShmHeader = hdr;
540
- gShmPixels = (const uint8_t *)map + sizeof(SimCamShmHeader);
541
- gShmTotalSize = (size_t)st.st_size;
572
+ gSurfaceTable = table;
542
573
  kFrameWidth = hdr->width;
543
574
  kFrameHeight = hdr->height;
544
- simcam_log(@"shm \"%s\" attached (%ux%u, %llu bytes)",
545
- shmName, hdr->width, hdr->height, (unsigned long long)st.st_size);
575
+ simcam_log(@"shm \"%s\" attached (%ux%u, %u/%u IOSurfaces resolved)",
576
+ shmName, hdr->width, hdr->height, resolved, count);
546
577
  }
@@ -25,6 +25,7 @@ xcrun --sdk iphonesimulator clang \
25
25
  -framework CoreMotion \
26
26
  -framework CoreVideo \
27
27
  -framework CoreGraphics \
28
+ -framework IOSurface \
28
29
  -framework QuartzCore \
29
30
  -install_name "@rpath/libSimCameraInjector.dylib" \
30
31
  -o "$DYLIB" \
@@ -1,27 +1,41 @@
1
- // Shared memory wire format for serve-sim's simulator camera feed.
1
+ // Wire format for serve-sim's simulator camera feed.
2
2
  //
3
- // One process (the host helper, running on macOS) captures webcam frames and
4
- // writes them into a POSIX shared-memory region. The injected dylib inside
5
- // the simulator app maps the same region and dispatches the latest frame to
6
- // AVFoundation delegates / preview layers.
3
+ // Frames travel over a small ring of IOSurfaces shared by global surface ID.
4
+ // The host helper (running on macOS) renders BGRA frames straight into the
5
+ // surfaces; the injected dylib inside the simulator app looks the surfaces up
6
+ // by ID and wraps the latest one as a CVPixelBuffer — no per-frame pixel copy.
7
7
  //
8
- // Format: BGRA, top-down, no padding except whatever bytesPerRow says.
8
+ // A tiny POSIX shared-memory region acts as the control channel: it carries
9
+ // the surface IDs, which surface holds the newest frame, the frame sequence
10
+ // number, and the mirror mode. The pixels themselves live in the IOSurfaces,
11
+ // not in this region.
9
12
  //
10
- // Synchronization is intentionally lock-free and lossy: the writer bumps
11
- // `frameSeq` last after writing pixels; the reader samples `frameSeq` before
12
- // and after copying and discards the read if they disagree. A single dropped
13
- // frame is fine for a 30 fps camera.
13
+ // Surfaces are BGRA. Row stride comes from IOSurfaceGetBytesPerRow (it may be
14
+ // padded beyond width*4); `bytesPerRow` below mirrors that actual stride.
15
+ //
16
+ // Synchronization is lock-free and lossy. The writer renders into a surface
17
+ // the reader is not holding, points `latestIndex` at it, then bumps `frameSeq`
18
+ // last. The reader samples `frameSeq` before and after wrapping the surface and
19
+ // retries on the next tick if they disagree. A single dropped frame is fine for
20
+ // a 30 fps camera.
14
21
 
15
22
  #ifndef SIM_CAM_SHARED_H
16
23
  #define SIM_CAM_SHARED_H
17
24
 
25
+ #include <stddef.h>
18
26
  #include <stdint.h>
27
+ #include <stdatomic.h>
19
28
 
20
29
  #define SIMCAM_SHM_MAGIC 0x53434D31u // 'SCM1'
21
30
  #define SIMCAM_PIXEL_BGRA 0u
22
31
  #define SIMCAM_DEFAULT_WIDTH 1280u
23
32
  #define SIMCAM_DEFAULT_HEIGHT 720u
24
33
 
34
+ // Number of IOSurfaces in the ring. The writer keeps off whichever surface the
35
+ // reader most recently published, so a few buffers absorb a reader that holds
36
+ // a frame for a tick or two without tearing.
37
+ #define SIMCAM_SURFACE_RING 4u
38
+
25
39
  // Mirror mode codes for SimCamShmHeader.mirrorMode.
26
40
  // "Unset" lets the dylib fall back to its env-var configuration (back-compat
27
41
  // with hosts that don't write the byte).
@@ -30,26 +44,36 @@
30
44
  #define SIMCAM_MIRROR_ON 1
31
45
  #define SIMCAM_MIRROR_OFF 2
32
46
 
33
- // Header is 64 bytes — keeps pixel data 16-byte aligned.
34
- typedef struct __attribute__((packed)) {
47
+ // Control header is 64 bytes. The surface-ID table follows immediately after.
48
+ typedef struct {
35
49
  uint32_t magic; // SIMCAM_SHM_MAGIC
36
50
  uint32_t version; // bumps on layout change
37
51
  uint32_t width;
38
52
  uint32_t height;
39
53
  uint32_t pixelFormat; // SIMCAM_PIXEL_BGRA
40
- uint32_t bytesPerRow;
41
- uint64_t pixelByteSize;
42
- uint64_t frameSeq; // written LAST; readers check tearing via re-read
54
+ uint32_t bytesPerRow; // actual IOSurface row stride (may exceed width*4)
55
+ uint64_t pixelByteSize;// logical frame size: width*height*4
56
+ _Atomic uint64_t frameSeq; // written LAST with release; readers acquire-load
43
57
  uint64_t timestampNs; // mach_absolute_time-based, host monotonic
44
58
  uint8_t mirrorMode; // SIMCAM_MIRROR_*; UNSET = ignore (use env)
45
59
  uint8_t reserved[15];
46
60
  } SimCamShmHeader;
47
61
 
48
62
  _Static_assert(sizeof(SimCamShmHeader) == 64, "SimCamShmHeader must be 64 bytes");
63
+ _Static_assert(offsetof(SimCamShmHeader, frameSeq) == 32, "frameSeq offset must stay stable");
64
+ _Static_assert(offsetof(SimCamShmHeader, mirrorMode) == 48, "mirrorMode offset must stay stable");
65
+
66
+ // Ring of global IOSurface IDs, written once at startup. `latestIndex` is
67
+ // updated each frame (before frameSeq) to point at the freshest surface.
68
+ typedef struct __attribute__((packed)) {
69
+ uint32_t surfaceCount; // valid entries in ids[]
70
+ uint32_t latestIndex; // ids[] slot holding the newest frame
71
+ uint32_t ids[SIMCAM_SURFACE_RING]; // global IOSurface IDs
72
+ } SimCamSurfaceTable;
49
73
 
50
- // Total shm size for given dimensions: header + width*height*4
51
- static inline uint64_t SimCamShmSizeFor(uint32_t w, uint32_t h) {
52
- return (uint64_t)sizeof(SimCamShmHeader) + (uint64_t)w * (uint64_t)h * 4ull;
74
+ // Total control-region size: header + surface table.
75
+ static inline uint64_t SimCamControlSize(void) {
76
+ return (uint64_t)sizeof(SimCamShmHeader) + (uint64_t)sizeof(SimCamSurfaceTable);
53
77
  }
54
78
 
55
79
  #endif
package/bin/serve-sim-bin CHANGED
Binary file