#define WIN32_LEAN_AND_MEAN #define COBJMACROS #include #include #include #include #include #include #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; }