feat(vgpu): coherent sub-rect capture in perception

Persistent capture window over a fixed sub-rectangle of the captured surface. open() allocates a stable buffer once; sync() re-fills it all-or-nothing from the current latest frame under one seqlock window (byte-exact, never torn). Pull only - no poll/thread inside; the caller drives. FLAT/ROWS layouts and BGRA/RGB24 formats chosen at open, orthogonal. Strict skip when the rect does not fit the current frame; no frame_id dedup.

Extract read_field into perception-internal.h as vgpup_read_field to share the producer-field read between sample.c and capture.c. Bump version to 0.3.14.
This commit is contained in:
2026-06-29 22:43:38 +03:00
parent 17ad439b9b
commit d66f19cb24
6 changed files with 746 additions and 23 deletions
+78
View File
@@ -234,6 +234,84 @@ int vgpup_read_status(vgpup_region* r, vmie_mem* m, vgpup_status* out);
* value; it holds no reset policy (what to reset is the caller's decision). */
uint32_t vgpup_run_epoch(const vgpup_region* r);
/* ---- region capture: coherent pull of a sub-rectangle of the CURRENT frame -- *
* A persistent window over a fixed sub-rectangle of the captured surface. Open
* allocates a STABLE buffer once (address fixed across syncs); each sync re-fills
* it, all-or-nothing, from the CURRENT latest frame under one seqlock window
* (byte-exact, never torn — pixels may carry encrypted structures). Pull only: no
* poll / sleep / thread inside; the caller drives. NOT zero-copy: the rect crosses
* pages and must survive a seqlock re-check, so it is copied via gva_read (zero-copy
* would be live, hence non-coherent). Unlike vgpup_sample_frame there is NO frame_id
* dedup: each sync yields the coherent rect of the current frame even if frame_id is
* unchanged (info reports frame_id so the caller may dedup itself). */
/* Rectangle in captured-surface pixels; (0,0) = top-left; bounds are validated at
* sync time against the CURRENT frame's desc.width/height. No geometry translation. */
typedef struct { uint32_t x, y, w, h; } vgpup_rect;
/* Destination layout (chosen at open, orthogonal to format). */
enum { VGPUP_LAYOUT_FLAT = 0, /* one contiguous block, rows back-to-back */
VGPUP_LAYOUT_ROWS = 1 }; /* H separate row blocks; rows[] points at each */
/* Destination pixel format (chosen at open, orthogonal to layout). Source is ABI
* VGPU_FMT_BGRA8888 (4 bpp, memory order B,G,R,A, stride == width*4). */
enum { VGPUP_PX_BGRA = 0, /* 4 bpp, verbatim B,G,R,A from source */
VGPUP_PX_RGB24 = 1 }; /* 3 bpp, BGRA->RGB: drop A, out byte order R,G,B */
/* Opaque capture window; owns the stable buffer(s). Heap-owned by the core. */
typedef struct vgpup_capture vgpup_capture;
/* Per-sync metadata. desc mirrors the current frame; bytes is the TOTAL coherent
* payload size: for FLAT == rect.w*rect.h*out_bpp; for ROWS == rect.h*row_bytes
* (the sum of the row payloads, padding excluded). out_bpp / row_bytes are also
* available via the accessors below. */
typedef struct {
vgpup_frame_desc desc; /* descriptor of the frame this rect was taken from */
uint64_t frame_id; /* == desc.frame_id; surfaced for caller-side dedup */
size_t bytes; /* total coherent payload bytes (see above) */
} vgpup_capture_info;
/* Open a capture window over region handle r for sub-rectangle `rect`, with the
* given layout (VGPUP_LAYOUT_*) and format (VGPUP_PX_*). r MUST outlive the window
* (open caches proc_cr3 + ring_gva from r; sync does not take r). rect.w/h must be
* > 0 and rect.x+rect.w / rect.y+rect.h must not exceed the ABI ceilings
* (VGPU_MAX_WIDTH / VGPU_MAX_HEIGHT) — per-frame fit is checked at sync. Returns a
* heap-owned window, or NULL on bad args / bad enum / allocation failure.
*
* If a later sync returns <0 (producer restarted), the old window is invalid: the
* caller re-discovers (vgpup_close + vgpup_open) and must vgpup_capture_close this
* window and re-open a fresh one on the new region handle. */
vgpup_capture* vgpup_capture_open(vgpup_region* r, vgpup_rect rect,
int layout, int format);
/* Coherently re-fill the stable buffer from the CURRENT latest frame, under one
* seqlock window (fill, then re-check seq unchanged & even; changed -> retry up to
* VGPUP_SEQLOCK_RETRIES). m is BORROWED (== vmie_win32_mem(v)). Returns:
* 1 = stable buffer now holds the COHERENT rect (info filled);
* 0 = no frame yet / writer busy past the retry limit / rect does not fit the
* current frame (lossy SKIP — buffer is INVALID, do not consume, as with
* vgpup_sample_frame partial writes);
* <0 = hard read error (producer restarted -> caller re-discovers).
* NO frame_id dedup: a repeat sync of the same frame returns 1 again. */
int vgpup_capture_sync(vgpup_capture* c, vmie_mem* m, vgpup_capture_info* info);
/* Buffer geometry, fixed at open (do not change across syncs). */
uint32_t vgpup_capture_out_bpp(const vgpup_capture* c); /* 4 (BGRA) | 3 (RGB24) */
uint32_t vgpup_capture_row_bytes(const vgpup_capture* c); /* rect.w * out_bpp */
uint32_t vgpup_capture_row_count(const vgpup_capture* c); /* rect.h */
/* FLAT accessors: one contiguous block, rows back-to-back, sub-stride == row_bytes,
* pixel (x,y) at mem[(y*rect.w + x) * out_bpp]. NULL/0 if the window is ROWS. */
const uint8_t* vgpup_capture_mem(const vgpup_capture* c);
size_t vgpup_capture_bytes(const vgpup_capture* c); /* row_bytes*row_count */
/* ROWS accessors: an array of row_count pointers, each at a row_bytes block. The
* pointer array and the blocks are stable across syncs. NULL if the window is FLAT. */
const uint8_t* const* vgpup_capture_rows(const vgpup_capture* c);
/* Release the window and its buffer(s). Does NOT touch r / m. Safe on NULL. */
void vgpup_capture_close(vgpup_capture* c);
/* ---- control-write — SEAM ONLY (this never writes) ------------------------ */
/* Desired control-block value (host-RW fields). The caller builds it and later