vgpu in-guest producer in-tree, release CI, flexible vmie discovery

- src/si/vgpu-stream: in-guest vgpu producer built as a Windows cross-compiled target (if(WIN32))
- .gitea: release workflow — cross-build the agent and build/publish the deb against system vmie
- cmake/makefile: resolve vmie from a source tree (LIBVMIE_PATH) or installed libvmie-dev
This commit is contained in:
2026-06-22 18:35:12 +03:00
parent 9bde398b6c
commit bd8b966017
31 changed files with 2393 additions and 8 deletions
+198
View File
@@ -0,0 +1,198 @@
#define WIN32_LEAN_AND_MEAN
#define COBJMACROS
#include <windows.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "capture_dda.h"
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
#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 {
ID3D11Device* dev;
ID3D11DeviceContext* dctx;
IDXGIOutput1* out1;
IDXGIOutputDuplication* dup;
ID3D11Texture2D* staging;
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;
/* 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) {
capture_thread_arg* arg = (capture_thread_arg*)param;
vgpu_ctx* ctx = arg->ctx;
dda_state* st = (dda_state*)arg->backend_state;
free(arg);
for (;;) {
DXGI_OUTDUPL_FRAME_INFO fi;
IDXGIResource* res = NULL;
HRESULT hr = st->dup->lpVtbl->AcquireNextFrame(st->dup, 1000, &fi, &res);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) continue;
if (hr == DXGI_ERROR_ACCESS_LOST) {
if (st->dup) { st->dup->lpVtbl->Release(st->dup); st->dup = NULL; }
if (FAILED(st->out1->lpVtbl->DuplicateOutput(st->out1,
(IUnknown*)st->dev, &st->dup))) {
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;
}
if (FAILED(hr)) { Sleep(50); continue; }
dda_source_cursor(ctx, st, &fi);
ID3D11Texture2D* tex = NULL;
res->lpVtbl->QueryInterface(res, &IID_ID3D11Texture2D, (void**)&tex);
if (tex) {
st->dctx->lpVtbl->CopyResource(st->dctx,
(ID3D11Resource*)st->staging, (ID3D11Resource*)tex);
D3D11_MAPPED_SUBRESOURCE m;
if (SUCCEEDED(st->dctx->lpVtbl->Map(st->dctx,
(ID3D11Resource*)st->staging, 0, D3D11_MAP_READ, 0, &m))) {
vgpu_present_submit(ctx, (const uint8_t*)m.pData, st->W, st->H, m.RowPitch);
st->dctx->lpVtbl->Unmap(st->dctx, (ID3D11Resource*)st->staging, 0);
}
tex->lpVtbl->Release(tex);
}
if (res) res->lpVtbl->Release(res);
st->dup->lpVtbl->ReleaseFrame(st->dup);
}
return 0; /* unreachable; satisfies -Wreturn-type */
}
int dda_start(vgpu_ctx* ctx, int fps) {
(void)fps;
dda_state* st = (dda_state*)calloc(1, sizeof *st);
if (!st) return 0;
D3D_FEATURE_LEVEL fl;
if (FAILED(D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0,
D3D11_SDK_VERSION, &st->dev, &fl, &st->dctx))) {
fprintf(stderr, "eyes(dda): D3D11CreateDevice failed\n");
goto fail;
}
IDXGIDevice* dxgiDev = NULL;
IDXGIAdapter* adapter = NULL;
IDXGIOutput* output = NULL;
st->dev->lpVtbl->QueryInterface(st->dev, &IID_IDXGIDevice, (void**)&dxgiDev);
if (dxgiDev) dxgiDev->lpVtbl->GetAdapter(dxgiDev, &adapter);
if (adapter) adapter->lpVtbl->EnumOutputs(adapter, 0, &output);
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 (adapter) adapter->lpVtbl->Release(adapter);
if (dxgiDev) dxgiDev->lpVtbl->Release(dxgiDev);
if (!st->out1 || FAILED(st->out1->lpVtbl->DuplicateOutput(st->out1,
(IUnknown*)st->dev, &st->dup))) {
fprintf(stderr, "eyes(dda): DuplicateOutput failed\n");
goto fail;
}
DXGI_OUTDUPL_DESC dd;
st->dup->lpVtbl->GetDesc(st->dup, &dd);
st->W = dd.ModeDesc.Width;
st->H = dd.ModeDesc.Height;
D3D11_TEXTURE2D_DESC td; memset(&td, 0, sizeof td);
td.Width = st->W; td.Height = st->H; td.MipLevels = 1; td.ArraySize = 1;
td.Format = DXGI_FORMAT_B8G8R8A8_UNORM; td.SampleDesc.Count = 1;
td.Usage = D3D11_USAGE_STAGING; td.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
if (FAILED(st->dev->lpVtbl->CreateTexture2D(st->dev, &td, NULL, &st->staging))) {
fprintf(stderr, "eyes(dda): CreateTexture2D failed\n");
goto fail;
}
capture_thread_arg* arg = (capture_thread_arg*)malloc(sizeof *arg);
if (!arg) goto fail;
arg->ctx = ctx; arg->fps = fps; arg->backend_state = st;
ctx->backend = VGPU_BK_DDA;
ctx->draw_cursor_cap = 1; /* DDA frames are content-only → presenter draws cursor */
HANDLE t = CreateThread(NULL, 0, dda_thread, arg, 0, NULL);
if (!t) { free(arg); goto fail; }
CloseHandle(t);
fprintf(stderr, "eyes(dda): desktop %ux%u (content-only; cursor by presenter)\n",
st->W, st->H);
return 1;
fail:
/* release any COM objects created before the failure (no ref leaks) */
if (st->staging) st->staging->lpVtbl->Release(st->staging);
if (st->dup) st->dup->lpVtbl->Release(st->dup);
if (st->out1) st->out1->lpVtbl->Release(st->out1);
if (st->dctx) st->dctx->lpVtbl->Release(st->dctx);
if (st->dev) st->dev->lpVtbl->Release(st->dev);
free(st);
return 0;
}