Files
vatrog-vm-vgpu-streamer/include/vgpu_stream.h
T
lirent bcf708d3cc feat: add graphics-context sensors to producer contract
Append three host-readable sensor groups to the producer block, ABI
frozen by offset asserts (producer still fits page 0):

- cursor Tier-1 (hotspot, glyph dims, shape identity), published under
  the existing cursor_seq gate
- content_change_ns: monotonic stamp of the last scene-content change
- display geometry on its own cache line under a geom_seq seqlock:
  virtual-desktop bbox, captured-output origin, DPI, refresh

Source the cursor from each backend's existing grab metadata instead of
polling GetCursorInfo in the present pump: DDA from the duplication frame
info pointer position (no extra calls), NvFBC visibility from the grab
info plus one GetCursorInfo per frame for position, GDI from
GetCursorInfo. Position sampling now rides the frame rate, not the pump
tick.

Sample display geometry once at startup and re-sample only on backend
session recreate or a captured-mode change. Drop per-tick cursor
sampling (cursor_sample_pos) from the present loop.

Add src/stream/win32/geometry.{c,h}.
2026-06-19 01:32:28 +03:00

170 lines
11 KiB
C

#ifndef VGPU_STREAM_H
#define VGPU_STREAM_H
#include <stdint.h>
#include <stddef.h> /* offsetof */
#include <stdalign.h> /* alignas */
#include <assert.h> /* static_assert */
/* ===== Geometry — single source of truth (bare ABI, both ends agree) ===== */
#define VGPU_PAGE 4096u
#define VGPU_SLOT_COUNT 3u
#define VGPU_SLOT_STRIDE (32u * 1024u * 1024u)
#define VGPU_RING_OFFSET (2u * 1024u * 1024u)
#define VGPU_PRODUCER_OFFSET 0u
#define VGPU_CONTROL_OFFSET VGPU_PAGE
#define VGPU_REGION_BYTES (VGPU_RING_OFFSET + (uint64_t)VGPU_SLOT_COUNT * VGPU_SLOT_STRIDE)
#define VGPU_MAX_WIDTH 3840u
#define VGPU_MAX_HEIGHT 2160u
#define VGPU_HEARTBEAT_PERIOD_MS 250u /* producer ticks heartbeat >= 4 Hz always */
#define VGPU_LATEST_NONE 0xFFFFFFFFu
static_assert((uint64_t)VGPU_MAX_WIDTH * VGPU_MAX_HEIGHT * 4u <= VGPU_SLOT_STRIDE,
"max-mode tight BGRA must fit one slot");
/* enum values travel as uint32 wire-values (not as enum fields → no width instability) */
enum { VGPU_FMT_BGRA8888 = 0 };
enum { VGPU_ST_INIT=0, VGPU_ST_CAPTURING=1, VGPU_ST_PAUSED=2, VGPU_ST_STOPPED=3, VGPU_ST_ERROR=4 };
enum { VGPU_BK_NONE=0, VGPU_BK_NVFBC=1, VGPU_BK_DDA=2, VGPU_BK_GDI=3 };
enum { VGPU_CMD_STOP=0, VGPU_CMD_RUN=1, VGPU_CMD_PAUSE=2 };
/* cursor shape identity (wire-uint32); UNKNOWN=0 → custom/unrecognized glyph */
enum { VGPU_CURSOR_ID_UNKNOWN=0, VGPU_CURSOR_ID_ARROW=1, VGPU_CURSOR_ID_IBEAM=2,
VGPU_CURSOR_ID_WAIT=3, VGPU_CURSOR_ID_CROSS=4, VGPU_CURSOR_ID_HAND=5,
VGPU_CURSOR_ID_SIZENS=6, VGPU_CURSOR_ID_SIZEWE=7, VGPU_CURSOR_ID_SIZENWSE=8,
VGPU_CURSOR_ID_SIZENESW=9, VGPU_CURSOR_ID_SIZEALL=10, VGPU_CURSOR_ID_NO=11,
VGPU_CURSOR_ID_APPSTARTING=12 };
/* ===== Per-slot descriptor (under hot.seq[slot]) ===== */
typedef struct {
uint32_t width; /* pixels */
uint32_t height; /* pixels */
uint32_t stride; /* bytes/row; INVARIANT: == width*4 (tight) */
uint32_t format; /* VGPU_FMT_* */
uint64_t frame_id; /* == producer.frame_id at publish time */
uint64_t timestamp_ns; /* capture time, monotonic */
} vgpu_desc_t;
static_assert(sizeof(vgpu_desc_t) == 32, "desc layout");
static_assert(offsetof(vgpu_desc_t, width) == 0, "desc.width");
static_assert(offsetof(vgpu_desc_t, height) == 4, "desc.height");
static_assert(offsetof(vgpu_desc_t, stride) == 8, "desc.stride");
static_assert(offsetof(vgpu_desc_t, format) == 12, "desc.format");
static_assert(offsetof(vgpu_desc_t, frame_id) == 16, "desc.frame_id");
static_assert(offsetof(vgpu_desc_t, timestamp_ns) == 24, "desc.timestamp_ns");
/* ===== Producer block (host-RO): hot publish line + cold status line ===== */
typedef struct {
/* --- hot publish line --- */
alignas(64)
uint32_t latest; /* index of last; VGPU_LATEST_NONE until 1st frame */
uint32_t _r0;
uint64_t frame_id; /* monotonic frame counter (8-aligned) */
uint32_t seq[VGPU_SLOT_COUNT]; /* per-slot seqlock: even=stable, odd=writing */
uint32_t _r1;
vgpu_desc_t desc[VGPU_SLOT_COUNT]; /* self-describing slots */
/* --- cold status line --- */
alignas(64)
uint64_t heartbeat; /* monotonic; ticks always (even STOPPED/PAUSED) */
uint32_t run_epoch; /* +1 per start (session break for host) */
uint32_t status; /* VGPU_ST_* */
uint32_t backend; /* VGPU_BK_* */
uint32_t error_code; /* 0=none; else fatal detail */
uint32_t applied_fps; /* publish-rate cap the producer actually applies;
actual rate may be lower on static content or
backend limits — host measures real fps from
desc.timestamp_ns */
uint32_t supported_formats; /* bitmask (1u<<VGPU_FMT_*) */
uint32_t ctrl_ack; /* echo of control.ctrl_gen (even) applied */
uint32_t full_frame_ack; /* echo of control.full_frame_req honored */
/* --- cursor reporting (host-RO; position is sensor data, independent
* of control.draw_cursor / cursor compositing) --- */
uint32_t cursor_seq; /* @168: monotonic; bumps each cursor publish.
Host reads it last (acquire) to gate a
consistent {cursor_pos,cursor_visible}; lets the
host tell "cursor idle" from "producer stopped
reporting". */
uint32_t cursor_visible; /* @172: 1=cursor shown (CURSOR_SHOWING), 0=hidden */
uint64_t cursor_pos; /* @176: packed screen position, 8-aligned single
atomic MOV. low 32 bits = x, high 32 = y, each a
signed int32 (two's-complement; multi-monitor →
negatives). Pair never tears (one 64-bit store). */
/* --- cursor Tier-1 (host-RO; same cursor_seq gate as cursor_pos/visible) --- */
uint32_t cursor_hotspot; /* @184: low16=hot_x, high16=hot_y (unsigned) */
uint32_t cursor_glyph; /* @188: low16=glyph_w, high16=glyph_h (unsigned) */
uint32_t cursor_id; /* @192: VGPU_CURSOR_ID_* shape identity */
/* --- graphics static-idle: monotonic stamp of last scene-content change --- */
alignas(8) uint64_t content_change_ns; /* @200: host derives idle-ms vs its own clock */
/* --- display geometry (own cache line; geom_seq seqlock; sampled rarely) ---
* captured-surface SIZE is NOT here: it is desc.width/height (authoritative, tight). */
alignas(64)
uint32_t geom_seq; /* @256: even=stable, odd=writing (frame-seqlock) */
int32_t virt_x; /* @260: virtual-desktop origin (signed) */
int32_t virt_y; /* @264 */
uint32_t virt_w; /* @268: virtual-desktop bbox size (interprets neg pos) */
uint32_t virt_h; /* @272 */
int32_t cap_x; /* @276: captured-output origin in virtual-desktop coords */
int32_t cap_y; /* @280: (captured size = desc.width/height, not here) */
uint32_t dpi; /* @284: captured-output effective DPI; 96=100%; 0=unknown */
uint32_t refresh_mhz; /* @288: captured-output refresh in milli-Hz; 0=unknown */
} vgpu_producer_t;
static_assert(alignof(vgpu_producer_t) == 64, "producer align");
static_assert(sizeof(vgpu_producer_t) <= VGPU_PAGE, "producer fits page 0");
/* host-read field layout frozen as ABI */
static_assert(offsetof(vgpu_producer_t, latest) == 0, "producer.latest");
static_assert(offsetof(vgpu_producer_t, frame_id) == 8, "producer.frame_id");
static_assert(offsetof(vgpu_producer_t, seq) == 16, "producer.seq");
static_assert(offsetof(vgpu_producer_t, desc) == 32, "producer.desc");
static_assert(offsetof(vgpu_producer_t, heartbeat) == 128, "producer.heartbeat");
static_assert(offsetof(vgpu_producer_t, run_epoch) == 136, "producer.run_epoch");
static_assert(offsetof(vgpu_producer_t, status) == 140, "producer.status");
static_assert(offsetof(vgpu_producer_t, backend) == 144, "producer.backend");
static_assert(offsetof(vgpu_producer_t, error_code) == 148, "producer.error_code");
static_assert(offsetof(vgpu_producer_t, applied_fps) == 152, "producer.applied_fps");
static_assert(offsetof(vgpu_producer_t, supported_formats) == 156, "producer.supported_formats");
static_assert(offsetof(vgpu_producer_t, ctrl_ack) == 160, "producer.ctrl_ack");
static_assert(offsetof(vgpu_producer_t, full_frame_ack) == 164, "producer.full_frame_ack");
static_assert(offsetof(vgpu_producer_t, cursor_seq) == 168, "producer.cursor_seq");
static_assert(offsetof(vgpu_producer_t, cursor_visible) == 172, "producer.cursor_visible");
static_assert(offsetof(vgpu_producer_t, cursor_pos) == 176, "producer.cursor_pos");
/* cursor Tier-1 (cursor line, gated by cursor_seq) */
static_assert(offsetof(vgpu_producer_t, cursor_hotspot) == 184, "producer.cursor_hotspot");
static_assert(offsetof(vgpu_producer_t, cursor_glyph) == 188, "producer.cursor_glyph");
static_assert(offsetof(vgpu_producer_t, cursor_id) == 192, "producer.cursor_id");
/* graphics static-idle */
static_assert(offsetof(vgpu_producer_t, content_change_ns) == 200, "producer.content_change_ns");
/* display geometry (own cache line; captured SIZE is desc.width/height, not here) */
static_assert(offsetof(vgpu_producer_t, geom_seq) == 256, "producer.geom_seq");
static_assert(offsetof(vgpu_producer_t, virt_x) == 260, "producer.virt_x");
static_assert(offsetof(vgpu_producer_t, virt_y) == 264, "producer.virt_y");
static_assert(offsetof(vgpu_producer_t, virt_w) == 268, "producer.virt_w");
static_assert(offsetof(vgpu_producer_t, virt_h) == 272, "producer.virt_h");
static_assert(offsetof(vgpu_producer_t, cap_x) == 276, "producer.cap_x");
static_assert(offsetof(vgpu_producer_t, cap_y) == 280, "producer.cap_y");
static_assert(offsetof(vgpu_producer_t, dpi) == 284, "producer.dpi");
static_assert(offsetof(vgpu_producer_t, refresh_mhz) == 288, "producer.refresh_mhz");
/* ===== Control block (host-RW), own page, generation-guarded ===== */
typedef struct {
alignas(64)
uint32_t ctrl_gen; /* generation seqlock: even=stable, odd=writing (host writes) */
uint32_t desired_state; /* VGPU_CMD_* (STOP/RUN/PAUSE) */
uint32_t target_fps; /* desired fps; 0=producer default */
uint32_t draw_cursor; /* 1=compose cursor */
uint32_t full_frame_req; /* edge counter: bump → force fresh full frame */
uint32_t consumer_tick; /* host heartbeat (producer watches with timeout) */
uint32_t attached; /* 1=host attached (intent, not death-proof) */
} vgpu_control_t;
static_assert(alignof(vgpu_control_t) == 64, "control align");
static_assert(sizeof(vgpu_control_t) <= VGPU_PAGE, "control fits page 1");
/* host-write field layout frozen as ABI */
static_assert(offsetof(vgpu_control_t, ctrl_gen) == 0, "control.ctrl_gen");
static_assert(offsetof(vgpu_control_t, desired_state) == 4, "control.desired_state");
static_assert(offsetof(vgpu_control_t, target_fps) == 8, "control.target_fps");
static_assert(offsetof(vgpu_control_t, draw_cursor) == 12, "control.draw_cursor");
static_assert(offsetof(vgpu_control_t, full_frame_req) == 16, "control.full_frame_req");
static_assert(offsetof(vgpu_control_t, consumer_tick) == 20, "control.consumer_tick");
static_assert(offsetof(vgpu_control_t, attached) == 24, "control.attached");
#endif