mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-06-18 02:16:38 +03:00
fefa736fb8
Capture backends (NvFBC/DDA/GDI), cursor/region/present helpers, publish API, vendor NvFBC headers; CMake build with mingw-w64 toolchain.
113 lines
6.6 KiB
C
113 lines
6.6 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 };
|
|
|
|
/* ===== 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 */
|
|
} 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");
|
|
|
|
/* ===== 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
|