mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-07-11 17:46:37 +03:00
d66f19cb24
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.
250 lines
9.5 KiB
C
250 lines
9.5 KiB
C
/* capture.c — coherent pull of a sub-rectangle of the CURRENT frame.
|
|
*
|
|
* A persistent capture window over a fixed rect of the captured surface. open()
|
|
* allocates a stable buffer once (its address is fixed across syncs); sync()
|
|
* re-fills it all-or-nothing from the current latest frame under one seqlock
|
|
* window — the exact mirror of vgpup_sample_frame's discipline, but for a
|
|
* sub-rectangle, and WITHOUT frame_id dedup (a repeat sync of the same frame
|
|
* still yields the coherent rect — see vgpu_perception.h).
|
|
*
|
|
* Every read goes through gva_read under c->proc_cr3 into the stable buffer (or
|
|
* a per-window scratch for the RGB24 conversion); no gva_ptr is ever held across
|
|
* the seqlock window. NOT zero-copy by design (the rect crosses pages and must
|
|
* survive the seq re-check). All GVA arithmetic is done in uint64_t.
|
|
*
|
|
* The window caches proc_cr3 + ring_gva (copied from the region handle at open);
|
|
* the producer block lives at region_gva == ring_gva - VGPU_RING_OFFSET, where
|
|
* latest / seq[] / desc[] are read (the same fields sample.c reads via region_gva).
|
|
*/
|
|
|
|
#include "perception-internal.h"
|
|
|
|
#include <stdlib.h>
|
|
|
|
/* Round `v` up to the next multiple of `a` (a is a power of two). */
|
|
static inline size_t vgpup_align_up(size_t v, size_t a)
|
|
{
|
|
return (v + (a - 1u)) & ~(a - 1u);
|
|
}
|
|
|
|
vgpup_capture* vgpup_capture_open(vgpup_region* r, vgpup_rect rect,
|
|
int layout, int format)
|
|
{
|
|
vgpup_capture* c;
|
|
uint32_t out_bpp, row_bytes;
|
|
|
|
/* validate args and enums; fit against the ABI ceilings (NOT the current
|
|
* desc — open may precede the first frame; per-frame fit is a sync check).
|
|
* x+w / y+h computed in uint64_t to avoid overflow. */
|
|
if (!r) { return NULL; }
|
|
if (rect.w == 0u || rect.h == 0u) { return NULL; }
|
|
if (layout != VGPUP_LAYOUT_FLAT && layout != VGPUP_LAYOUT_ROWS) { return NULL; }
|
|
if (format != VGPUP_PX_BGRA && format != VGPUP_PX_RGB24) { return NULL; }
|
|
if ((uint64_t)rect.x + rect.w > VGPU_MAX_WIDTH) { return NULL; }
|
|
if ((uint64_t)rect.y + rect.h > VGPU_MAX_HEIGHT) { return NULL; }
|
|
|
|
out_bpp = (format == VGPUP_PX_RGB24) ? 3u : 4u;
|
|
row_bytes = rect.w * out_bpp;
|
|
|
|
c = calloc(1, sizeof *c);
|
|
if (!c) { return NULL; }
|
|
|
|
c->proc_cr3 = r->proc_cr3;
|
|
c->ring_gva = r->ring_gva;
|
|
c->x = rect.x;
|
|
c->y = rect.y;
|
|
c->w = rect.w;
|
|
c->h = rect.h;
|
|
c->out_bpp = out_bpp;
|
|
c->row_bytes = row_bytes;
|
|
c->layout = layout;
|
|
c->format = format;
|
|
|
|
/* RGB24 stages each source BGRA row in a per-window scratch (the source row
|
|
* is too large for the stack: up to VGPU_MAX_WIDTH*4). BGRA reads straight
|
|
* into the destination row, no scratch needed. */
|
|
if (format == VGPUP_PX_RGB24) {
|
|
c->scratch = malloc((size_t)VGPU_MAX_WIDTH * 4u);
|
|
if (!c->scratch) { vgpup_capture_close(c); return NULL; }
|
|
}
|
|
|
|
if (layout == VGPUP_LAYOUT_FLAT) {
|
|
/* one contiguous block, rows back-to-back (sub-stride == row_bytes) */
|
|
c->buf_bytes = (size_t)row_bytes * rect.h;
|
|
c->buf = calloc(1, c->buf_bytes);
|
|
if (!c->buf) { vgpup_capture_close(c); return NULL; }
|
|
} else {
|
|
/* ROWS: ONE backing block sliced into h cache-line-aligned sub-blocks +
|
|
* an array of h pointers. One allocation (less fragmentation, addresses
|
|
* stable since the backing is never reallocated); the padding between
|
|
* row_bytes..row_stride is never filled and is excluded from info->bytes. */
|
|
size_t row_stride = vgpup_align_up(row_bytes, VGPUP_ROW_ALIGN);
|
|
uint32_t i;
|
|
c->buf_bytes = row_stride * rect.h;
|
|
c->buf = calloc(1, c->buf_bytes);
|
|
if (!c->buf) { vgpup_capture_close(c); return NULL; }
|
|
c->rows = calloc(rect.h, sizeof *c->rows);
|
|
if (!c->rows) { vgpup_capture_close(c); return NULL; }
|
|
for (i = 0; i < rect.h; ++i) {
|
|
c->rows[i] = c->buf + (size_t)i * row_stride;
|
|
}
|
|
}
|
|
|
|
return c;
|
|
}
|
|
|
|
/* Convert w source BGRA pixels in `src` (B,G,R,A) to RGB24 (R,G,B) in `dst`. */
|
|
static inline void vgpup_bgra_to_rgb24(const uint8_t* src, uint8_t* dst, uint32_t w)
|
|
{
|
|
uint32_t i;
|
|
for (i = 0; i < w; ++i) {
|
|
dst[3u * i + 0u] = src[4u * i + 2u]; /* R */
|
|
dst[3u * i + 1u] = src[4u * i + 1u]; /* G */
|
|
dst[3u * i + 2u] = src[4u * i + 0u]; /* B */
|
|
}
|
|
}
|
|
|
|
int vgpup_capture_sync(vgpup_capture* c, vmie_mem* m, vgpup_capture_info* info)
|
|
{
|
|
unsigned attempt;
|
|
uint64_t region_gva;
|
|
|
|
if (!c || !m || !info) { return -1; }
|
|
|
|
/* the producer block sits one ring-offset below the cached ring base */
|
|
region_gva = c->ring_gva - VGPU_RING_OFFSET;
|
|
|
|
for (attempt = 0; attempt < VGPUP_SEQLOCK_RETRIES; ++attempt) {
|
|
uint32_t latest = 0, seq_before = 0, seq_after = 0;
|
|
vgpu_desc_t d;
|
|
uint64_t seq_gva, desc_gva, slot_base, src_bytes;
|
|
uint32_t row;
|
|
|
|
/* latest (acquire-equivalent: its own read) */
|
|
if (vgpup_read_field(m, c->proc_cr3, region_gva,
|
|
offsetof(vgpu_producer_t, latest),
|
|
&latest, sizeof latest) < 0) {
|
|
return -1;
|
|
}
|
|
if (latest == VGPU_LATEST_NONE || latest >= VGPU_SLOT_COUNT) {
|
|
return 0; /* no frame yet */
|
|
}
|
|
|
|
seq_gva = region_gva + offsetof(vgpu_producer_t, seq)
|
|
+ (uint64_t)latest * sizeof(uint32_t);
|
|
desc_gva = region_gva + offsetof(vgpu_producer_t, desc)
|
|
+ (uint64_t)latest * sizeof(vgpu_desc_t);
|
|
|
|
if (vgpup_read_seq(m, c->proc_cr3, seq_gva, &seq_before) < 0) { return -1; }
|
|
if (vgpup_seq_is_writing(seq_before)) { continue; } /* writer in slot */
|
|
|
|
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)desc_gva,
|
|
&d, sizeof d) < 0) {
|
|
return -1;
|
|
}
|
|
|
|
/* descriptor sanity within the read window (tight BGRA, bounded dims) */
|
|
if (d.format != VGPU_FMT_BGRA8888 || d.stride != d.width * 4u ||
|
|
d.width == 0u || d.width > VGPU_MAX_WIDTH ||
|
|
d.height == 0u || d.height > VGPU_MAX_HEIGHT) {
|
|
continue; /* likely a torn read; retry */
|
|
}
|
|
|
|
/* NO frame_id dedup (intentional): a repeat sync of the same frame must
|
|
* still return the coherent rect (see vgpu_perception.h). */
|
|
|
|
/* fit against THIS frame: rect must lie within the current dimensions.
|
|
* A frame that became smaller is a strict lossy skip (no partial read). */
|
|
if ((uint64_t)c->x + c->w > d.width || (uint64_t)c->y + c->h > d.height) {
|
|
return 0;
|
|
}
|
|
|
|
/* fill the whole rect, row by row, from the latest slot under proc_cr3 */
|
|
slot_base = c->ring_gva + (uint64_t)latest * VGPU_SLOT_STRIDE;
|
|
src_bytes = (uint64_t)c->w * 4u; /* source is BGRA, 4 bpp */
|
|
for (row = 0; row < c->h; ++row) {
|
|
uint64_t src_gva = slot_base
|
|
+ (uint64_t)(c->y + row) * d.stride
|
|
+ (uint64_t)c->x * 4u;
|
|
uint8_t* dst = (c->layout == VGPUP_LAYOUT_ROWS)
|
|
? (uint8_t*)c->rows[row]
|
|
: c->buf + (size_t)row * c->row_bytes;
|
|
|
|
if (c->format == VGPUP_PX_RGB24) {
|
|
/* stage the source BGRA row, then convert into the dst row */
|
|
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)src_gva,
|
|
c->scratch, src_bytes) < 0) {
|
|
return -1;
|
|
}
|
|
vgpup_bgra_to_rgb24(c->scratch, dst, c->w);
|
|
} else {
|
|
/* BGRA verbatim: read straight into the dst row */
|
|
if (gva_read(m, (uintptr_t)c->proc_cr3, (uintptr_t)src_gva,
|
|
dst, src_bytes) < 0) {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* re-check the slot seq ONCE after the whole rect is filled: unchanged
|
|
* and still even → the rect is one coherent snapshot (all-or-nothing). */
|
|
if (vgpup_read_seq(m, c->proc_cr3, seq_gva, &seq_after) < 0) { return -1; }
|
|
if (seq_after != seq_before || vgpup_seq_is_writing(seq_after)) {
|
|
continue; /* the slot was rewritten under us — re-fill the rect */
|
|
}
|
|
|
|
info->desc.width = d.width;
|
|
info->desc.height = d.height;
|
|
info->desc.stride = d.stride;
|
|
info->desc.format = d.format;
|
|
info->desc.frame_id = d.frame_id;
|
|
info->desc.timestamp_ns = d.timestamp_ns;
|
|
info->frame_id = d.frame_id;
|
|
info->bytes = (size_t)c->row_bytes * c->h; /* payload only */
|
|
return 1;
|
|
}
|
|
return 0; /* writer kept the slot busy past the retry limit — lossy skip */
|
|
}
|
|
|
|
uint32_t vgpup_capture_out_bpp(const vgpup_capture* c)
|
|
{
|
|
return c ? c->out_bpp : 0u;
|
|
}
|
|
|
|
uint32_t vgpup_capture_row_bytes(const vgpup_capture* c)
|
|
{
|
|
return c ? c->row_bytes : 0u;
|
|
}
|
|
|
|
uint32_t vgpup_capture_row_count(const vgpup_capture* c)
|
|
{
|
|
return c ? c->h : 0u;
|
|
}
|
|
|
|
const uint8_t* vgpup_capture_mem(const vgpup_capture* c)
|
|
{
|
|
if (!c || c->layout != VGPUP_LAYOUT_FLAT) { return NULL; }
|
|
return c->buf;
|
|
}
|
|
|
|
size_t vgpup_capture_bytes(const vgpup_capture* c)
|
|
{
|
|
if (!c || c->layout != VGPUP_LAYOUT_FLAT) { return 0u; }
|
|
return (size_t)c->row_bytes * c->h;
|
|
}
|
|
|
|
const uint8_t* const* vgpup_capture_rows(const vgpup_capture* c)
|
|
{
|
|
if (!c || c->layout != VGPUP_LAYOUT_ROWS) { return NULL; }
|
|
return c->rows;
|
|
}
|
|
|
|
void vgpup_capture_close(vgpup_capture* c)
|
|
{
|
|
if (!c) { return; }
|
|
free(c->rows);
|
|
free(c->scratch);
|
|
free(c->buf);
|
|
free(c);
|
|
}
|