mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-07-09 01:46:38 +03:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
bcf708d3cc
|
@@ -11,6 +11,7 @@ add_executable(vgpu-streamer
|
|||||||
src/stream/win32/region.c
|
src/stream/win32/region.c
|
||||||
src/stream/win32/present.c
|
src/stream/win32/present.c
|
||||||
src/stream/win32/cursor.c
|
src/stream/win32/cursor.c
|
||||||
|
src/stream/win32/geometry.c
|
||||||
src/stream/win32/capture.c
|
src/stream/win32/capture.c
|
||||||
src/stream/win32/capture_nvfbc.c
|
src/stream/win32/capture_nvfbc.c
|
||||||
src/stream/win32/capture_dda.c
|
src/stream/win32/capture_dda.c
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ 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_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_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 };
|
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]) ===== */
|
/* ===== Per-slot descriptor (under hot.seq[slot]) ===== */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
@@ -81,6 +87,26 @@ typedef struct {
|
|||||||
atomic MOV. low 32 bits = x, high 32 = y, each a
|
atomic MOV. low 32 bits = x, high 32 = y, each a
|
||||||
signed int32 (two's-complement; multi-monitor →
|
signed int32 (two's-complement; multi-monitor →
|
||||||
negatives). Pair never tears (one 64-bit store). */
|
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;
|
} vgpu_producer_t;
|
||||||
static_assert(alignof(vgpu_producer_t) == 64, "producer align");
|
static_assert(alignof(vgpu_producer_t) == 64, "producer align");
|
||||||
static_assert(sizeof(vgpu_producer_t) <= VGPU_PAGE, "producer fits page 0");
|
static_assert(sizeof(vgpu_producer_t) <= VGPU_PAGE, "producer fits page 0");
|
||||||
@@ -101,6 +127,22 @@ static_assert(offsetof(vgpu_producer_t, full_frame_ack) == 164, "producer.ful
|
|||||||
static_assert(offsetof(vgpu_producer_t, cursor_seq) == 168, "producer.cursor_seq");
|
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_visible) == 172, "producer.cursor_visible");
|
||||||
static_assert(offsetof(vgpu_producer_t, cursor_pos) == 176, "producer.cursor_pos");
|
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 ===== */
|
/* ===== Control block (host-RW), own page, generation-guarded ===== */
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
|||||||
@@ -60,4 +60,29 @@ void vgpu_publish_full_frame_ack(const vgpu_region_view* rv, uint32_t req);
|
|||||||
* field (single atomic store) and bumps cursor_seq last. */
|
* field (single atomic store) and bumps cursor_seq last. */
|
||||||
void vgpu_publish_cursor(const vgpu_region_view* rv, int32_t x, int32_t y, uint32_t visible);
|
void vgpu_publish_cursor(const vgpu_region_view* rv, int32_t x, int32_t y, uint32_t visible);
|
||||||
|
|
||||||
|
/* Publish Tier-1 cursor shape data (host-RO), written under the same cursor_seq gate as
|
||||||
|
* vgpu_publish_cursor: call this BEFORE vgpu_publish_cursor so the position publish bumps
|
||||||
|
* cursor_seq last and gates the whole cursor line consistently. hot_x/hot_y are the glyph
|
||||||
|
* hotspot; gw/gh are glyph dims; cursor_id is a VGPU_CURSOR_ID_* shape identity. */
|
||||||
|
void vgpu_publish_cursor_shape(const vgpu_region_view* rv,
|
||||||
|
uint32_t hot_x, uint32_t hot_y,
|
||||||
|
uint32_t gw, uint32_t gh, uint32_t cursor_id);
|
||||||
|
|
||||||
|
/* Publish the monotonic timestamp (ns) of the last scene-content change. Single 8-aligned
|
||||||
|
* atomic store (heartbeat pattern). The producer reports the raw stamp only; the host derives
|
||||||
|
* "ms idle" by subtracting from its own clock — no behavioural distillation in the producer. */
|
||||||
|
void vgpu_publish_content_change(const vgpu_region_view* rv, uint64_t change_ns);
|
||||||
|
|
||||||
|
/* Publish display geometry under the geom_seq seqlock (odd/even, like the frame seqlock).
|
||||||
|
* Sampled rarely (session start + reactive resample on desc-size delta / backend recreate),
|
||||||
|
* read by the host with bounded retry. virt_* is the virtual-desktop bbox (interprets negative
|
||||||
|
* cursor_pos); cap_x/cap_y is the captured output's origin in virtual-desktop coords (the
|
||||||
|
* captured surface SIZE comes from desc.width/height, not from here). dpi/refresh_mhz describe
|
||||||
|
* the captured output (96=100% / milli-Hz; 0=unknown). */
|
||||||
|
void vgpu_publish_geometry(const vgpu_region_view* rv,
|
||||||
|
int32_t virt_x, int32_t virt_y,
|
||||||
|
uint32_t virt_w, uint32_t virt_h,
|
||||||
|
int32_t cap_x, int32_t cap_y,
|
||||||
|
uint32_t dpi, uint32_t refresh_mhz);
|
||||||
|
|
||||||
#endif /* VGPU_STREAM_ENGINE_H */
|
#endif /* VGPU_STREAM_ENGINE_H */
|
||||||
|
|||||||
@@ -127,3 +127,37 @@ void vgpu_publish_cursor(const vgpu_region_view* rv, int32_t x, int32_t y, uint3
|
|||||||
/* publish seq last: its release-store gates the pos/visible writes above for the host */
|
/* publish seq last: its release-store gates the pos/visible writes above for the host */
|
||||||
vgpu_store_release32(&p->cursor_seq, p->cursor_seq + 1u);
|
vgpu_store_release32(&p->cursor_seq, p->cursor_seq + 1u);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void vgpu_publish_cursor_shape(const vgpu_region_view* rv, uint32_t hot_x, uint32_t hot_y,
|
||||||
|
uint32_t gw, uint32_t gh, uint32_t cursor_id) {
|
||||||
|
vgpu_producer_t* p = rv->producer;
|
||||||
|
/* pack 16|16 strictly unsigned (mask low half so no sign bits bleed into the high half).
|
||||||
|
* No own seq: the following vgpu_publish_cursor bumps cursor_seq last and gates this line. */
|
||||||
|
vgpu_store_release32(&p->cursor_hotspot, (hot_y << 16) | (hot_x & 0xFFFFu));
|
||||||
|
vgpu_store_release32(&p->cursor_glyph, (gh << 16) | (gw & 0xFFFFu));
|
||||||
|
vgpu_store_release32(&p->cursor_id, cursor_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_publish_content_change(const vgpu_region_view* rv, uint64_t change_ns) {
|
||||||
|
/* 64-bit aligned single MOV is atomic on x86_64; barrier orders it (heartbeat pattern) */
|
||||||
|
rv->producer->content_change_ns = change_ns;
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_publish_geometry(const vgpu_region_view* rv, int32_t virt_x, int32_t virt_y,
|
||||||
|
uint32_t virt_w, uint32_t virt_h,
|
||||||
|
int32_t cap_x, int32_t cap_y,
|
||||||
|
uint32_t dpi, uint32_t refresh_mhz) {
|
||||||
|
vgpu_producer_t* p = rv->producer;
|
||||||
|
/* seqlock: even -> odd (writing) */
|
||||||
|
vgpu_store_release32(&p->geom_seq, p->geom_seq + 1u);
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
p->virt_x = virt_x; p->virt_y = virt_y;
|
||||||
|
p->virt_w = virt_w; p->virt_h = virt_h;
|
||||||
|
p->cap_x = cap_x; p->cap_y = cap_y;
|
||||||
|
p->dpi = dpi; p->refresh_mhz = refresh_mhz;
|
||||||
|
vgpu_sfence();
|
||||||
|
/* seqlock: odd -> even (stable) */
|
||||||
|
vgpu_store_release32(&p->geom_seq, p->geom_seq + 1u);
|
||||||
|
vgpu_sfence();
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,6 +9,9 @@
|
|||||||
#include "capture_dda.h"
|
#include "capture_dda.h"
|
||||||
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
||||||
#include "present.h"
|
#include "present.h"
|
||||||
|
#include "cursor.h" /* cursor_resolve_id + ctx->cursor compose state */
|
||||||
|
#include "geometry.h" /* reactive geometry resample on recreate */
|
||||||
|
#include "stream.h" /* vgpu_publish_cursor / vgpu_publish_cursor_shape */
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
ID3D11Device* dev;
|
ID3D11Device* dev;
|
||||||
@@ -17,8 +20,61 @@ typedef struct {
|
|||||||
IDXGIOutputDuplication* dup;
|
IDXGIOutputDuplication* dup;
|
||||||
ID3D11Texture2D* staging;
|
ID3D11Texture2D* staging;
|
||||||
UINT W, H;
|
UINT W, H;
|
||||||
|
int32_t cap_x, cap_y; /* captured output origin (virt coords) */
|
||||||
|
UINT64 last_mouse_update; /* shape-gate by fi.LastMouseUpdateTime */
|
||||||
|
int seeded; /* cold-start position seed done */
|
||||||
} dda_state;
|
} dda_state;
|
||||||
|
|
||||||
|
/* Source the cursor from the already-fetched frame info (0 syscalls for position) and publish
|
||||||
|
* it under the cursor_seq gate. Position/visibility come from fi.PointerPosition; the shape is
|
||||||
|
* re-extracted only when fi.LastMouseUpdateTime changed (shape-gate). Cold start: fi is invalid
|
||||||
|
* until the mouse first moves (LastMouseUpdateTime==0) — seed the position once via one
|
||||||
|
* GetCursorInfo, then rely on fi. ctx->cursor compose fields are written under ctx->lock; the
|
||||||
|
* producer-block publish uses release/seq, no lock. */
|
||||||
|
static void dda_source_cursor(vgpu_ctx* ctx, dda_state* st,
|
||||||
|
const DXGI_OUTDUPL_FRAME_INFO* fi) {
|
||||||
|
int vis = fi->PointerPosition.Visible ? 1 : 0;
|
||||||
|
int x, y;
|
||||||
|
UINT64 upd = (UINT64)fi->LastMouseUpdateTime.QuadPart;
|
||||||
|
|
||||||
|
if (!st->seeded && upd == 0) {
|
||||||
|
CURSORINFO ci; ci.cbSize = sizeof ci;
|
||||||
|
if (GetCursorInfo(&ci)) {
|
||||||
|
vis = (ci.flags & CURSOR_SHOWING) != 0;
|
||||||
|
x = ci.ptScreenPos.x; y = ci.ptScreenPos.y;
|
||||||
|
} else {
|
||||||
|
x = ctx->cursor.x; y = ctx->cursor.y;
|
||||||
|
}
|
||||||
|
st->seeded = 1;
|
||||||
|
} else {
|
||||||
|
x = fi->PointerPosition.Position.x;
|
||||||
|
y = fi->PointerPosition.Position.y;
|
||||||
|
if (upd != 0) st->seeded = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* shape-gate: re-extract only when the mouse-update stamp advanced */
|
||||||
|
if (upd != 0 && upd != st->last_mouse_update) {
|
||||||
|
CURSORINFO ci; ci.cbSize = sizeof ci;
|
||||||
|
if (GetCursorInfo(&ci) && ci.hCursor && ci.hCursor != ctx->cursor.handle) {
|
||||||
|
EnterCriticalSection(&ctx->lock);
|
||||||
|
cursor_apply_shape(ctx, ci.hCursor);
|
||||||
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
}
|
||||||
|
st->last_mouse_update = upd;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnterCriticalSection(&ctx->lock);
|
||||||
|
ctx->cursor.visible = vis;
|
||||||
|
ctx->cursor.x = x; ctx->cursor.y = y;
|
||||||
|
uint32_t hx = (uint32_t)ctx->cursor.hot_x, hy = (uint32_t)ctx->cursor.hot_y;
|
||||||
|
uint32_t gw = (uint32_t)ctx->cursor.gw, gh = (uint32_t)ctx->cursor.gh;
|
||||||
|
uint32_t cid = (uint32_t)ctx->cursor.cursor_id;
|
||||||
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
|
||||||
|
vgpu_publish_cursor_shape(&ctx->view, hx, hy, gw, gh, cid);
|
||||||
|
vgpu_publish_cursor(&ctx->view, (int32_t)x, (int32_t)y, (uint32_t)vis);
|
||||||
|
}
|
||||||
|
|
||||||
static DWORD WINAPI dda_thread(LPVOID param) {
|
static DWORD WINAPI dda_thread(LPVOID param) {
|
||||||
capture_thread_arg* arg = (capture_thread_arg*)param;
|
capture_thread_arg* arg = (capture_thread_arg*)param;
|
||||||
vgpu_ctx* ctx = arg->ctx;
|
vgpu_ctx* ctx = arg->ctx;
|
||||||
@@ -33,12 +89,18 @@ static DWORD WINAPI dda_thread(LPVOID param) {
|
|||||||
if (hr == DXGI_ERROR_ACCESS_LOST) {
|
if (hr == DXGI_ERROR_ACCESS_LOST) {
|
||||||
if (st->dup) { st->dup->lpVtbl->Release(st->dup); st->dup = NULL; }
|
if (st->dup) { st->dup->lpVtbl->Release(st->dup); st->dup = NULL; }
|
||||||
if (FAILED(st->out1->lpVtbl->DuplicateOutput(st->out1,
|
if (FAILED(st->out1->lpVtbl->DuplicateOutput(st->out1,
|
||||||
(IUnknown*)st->dev, &st->dup)))
|
(IUnknown*)st->dev, &st->dup))) {
|
||||||
Sleep(200);
|
Sleep(200);
|
||||||
|
} else {
|
||||||
|
/* display config may have changed across the access loss → resample geometry */
|
||||||
|
geometry_sample_and_publish(ctx, st->cap_x, st->cap_y);
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (FAILED(hr)) { Sleep(50); continue; }
|
if (FAILED(hr)) { Sleep(50); continue; }
|
||||||
|
|
||||||
|
dda_source_cursor(ctx, st, &fi);
|
||||||
|
|
||||||
ID3D11Texture2D* tex = NULL;
|
ID3D11Texture2D* tex = NULL;
|
||||||
res->lpVtbl->QueryInterface(res, &IID_ID3D11Texture2D, (void**)&tex);
|
res->lpVtbl->QueryInterface(res, &IID_ID3D11Texture2D, (void**)&tex);
|
||||||
if (tex) {
|
if (tex) {
|
||||||
@@ -76,7 +138,14 @@ int dda_start(vgpu_ctx* ctx, int fps) {
|
|||||||
st->dev->lpVtbl->QueryInterface(st->dev, &IID_IDXGIDevice, (void**)&dxgiDev);
|
st->dev->lpVtbl->QueryInterface(st->dev, &IID_IDXGIDevice, (void**)&dxgiDev);
|
||||||
if (dxgiDev) dxgiDev->lpVtbl->GetAdapter(dxgiDev, &adapter);
|
if (dxgiDev) dxgiDev->lpVtbl->GetAdapter(dxgiDev, &adapter);
|
||||||
if (adapter) adapter->lpVtbl->EnumOutputs(adapter, 0, &output);
|
if (adapter) adapter->lpVtbl->EnumOutputs(adapter, 0, &output);
|
||||||
if (output) output->lpVtbl->QueryInterface(output, &IID_IDXGIOutput1, (void**)&st->out1);
|
if (output) {
|
||||||
|
DXGI_OUTPUT_DESC od;
|
||||||
|
if (SUCCEEDED(output->lpVtbl->GetDesc(output, &od))) {
|
||||||
|
st->cap_x = (int32_t)od.DesktopCoordinates.left;
|
||||||
|
st->cap_y = (int32_t)od.DesktopCoordinates.top;
|
||||||
|
}
|
||||||
|
output->lpVtbl->QueryInterface(output, &IID_IDXGIOutput1, (void**)&st->out1);
|
||||||
|
}
|
||||||
|
|
||||||
if (output) output->lpVtbl->Release(output);
|
if (output) output->lpVtbl->Release(output);
|
||||||
if (adapter) adapter->lpVtbl->Release(adapter);
|
if (adapter) adapter->lpVtbl->Release(adapter);
|
||||||
|
|||||||
@@ -6,6 +6,9 @@
|
|||||||
#include "capture_gdi.h"
|
#include "capture_gdi.h"
|
||||||
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
||||||
#include "present.h"
|
#include "present.h"
|
||||||
|
#include "cursor.h" /* cursor_sample (position+shape+id) for compose+publish */
|
||||||
|
#include "geometry.h" /* reactive geometry resample on capture-size change */
|
||||||
|
#include "stream.h" /* vgpu_publish_cursor / vgpu_publish_cursor_shape */
|
||||||
|
|
||||||
static DWORD WINAPI gdi_thread(LPVOID param) {
|
static DWORD WINAPI gdi_thread(LPVOID param) {
|
||||||
capture_thread_arg* arg = (capture_thread_arg*)param;
|
capture_thread_arg* arg = (capture_thread_arg*)param;
|
||||||
@@ -38,10 +41,25 @@ static DWORD WINAPI gdi_thread(LPVOID param) {
|
|||||||
SelectObject(mem, dib);
|
SelectObject(mem, dib);
|
||||||
W = w; H = h;
|
W = w; H = h;
|
||||||
fprintf(stderr, "eyes(gdi): desktop %dx%d (BitBlt; cursor by presenter)\n", W, H);
|
fprintf(stderr, "eyes(gdi): desktop %dx%d (BitBlt; cursor by presenter)\n", W, H);
|
||||||
|
/* capture size changed (primary at origin (0,0)) → resample geometry */
|
||||||
|
geometry_sample_and_publish(ctx, 0, 0);
|
||||||
}
|
}
|
||||||
if (BitBlt(mem, 0, 0, W, H, screen, 0, 0, SRCCOPY))
|
if (BitBlt(mem, 0, 0, W, H, screen, 0, 0, SRCCOPY))
|
||||||
vgpu_present_submit(ctx, (const uint8_t*)bits,
|
vgpu_present_submit(ctx, (const uint8_t*)bits,
|
||||||
(uint32_t)W, (uint32_t)H, (uint32_t)W * 4u);
|
(uint32_t)W, (uint32_t)H, (uint32_t)W * 4u);
|
||||||
|
|
||||||
|
/* source the cursor for present's compositing (under ctx->lock) and publish it */
|
||||||
|
EnterCriticalSection(&ctx->lock);
|
||||||
|
cursor_sample(ctx);
|
||||||
|
uint32_t hx = (uint32_t)ctx->cursor.hot_x, hy = (uint32_t)ctx->cursor.hot_y;
|
||||||
|
uint32_t gw = (uint32_t)ctx->cursor.gw, gh = (uint32_t)ctx->cursor.gh;
|
||||||
|
uint32_t cid = (uint32_t)ctx->cursor.cursor_id;
|
||||||
|
int32_t cx = (int32_t)ctx->cursor.x, cy = (int32_t)ctx->cursor.y;
|
||||||
|
uint32_t cvis = (uint32_t)(ctx->cursor.visible != 0);
|
||||||
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
vgpu_publish_cursor_shape(&ctx->view, hx, hy, gw, gh, cid);
|
||||||
|
vgpu_publish_cursor(&ctx->view, cx, cy, cvis);
|
||||||
|
|
||||||
Sleep(interval);
|
Sleep(interval);
|
||||||
}
|
}
|
||||||
return 0; /* unreachable; satisfies -Wreturn-type */
|
return 0; /* unreachable; satisfies -Wreturn-type */
|
||||||
|
|||||||
@@ -6,14 +6,45 @@
|
|||||||
#include "capture_nvfbc.h"
|
#include "capture_nvfbc.h"
|
||||||
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
||||||
#include "present.h"
|
#include "present.h"
|
||||||
|
#include "cursor.h" /* cursor_apply_shape / ctx->cursor */
|
||||||
|
#include "geometry.h" /* reactive geometry resample on recreate */
|
||||||
|
#include "stream.h" /* vgpu_publish_cursor / vgpu_publish_cursor_shape */
|
||||||
#include "nvfbc_tosys_c.h"
|
#include "nvfbc_tosys_c.h"
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
NvFBCToSys_c* fbc;
|
NvFBCToSys_c* fbc;
|
||||||
void* buf;
|
void* buf;
|
||||||
NvFBC_CreateFunctionExType create;
|
NvFBC_CreateFunctionExType create;
|
||||||
|
HCURSOR last_handle; /* shape-gate by HCURSOR change */
|
||||||
} nvfbc_state;
|
} nvfbc_state;
|
||||||
|
|
||||||
|
/* Source the cursor for an NvFBC grab and publish it under the cursor_seq gate. NvFBC reports
|
||||||
|
* only HW-cursor visibility (gi.bHWMouseVisible); position is not exposed, so one GetCursorInfo
|
||||||
|
* per frame supplies x/y (the minimum possible). Shape is re-extracted only on HCURSOR change.
|
||||||
|
* NvFBC composites the cursor itself (draw_cursor_cap==0) → present never reads ctx->cursor for
|
||||||
|
* drawing, so no ctx->lock is required around the compose fields here.
|
||||||
|
* gi.bProtectedContent / gi.dwSourcePID are available but out of scope (not in the contract). */
|
||||||
|
static void nvfbc_source_cursor(vgpu_ctx* ctx, nvfbc_state* st,
|
||||||
|
const NvFBCFrameGrabInfo* gi) {
|
||||||
|
CURSORINFO ci; ci.cbSize = sizeof ci;
|
||||||
|
int vis = gi->bHWMouseVisible ? 1 : 0;
|
||||||
|
int x = ctx->cursor.x, y = ctx->cursor.y;
|
||||||
|
if (GetCursorInfo(&ci)) {
|
||||||
|
x = ci.ptScreenPos.x; y = ci.ptScreenPos.y;
|
||||||
|
if (ci.hCursor && ci.hCursor != st->last_handle) {
|
||||||
|
cursor_apply_shape(ctx, ci.hCursor);
|
||||||
|
st->last_handle = ci.hCursor;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx->cursor.visible = vis; ctx->cursor.x = x; ctx->cursor.y = y;
|
||||||
|
|
||||||
|
vgpu_publish_cursor_shape(&ctx->view,
|
||||||
|
(uint32_t)ctx->cursor.hot_x, (uint32_t)ctx->cursor.hot_y,
|
||||||
|
(uint32_t)ctx->cursor.gw, (uint32_t)ctx->cursor.gh,
|
||||||
|
(uint32_t)ctx->cursor.cursor_id);
|
||||||
|
vgpu_publish_cursor(&ctx->view, (int32_t)x, (int32_t)y, (uint32_t)vis);
|
||||||
|
}
|
||||||
|
|
||||||
static NvFBCToSys_c* nvfbc_create(NvFBC_CreateFunctionExType pCreate, void** ppBuf) {
|
static NvFBCToSys_c* nvfbc_create(NvFBC_CreateFunctionExType pCreate, void** ppBuf) {
|
||||||
NvFBCCreateParams cp; memset(&cp, 0, sizeof cp);
|
NvFBCCreateParams cp; memset(&cp, 0, sizeof cp);
|
||||||
cp.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
cp.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
||||||
@@ -62,6 +93,8 @@ static DWORD WINAPI nvfbc_thread(LPVOID param) {
|
|||||||
fbc = NULL;
|
fbc = NULL;
|
||||||
while (!(fbc = nvfbc_create(st->create, &buf))) Sleep(200);
|
while (!(fbc = nvfbc_create(st->create, &buf))) Sleep(200);
|
||||||
st->fbc = fbc; st->buf = buf;
|
st->fbc = fbc; st->buf = buf;
|
||||||
|
/* grab session was recreated → display config may have changed: resample */
|
||||||
|
geometry_sample_and_publish(ctx, 0, 0);
|
||||||
} else {
|
} else {
|
||||||
Sleep(50);
|
Sleep(50);
|
||||||
}
|
}
|
||||||
@@ -70,6 +103,7 @@ static DWORD WINAPI nvfbc_thread(LPVOID param) {
|
|||||||
if (gi.dwWidth && gi.dwHeight)
|
if (gi.dwWidth && gi.dwHeight)
|
||||||
vgpu_present_submit(ctx, (const uint8_t*)buf,
|
vgpu_present_submit(ctx, (const uint8_t*)buf,
|
||||||
gi.dwWidth, gi.dwHeight, gi.dwBufferWidth * 4u);
|
gi.dwWidth, gi.dwHeight, gi.dwBufferWidth * 4u);
|
||||||
|
nvfbc_source_cursor(ctx, st, &gi);
|
||||||
}
|
}
|
||||||
return 0; /* unreachable; satisfies -Wreturn-type */
|
return 0; /* unreachable; satisfies -Wreturn-type */
|
||||||
}
|
}
|
||||||
@@ -109,7 +143,7 @@ int nvfbc_start(vgpu_ctx* ctx, int fps) {
|
|||||||
|
|
||||||
nvfbc_state* st = (nvfbc_state*)malloc(sizeof *st);
|
nvfbc_state* st = (nvfbc_state*)malloc(sizeof *st);
|
||||||
if (!st) { fbc->lpVtbl->NvFBCToSysRelease(fbc); return 0; }
|
if (!st) { fbc->lpVtbl->NvFBCToSysRelease(fbc); return 0; }
|
||||||
st->fbc = fbc; st->buf = buf; st->create = pCreate;
|
st->fbc = fbc; st->buf = buf; st->create = pCreate; st->last_handle = NULL;
|
||||||
|
|
||||||
capture_thread_arg* arg = (capture_thread_arg*)malloc(sizeof *arg);
|
capture_thread_arg* arg = (capture_thread_arg*)malloc(sizeof *arg);
|
||||||
if (!arg) { fbc->lpVtbl->NvFBCToSysRelease(fbc); free(st); return 0; }
|
if (!arg) { fbc->lpVtbl->NvFBCToSysRelease(fbc); free(st); return 0; }
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ typedef struct {
|
|||||||
int x, y;
|
int x, y;
|
||||||
int hot_x, hot_y;
|
int hot_x, hot_y;
|
||||||
int gw, gh; /* glyph dims */
|
int gw, gh; /* glyph dims */
|
||||||
|
int cursor_id; /* VGPU_CURSOR_ID_* resolved on shape change */
|
||||||
int mono; /* 1 = AND/XOR monochrome cursor */
|
int mono; /* 1 = AND/XOR monochrome cursor */
|
||||||
uint8_t* bgra; /* color cursor BGRA (arena) */
|
uint8_t* bgra; /* color cursor BGRA (arena) */
|
||||||
uint8_t* and_mask; /* mono AND (arena) */
|
uint8_t* and_mask; /* mono AND (arena) */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include "cursor.h"
|
#include "cursor.h"
|
||||||
|
#include "vgpu_stream.h" /* VGPU_CURSOR_ID_* */
|
||||||
|
|
||||||
/* Max supported cursor glyph; buffers are pre-arena'd in ctx (no heap here). */
|
/* Max supported cursor glyph; buffers are pre-arena'd in ctx (no heap here). */
|
||||||
#define VGPU_CURSOR_MAX 256
|
#define VGPU_CURSOR_MAX 256
|
||||||
@@ -85,6 +86,42 @@ static void extract(vgpu_ctx* ctx, HCURSOR hc) {
|
|||||||
if (ii.hbmMask) DeleteObject(ii.hbmMask);
|
if (ii.hbmMask) DeleteObject(ii.hbmMask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int cursor_resolve_id(HCURSOR hc) {
|
||||||
|
/* System-cursor table loaded once (IDC_* are stable per session). Lazy: built on first
|
||||||
|
* call, then a linear handle compare. UNKNOWN for custom/unrecognized cursors. */
|
||||||
|
static const struct { LPCTSTR idc; int id; } kSpec[] = {
|
||||||
|
{ IDC_ARROW, VGPU_CURSOR_ID_ARROW },
|
||||||
|
{ IDC_IBEAM, VGPU_CURSOR_ID_IBEAM },
|
||||||
|
{ IDC_WAIT, VGPU_CURSOR_ID_WAIT },
|
||||||
|
{ IDC_CROSS, VGPU_CURSOR_ID_CROSS },
|
||||||
|
{ IDC_HAND, VGPU_CURSOR_ID_HAND },
|
||||||
|
{ IDC_SIZENS, VGPU_CURSOR_ID_SIZENS },
|
||||||
|
{ IDC_SIZEWE, VGPU_CURSOR_ID_SIZEWE },
|
||||||
|
{ IDC_SIZENWSE, VGPU_CURSOR_ID_SIZENWSE },
|
||||||
|
{ IDC_SIZENESW, VGPU_CURSOR_ID_SIZENESW },
|
||||||
|
{ IDC_SIZEALL, VGPU_CURSOR_ID_SIZEALL },
|
||||||
|
{ IDC_NO, VGPU_CURSOR_ID_NO },
|
||||||
|
{ IDC_APPSTARTING, VGPU_CURSOR_ID_APPSTARTING },
|
||||||
|
};
|
||||||
|
enum { N = (int)(sizeof kSpec / sizeof kSpec[0]) };
|
||||||
|
static HCURSOR cache[N];
|
||||||
|
static int loaded = 0;
|
||||||
|
if (!loaded) {
|
||||||
|
for (int i = 0; i < N; i++) cache[i] = LoadCursor(NULL, kSpec[i].idc);
|
||||||
|
loaded = 1;
|
||||||
|
}
|
||||||
|
if (!hc) return VGPU_CURSOR_ID_UNKNOWN;
|
||||||
|
for (int i = 0; i < N; i++)
|
||||||
|
if (cache[i] == hc) return kSpec[i].id;
|
||||||
|
return VGPU_CURSOR_ID_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cursor_apply_shape(vgpu_ctx* ctx, HCURSOR hc) {
|
||||||
|
extract(ctx, hc);
|
||||||
|
ctx->cursor.cursor_id = cursor_resolve_id(hc);
|
||||||
|
ctx->cursor.handle = hc;
|
||||||
|
}
|
||||||
|
|
||||||
int cursor_sample(vgpu_ctx* ctx) {
|
int cursor_sample(vgpu_ctx* ctx) {
|
||||||
vgpu_cursor_t* cur = &ctx->cursor;
|
vgpu_cursor_t* cur = &ctx->cursor;
|
||||||
CURSORINFO ci; ci.cbSize = sizeof ci;
|
CURSORINFO ci; ci.cbSize = sizeof ci;
|
||||||
@@ -99,21 +136,13 @@ int cursor_sample(vgpu_ctx* ctx) {
|
|||||||
|| (ci.hCursor != cur->handle);
|
|| (ci.hCursor != cur->handle);
|
||||||
if (vis && ci.hCursor && ci.hCursor != cur->handle) {
|
if (vis && ci.hCursor && ci.hCursor != cur->handle) {
|
||||||
extract(ctx, ci.hCursor);
|
extract(ctx, ci.hCursor);
|
||||||
|
cur->cursor_id = cursor_resolve_id(ci.hCursor);
|
||||||
cur->handle = ci.hCursor;
|
cur->handle = ci.hCursor;
|
||||||
}
|
}
|
||||||
cur->visible = vis; cur->x = x; cur->y = y;
|
cur->visible = vis; cur->x = x; cur->y = y;
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void cursor_sample_pos(vgpu_ctx* ctx) {
|
|
||||||
vgpu_cursor_t* cur = &ctx->cursor;
|
|
||||||
CURSORINFO ci; ci.cbSize = sizeof ci;
|
|
||||||
if (!GetCursorInfo(&ci)) { cur->visible = 0; return; }
|
|
||||||
cur->visible = (ci.flags & CURSOR_SHOWING) != 0;
|
|
||||||
cur->x = ci.ptScreenPos.x;
|
|
||||||
cur->y = ci.ptScreenPos.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
void cursor_draw(vgpu_ctx* ctx, uint8_t* dst, uint32_t W, uint32_t H) {
|
void cursor_draw(vgpu_ctx* ctx, uint8_t* dst, uint32_t W, uint32_t H) {
|
||||||
vgpu_cursor_t* cur = &ctx->cursor;
|
vgpu_cursor_t* cur = &ctx->cursor;
|
||||||
if (!cur->visible || cur->gw == 0) return;
|
if (!cur->visible || cur->gw == 0) return;
|
||||||
|
|||||||
@@ -10,9 +10,15 @@
|
|||||||
* Returns 1 if anything changed since last sample, else 0. */
|
* Returns 1 if anything changed since last sample, else 0. */
|
||||||
int cursor_sample(vgpu_ctx* ctx);
|
int cursor_sample(vgpu_ctx* ctx);
|
||||||
|
|
||||||
/* Sample only cursor position/visibility (no shape extract). Updates
|
/* Resolve a HCURSOR to a VGPU_CURSOR_ID_* by comparing against the system cursor table
|
||||||
* ctx->cursor.{visible,x,y}. Cheap (one GetCursorInfo); safe to call every pump tick. */
|
* (LoadCursor(NULL, IDC_*) loaded once on first use). Returns VGPU_CURSOR_ID_UNKNOWN for
|
||||||
void cursor_sample_pos(vgpu_ctx* ctx);
|
* custom cursors. Not hot-path: called only under the shape-change gate. */
|
||||||
|
int cursor_resolve_id(HCURSOR hc);
|
||||||
|
|
||||||
|
/* Extract glyph/hotspot/dims for hc into ctx->cursor, resolve its cursor_id, and record it as
|
||||||
|
* the current handle. For backends that source position elsewhere (DDA from frame info) and
|
||||||
|
* only need the shape on a shape-change gate. Caller serializes ctx->cursor writes. */
|
||||||
|
void cursor_apply_shape(vgpu_ctx* ctx, HCURSOR hc);
|
||||||
|
|
||||||
/* Alpha/AND-XOR compose the sampled cursor onto a tight BGRA frame. */
|
/* Alpha/AND-XOR compose the sampled cursor onto a tight BGRA frame. */
|
||||||
void cursor_draw(vgpu_ctx* ctx, uint8_t* bgra, uint32_t width, uint32_t height);
|
void cursor_draw(vgpu_ctx* ctx, uint8_t* bgra, uint32_t width, uint32_t height);
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include "geometry.h"
|
||||||
|
#include "stream.h" /* vgpu_publish_geometry */
|
||||||
|
|
||||||
|
/* GetDpiForMonitor lives in Shcore.dll (per-monitor DPI awareness API). Loaded dynamically so
|
||||||
|
* the binary does not hard-depend on it; absence degrades dpi to "unknown" (0). */
|
||||||
|
typedef HRESULT (WINAPI *GetDpiForMonitor_t)(HMONITOR, int /*MDT_*/, UINT*, UINT*);
|
||||||
|
#define VGPU_MDT_EFFECTIVE_DPI 0
|
||||||
|
|
||||||
|
static UINT monitor_dpi(HMONITOR mon) {
|
||||||
|
static GetDpiForMonitor_t fn = NULL;
|
||||||
|
static int tried = 0;
|
||||||
|
if (!tried) {
|
||||||
|
HMODULE lib = LoadLibraryA("Shcore.dll");
|
||||||
|
if (lib) fn = (GetDpiForMonitor_t)(void*)GetProcAddress(lib, "GetDpiForMonitor");
|
||||||
|
tried = 1;
|
||||||
|
}
|
||||||
|
if (!fn || !mon) return 0u;
|
||||||
|
UINT dx = 0, dy = 0;
|
||||||
|
if (fn(mon, VGPU_MDT_EFFECTIVE_DPI, &dx, &dy) != S_OK || dx == 0u)
|
||||||
|
return 0u;
|
||||||
|
return dx;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t monitor_refresh_mhz(HMONITOR mon) {
|
||||||
|
MONITORINFOEXW mi; mi.cbSize = sizeof mi;
|
||||||
|
if (!mon || !GetMonitorInfoW(mon, (MONITORINFO*)&mi))
|
||||||
|
return 0u;
|
||||||
|
DEVMODEW dm; ZeroMemory(&dm, sizeof dm); dm.dmSize = sizeof dm;
|
||||||
|
if (!EnumDisplaySettingsW(mi.szDevice, ENUM_CURRENT_SETTINGS, &dm))
|
||||||
|
return 0u;
|
||||||
|
if (dm.dmDisplayFrequency <= 1u) /* 0/1 = hardware default, not a real rate */
|
||||||
|
return 0u;
|
||||||
|
return (uint32_t)dm.dmDisplayFrequency * 1000u; /* whole Hz -> milli-Hz */
|
||||||
|
}
|
||||||
|
|
||||||
|
void geometry_sample_and_publish(vgpu_ctx* ctx, int32_t cap_x, int32_t cap_y) {
|
||||||
|
int32_t virt_x = (int32_t)GetSystemMetrics(SM_XVIRTUALSCREEN);
|
||||||
|
int32_t virt_y = (int32_t)GetSystemMetrics(SM_YVIRTUALSCREEN);
|
||||||
|
uint32_t virt_w = (uint32_t)GetSystemMetrics(SM_CXVIRTUALSCREEN);
|
||||||
|
uint32_t virt_h = (uint32_t)GetSystemMetrics(SM_CYVIRTUALSCREEN);
|
||||||
|
|
||||||
|
POINT origin = { cap_x, cap_y };
|
||||||
|
HMONITOR mon = MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY);
|
||||||
|
|
||||||
|
uint32_t dpi = monitor_dpi(mon);
|
||||||
|
uint32_t refresh = monitor_refresh_mhz(mon);
|
||||||
|
|
||||||
|
vgpu_publish_geometry(&ctx->view, virt_x, virt_y, virt_w, virt_h,
|
||||||
|
cap_x, cap_y, dpi, refresh);
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef VGPU_GEOMETRY_H
|
||||||
|
#define VGPU_GEOMETRY_H
|
||||||
|
|
||||||
|
/* geometry.h — win32 display-geometry sampler. Samples the virtual-desktop bbox plus the
|
||||||
|
* captured output's origin / DPI / refresh and publishes them under the geom_seq seqlock.
|
||||||
|
* Not per-frame: called once at session start and reactively on backend recreate / capture-
|
||||||
|
* size change (the captured surface SIZE itself travels in desc.width/height, not here). */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx (region-view) */
|
||||||
|
|
||||||
|
/* Sample display geometry for the captured output whose top-left origin is (cap_x,cap_y) in
|
||||||
|
* virtual-desktop coordinates, and publish it. cap_x/cap_y is (0,0) for primary/full-screen
|
||||||
|
* backends and the duplicated output's DesktopCoordinates for DDA. The captured size is taken
|
||||||
|
* from desc.width/height and is not sampled here. */
|
||||||
|
void geometry_sample_and_publish(vgpu_ctx* ctx, int32_t cap_x, int32_t cap_y);
|
||||||
|
|
||||||
|
#endif /* VGPU_GEOMETRY_H */
|
||||||
+27
-13
@@ -5,6 +5,7 @@
|
|||||||
#include "present.h"
|
#include "present.h"
|
||||||
#include "stream.h" /* OS-agnostic publish / control API + region-view */
|
#include "stream.h" /* OS-agnostic publish / control API + region-view */
|
||||||
#include "cursor.h"
|
#include "cursor.h"
|
||||||
|
#include "geometry.h" /* one-shot display-geometry sample at session start */
|
||||||
|
|
||||||
/* cursor arena sizing */
|
/* cursor arena sizing */
|
||||||
#define VGPU_CUR_MAX 256u
|
#define VGPU_CUR_MAX 256u
|
||||||
@@ -78,6 +79,9 @@ void vgpu_present_submit(vgpu_ctx* ctx, const uint8_t* src,
|
|||||||
ctx->content_h = H;
|
ctx->content_h = H;
|
||||||
ctx->content_seq++;
|
ctx->content_seq++;
|
||||||
LeaveCriticalSection(&ctx->lock);
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
/* static-idle: stamp the moment the source delivered new content (the raw perception;
|
||||||
|
* the host derives "ms idle" from its own clock). Single 8-aligned MOV, off the lock. */
|
||||||
|
vgpu_publish_content_change(&ctx->view, now_ns());
|
||||||
SetEvent(ctx->submit_event);
|
SetEvent(ctx->submit_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +93,13 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
|||||||
uint32_t last_ff_ack = rv->producer->full_frame_ack;
|
uint32_t last_ff_ack = rv->producer->full_frame_ack;
|
||||||
DWORD last_beat = GetTickCount();
|
DWORD last_beat = GetTickCount();
|
||||||
uint64_t last_publish_ns = 0; /* 0 → first eligible frame publishes immediately */
|
uint64_t last_publish_ns = 0; /* 0 → first eligible frame publishes immediately */
|
||||||
|
int last_cur_x = 0, last_cur_y = 0, last_cur_vis = 0;
|
||||||
|
HCURSOR last_cur_handle = NULL;
|
||||||
|
|
||||||
|
/* one-shot display geometry: publish once before the loop (flat pull contract). The
|
||||||
|
* captured-output origin is (0,0) for the primary/full-screen capture path; backends
|
||||||
|
* resample reactively on recreate / capture-size change. No periodic poll in the loop. */
|
||||||
|
geometry_sample_and_publish(ctx, 0, 0);
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
WaitForSingleObject(ctx->submit_event, poll_ms);
|
WaitForSingleObject(ctx->submit_event, poll_ms);
|
||||||
@@ -100,12 +111,6 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
|||||||
last_beat = nowt;
|
last_beat = nowt;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- cursor position: sensor data, reported every tick independent of
|
|
||||||
* draw_cursor / compositing (host may overlay it itself) --- */
|
|
||||||
cursor_sample_pos(ctx);
|
|
||||||
vgpu_publish_cursor(rv, (int32_t)ctx->cursor.x, (int32_t)ctx->cursor.y,
|
|
||||||
(uint32_t)(ctx->cursor.visible != 0));
|
|
||||||
|
|
||||||
/* --- reconcile control (gen-seqlock -> apply -> ack) --- */
|
/* --- reconcile control (gen-seqlock -> apply -> ack) --- */
|
||||||
vgpu_control_view cv;
|
vgpu_control_view cv;
|
||||||
uint32_t desired = prev_state;
|
uint32_t desired = prev_state;
|
||||||
@@ -152,18 +157,25 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
|||||||
/* --- compose + publish on content change OR forced full frame, but
|
/* --- compose + publish on content change OR forced full frame, but
|
||||||
* rate-limited to the applied fps cap (the single publish point →
|
* rate-limited to the applied fps cap (the single publish point →
|
||||||
* contract-level cap, independent of the capture backend). A
|
* contract-level cap, independent of the capture backend). A
|
||||||
* force_full bypasses the cap (due=1). --- */
|
* force_full bypasses the cap (due=1). present does NOT sample the
|
||||||
int cur_changed = (ctx->draw_cursor_cap && draw_cursor)
|
* cursor (capture threads source it); it only reads ctx->cursor under
|
||||||
? cursor_sample(ctx) : 0;
|
* ctx->lock for compositing, and detects cursor motion via a delta so
|
||||||
|
* a pure cursor move over static desktop still recomposes. --- */
|
||||||
uint64_t interval_ns = fps > 0 ? (1000000000ull / fps) : 0;
|
uint64_t interval_ns = fps > 0 ? (1000000000ull / fps) : 0;
|
||||||
uint64_t now = now_ns();
|
uint64_t now = now_ns();
|
||||||
int due = force_full || interval_ns == 0
|
int due = force_full || interval_ns == 0
|
||||||
|| (now - last_publish_ns) >= interval_ns;
|
|| (now - last_publish_ns) >= interval_ns;
|
||||||
|
|
||||||
|
int compose_cursor = (ctx->draw_cursor_cap && draw_cursor);
|
||||||
|
|
||||||
EnterCriticalSection(&ctx->lock);
|
EnterCriticalSection(&ctx->lock);
|
||||||
int64_t seq = ctx->content_seq;
|
int64_t seq = ctx->content_seq;
|
||||||
uint32_t W = ctx->content_w, H = ctx->content_h;
|
uint32_t W = ctx->content_w, H = ctx->content_h;
|
||||||
|
int cur_changed = compose_cursor
|
||||||
|
&& ((ctx->cursor.visible != last_cur_vis)
|
||||||
|
|| (ctx->cursor.x != last_cur_x)
|
||||||
|
|| (ctx->cursor.y != last_cur_y)
|
||||||
|
|| (ctx->cursor.handle != last_cur_handle));
|
||||||
int have = (W && H);
|
int have = (W && H);
|
||||||
int content_new = have && (seq != last_seq || cur_changed || force_full);
|
int content_new = have && (seq != last_seq || cur_changed || force_full);
|
||||||
/* take the frame ONLY when due — so we never drop the latest content;
|
/* take the frame ONLY when due — so we never drop the latest content;
|
||||||
@@ -172,6 +184,11 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
|||||||
if (dirty) {
|
if (dirty) {
|
||||||
memcpy(ctx->frame_buf, ctx->content_buf, (size_t)W * H * 4u);
|
memcpy(ctx->frame_buf, ctx->content_buf, (size_t)W * H * 4u);
|
||||||
last_seq = seq;
|
last_seq = seq;
|
||||||
|
if (compose_cursor)
|
||||||
|
cursor_draw(ctx, ctx->frame_buf, W, H);
|
||||||
|
last_cur_vis = ctx->cursor.visible;
|
||||||
|
last_cur_x = ctx->cursor.x; last_cur_y = ctx->cursor.y;
|
||||||
|
last_cur_handle = ctx->cursor.handle;
|
||||||
}
|
}
|
||||||
LeaveCriticalSection(&ctx->lock);
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
|
||||||
@@ -182,9 +199,6 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx->draw_cursor_cap && draw_cursor)
|
|
||||||
cursor_draw(ctx, ctx->frame_buf, W, H);
|
|
||||||
|
|
||||||
if (vgpu_publish_frame(rv, ctx->frame_buf, W, H, now) == 0) {
|
if (vgpu_publish_frame(rv, ctx->frame_buf, W, H, now) == 0) {
|
||||||
last_publish_ns = now;
|
last_publish_ns = now;
|
||||||
if (force_full) {
|
if (force_full) {
|
||||||
|
|||||||
Reference in New Issue
Block a user