/*
* Shared-memory frame ring for the frame-copy embedded MPV helper.
*
* Same triple-buffer seqlock protocol as spikes/mpv-frame-copy (validated
* there on M1 Pro up to 4K60), plus a `generation` field: every viewport
* resize creates a fresh shm segment named `-g` and the
* reader re-attaches when the helper announces the new generation.
*
* Must compile as C11 (reader addon) and C++17 (helper) on POSIX; on
* Windows both consumers build as C++ (MSVC's C mode lacks ).
* POSIX builds use node-gyp's GNU dialects; frame_shm_now_ns() relies on
* clock_gettime there, which strict -std=c11 (__STRICT_ANSI__) would hide.
*/
#pragma once
#include
#if defined(_WIN32)
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include
#include
#include
#else
#include
#endif
#ifdef __cplusplus
#include
typedef std::atomic frame_shm_atomic_u64;
#else
#include
typedef _Atomic uint64_t frame_shm_atomic_u64;
#endif
/* Monotonic clock for produce_time_ns/heartbeat_ns. Producer (helper) and
* consumer (reader addon) MUST use this same clock so age math stays valid.
* POSIX: CLOCK_MONOTONIC (macOS 10.12+, Linux). Windows: QPC, scaled to ns
* without overflow by splitting whole seconds from the remainder. */
static inline uint64_t frame_shm_now_ns(void) {
#if defined(_WIN32)
/* C++11 function-local initialization is synchronized, so the helper's
* render/event threads cannot race while caching the boot-stable QPF. */
static const uint64_t frequency = []() -> uint64_t {
LARGE_INTEGER value;
if (QueryPerformanceFrequency(&value) != TRUE ||
value.QuadPart <= 0) {
return 1;
}
return (uint64_t)value.QuadPart;
}();
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
const uint64_t ticks = (uint64_t)counter.QuadPart;
return (ticks / frequency) * 1000000000ull +
(ticks % frequency) * 1000000000ull / frequency;
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return (uint64_t)ts.tv_sec * 1000000000ull + (uint64_t)ts.tv_nsec;
#endif
}
#if defined(_WIN32)
/* The stdio protocol carries POSIX-style names ("/impv-...-gN") on every
* platform so the TypeScript layer stays platform-agnostic; on Windows the
* helper (create) and reader (open) both derive the actual file-mapping
* object name from it: session-local namespace, slashes stripped. */
static inline void frame_shm_windows_name(const char* posix_name, char* out,
size_t out_size) {
while (*posix_name == '/') posix_name++;
snprintf(out, out_size, "Local\\%s", posix_name);
}
#endif
#define FRAME_SHM_MAGIC 0x564d5046u /* 'FPMV' */
#define FRAME_SHM_VERSION 1u
#define FRAME_SHM_RING_SLOTS 3u
#define FRAME_SHM_DATA_ALIGN 4096u
typedef struct {
frame_shm_atomic_u64 seq; /* 0 while the slot is being (re)written */
uint64_t produce_time_ns; /* frame_shm_now_ns() after copy completes */
} FrameShmSlot;
typedef struct {
uint32_t magic; /* written last during init; readers gate on it */
uint32_t version;
uint32_t width;
uint32_t height;
uint32_t stride; /* bytes per row, tightly packed BGRA */
uint32_t generation;
uint64_t frame_bytes;
uint64_t data_offset;
frame_shm_atomic_u64 latest_seq; /* newest complete frame, 0 = none */
frame_shm_atomic_u64 heartbeat_ns;
FrameShmSlot slots[FRAME_SHM_RING_SLOTS];
} FrameShmHeader;