mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-07-08 23:46:36 +03:00
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}.
This commit is contained in:
+27
-13
@@ -5,6 +5,7 @@
|
||||
#include "present.h"
|
||||
#include "stream.h" /* OS-agnostic publish / control API + region-view */
|
||||
#include "cursor.h"
|
||||
#include "geometry.h" /* one-shot display-geometry sample at session start */
|
||||
|
||||
/* cursor arena sizing */
|
||||
#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_seq++;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -89,6 +93,13 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
||||
uint32_t last_ff_ack = rv->producer->full_frame_ack;
|
||||
DWORD last_beat = GetTickCount();
|
||||
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 (;;) {
|
||||
WaitForSingleObject(ctx->submit_event, poll_ms);
|
||||
@@ -100,12 +111,6 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
||||
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) --- */
|
||||
vgpu_control_view cv;
|
||||
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
|
||||
* rate-limited to the applied fps cap (the single publish point →
|
||||
* contract-level cap, independent of the capture backend). A
|
||||
* force_full bypasses the cap (due=1). --- */
|
||||
int cur_changed = (ctx->draw_cursor_cap && draw_cursor)
|
||||
? cursor_sample(ctx) : 0;
|
||||
|
||||
* force_full bypasses the cap (due=1). present does NOT sample the
|
||||
* cursor (capture threads source it); it only reads ctx->cursor under
|
||||
* 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 now = now_ns();
|
||||
int due = force_full || interval_ns == 0
|
||||
|| (now - last_publish_ns) >= interval_ns;
|
||||
|
||||
int compose_cursor = (ctx->draw_cursor_cap && draw_cursor);
|
||||
|
||||
EnterCriticalSection(&ctx->lock);
|
||||
int64_t seq = ctx->content_seq;
|
||||
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 content_new = have && (seq != last_seq || cur_changed || force_full);
|
||||
/* 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) {
|
||||
memcpy(ctx->frame_buf, ctx->content_buf, (size_t)W * H * 4u);
|
||||
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);
|
||||
|
||||
@@ -182,9 +199,6 @@ void vgpu_present_run(vgpu_ctx* ctx) {
|
||||
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) {
|
||||
last_publish_ns = now;
|
||||
if (force_full) {
|
||||
|
||||
Reference in New Issue
Block a user