mirror of
https://dev.lirent.ru/Vatrog/vm-automation-signaling.git
synced 2026-06-26 04:36:37 +03:00
199 lines
7.9 KiB
C
199 lines
7.9 KiB
C
|
|
#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;
|
||
|
|
}
|