serve-sim 0.1.39 → 0.1.40-beta.73.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/Sources/SimCameraHelper/build.sh +1 -0
- package/Sources/SimCameraHelper/main.m +87 -15
- package/Sources/SimCameraInjector/SimCamFrameSource.m +65 -34
- package/Sources/SimCameraInjector/build.sh +1 -0
- package/Sources/SimCameraInjector/include/SimCamShared.h +42 -18
- package/bin/serve-sim-bin +0 -0
- package/dist/middleware.js +32 -24
- package/dist/serve-sim.js +69 -61
- package/dist/simcam/libSimCameraInjector.dylib +0 -0
- package/dist/simcam/serve-sim-camera-helper +0 -0
- package/package.json +1 -1
- package/src/middleware.ts +105 -7
|
@@ -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
|
|
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)
|
|
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 || !
|
|
72
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
766
|
-
memset(
|
|
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 =
|
|
839
|
+
gHeader->version = 2;
|
|
769
840
|
gHeader->width = gWidth;
|
|
770
841
|
gHeader->height = gHeight;
|
|
771
842
|
gHeader->pixelFormat = SIMCAM_PIXEL_BGRA;
|
|
772
|
-
gHeader->bytesPerRow =
|
|
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
|
-
|
|
828
|
-
|
|
829
|
-
|
|
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
|
|
30
|
-
static
|
|
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)
|
|
240
|
-
return [self
|
|
240
|
+
- (CVPixelBufferRef)newPixelBufferFromSurface CF_RETURNS_RETAINED {
|
|
241
|
+
return [self newPixelBufferFromSurfaceForceFresh:NO];
|
|
241
242
|
}
|
|
242
243
|
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
|
|
250
|
-
uint32_t
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
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 =
|
|
258
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 <
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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, %
|
|
545
|
-
shmName, hdr->width, hdr->height,
|
|
575
|
+
simcam_log(@"shm \"%s\" attached (%ux%u, %u/%u IOSurfaces resolved)",
|
|
576
|
+
shmName, hdr->width, hdr->height, resolved, count);
|
|
546
577
|
}
|
|
@@ -1,27 +1,41 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Wire format for serve-sim's simulator camera feed.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// the
|
|
6
|
-
//
|
|
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
|
-
//
|
|
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
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
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
|
-
//
|
|
34
|
-
typedef struct
|
|
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;
|
|
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
|
|
51
|
-
static inline uint64_t
|
|
52
|
-
return (uint64_t)sizeof(SimCamShmHeader) + (uint64_t)
|
|
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
|