mirror of
https://dev.lirent.ru/Vatrog/vm-vgpu-streamer.git
synced 2026-06-18 02:16:38 +03:00
Initial commit: win32 vGPU stream capture module
Capture backends (NvFBC/DDA/GDI), cursor/region/present helpers, publish API, vendor NvFBC headers; CMake build with mingw-w64 toolchain.
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
.*/
|
||||||
|
cmake-*/
|
||||||
|
compile*
|
||||||
|
docs/plans/
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.16)
|
||||||
|
project(vgpu-streamer C)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_C_EXTENSIONS OFF) # strict c11; shim uses __asm__/__atomic_* — ok
|
||||||
|
|
||||||
|
add_library(vgpustream STATIC
|
||||||
|
src/stream/publish.c
|
||||||
|
src/stream/win32/region.c
|
||||||
|
src/stream/win32/present.c
|
||||||
|
src/stream/win32/cursor.c
|
||||||
|
src/stream/win32/capture.c
|
||||||
|
src/stream/win32/capture_nvfbc.c
|
||||||
|
src/stream/win32/capture_dda.c
|
||||||
|
src/stream/win32/capture_gdi.c)
|
||||||
|
target_include_directories(vgpustream
|
||||||
|
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src/stream/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/stream/win32
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/third_party) # vendor NvFBC + Windows.h shim
|
||||||
|
target_compile_definitions(vgpustream PRIVATE CINTERFACE WIN32_LEAN_AND_MEAN=)
|
||||||
|
target_compile_options(vgpustream PRIVATE
|
||||||
|
$<$<C_COMPILER_ID:GNU>:-O2;-Wall;-Wextra>
|
||||||
|
$<$<C_COMPILER_ID:MSVC>:/O2;/W3>)
|
||||||
|
target_link_libraries(vgpustream PRIVATE d3d11 dxgi dxguid uuid user32 gdi32)
|
||||||
|
|
||||||
|
add_executable(vgpu-streamer src/stream/win32/main.c)
|
||||||
|
set_target_properties(vgpu-streamer PROPERTIES OUTPUT_NAME vgpu-streamer)
|
||||||
|
target_include_directories(vgpu-streamer
|
||||||
|
PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/stream/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/src/stream/win32)
|
||||||
|
target_link_libraries(vgpu-streamer PRIVATE vgpustream)
|
||||||
|
target_link_options(vgpu-streamer PRIVATE $<$<C_COMPILER_ID:GNU>:-static;-s>)
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
set(CMAKE_SYSTEM_NAME Windows)
|
||||||
|
set(CMAKE_SYSTEM_PROCESSOR x86_64)
|
||||||
|
set(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)
|
||||||
|
set(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
|
||||||
|
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#ifndef VGPU_STREAM_H
|
||||||
|
#define VGPU_STREAM_H
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h> /* offsetof */
|
||||||
|
#include <stdalign.h> /* alignas */
|
||||||
|
#include <assert.h> /* static_assert */
|
||||||
|
|
||||||
|
/* ===== Geometry — single source of truth (bare ABI, both ends agree) ===== */
|
||||||
|
#define VGPU_PAGE 4096u
|
||||||
|
#define VGPU_SLOT_COUNT 3u
|
||||||
|
#define VGPU_SLOT_STRIDE (32u * 1024u * 1024u)
|
||||||
|
#define VGPU_RING_OFFSET (2u * 1024u * 1024u)
|
||||||
|
#define VGPU_PRODUCER_OFFSET 0u
|
||||||
|
#define VGPU_CONTROL_OFFSET VGPU_PAGE
|
||||||
|
#define VGPU_REGION_BYTES (VGPU_RING_OFFSET + (uint64_t)VGPU_SLOT_COUNT * VGPU_SLOT_STRIDE)
|
||||||
|
#define VGPU_MAX_WIDTH 3840u
|
||||||
|
#define VGPU_MAX_HEIGHT 2160u
|
||||||
|
#define VGPU_HEARTBEAT_PERIOD_MS 250u /* producer ticks heartbeat >= 4 Hz always */
|
||||||
|
#define VGPU_LATEST_NONE 0xFFFFFFFFu
|
||||||
|
|
||||||
|
static_assert((uint64_t)VGPU_MAX_WIDTH * VGPU_MAX_HEIGHT * 4u <= VGPU_SLOT_STRIDE,
|
||||||
|
"max-mode tight BGRA must fit one slot");
|
||||||
|
|
||||||
|
/* enum values travel as uint32 wire-values (not as enum fields → no width instability) */
|
||||||
|
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_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 };
|
||||||
|
|
||||||
|
/* ===== Per-slot descriptor (under hot.seq[slot]) ===== */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t width; /* pixels */
|
||||||
|
uint32_t height; /* pixels */
|
||||||
|
uint32_t stride; /* bytes/row; INVARIANT: == width*4 (tight) */
|
||||||
|
uint32_t format; /* VGPU_FMT_* */
|
||||||
|
uint64_t frame_id; /* == producer.frame_id at publish time */
|
||||||
|
uint64_t timestamp_ns; /* capture time, monotonic */
|
||||||
|
} vgpu_desc_t;
|
||||||
|
static_assert(sizeof(vgpu_desc_t) == 32, "desc layout");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, width) == 0, "desc.width");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, height) == 4, "desc.height");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, stride) == 8, "desc.stride");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, format) == 12, "desc.format");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, frame_id) == 16, "desc.frame_id");
|
||||||
|
static_assert(offsetof(vgpu_desc_t, timestamp_ns) == 24, "desc.timestamp_ns");
|
||||||
|
|
||||||
|
/* ===== Producer block (host-RO): hot publish line + cold status line ===== */
|
||||||
|
typedef struct {
|
||||||
|
/* --- hot publish line --- */
|
||||||
|
alignas(64)
|
||||||
|
uint32_t latest; /* index of last; VGPU_LATEST_NONE until 1st frame */
|
||||||
|
uint32_t _r0;
|
||||||
|
uint64_t frame_id; /* monotonic frame counter (8-aligned) */
|
||||||
|
uint32_t seq[VGPU_SLOT_COUNT]; /* per-slot seqlock: even=stable, odd=writing */
|
||||||
|
uint32_t _r1;
|
||||||
|
vgpu_desc_t desc[VGPU_SLOT_COUNT]; /* self-describing slots */
|
||||||
|
|
||||||
|
/* --- cold status line --- */
|
||||||
|
alignas(64)
|
||||||
|
uint64_t heartbeat; /* monotonic; ticks always (even STOPPED/PAUSED) */
|
||||||
|
uint32_t run_epoch; /* +1 per start (session break for host) */
|
||||||
|
uint32_t status; /* VGPU_ST_* */
|
||||||
|
uint32_t backend; /* VGPU_BK_* */
|
||||||
|
uint32_t error_code; /* 0=none; else fatal detail */
|
||||||
|
uint32_t applied_fps; /* publish-rate cap the producer actually applies;
|
||||||
|
actual rate may be lower on static content or
|
||||||
|
backend limits — host measures real fps from
|
||||||
|
desc.timestamp_ns */
|
||||||
|
uint32_t supported_formats; /* bitmask (1u<<VGPU_FMT_*) */
|
||||||
|
uint32_t ctrl_ack; /* echo of control.ctrl_gen (even) applied */
|
||||||
|
uint32_t full_frame_ack; /* echo of control.full_frame_req honored */
|
||||||
|
} vgpu_producer_t;
|
||||||
|
static_assert(alignof(vgpu_producer_t) == 64, "producer align");
|
||||||
|
static_assert(sizeof(vgpu_producer_t) <= VGPU_PAGE, "producer fits page 0");
|
||||||
|
/* host-read field layout frozen as ABI */
|
||||||
|
static_assert(offsetof(vgpu_producer_t, latest) == 0, "producer.latest");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, frame_id) == 8, "producer.frame_id");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, seq) == 16, "producer.seq");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, desc) == 32, "producer.desc");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, heartbeat) == 128, "producer.heartbeat");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, run_epoch) == 136, "producer.run_epoch");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, status) == 140, "producer.status");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, backend) == 144, "producer.backend");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, error_code) == 148, "producer.error_code");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, applied_fps) == 152, "producer.applied_fps");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, supported_formats) == 156, "producer.supported_formats");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, ctrl_ack) == 160, "producer.ctrl_ack");
|
||||||
|
static_assert(offsetof(vgpu_producer_t, full_frame_ack) == 164, "producer.full_frame_ack");
|
||||||
|
|
||||||
|
/* ===== Control block (host-RW), own page, generation-guarded ===== */
|
||||||
|
typedef struct {
|
||||||
|
alignas(64)
|
||||||
|
uint32_t ctrl_gen; /* generation seqlock: even=stable, odd=writing (host writes) */
|
||||||
|
uint32_t desired_state; /* VGPU_CMD_* (STOP/RUN/PAUSE) */
|
||||||
|
uint32_t target_fps; /* desired fps; 0=producer default */
|
||||||
|
uint32_t draw_cursor; /* 1=compose cursor */
|
||||||
|
uint32_t full_frame_req; /* edge counter: bump → force fresh full frame */
|
||||||
|
uint32_t consumer_tick; /* host heartbeat (producer watches with timeout) */
|
||||||
|
uint32_t attached; /* 1=host attached (intent, not death-proof) */
|
||||||
|
} vgpu_control_t;
|
||||||
|
static_assert(alignof(vgpu_control_t) == 64, "control align");
|
||||||
|
static_assert(sizeof(vgpu_control_t) <= VGPU_PAGE, "control fits page 1");
|
||||||
|
/* host-write field layout frozen as ABI */
|
||||||
|
static_assert(offsetof(vgpu_control_t, ctrl_gen) == 0, "control.ctrl_gen");
|
||||||
|
static_assert(offsetof(vgpu_control_t, desired_state) == 4, "control.desired_state");
|
||||||
|
static_assert(offsetof(vgpu_control_t, target_fps) == 8, "control.target_fps");
|
||||||
|
static_assert(offsetof(vgpu_control_t, draw_cursor) == 12, "control.draw_cursor");
|
||||||
|
static_assert(offsetof(vgpu_control_t, full_frame_req) == 16, "control.full_frame_req");
|
||||||
|
static_assert(offsetof(vgpu_control_t, consumer_tick) == 20, "control.consumer_tick");
|
||||||
|
static_assert(offsetof(vgpu_control_t, attached) == 24, "control.attached");
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#ifndef VGPU_ATOMIC_SHIM_H
|
||||||
|
#define VGPU_ATOMIC_SHIM_H
|
||||||
|
|
||||||
|
/* atomic-shim.h — x86-TSO memory-order accessors (arch, not OS).
|
||||||
|
*
|
||||||
|
* x86-TSO memory-order shim. NO _Atomic in the shared region type: the consumer
|
||||||
|
* maps the region as raw bytes. Synchronization lives entirely in the producer's
|
||||||
|
* accessors here. Per-compiler implementation, never exposed in the contract.
|
||||||
|
*
|
||||||
|
* On x86_64 every naturally-aligned MOV up to 8 bytes is atomic and stores are
|
||||||
|
* already release / loads already acquire at the hardware level; the only things
|
||||||
|
* we must prevent are (1) compiler reordering across the sync point and
|
||||||
|
* (2) store-buffer visibility delay between the data writes and the publish
|
||||||
|
* store, for which an explicit SFENCE is used at publish boundaries.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
|
||||||
|
#include <intrin.h>
|
||||||
|
|
||||||
|
static inline void vgpu_compiler_barrier(void) { _ReadWriteBarrier(); }
|
||||||
|
static inline void vgpu_sfence(void) { _mm_sfence(); }
|
||||||
|
|
||||||
|
static inline void vgpu_store_release32(volatile uint32_t* p, uint32_t v) {
|
||||||
|
_ReadWriteBarrier();
|
||||||
|
*p = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t vgpu_load_acquire32(const volatile uint32_t* p) {
|
||||||
|
uint32_t v = *p;
|
||||||
|
_ReadWriteBarrier();
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else /* gcc / mingw / clang */
|
||||||
|
|
||||||
|
static inline void vgpu_compiler_barrier(void) { __asm__ __volatile__("" ::: "memory"); }
|
||||||
|
static inline void vgpu_sfence(void) { __asm__ __volatile__("sfence" ::: "memory"); }
|
||||||
|
|
||||||
|
static inline void vgpu_store_release32(volatile uint32_t* p, uint32_t v) {
|
||||||
|
__atomic_store_n(p, v, __ATOMIC_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint32_t vgpu_load_acquire32(const volatile uint32_t* p) {
|
||||||
|
return __atomic_load_n(p, __ATOMIC_ACQUIRE);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* VGPU_ATOMIC_SHIM_H */
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef VGPU_CAPTURE_H
|
||||||
|
#define VGPU_CAPTURE_H
|
||||||
|
|
||||||
|
/* capture.h — extension seam for capture backends.
|
||||||
|
* A backend produces desktop frames and submits them to the presenter. This
|
||||||
|
* header is OS-agnostic: it names backends through an opaque vgpu_ctx* and a
|
||||||
|
* uniform start contract. A platform layer defines vgpu_ctx and any private
|
||||||
|
* backend plumbing (see src/stream/win32/capture-win32.h). A future Linux layer
|
||||||
|
* implements the same seam against its own vgpu_ctx + region/sync/clock. */
|
||||||
|
|
||||||
|
/* Opaque runtime context, defined by the platform layer (win32: ctx.h). */
|
||||||
|
typedef struct vgpu_ctx vgpu_ctx;
|
||||||
|
|
||||||
|
/* Start a capture backend. Returns 1 on success; on success the backend has
|
||||||
|
* spawned its capture thread(s) (which received ctx) and set ctx->backend /
|
||||||
|
* ctx->draw_cursor_cap. The submit contract: each captured desktop frame is
|
||||||
|
* handed to the presenter via vgpu_present_submit(). */
|
||||||
|
typedef int (*capture_start_fn)(vgpu_ctx* ctx, int fps);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const char* name;
|
||||||
|
capture_start_fn start;
|
||||||
|
} capture_backend;
|
||||||
|
|
||||||
|
/* Data-driven backend table; the entry point selects by env or availability. */
|
||||||
|
const capture_backend* capture_backends(int* count);
|
||||||
|
|
||||||
|
#endif /* VGPU_CAPTURE_H */
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
#ifndef VGPU_STREAM_ENGINE_H
|
||||||
|
#define VGPU_STREAM_ENGINE_H
|
||||||
|
|
||||||
|
/* stream.h — OS-agnostic streaming protocol over the shared contract.
|
||||||
|
* Declares the neutral region-view handle (resolved contract pointers) and the
|
||||||
|
* seqlock publish / control-reconcile API. No platform headers: the engine
|
||||||
|
* operates purely on the contract; a platform layer (e.g. src/stream/win32/)
|
||||||
|
* builds the region and hands its pointers in as a vgpu_region_view. */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "vgpu_stream.h" /* contract: producer/control types, slot geometry */
|
||||||
|
|
||||||
|
/* Neutral view of the live contract: the three resolved blocks the engine
|
||||||
|
* publishes into / reconciles against. The platform region owns the backing
|
||||||
|
* memory; this is a borrowed view (no ownership). */
|
||||||
|
typedef struct {
|
||||||
|
vgpu_producer_t* producer;
|
||||||
|
vgpu_control_t* control;
|
||||||
|
uint8_t* ring;
|
||||||
|
} vgpu_region_view;
|
||||||
|
|
||||||
|
/* Resolved view of the control block after a clean generation read. */
|
||||||
|
typedef struct {
|
||||||
|
uint32_t gen; /* even generation that was read (for ctrl_ack) */
|
||||||
|
uint32_t desired_state; /* VGPU_CMD_* */
|
||||||
|
uint32_t target_fps;
|
||||||
|
uint32_t draw_cursor;
|
||||||
|
uint32_t full_frame_req;
|
||||||
|
uint32_t consumer_tick;
|
||||||
|
uint32_t attached;
|
||||||
|
} vgpu_control_view;
|
||||||
|
|
||||||
|
/* Seqlock-publish a tight BGRA frame into the next ring slot.
|
||||||
|
* Clamps by SLOT_STRIDE (rejects frames that do not fit). Writes desc[],
|
||||||
|
* bumps frame_id, release-stores latest. Returns 0 on publish, 1 if dropped
|
||||||
|
* (frame too large for a slot). */
|
||||||
|
int vgpu_publish_frame(const vgpu_region_view* rv, const uint8_t* tight_bgra,
|
||||||
|
uint32_t width, uint32_t height, uint64_t timestamp_ns);
|
||||||
|
|
||||||
|
/* Read control block under its generation seqlock (bounded retry). Returns 1
|
||||||
|
* on a clean read (view filled), 0 if the writer kept it busy past the limit. */
|
||||||
|
int vgpu_control_read(const vgpu_region_view* rv, vgpu_control_view* out);
|
||||||
|
|
||||||
|
/* Echo the applied generation back to the host. */
|
||||||
|
void vgpu_publish_ctrl_ack(const vgpu_region_view* rv, uint32_t gen);
|
||||||
|
|
||||||
|
/* Status / lifecycle helpers (cold line). */
|
||||||
|
void vgpu_set_status(const vgpu_region_view* rv, uint32_t status);
|
||||||
|
void vgpu_set_backend(const vgpu_region_view* rv, uint32_t backend);
|
||||||
|
void vgpu_set_error(const vgpu_region_view* rv, uint32_t error_code);
|
||||||
|
void vgpu_set_applied_fps(const vgpu_region_view* rv, uint32_t fps);
|
||||||
|
void vgpu_bump_run_epoch(const vgpu_region_view* rv);
|
||||||
|
void vgpu_tick_heartbeat(const vgpu_region_view* rv);
|
||||||
|
void vgpu_publish_full_frame_ack(const vgpu_region_view* rv, uint32_t req);
|
||||||
|
|
||||||
|
#endif /* VGPU_STREAM_ENGINE_H */
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/* publish.c — OS-agnostic implementation of the streaming protocol.
|
||||||
|
* Operates purely on the contract through a borrowed vgpu_region_view; no
|
||||||
|
* platform headers, no runtime context. The x86-TSO ordering lives in the
|
||||||
|
* atomic shim. */
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "vgpu_stream.h" /* contract types / slot geometry */
|
||||||
|
#include "atomic-shim.h" /* x86-TSO memory-order accessors */
|
||||||
|
#include "stream.h" /* region-view handle + this API */
|
||||||
|
|
||||||
|
#define VGPU_CTRL_READ_TRIES 16u
|
||||||
|
|
||||||
|
int vgpu_publish_frame(const vgpu_region_view* rv, const uint8_t* tight_bgra,
|
||||||
|
uint32_t width, uint32_t height, uint64_t timestamp_ns) {
|
||||||
|
vgpu_producer_t* p = rv->producer;
|
||||||
|
|
||||||
|
const uint32_t stride = width * 4u; /* tight invariant */
|
||||||
|
const uint64_t need = (uint64_t)height * stride;
|
||||||
|
if (need > VGPU_SLOT_STRIDE) /* clamp by slot size */
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
uint32_t cur = vgpu_load_acquire32(&p->latest);
|
||||||
|
uint32_t S = (cur == VGPU_LATEST_NONE) ? 0u : ((cur + 1u) % VGPU_SLOT_COUNT);
|
||||||
|
|
||||||
|
uint8_t* dst = rv->ring + (size_t)S * VGPU_SLOT_STRIDE;
|
||||||
|
|
||||||
|
/* seqlock: even -> odd (writing) */
|
||||||
|
vgpu_store_release32(&p->seq[S], p->seq[S] + 1u);
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
|
||||||
|
/* descriptor (self-describing slot) */
|
||||||
|
p->desc[S].width = width;
|
||||||
|
p->desc[S].height = height;
|
||||||
|
p->desc[S].stride = stride;
|
||||||
|
p->desc[S].format = VGPU_FMT_BGRA8888;
|
||||||
|
p->desc[S].frame_id = p->frame_id + 1u;
|
||||||
|
p->desc[S].timestamp_ns = timestamp_ns;
|
||||||
|
|
||||||
|
/* pixels (source is already tight) */
|
||||||
|
memcpy(dst, tight_bgra, (size_t)need);
|
||||||
|
|
||||||
|
vgpu_sfence();
|
||||||
|
/* seqlock: odd -> even (stable) */
|
||||||
|
vgpu_store_release32(&p->seq[S], p->seq[S] + 1u);
|
||||||
|
vgpu_sfence();
|
||||||
|
|
||||||
|
p->frame_id += 1u;
|
||||||
|
vgpu_store_release32(&p->latest, S);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vgpu_control_read(const vgpu_region_view* rv, vgpu_control_view* out) {
|
||||||
|
volatile vgpu_control_t* c = rv->control;
|
||||||
|
|
||||||
|
for (uint32_t t = 0; t < VGPU_CTRL_READ_TRIES; t++) {
|
||||||
|
uint32_t g0 = vgpu_load_acquire32(&c->ctrl_gen);
|
||||||
|
if (g0 & 1u)
|
||||||
|
continue; /* writer in progress */
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
|
||||||
|
uint32_t desired = c->desired_state;
|
||||||
|
uint32_t fps = c->target_fps;
|
||||||
|
uint32_t cursor = c->draw_cursor;
|
||||||
|
uint32_t ffreq = c->full_frame_req;
|
||||||
|
uint32_t ctick = c->consumer_tick;
|
||||||
|
uint32_t att = c->attached;
|
||||||
|
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
uint32_t g1 = vgpu_load_acquire32(&c->ctrl_gen);
|
||||||
|
if (g0 != g1)
|
||||||
|
continue; /* torn read, retry */
|
||||||
|
|
||||||
|
out->gen = g0;
|
||||||
|
out->desired_state = desired;
|
||||||
|
out->target_fps = fps;
|
||||||
|
out->draw_cursor = cursor;
|
||||||
|
out->full_frame_req = ffreq;
|
||||||
|
out->consumer_tick = ctick;
|
||||||
|
out->attached = att;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_publish_ctrl_ack(const vgpu_region_view* rv, uint32_t gen) {
|
||||||
|
vgpu_store_release32(&rv->producer->ctrl_ack, gen);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_set_status(const vgpu_region_view* rv, uint32_t status) {
|
||||||
|
vgpu_store_release32(&rv->producer->status, status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_set_backend(const vgpu_region_view* rv, uint32_t backend) {
|
||||||
|
vgpu_store_release32(&rv->producer->backend, backend);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_set_error(const vgpu_region_view* rv, uint32_t error_code) {
|
||||||
|
vgpu_store_release32(&rv->producer->error_code, error_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_set_applied_fps(const vgpu_region_view* rv, uint32_t fps) {
|
||||||
|
vgpu_store_release32(&rv->producer->applied_fps, fps);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_bump_run_epoch(const vgpu_region_view* rv) {
|
||||||
|
vgpu_producer_t* p = rv->producer;
|
||||||
|
vgpu_store_release32(&p->run_epoch, p->run_epoch + 1u);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_tick_heartbeat(const vgpu_region_view* rv) {
|
||||||
|
/* 64-bit aligned single MOV is atomic on x86_64; barrier orders it */
|
||||||
|
rv->producer->heartbeat += 1u;
|
||||||
|
vgpu_compiler_barrier();
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_publish_full_frame_ack(const vgpu_region_view* rv, uint32_t req) {
|
||||||
|
vgpu_store_release32(&rv->producer->full_frame_ack, req);
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
#ifndef VGPU_CAPTURE_WIN32_H
|
||||||
|
#define VGPU_CAPTURE_WIN32_H
|
||||||
|
|
||||||
|
/* capture-win32.h — private win32 plumbing shared by the capture backends.
|
||||||
|
* Not part of the OS-agnostic capture seam (see src/stream/include/capture.h):
|
||||||
|
* it depends on the win32 vgpu_ctx and the thread-handoff convention. */
|
||||||
|
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx (full definition) */
|
||||||
|
|
||||||
|
/* Thread argument passed to capture threads via LPVOID. Heap-allocated by the
|
||||||
|
* backend's *_start, owned and freed by the thread. Carries the explicit ctx
|
||||||
|
* (no global state) plus per-backend state pointer. */
|
||||||
|
typedef struct {
|
||||||
|
vgpu_ctx* ctx;
|
||||||
|
int fps;
|
||||||
|
void* backend_state; /* opaque per-backend handle block */
|
||||||
|
} capture_thread_arg;
|
||||||
|
|
||||||
|
#endif /* VGPU_CAPTURE_WIN32_H */
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
/* capture.c — win32 registration of the capture backends into the neutral
|
||||||
|
* capture seam's backend table (data-driven; no per-backend branching). */
|
||||||
|
|
||||||
|
#include "capture.h" /* neutral seam: capture_backend / capture_backends */
|
||||||
|
#include "capture_nvfbc.h"
|
||||||
|
#include "capture_dda.h"
|
||||||
|
#include "capture_gdi.h"
|
||||||
|
|
||||||
|
/* data-driven backend table; main selects by EYES env or first available */
|
||||||
|
static const capture_backend g_backends[] = {
|
||||||
|
{ "nvfbc", nvfbc_start },
|
||||||
|
{ "dda", dda_start },
|
||||||
|
{ "gdi", gdi_start },
|
||||||
|
};
|
||||||
|
|
||||||
|
const capture_backend* capture_backends(int* count) {
|
||||||
|
*count = (int)(sizeof g_backends / sizeof g_backends[0]);
|
||||||
|
return g_backends;
|
||||||
|
}
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
#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"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ID3D11Device* dev;
|
||||||
|
ID3D11DeviceContext* dctx;
|
||||||
|
IDXGIOutput1* out1;
|
||||||
|
IDXGIOutputDuplication* dup;
|
||||||
|
ID3D11Texture2D* staging;
|
||||||
|
UINT W, H;
|
||||||
|
} dda_state;
|
||||||
|
|
||||||
|
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);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (FAILED(hr)) { Sleep(50); continue; }
|
||||||
|
|
||||||
|
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) 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef VGPU_CAPTURE_DDA_H
|
||||||
|
#define VGPU_CAPTURE_DDA_H
|
||||||
|
|
||||||
|
/* capture_dda.h — DXGI Desktop Duplication capture backend (win32). */
|
||||||
|
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx */
|
||||||
|
|
||||||
|
int dda_start(vgpu_ctx* ctx, int fps);
|
||||||
|
|
||||||
|
#endif /* VGPU_CAPTURE_DDA_H */
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "capture_gdi.h"
|
||||||
|
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
||||||
|
#include "present.h"
|
||||||
|
|
||||||
|
static DWORD WINAPI gdi_thread(LPVOID param) {
|
||||||
|
capture_thread_arg* arg = (capture_thread_arg*)param;
|
||||||
|
vgpu_ctx* ctx = arg->ctx;
|
||||||
|
int fps = arg->fps > 0 ? arg->fps : 30;
|
||||||
|
free(arg);
|
||||||
|
|
||||||
|
HDC screen = GetDC(NULL);
|
||||||
|
HDC mem = CreateCompatibleDC(screen);
|
||||||
|
HBITMAP dib = NULL;
|
||||||
|
void* bits = NULL;
|
||||||
|
int W = 0, H = 0;
|
||||||
|
const DWORD interval = (DWORD)(1000 / fps);
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
int w = GetSystemMetrics(SM_CXSCREEN), h = GetSystemMetrics(SM_CYSCREEN);
|
||||||
|
if (w <= 0 || h <= 0) { Sleep(200); continue; }
|
||||||
|
if (w != W || h != H || !dib) {
|
||||||
|
if (dib) DeleteObject(dib);
|
||||||
|
BITMAPINFO bi; memset(&bi, 0, sizeof bi);
|
||||||
|
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = -h;
|
||||||
|
bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32;
|
||||||
|
bi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
dib = CreateDIBSection(screen, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
|
||||||
|
if (!dib) {
|
||||||
|
fprintf(stderr, "eyes(gdi): CreateDIBSection %dx%d failed\n", w, h);
|
||||||
|
Sleep(200); continue;
|
||||||
|
}
|
||||||
|
SelectObject(mem, dib);
|
||||||
|
W = w; H = h;
|
||||||
|
fprintf(stderr, "eyes(gdi): desktop %dx%d (BitBlt; cursor by presenter)\n", W, H);
|
||||||
|
}
|
||||||
|
if (BitBlt(mem, 0, 0, W, H, screen, 0, 0, SRCCOPY))
|
||||||
|
vgpu_present_submit(ctx, (const uint8_t*)bits,
|
||||||
|
(uint32_t)W, (uint32_t)H, (uint32_t)W * 4u);
|
||||||
|
Sleep(interval);
|
||||||
|
}
|
||||||
|
return 0; /* unreachable; satisfies -Wreturn-type */
|
||||||
|
}
|
||||||
|
|
||||||
|
int gdi_start(vgpu_ctx* ctx, int fps) {
|
||||||
|
ctx->backend = VGPU_BK_GDI;
|
||||||
|
ctx->draw_cursor_cap = 1; /* GDI BitBlt excludes cursor → presenter draws it */
|
||||||
|
|
||||||
|
capture_thread_arg* arg = (capture_thread_arg*)malloc(sizeof *arg);
|
||||||
|
if (!arg) return 0;
|
||||||
|
arg->ctx = ctx; arg->fps = fps; arg->backend_state = NULL;
|
||||||
|
HANDLE t = CreateThread(NULL, 0, gdi_thread, arg, 0, NULL);
|
||||||
|
if (!t) { free(arg); return 0; }
|
||||||
|
CloseHandle(t);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef VGPU_CAPTURE_GDI_H
|
||||||
|
#define VGPU_CAPTURE_GDI_H
|
||||||
|
|
||||||
|
/* capture_gdi.h — GDI BitBlt capture backend (win32, universal fallback). */
|
||||||
|
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx */
|
||||||
|
|
||||||
|
int gdi_start(vgpu_ctx* ctx, int fps);
|
||||||
|
|
||||||
|
#endif /* VGPU_CAPTURE_GDI_H */
|
||||||
@@ -0,0 +1,128 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "capture_nvfbc.h"
|
||||||
|
#include "capture-win32.h" /* capture_thread_arg (win32-private) */
|
||||||
|
#include "present.h"
|
||||||
|
#include "nvfbc_tosys_c.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NvFBCToSys_c* fbc;
|
||||||
|
void* buf;
|
||||||
|
NvFBC_CreateFunctionExType create;
|
||||||
|
} nvfbc_state;
|
||||||
|
|
||||||
|
static NvFBCToSys_c* nvfbc_create(NvFBC_CreateFunctionExType pCreate, void** ppBuf) {
|
||||||
|
NvFBCCreateParams cp; memset(&cp, 0, sizeof cp);
|
||||||
|
cp.dwVersion = NVFBC_CREATE_PARAMS_VER;
|
||||||
|
cp.dwInterfaceType = NVFBC_TO_SYS_C;
|
||||||
|
cp.dwAdapterIdx = 0;
|
||||||
|
if (pCreate(&cp) != NVFBC_SUCCESS || !cp.pNvFBC) return NULL;
|
||||||
|
|
||||||
|
NvFBCToSys_c* fbc = (NvFBCToSys_c*)cp.pNvFBC;
|
||||||
|
*ppBuf = NULL;
|
||||||
|
|
||||||
|
NVFBC_TOSYS_SETUP_PARAMS_C sp; memset(&sp, 0, sizeof sp);
|
||||||
|
sp.dwVersion = NVFBC_TOSYS_SETUP_PARAMS_VER_C;
|
||||||
|
sp.bits = 1u; /* bWithHWCursor = 1 (bit 0) */
|
||||||
|
sp.eMode = NVFBC_TOSYS_ARGB;
|
||||||
|
sp.ppBuffer = ppBuf;
|
||||||
|
if (fbc->lpVtbl->NvFBCToSysSetUp(fbc, &sp) != NVFBC_SUCCESS || !*ppBuf) {
|
||||||
|
fbc->lpVtbl->NvFBCToSysRelease(fbc);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return fbc;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD WINAPI nvfbc_thread(LPVOID param) {
|
||||||
|
capture_thread_arg* arg = (capture_thread_arg*)param;
|
||||||
|
vgpu_ctx* ctx = arg->ctx;
|
||||||
|
nvfbc_state* st = (nvfbc_state*)arg->backend_state;
|
||||||
|
free(arg);
|
||||||
|
|
||||||
|
NvFBCToSys_c* fbc = st->fbc;
|
||||||
|
void* buf = st->buf;
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
NvFBCFrameGrabInfo gi; memset(&gi, 0, sizeof gi);
|
||||||
|
NVFBC_TOSYS_GRAB_FRAME_PARAMS_C gp; memset(&gp, 0, sizeof gp);
|
||||||
|
gp.dwVersion = NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER_C;
|
||||||
|
gp.dwFlags = NVFBC_TOSYS_WAIT_WITH_TIMEOUT_C;
|
||||||
|
gp.dwWaitTime = 1000;
|
||||||
|
gp.eGMode = NVFBC_TOSYS_SOURCEMODE_FULL;
|
||||||
|
gp.pNvFBCFrameGrabInfo = &gi;
|
||||||
|
|
||||||
|
NVFBCRESULT r = fbc->lpVtbl->NvFBCToSysGrabFrame(fbc, &gp);
|
||||||
|
if (r != NVFBC_SUCCESS) {
|
||||||
|
if (r == NVFBC_ERROR_INVALIDATED_SESSION || gi.bMustRecreate) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): session invalidated (r=%d), recreating\n", (int)r);
|
||||||
|
fbc->lpVtbl->NvFBCToSysRelease(fbc);
|
||||||
|
fbc = NULL;
|
||||||
|
while (!(fbc = nvfbc_create(st->create, &buf))) Sleep(200);
|
||||||
|
st->fbc = fbc; st->buf = buf;
|
||||||
|
} else {
|
||||||
|
Sleep(50);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (gi.dwWidth && gi.dwHeight)
|
||||||
|
vgpu_present_submit(ctx, (const uint8_t*)buf,
|
||||||
|
gi.dwWidth, gi.dwHeight, gi.dwBufferWidth * 4u);
|
||||||
|
}
|
||||||
|
return 0; /* unreachable; satisfies -Wreturn-type */
|
||||||
|
}
|
||||||
|
|
||||||
|
int nvfbc_start(vgpu_ctx* ctx, int fps) {
|
||||||
|
(void)fps;
|
||||||
|
HMODULE lib = LoadLibraryA("NvFBC64.dll");
|
||||||
|
if (!lib) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): LoadLibrary NvFBC64.dll failed (%lu)\n", GetLastError());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
NvFBC_SetGlobalFlagsType pSetFlags = (NvFBC_SetGlobalFlagsType)(void*)GetProcAddress(lib, "NvFBC_SetGlobalFlags");
|
||||||
|
NvFBC_EnableFunctionType pEnable = (NvFBC_EnableFunctionType)(void*)GetProcAddress(lib, "NvFBC_Enable");
|
||||||
|
NvFBC_CreateFunctionExType pCreate = (NvFBC_CreateFunctionExType)(void*)GetProcAddress(lib, "NvFBC_CreateEx");
|
||||||
|
NvFBC_GetStatusExFunctionType pStatus = (NvFBC_GetStatusExFunctionType)(void*)GetProcAddress(lib, "NvFBC_GetStatusEx");
|
||||||
|
if (!pEnable || !pCreate || !pStatus) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): missing exports\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (pSetFlags) pSetFlags(NVFBC_GLOBAL_FLAGS_NO_INITIAL_REFRESH);
|
||||||
|
if (pEnable(NVFBC_STATE_ENABLE) != NVFBC_SUCCESS) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): NvFBC_Enable failed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
NvFBCStatusEx stx; memset(&stx, 0, sizeof stx);
|
||||||
|
stx.dwVersion = NVFBC_STATUS_VER; stx.dwAdapterIdx = 0;
|
||||||
|
if (pStatus(&stx) != NVFBC_SUCCESS || !stx.bIsCapturePossible) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): capture NOT possible on this GPU/license\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
void* buf = NULL;
|
||||||
|
NvFBCToSys_c* fbc = nvfbc_create(pCreate, &buf);
|
||||||
|
if (!fbc) {
|
||||||
|
fprintf(stderr, "eyes(nvfbc): CreateEx/ToSysSetUp failed\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
nvfbc_state* st = (nvfbc_state*)malloc(sizeof *st);
|
||||||
|
if (!st) { fbc->lpVtbl->NvFBCToSysRelease(fbc); return 0; }
|
||||||
|
st->fbc = fbc; st->buf = buf; st->create = pCreate;
|
||||||
|
|
||||||
|
capture_thread_arg* arg = (capture_thread_arg*)malloc(sizeof *arg);
|
||||||
|
if (!arg) { fbc->lpVtbl->NvFBCToSysRelease(fbc); free(st); return 0; }
|
||||||
|
arg->ctx = ctx; arg->fps = fps; arg->backend_state = st;
|
||||||
|
|
||||||
|
ctx->backend = VGPU_BK_NVFBC;
|
||||||
|
ctx->draw_cursor_cap = 0; /* NvFBC composites HW cursor itself */
|
||||||
|
|
||||||
|
HANDLE t = CreateThread(NULL, 0, nvfbc_thread, arg, 0, NULL);
|
||||||
|
if (!t) { fbc->lpVtbl->NvFBCToSysRelease(fbc); free(st); free(arg); return 0; }
|
||||||
|
CloseHandle(t);
|
||||||
|
|
||||||
|
fprintf(stderr, "eyes(nvfbc): session up (ToSys ARGB/BGRA), iface=0x%lx\n",
|
||||||
|
(unsigned long)stx.dwNvFBCVersion);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef VGPU_CAPTURE_NVFBC_H
|
||||||
|
#define VGPU_CAPTURE_NVFBC_H
|
||||||
|
|
||||||
|
/* capture_nvfbc.h — NVIDIA NvFBC ToSys capture backend (win32). */
|
||||||
|
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx */
|
||||||
|
|
||||||
|
int nvfbc_start(vgpu_ctx* ctx, int fps);
|
||||||
|
|
||||||
|
#endif /* VGPU_CAPTURE_NVFBC_H */
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#ifndef VGPU_CTX_H
|
||||||
|
#define VGPU_CTX_H
|
||||||
|
|
||||||
|
/* ctx.h — win32 runtime context. Embeds the neutral region-view (the engine's
|
||||||
|
* borrowed handle onto the contract) alongside win32-owned staging/cursor/sync
|
||||||
|
* state. Object = memory: ctx owns the staging arena and cursor state. */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include "stream.h" /* vgpu_region_view (neutral contract handle) */
|
||||||
|
#include "region.h" /* vgpu_region_t (win32 pinned region) */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* vgpu_ctx — the explicitly-passed context. Replaces all former g_* shared
|
||||||
|
* state. Object = memory: ctx owns the producer staging arena and cursor
|
||||||
|
* state; capture threads receive a vgpu_ctx* via their LPVOID thread param.
|
||||||
|
*
|
||||||
|
* Staging is a fixed arena sized for the max mode (no STL, no per-frame
|
||||||
|
* malloc). content_buf holds the latest submitted desktop; frame_buf is the
|
||||||
|
* composed (cursor-drawn) frame the publisher copies into a ring slot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define VGPU_STAGING_BYTES ((size_t)VGPU_MAX_WIDTH * VGPU_MAX_HEIGHT * 4u)
|
||||||
|
|
||||||
|
/* Cursor sample/compose state (GDI). Fixed buffers, no heap. */
|
||||||
|
typedef struct {
|
||||||
|
HCURSOR handle;
|
||||||
|
int visible;
|
||||||
|
int x, y;
|
||||||
|
int hot_x, hot_y;
|
||||||
|
int gw, gh; /* glyph dims */
|
||||||
|
int mono; /* 1 = AND/XOR monochrome cursor */
|
||||||
|
uint8_t* bgra; /* color cursor BGRA (arena) */
|
||||||
|
uint8_t* and_mask; /* mono AND (arena) */
|
||||||
|
uint8_t* xor_mask; /* mono XOR (arena) */
|
||||||
|
} vgpu_cursor_t;
|
||||||
|
|
||||||
|
typedef struct vgpu_ctx {
|
||||||
|
/* neutral contract handle (borrowed from region) — engine publishes through
|
||||||
|
* this; win32 code reads region blocks via view.producer / view.control */
|
||||||
|
vgpu_region_view view;
|
||||||
|
|
||||||
|
/* producer staging arena (owned) */
|
||||||
|
uint8_t* arena; /* one VirtualAlloc block for all buffers */
|
||||||
|
size_t arena_bytes;
|
||||||
|
uint8_t* content_buf; /* latest submitted desktop, tight BGRA */
|
||||||
|
uint8_t* frame_buf; /* composed frame to publish, tight BGRA */
|
||||||
|
|
||||||
|
/* submit handoff (capture thread -> publish pump) */
|
||||||
|
CRITICAL_SECTION lock;
|
||||||
|
HANDLE submit_event;
|
||||||
|
int64_t content_seq; /* bumped on every submit */
|
||||||
|
uint32_t content_w, content_h;
|
||||||
|
|
||||||
|
/* cursor */
|
||||||
|
vgpu_cursor_t cursor;
|
||||||
|
|
||||||
|
/* runtime config (resolved from control) */
|
||||||
|
uint32_t default_fps; /* fps from CLI; used when target_fps==0 */
|
||||||
|
uint32_t backend; /* VGPU_BK_* chosen */
|
||||||
|
int draw_cursor_cap; /* backend capability: does it need SW cursor */
|
||||||
|
} vgpu_ctx;
|
||||||
|
|
||||||
|
#endif /* VGPU_CTX_H */
|
||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "cursor.h"
|
||||||
|
|
||||||
|
/* Max supported cursor glyph; buffers are pre-arena'd in ctx (no heap here). */
|
||||||
|
#define VGPU_CURSOR_MAX 256
|
||||||
|
|
||||||
|
static void read_mono(HBITMAP hbm, int w, int h, uint8_t* out /* w*h */) {
|
||||||
|
int stride = ((w + 31) / 32) * 4;
|
||||||
|
/* bounded scratch on stack: max (256/32*4)=32 bytes/row * 512 rows */
|
||||||
|
static const int kMaxRows = VGPU_CURSOR_MAX * 2;
|
||||||
|
uint8_t raw[(VGPU_CURSOR_MAX / 32 * 4) * (VGPU_CURSOR_MAX * 2)];
|
||||||
|
if (h > kMaxRows) h = kMaxRows;
|
||||||
|
if ((size_t)stride * h > sizeof raw) return;
|
||||||
|
|
||||||
|
struct { BITMAPINFOHEADER hdr; RGBQUAD pal[2]; } bi;
|
||||||
|
memset(&bi, 0, sizeof bi);
|
||||||
|
bi.hdr.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bi.hdr.biWidth = w; bi.hdr.biHeight = -h;
|
||||||
|
bi.hdr.biPlanes = 1; bi.hdr.biBitCount = 1; bi.hdr.biCompression = BI_RGB;
|
||||||
|
HDC dc = GetDC(NULL);
|
||||||
|
GetDIBits(dc, hbm, 0, h, raw, (BITMAPINFO*)&bi, DIB_RGB_COLORS);
|
||||||
|
ReleaseDC(NULL, dc);
|
||||||
|
|
||||||
|
memset(out, 0, (size_t)w * h);
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
int bit = 7 - (x & 7);
|
||||||
|
out[(size_t)y * w + x] = (raw[(size_t)y * stride + (x >> 3)] >> bit) & 1u;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extract(vgpu_ctx* ctx, HCURSOR hc) {
|
||||||
|
vgpu_cursor_t* cur = &ctx->cursor;
|
||||||
|
cur->gw = cur->gh = 0;
|
||||||
|
cur->mono = 0;
|
||||||
|
|
||||||
|
ICONINFO ii;
|
||||||
|
if (!GetIconInfo(hc, &ii)) return;
|
||||||
|
cur->hot_x = (int)ii.xHotspot;
|
||||||
|
cur->hot_y = (int)ii.yHotspot;
|
||||||
|
|
||||||
|
if (ii.hbmColor) {
|
||||||
|
BITMAP bm; GetObject(ii.hbmColor, sizeof bm, &bm);
|
||||||
|
int w = bm.bmWidth, h = bm.bmHeight;
|
||||||
|
if (w > VGPU_CURSOR_MAX) w = VGPU_CURSOR_MAX;
|
||||||
|
if (h > VGPU_CURSOR_MAX) h = VGPU_CURSOR_MAX;
|
||||||
|
BITMAPINFO bi; memset(&bi, 0, sizeof bi);
|
||||||
|
bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bi.bmiHeader.biWidth = w; bi.bmiHeader.biHeight = -h;
|
||||||
|
bi.bmiHeader.biPlanes = 1; bi.bmiHeader.biBitCount = 32;
|
||||||
|
bi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
memset(cur->bgra, 0, (size_t)w * h * 4);
|
||||||
|
HDC dc = GetDC(NULL);
|
||||||
|
GetDIBits(dc, ii.hbmColor, 0, h, cur->bgra, &bi, DIB_RGB_COLORS);
|
||||||
|
ReleaseDC(NULL, dc);
|
||||||
|
cur->gw = w; cur->gh = h; cur->mono = 0;
|
||||||
|
|
||||||
|
int has_alpha = 0;
|
||||||
|
for (size_t i = 0; i < (size_t)w * h; i++)
|
||||||
|
if (cur->bgra[i * 4 + 3]) { has_alpha = 1; break; }
|
||||||
|
if (!has_alpha && ii.hbmMask) {
|
||||||
|
read_mono(ii.hbmMask, w, h, cur->and_mask);
|
||||||
|
for (size_t i = 0; i < (size_t)w * h; i++)
|
||||||
|
cur->bgra[i * 4 + 3] = cur->and_mask[i] ? 0 : 255;
|
||||||
|
}
|
||||||
|
} else if (ii.hbmMask) {
|
||||||
|
BITMAP bm; GetObject(ii.hbmMask, sizeof bm, &bm);
|
||||||
|
int w = bm.bmWidth, h = bm.bmHeight / 2;
|
||||||
|
if (w > VGPU_CURSOR_MAX) w = VGPU_CURSOR_MAX;
|
||||||
|
if (h > VGPU_CURSOR_MAX) h = VGPU_CURSOR_MAX;
|
||||||
|
/* read both halves into a scratch laid over xor_mask region: reuse
|
||||||
|
* and_mask for AND and xor_mask for XOR; read full into a stack pass */
|
||||||
|
static uint8_t both[VGPU_CURSOR_MAX * VGPU_CURSOR_MAX * 2];
|
||||||
|
read_mono(ii.hbmMask, w, bm.bmHeight, both);
|
||||||
|
for (int y = 0; y < h; y++)
|
||||||
|
for (int x = 0; x < w; x++) {
|
||||||
|
cur->and_mask[(size_t)y * w + x] = both[(size_t)y * w + x];
|
||||||
|
cur->xor_mask[(size_t)y * w + x] = both[(size_t)(y + h) * w + x];
|
||||||
|
}
|
||||||
|
cur->gw = w; cur->gh = h; cur->mono = 1;
|
||||||
|
}
|
||||||
|
if (ii.hbmColor) DeleteObject(ii.hbmColor);
|
||||||
|
if (ii.hbmMask) DeleteObject(ii.hbmMask);
|
||||||
|
}
|
||||||
|
|
||||||
|
int cursor_sample(vgpu_ctx* ctx) {
|
||||||
|
vgpu_cursor_t* cur = &ctx->cursor;
|
||||||
|
CURSORINFO ci; ci.cbSize = sizeof ci;
|
||||||
|
if (!GetCursorInfo(&ci)) {
|
||||||
|
int changed = cur->visible;
|
||||||
|
cur->visible = 0;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
int vis = (ci.flags & CURSOR_SHOWING) != 0;
|
||||||
|
int x = ci.ptScreenPos.x, y = ci.ptScreenPos.y;
|
||||||
|
int changed = (vis != cur->visible) || (x != cur->x) || (y != cur->y)
|
||||||
|
|| (ci.hCursor != cur->handle);
|
||||||
|
if (vis && ci.hCursor && ci.hCursor != cur->handle) {
|
||||||
|
extract(ctx, ci.hCursor);
|
||||||
|
cur->handle = ci.hCursor;
|
||||||
|
}
|
||||||
|
cur->visible = vis; cur->x = x; cur->y = y;
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cursor_draw(vgpu_ctx* ctx, uint8_t* dst, uint32_t W, uint32_t H) {
|
||||||
|
vgpu_cursor_t* cur = &ctx->cursor;
|
||||||
|
if (!cur->visible || cur->gw == 0) return;
|
||||||
|
int ox = cur->x - cur->hot_x, oy = cur->y - cur->hot_y;
|
||||||
|
for (int gy = 0; gy < cur->gh; gy++) {
|
||||||
|
int dy = oy + gy;
|
||||||
|
if (dy < 0 || dy >= (int)H) continue;
|
||||||
|
for (int gx = 0; gx < cur->gw; gx++) {
|
||||||
|
int dx = ox + gx;
|
||||||
|
if (dx < 0 || dx >= (int)W) continue;
|
||||||
|
uint8_t* d = dst + ((size_t)dy * W + dx) * 4;
|
||||||
|
if (!cur->mono) {
|
||||||
|
const uint8_t* s = &cur->bgra[((size_t)gy * cur->gw + gx) * 4];
|
||||||
|
uint32_t a = s[3];
|
||||||
|
if (!a) continue;
|
||||||
|
d[0] = (uint8_t)((s[0] * a + d[0] * (255 - a)) / 255);
|
||||||
|
d[1] = (uint8_t)((s[1] * a + d[1] * (255 - a)) / 255);
|
||||||
|
d[2] = (uint8_t)((s[2] * a + d[2] * (255 - a)) / 255);
|
||||||
|
} else {
|
||||||
|
int a = cur->and_mask[(size_t)gy * cur->gw + gx];
|
||||||
|
int xr = cur->xor_mask[(size_t)gy * cur->gw + gx];
|
||||||
|
if (a == 0 && xr == 0) { d[0] = d[1] = d[2] = 0; }
|
||||||
|
else if (a == 0 && xr == 1) { d[0] = d[1] = d[2] = 255; }
|
||||||
|
else if (a == 1 && xr == 1) { d[0] = (uint8_t)(255 - d[0]);
|
||||||
|
d[1] = (uint8_t)(255 - d[1]);
|
||||||
|
d[2] = (uint8_t)(255 - d[2]); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
#ifndef VGPU_CURSOR_H
|
||||||
|
#define VGPU_CURSOR_H
|
||||||
|
|
||||||
|
/* cursor.h — win32 GDI cursor sample/compose onto a tight BGRA frame. */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx (cursor state) */
|
||||||
|
|
||||||
|
/* Sample the current cursor (position/shape) into ctx->cursor.
|
||||||
|
* Returns 1 if anything changed since last sample, else 0. */
|
||||||
|
int cursor_sample(vgpu_ctx* ctx);
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
|
||||||
|
#endif /* VGPU_CURSOR_H */
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "region.h" /* win32 pinned region */
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx (embeds region-view) */
|
||||||
|
#include "present.h" /* present/pump lifecycle */
|
||||||
|
#include "stream.h" /* OS-agnostic status/error/backend setters */
|
||||||
|
#include "capture.h" /* backend table */
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
int fps = argc > 1 ? atoi(argv[1]) : 30;
|
||||||
|
if (fps <= 0) fps = 30;
|
||||||
|
|
||||||
|
vgpu_region_t region;
|
||||||
|
if (vgpu_region_create(®ion) != 0) {
|
||||||
|
fprintf(stderr, "main: region_create failed\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vgpu_ctx ctx;
|
||||||
|
if (vgpu_present_init(&ctx, ®ion, (uint32_t)fps) != 0) {
|
||||||
|
fprintf(stderr, "main: present_init failed\n");
|
||||||
|
vgpu_region_destroy(®ion);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* eyes = getenv("EYES");
|
||||||
|
int n = 0;
|
||||||
|
const capture_backend* bks = capture_backends(&n);
|
||||||
|
int started = 0;
|
||||||
|
for (int i = 0; i < n && !started; i++) {
|
||||||
|
if (eyes && _stricmp(eyes, bks[i].name) != 0) continue;
|
||||||
|
fprintf(stderr, "eyes: trying %s\n", bks[i].name);
|
||||||
|
started = bks[i].start(&ctx, fps);
|
||||||
|
if (!started) fprintf(stderr, "eyes: %s unavailable\n", bks[i].name);
|
||||||
|
}
|
||||||
|
if (!started) {
|
||||||
|
fprintf(stderr, "eyes: no capture backend available\n");
|
||||||
|
vgpu_set_status(&ctx.view, VGPU_ST_ERROR);
|
||||||
|
vgpu_set_error(&ctx.view, 2u);
|
||||||
|
vgpu_present_deinit(&ctx);
|
||||||
|
vgpu_region_destroy(®ion);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
vgpu_set_backend(&ctx.view, ctx.backend);
|
||||||
|
vgpu_present_run(&ctx); /* never returns */
|
||||||
|
|
||||||
|
vgpu_present_deinit(&ctx);
|
||||||
|
vgpu_region_destroy(®ion);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
#ifndef VGPU_NVFBC_TOSYS_C_H
|
||||||
|
#define VGPU_NVFBC_TOSYS_C_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* C mirror of NvFBC's ToSys interface. The vendor header
|
||||||
|
* third_party/NvFBC/nvFBCToSys.h declares INvFBCToSys_v3 as a C++ abstract
|
||||||
|
* class (vtable of 5 pure-virtual
|
||||||
|
* __stdcall methods). We do NOT edit the vendor header; instead we replicate its
|
||||||
|
* single-inheritance vtable ABI as a COM-in-C interface so the producer stays
|
||||||
|
* pure C. Slot order MUST match declaration order in nvFBCToSys.h:
|
||||||
|
* 0 NvFBCToSysSetUp
|
||||||
|
* 1 NvFBCToSysGrabFrame
|
||||||
|
* 2 NvFBCToSysCursorCapture
|
||||||
|
* 3 NvFBCToSysGPUBasedCPUSleep
|
||||||
|
* 4 NvFBCToSysRelease
|
||||||
|
* On x64 (mingw/MSVC) `this` is the implicit first integer argument; __stdcall
|
||||||
|
* is a no-op for x64 so a plain pointer arg matches the vtable slot.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "NvFBC/nvFBC.h" /* vendor (third_party/): NVFBCRESULT, NvU32, param structs */
|
||||||
|
|
||||||
|
/* SetUp / GrabFrame param structs come from nvFBCToSys.h, but that header is C++.
|
||||||
|
* Redeclare the two we use here (layout-identical, C-clean). */
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NVFBC_TOSYS_ARGB = 0,
|
||||||
|
NVFBC_TOSYS_RGB,
|
||||||
|
NVFBC_TOSYS_YYYYUV420p,
|
||||||
|
NVFBC_TOSYS_RGB_PLANAR,
|
||||||
|
NVFBC_TOSYS_XOR,
|
||||||
|
NVFBC_TOSYS_YUV444p,
|
||||||
|
NVFBC_TOSYS_BUF_FMT_LAST
|
||||||
|
} NVFBCToSysBufferFormat_c;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_FULL = 0,
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_SCALE,
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_CROP,
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_LAST
|
||||||
|
} NVFBCToSysGrabMode_c;
|
||||||
|
|
||||||
|
enum {
|
||||||
|
NVFBC_TOSYS_NOFLAGS_C = 0x0,
|
||||||
|
NVFBC_TOSYS_NOWAIT_C = 0x1,
|
||||||
|
NVFBC_TOSYS_WAIT_WITH_TIMEOUT_C = 0x10
|
||||||
|
};
|
||||||
|
|
||||||
|
#define NVFBC_TO_SYS_C (0x1204)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NvU32 dwVersion;
|
||||||
|
NvU32 bits; /* bWithHWCursor:1, bDiffMap:1, bSep:1, rsvd:29 */
|
||||||
|
NVFBCToSysBufferFormat_c eMode;
|
||||||
|
NvU32 dwReserved1;
|
||||||
|
void **ppBuffer;
|
||||||
|
void **ppDiffMap;
|
||||||
|
void *hCursorCaptureEvent;
|
||||||
|
NvU32 dwReserved[58];
|
||||||
|
void *pReserved[29];
|
||||||
|
} NVFBC_TOSYS_SETUP_PARAMS_C;
|
||||||
|
#define NVFBC_TOSYS_SETUP_PARAMS_VER_C \
|
||||||
|
NVFBC_STRUCT_VERSION(NVFBC_TOSYS_SETUP_PARAMS_C, 2)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
NvU32 dwVersion;
|
||||||
|
NvU32 dwFlags;
|
||||||
|
NvU32 dwTargetWidth;
|
||||||
|
NvU32 dwTargetHeight;
|
||||||
|
NvU32 dwStartX;
|
||||||
|
NvU32 dwStartY;
|
||||||
|
NVFBCToSysGrabMode_c eGMode;
|
||||||
|
NvU32 dwWaitTime;
|
||||||
|
NvFBCFrameGrabInfo *pNvFBCFrameGrabInfo;
|
||||||
|
NvU32 dwReserved[56];
|
||||||
|
void *pReserved[31];
|
||||||
|
} NVFBC_TOSYS_GRAB_FRAME_PARAMS_C;
|
||||||
|
#define NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER_C \
|
||||||
|
NVFBC_STRUCT_VERSION(NVFBC_TOSYS_GRAB_FRAME_PARAMS_C, 1)
|
||||||
|
|
||||||
|
/* COM-in-C interface mirror */
|
||||||
|
typedef struct NvFBCToSys_c NvFBCToSys_c;
|
||||||
|
typedef struct {
|
||||||
|
NVFBCRESULT (__stdcall *NvFBCToSysSetUp)(NvFBCToSys_c*, NVFBC_TOSYS_SETUP_PARAMS_C*);
|
||||||
|
NVFBCRESULT (__stdcall *NvFBCToSysGrabFrame)(NvFBCToSys_c*, NVFBC_TOSYS_GRAB_FRAME_PARAMS_C*);
|
||||||
|
NVFBCRESULT (__stdcall *NvFBCToSysCursorCapture)(NvFBCToSys_c*, void*);
|
||||||
|
NVFBCRESULT (__stdcall *NvFBCToSysGPUBasedCPUSleep)(NvFBCToSys_c*, __int64);
|
||||||
|
NVFBCRESULT (__stdcall *NvFBCToSysRelease)(NvFBCToSys_c*);
|
||||||
|
} NvFBCToSys_c_vtbl;
|
||||||
|
struct NvFBCToSys_c {
|
||||||
|
const NvFBCToSys_c_vtbl* lpVtbl;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* VGPU_NVFBC_TOSYS_C_H */
|
||||||
@@ -0,0 +1,192 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "present.h"
|
||||||
|
#include "stream.h" /* OS-agnostic publish / control API + region-view */
|
||||||
|
#include "cursor.h"
|
||||||
|
|
||||||
|
/* cursor arena sizing */
|
||||||
|
#define VGPU_CUR_MAX 256u
|
||||||
|
#define VGPU_CUR_BGRA (VGPU_CUR_MAX * VGPU_CUR_MAX * 4u)
|
||||||
|
#define VGPU_CUR_MASK (VGPU_CUR_MAX * VGPU_CUR_MAX)
|
||||||
|
|
||||||
|
static uint64_t now_ns(void) {
|
||||||
|
static LARGE_INTEGER freq = { .QuadPart = 0 };
|
||||||
|
if (freq.QuadPart == 0) QueryPerformanceFrequency(&freq);
|
||||||
|
LARGE_INTEGER c; QueryPerformanceCounter(&c);
|
||||||
|
return (uint64_t)((double)c.QuadPart * 1e9 / (double)freq.QuadPart);
|
||||||
|
}
|
||||||
|
|
||||||
|
int vgpu_present_init(vgpu_ctx* ctx, vgpu_region_t* region, uint32_t default_fps) {
|
||||||
|
memset(ctx, 0, sizeof *ctx);
|
||||||
|
ctx->view.producer = region->producer;
|
||||||
|
ctx->view.control = region->control;
|
||||||
|
ctx->view.ring = region->ring;
|
||||||
|
ctx->default_fps = default_fps ? default_fps : 30u;
|
||||||
|
ctx->backend = VGPU_BK_NONE;
|
||||||
|
ctx->draw_cursor_cap = 1;
|
||||||
|
|
||||||
|
/* one arena: content + frame + cursor buffers */
|
||||||
|
size_t bytes = VGPU_STAGING_BYTES /* content */
|
||||||
|
+ VGPU_STAGING_BYTES /* frame */
|
||||||
|
+ VGPU_CUR_BGRA /* cursor bgra */
|
||||||
|
+ VGPU_CUR_MASK /* and */
|
||||||
|
+ VGPU_CUR_MASK; /* xor */
|
||||||
|
uint8_t* a = (uint8_t*)VirtualAlloc(NULL, bytes, MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
if (!a) {
|
||||||
|
fprintf(stderr, "present: arena VirtualAlloc %zu MiB failed (%lu)\n",
|
||||||
|
bytes / (1024 * 1024), GetLastError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
ctx->arena = a;
|
||||||
|
ctx->arena_bytes = bytes;
|
||||||
|
|
||||||
|
size_t off = 0;
|
||||||
|
ctx->content_buf = a + off; off += VGPU_STAGING_BYTES;
|
||||||
|
ctx->frame_buf = a + off; off += VGPU_STAGING_BYTES;
|
||||||
|
ctx->cursor.bgra = a + off; off += VGPU_CUR_BGRA;
|
||||||
|
ctx->cursor.and_mask = a + off; off += VGPU_CUR_MASK;
|
||||||
|
ctx->cursor.xor_mask = a + off; off += VGPU_CUR_MASK;
|
||||||
|
|
||||||
|
InitializeCriticalSection(&ctx->lock);
|
||||||
|
ctx->submit_event = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||||
|
ctx->content_seq = 0;
|
||||||
|
ctx->content_w = ctx->content_h = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_present_deinit(vgpu_ctx* ctx) {
|
||||||
|
if (ctx->submit_event) { CloseHandle(ctx->submit_event); ctx->submit_event = NULL; }
|
||||||
|
DeleteCriticalSection(&ctx->lock);
|
||||||
|
if (ctx->arena) { VirtualFree(ctx->arena, 0, MEM_RELEASE); ctx->arena = NULL; }
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_present_submit(vgpu_ctx* ctx, const uint8_t* src,
|
||||||
|
uint32_t W, uint32_t H, uint32_t src_pitch) {
|
||||||
|
if (W > VGPU_MAX_WIDTH) W = VGPU_MAX_WIDTH;
|
||||||
|
if (H > VGPU_MAX_HEIGHT) H = VGPU_MAX_HEIGHT;
|
||||||
|
if (W == 0 || H == 0) return;
|
||||||
|
|
||||||
|
EnterCriticalSection(&ctx->lock);
|
||||||
|
uint8_t* d = ctx->content_buf;
|
||||||
|
const uint32_t row = W * 4u;
|
||||||
|
for (uint32_t y = 0; y < H; y++)
|
||||||
|
memcpy(d + (size_t)y * row, src + (size_t)y * src_pitch, row);
|
||||||
|
ctx->content_w = W;
|
||||||
|
ctx->content_h = H;
|
||||||
|
ctx->content_seq++;
|
||||||
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
SetEvent(ctx->submit_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_present_run(vgpu_ctx* ctx) {
|
||||||
|
const vgpu_region_view* rv = &ctx->view; /* neutral handle for the engine */
|
||||||
|
const DWORD poll_ms = 8;
|
||||||
|
int64_t last_seq = -1;
|
||||||
|
uint32_t prev_state = VGPU_CMD_STOP;
|
||||||
|
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 */
|
||||||
|
|
||||||
|
for (;;) {
|
||||||
|
WaitForSingleObject(ctx->submit_event, poll_ms);
|
||||||
|
|
||||||
|
/* --- heartbeat: always ticks, independent of desired_state --- */
|
||||||
|
DWORD nowt = GetTickCount();
|
||||||
|
if (nowt - last_beat >= VGPU_HEARTBEAT_PERIOD_MS) {
|
||||||
|
vgpu_tick_heartbeat(rv);
|
||||||
|
last_beat = nowt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- reconcile control (gen-seqlock -> apply -> ack) --- */
|
||||||
|
vgpu_control_view cv;
|
||||||
|
uint32_t desired = prev_state;
|
||||||
|
uint32_t draw_cursor = 1;
|
||||||
|
int force_full = 0;
|
||||||
|
uint32_t fps = ctx->default_fps; /* publish-rate cap (applied) */
|
||||||
|
uint32_t ff_req = last_ff_ack; /* full_frame_req value to honor */
|
||||||
|
if (vgpu_control_read(rv, &cv)) {
|
||||||
|
desired = cv.desired_state;
|
||||||
|
draw_cursor = cv.draw_cursor;
|
||||||
|
fps = cv.target_fps ? cv.target_fps : ctx->default_fps;
|
||||||
|
vgpu_set_applied_fps(rv, fps);
|
||||||
|
vgpu_publish_ctrl_ack(rv, cv.gen);
|
||||||
|
|
||||||
|
ff_req = cv.full_frame_req;
|
||||||
|
if ((ff_req - last_ff_ack) != 0u)
|
||||||
|
force_full = 1; /* edge pending, wrap-tolerant */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- lifecycle transitions --- */
|
||||||
|
if (desired != prev_state) {
|
||||||
|
if (desired == VGPU_CMD_RUN && prev_state != VGPU_CMD_RUN) {
|
||||||
|
vgpu_bump_run_epoch(rv);
|
||||||
|
vgpu_set_status(rv, VGPU_ST_CAPTURING);
|
||||||
|
force_full = 1; /* fresh frame on start */
|
||||||
|
} else if (desired == VGPU_CMD_PAUSE) {
|
||||||
|
vgpu_set_status(rv, VGPU_ST_PAUSED);
|
||||||
|
} else if (desired == VGPU_CMD_STOP) {
|
||||||
|
vgpu_set_status(rv, VGPU_ST_STOPPED);
|
||||||
|
}
|
||||||
|
prev_state = desired;
|
||||||
|
} else if (last_seq < 0 && desired == VGPU_CMD_RUN) {
|
||||||
|
vgpu_set_status(rv, VGPU_ST_CAPTURING);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desired != VGPU_CMD_RUN) {
|
||||||
|
/* PAUSED/STOPPED: no new frames; heartbeat still ticks. We do NOT
|
||||||
|
* ack a pending full_frame here — acking without publishing would
|
||||||
|
* be a false "honored". A pending request is honored on the next
|
||||||
|
* transition to RUN (force_full=1 there → publish + ack). */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- 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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
EnterCriticalSection(&ctx->lock);
|
||||||
|
int64_t seq = ctx->content_seq;
|
||||||
|
uint32_t W = ctx->content_w, H = ctx->content_h;
|
||||||
|
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;
|
||||||
|
* if not due, last_seq is left untouched and it publishes next due. */
|
||||||
|
int dirty = content_new && due;
|
||||||
|
if (dirty) {
|
||||||
|
memcpy(ctx->frame_buf, ctx->content_buf, (size_t)W * H * 4u);
|
||||||
|
last_seq = seq;
|
||||||
|
}
|
||||||
|
LeaveCriticalSection(&ctx->lock);
|
||||||
|
|
||||||
|
if (!dirty) {
|
||||||
|
/* not due, or nothing to publish. A force_full with content has
|
||||||
|
* due=1 → dirty=1, so it never lands here while have is true; thus
|
||||||
|
* no spurious ack edge. */
|
||||||
|
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) {
|
||||||
|
vgpu_publish_full_frame_ack(rv, ff_req);
|
||||||
|
last_ff_ack = ff_req;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
vgpu_set_error(rv, 1u); /* frame too large for slot (mode > max) */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef VGPU_PRESENT_H
|
||||||
|
#define VGPU_PRESENT_H
|
||||||
|
|
||||||
|
/* present.h — win32 present/pump lifecycle: staging arena, submit handoff, and
|
||||||
|
* the publish loop driving the OS-agnostic engine over ctx's region-view. */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "ctx.h" /* win32 vgpu_ctx + vgpu_region_t */
|
||||||
|
|
||||||
|
/* Initialize present/staging state inside ctx over an already-created region.
|
||||||
|
* Allocates the staging+cursor arena. Returns 0 on success. */
|
||||||
|
int vgpu_present_init(vgpu_ctx* ctx, vgpu_region_t* region, uint32_t default_fps);
|
||||||
|
void vgpu_present_deinit(vgpu_ctx* ctx);
|
||||||
|
|
||||||
|
/* Capture backends submit a freshly captured desktop frame (any source pitch).
|
||||||
|
* Repacked tight into ctx->content_buf, clamped to max mode. Thread-safe. */
|
||||||
|
void vgpu_present_submit(vgpu_ctx* ctx, const uint8_t* bgra,
|
||||||
|
uint32_t width, uint32_t height, uint32_t src_pitch);
|
||||||
|
|
||||||
|
/* Run the publish pump: reconcile control, tick heartbeat, compose cursor,
|
||||||
|
* publish on change / on full_frame_req. Never returns (process lifetime). */
|
||||||
|
void vgpu_present_run(vgpu_ctx* ctx);
|
||||||
|
|
||||||
|
#endif /* VGPU_PRESENT_H */
|
||||||
@@ -0,0 +1,146 @@
|
|||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "region.h"
|
||||||
|
#include "atomic-shim.h" /* x86-TSO ordering for contract init publish */
|
||||||
|
|
||||||
|
#define VGPU_2MB (2u * 1024u * 1024u)
|
||||||
|
|
||||||
|
/* Page-segregated init of the contract over an already-pinned region base.
|
||||||
|
* Init-ordering per contract: status=INIT, latest=NONE, backend, supported_formats,
|
||||||
|
* release-barrier; heartbeat starts later (in the run pump). */
|
||||||
|
static void region_init_contract(vgpu_region_t* r) {
|
||||||
|
vgpu_producer_t* p = r->producer;
|
||||||
|
vgpu_control_t* c = r->control;
|
||||||
|
|
||||||
|
memset(p, 0, sizeof *p);
|
||||||
|
memset(c, 0, sizeof *c);
|
||||||
|
|
||||||
|
p->status = VGPU_ST_INIT;
|
||||||
|
p->backend = VGPU_BK_NONE;
|
||||||
|
p->error_code = 0;
|
||||||
|
p->applied_fps = 0;
|
||||||
|
p->supported_formats = (1u << VGPU_FMT_BGRA8888);
|
||||||
|
p->run_epoch = 0;
|
||||||
|
p->heartbeat = 0;
|
||||||
|
p->frame_id = 0;
|
||||||
|
p->ctrl_ack = 0;
|
||||||
|
p->full_frame_ack = 0;
|
||||||
|
for (uint32_t i = 0; i < VGPU_SLOT_COUNT; i++)
|
||||||
|
p->seq[i] = 0;
|
||||||
|
|
||||||
|
/* control starts RUN: producer captures immediately; host may STOP/PAUSE */
|
||||||
|
c->ctrl_gen = 0;
|
||||||
|
c->desired_state = VGPU_CMD_RUN;
|
||||||
|
c->target_fps = 0;
|
||||||
|
c->draw_cursor = 1;
|
||||||
|
c->full_frame_req = 0;
|
||||||
|
c->consumer_tick = 0;
|
||||||
|
c->attached = 0;
|
||||||
|
|
||||||
|
/* publish latest last with a release store gating all of the above */
|
||||||
|
vgpu_sfence();
|
||||||
|
vgpu_store_release32(&p->latest, VGPU_LATEST_NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int adjust_lock_memory_privilege(void) {
|
||||||
|
HANDLE tok;
|
||||||
|
if (!OpenProcessToken(GetCurrentProcess(),
|
||||||
|
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &tok))
|
||||||
|
return 0;
|
||||||
|
TOKEN_PRIVILEGES tp;
|
||||||
|
memset(&tp, 0, sizeof tp);
|
||||||
|
tp.PrivilegeCount = 1;
|
||||||
|
if (!LookupPrivilegeValueA(NULL, SE_LOCK_MEMORY_NAME, &tp.Privileges[0].Luid)) {
|
||||||
|
CloseHandle(tok);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||||
|
int ok = AdjustTokenPrivileges(tok, FALSE, &tp, sizeof tp, NULL, NULL)
|
||||||
|
&& GetLastError() == ERROR_SUCCESS;
|
||||||
|
CloseHandle(tok);
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
int vgpu_region_create(vgpu_region_t* out) {
|
||||||
|
memset(out, 0, sizeof *out);
|
||||||
|
|
||||||
|
const uint64_t bytes = VGPU_REGION_BYTES;
|
||||||
|
|
||||||
|
void* os_base = NULL;
|
||||||
|
uint8_t* base = NULL;
|
||||||
|
uint64_t os_total = 0;
|
||||||
|
|
||||||
|
if (adjust_lock_memory_privilege()) {
|
||||||
|
SIZE_T large_min = GetLargePageMinimum();
|
||||||
|
if (large_min && large_min <= VGPU_2MB) {
|
||||||
|
SIZE_T rounded = (SIZE_T)((bytes + VGPU_2MB - 1) & ~(uint64_t)(VGPU_2MB - 1));
|
||||||
|
void* p = VirtualAlloc(NULL, rounded,
|
||||||
|
MEM_RESERVE | MEM_COMMIT | MEM_LARGE_PAGES,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
if (p) {
|
||||||
|
/* large pages are >= 2 MiB → base is already 2 MiB-aligned */
|
||||||
|
os_base = p;
|
||||||
|
base = (uint8_t*)p;
|
||||||
|
os_total = rounded;
|
||||||
|
fprintf(stderr, "region: MEM_LARGE_PAGES %llu MiB at %p\n",
|
||||||
|
(unsigned long long)(rounded / (1024 * 1024)), p);
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "region: MEM_LARGE_PAGES failed (%lu), fallback\n",
|
||||||
|
GetLastError());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "region: SE_LOCK_MEMORY unavailable, fallback\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!base) {
|
||||||
|
uint64_t total = bytes + VGPU_2MB;
|
||||||
|
void* p = VirtualAlloc(NULL, (SIZE_T)total, MEM_RESERVE | MEM_COMMIT,
|
||||||
|
PAGE_READWRITE);
|
||||||
|
if (!p) {
|
||||||
|
fprintf(stderr, "region: VirtualAlloc %llu MiB failed (%lu)\n",
|
||||||
|
(unsigned long long)(total / (1024 * 1024)), GetLastError());
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
uintptr_t addr = (uintptr_t)p;
|
||||||
|
uintptr_t aligned = (addr + VGPU_2MB - 1) & ~(uintptr_t)(VGPU_2MB - 1);
|
||||||
|
if (!VirtualLock((void*)aligned, (SIZE_T)bytes)) {
|
||||||
|
fprintf(stderr, "region: VirtualLock failed (%lu) — pages may not be pinned\n",
|
||||||
|
GetLastError());
|
||||||
|
}
|
||||||
|
os_base = p;
|
||||||
|
base = (uint8_t*)aligned;
|
||||||
|
os_total = total;
|
||||||
|
fprintf(stderr, "region: fallback VirtualAlloc+VirtualLock %llu MiB, aligned at %p\n",
|
||||||
|
(unsigned long long)(bytes / (1024 * 1024)), (void*)aligned);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (((uintptr_t)base & (VGPU_2MB - 1)) != 0) {
|
||||||
|
fprintf(stderr, "region: base %p not 2 MiB aligned\n", (void*)base);
|
||||||
|
VirtualFree(os_base, 0, MEM_RELEASE);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out->os_base = os_base;
|
||||||
|
out->base = base;
|
||||||
|
out->os_total = os_total;
|
||||||
|
out->producer = (vgpu_producer_t*)(base + VGPU_PRODUCER_OFFSET);
|
||||||
|
out->control = (vgpu_control_t*)(base + VGPU_CONTROL_OFFSET);
|
||||||
|
out->ring = base + VGPU_RING_OFFSET;
|
||||||
|
|
||||||
|
region_init_contract(out);
|
||||||
|
|
||||||
|
fprintf(stderr, "region: contract ready (producer=%p control=%p ring=%p)\n",
|
||||||
|
(void*)out->producer, (void*)out->control, (void*)out->ring);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vgpu_region_destroy(vgpu_region_t* r) {
|
||||||
|
if (r && r->os_base) {
|
||||||
|
VirtualUnlock(r->base, (SIZE_T)VGPU_REGION_BYTES);
|
||||||
|
VirtualFree(r->os_base, 0, MEM_RELEASE);
|
||||||
|
memset(r, 0, sizeof *r);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#ifndef VGPU_REGION_H
|
||||||
|
#define VGPU_REGION_H
|
||||||
|
|
||||||
|
/* region.h — win32 pinned contract region (resolves blocks for the region-view). */
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include "vgpu_stream.h" /* public contract: blocks, offsets, slot geometry */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One contiguous 2 MiB-aligned pinned region holding the full contract:
|
||||||
|
* producer block (page 0), control block (page 1), then SLOT_COUNT frame slots
|
||||||
|
* starting at VGPU_RING_OFFSET. Object = memory: the region owns the mapping,
|
||||||
|
* its lifetime is the mapping's lifetime. No hidden global state.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
void* os_base; /* raw allocation base (for free) */
|
||||||
|
uint8_t* base; /* 2 MiB-aligned region base (== contract origin) */
|
||||||
|
uint64_t os_total; /* bytes reserved at os_base */
|
||||||
|
vgpu_producer_t* producer; /* base + VGPU_PRODUCER_OFFSET */
|
||||||
|
vgpu_control_t* control; /* base + VGPU_CONTROL_OFFSET */
|
||||||
|
uint8_t* ring; /* base + VGPU_RING_OFFSET */
|
||||||
|
} vgpu_region_t;
|
||||||
|
|
||||||
|
/* Returns 0 on success, non-zero on failure (region zeroed on failure). */
|
||||||
|
int vgpu_region_create(vgpu_region_t* out);
|
||||||
|
void vgpu_region_destroy(vgpu_region_t* r);
|
||||||
|
|
||||||
|
#endif /* VGPU_REGION_H */
|
||||||
Vendored
+275
@@ -0,0 +1,275 @@
|
|||||||
|
/**
|
||||||
|
* \file This file contains definitions for NVFBC API.
|
||||||
|
* \copyright
|
||||||
|
*
|
||||||
|
* Copyright 1993-2016 NVIDIA Corporation. All rights reserved.
|
||||||
|
* NOTICE TO LICENSEE: This source code and/or documentation ("Licensed Deliverables")
|
||||||
|
* are subject to the applicable NVIDIA license agreement
|
||||||
|
* that governs the use of the Licensed Deliverables.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
typedef unsigned char NvU8;
|
||||||
|
typedef unsigned long NvU32;
|
||||||
|
typedef unsigned long long NvU64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC The NVIDIA Frame Buffer Capture API.
|
||||||
|
* \brief Defines a set of interfaces for high performance Capture of desktop content.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_ENUMS Enums
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* \brief Enumerations to be used with NVFBC API
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_STRUCTS Structs
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* \brief Defines Parameter Structures to be used with NVFBC APIs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_ENTRYPOINTS Entrypoints
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* \brief Declarations for NVFBC Entrypoint functions
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Macro to define the NVFBC API version corresponding to this distribution.
|
||||||
|
*/
|
||||||
|
#define NVFBC_DLL_VERSION 0x50
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Macro to construct version numbers for parameter structs.
|
||||||
|
*/
|
||||||
|
#define NVFBC_STRUCT_VERSION(typeName, ver) (NvU32)(sizeof(typeName) | ((ver)<<16) | (NVFBC_DLL_VERSION << 24))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Calling Convention
|
||||||
|
*/
|
||||||
|
#define NVFBCAPI __stdcall
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Indicates that there are no global overrides specified for NVFBC. To be used with NVFBC_SetGlobalFlags API
|
||||||
|
*/
|
||||||
|
#define NVFBC_GLOBAL_FLAGS_NONE 0x00000000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Indicates to NVFBC that stereo rendering is enabled. Currently unsupported. To be used with NVFBC_SetGlobalFlags API.
|
||||||
|
*/
|
||||||
|
#define NVFBC_GLOBAL_FLAGS_STEREO_BUFFER 0x00000001
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Indicates that NVFBC should not request a repaint of the desktop when initiating NVFBC capture. To be used with NVFBC_SetGlobalFlags API.
|
||||||
|
*/
|
||||||
|
#define NVFBC_GLOBAL_FLAGS_NO_INITIAL_REFRESH 0x00000002
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC
|
||||||
|
* Indicates that NVFBC should not reset the graphics driver while servicing subsequent NVFBC_Enable API requests.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define NVFBC_GLOBAL_FLAGS_NO_DEVICE_RESET_TOGGLE 0x00000004
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENUMS
|
||||||
|
* \brief Enumerates status codes returned by NVFBC APIs.
|
||||||
|
*/
|
||||||
|
typedef enum _NVFBCRESULT
|
||||||
|
{
|
||||||
|
NVFBC_SUCCESS = 0,
|
||||||
|
NVFBC_ERROR_GENERIC = -1, /**< Unexpected failure in NVFBC. */
|
||||||
|
NVFBC_ERROR_INVALID_PARAM = -2, /**< One or more of the paramteres passed to NvFBC are invalid [This include NULL pointers]. */
|
||||||
|
NVFBC_ERROR_INVALIDATED_SESSION = -3, /**< NvFBC session is invalid. Client needs to recreate session. */
|
||||||
|
NVFBC_ERROR_PROTECTED_CONTENT = -4, /**< Protected content detected. Capture failed. */
|
||||||
|
NVFBC_ERROR_DRIVER_FAILURE = -5, /**< GPU driver returned failure to process NvFBC command. */
|
||||||
|
NVFBC_ERROR_CUDA_FAILURE = -6, /**< CUDA driver returned failure to process NvFBC command. */
|
||||||
|
NVFBC_ERROR_UNSUPPORTED = -7, /**< API Unsupported on this version of NvFBC. */
|
||||||
|
NVFBC_ERROR_HW_ENC_FAILURE = -8, /**< HW Encoder returned failure to process NVFBC command. */
|
||||||
|
NVFBC_ERROR_INCOMPATIBLE_DRIVER = -9, /**< NVFBC is not compatible with this version of the GPU driver. */
|
||||||
|
NVFBC_ERROR_UNSUPPORTED_PLATFORM = -10, /**< NVFBC is not supported on this platform. */
|
||||||
|
NVFBC_ERROR_OUT_OF_MEMORY = -11, /**< Failed to allocate memory. */
|
||||||
|
NVFBC_ERROR_INVALID_PTR = -12, /**< A NULL pointer was passed. */
|
||||||
|
NVFBC_ERROR_INCOMPATIBLE_VERSION = -13, /**< An API was called with a parameter struct that has an incompatible version. Check dwVersion field of paramter struct. */
|
||||||
|
NVFBC_ERROR_OPT_CAPTURE_FAILURE = -14, /**< Desktop Capture failed. */
|
||||||
|
NVFBC_ERROR_INSUFFICIENT_PRIVILEGES = -15, /**< User doesn't have appropriate previlages. */
|
||||||
|
NVFBC_ERROR_INVALID_CALL = -16, /**< NVFBC APIs called in wrong sequence. */
|
||||||
|
NVFBC_ERROR_SYSTEM_ERROR = -17, /**< Win32 error. */
|
||||||
|
NVFBC_ERROR_INVALID_TARGET = -18, /**< The target adapter idx can not be used for NVFBC capture. It may not correspond to an NVIDIA GPU, or may not be attached to desktop. */
|
||||||
|
NVFBC_ERROR_DYNAMIC_DISABLE = -20, /**< NvFBC is dynamically disabled. Cannot continue to capture */
|
||||||
|
} NVFBCRESULT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENUMS
|
||||||
|
* \brief Enumerates NVFBC states. To be used with NvFBC_Enable API
|
||||||
|
*/
|
||||||
|
typedef enum _NVFBC_STATE
|
||||||
|
{
|
||||||
|
NVFBC_STATE_DISABLE = 0, /** Disables NvFBC. */
|
||||||
|
NVFBC_STATE_ENABLE , /** Enables NvFBC. */
|
||||||
|
NVFBC_STATE_LAST , /** Sentinel value. Shouldn't be used. */
|
||||||
|
} NVFBC_STATE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_STRUCTS
|
||||||
|
* \brief Defines parameters that describe the grabbed data, and provides detailed information about status of the NVFBC session.
|
||||||
|
*/
|
||||||
|
typedef struct _NvFBCFrameGrabInfo
|
||||||
|
{
|
||||||
|
DWORD dwWidth; /**< [out] Indicates the current width of captured buffer. */
|
||||||
|
DWORD dwHeight; /**< [out] Indicates the current height of captured buffer. */
|
||||||
|
DWORD dwBufferWidth; /**< [out] Indicates the current width of the pixel buffer(padded width). */
|
||||||
|
DWORD dwReserved; /**< [in] Reserved, do not use. */
|
||||||
|
BOOL bOverlayActive; /**< [out] Is set to 1 if overlay was active. */
|
||||||
|
BOOL bMustRecreate; /**< [out] Is set to 1 if the compressor must call NvBFC_Create again. */
|
||||||
|
BOOL bFirstBuffer; /**< [out] Is set to 1 is this was the first capture call, or first call after a desktop mode change.
|
||||||
|
Relevant only for XOR and diff modes supported by NVFBCToSys interface. */
|
||||||
|
BOOL bHWMouseVisible; /**< [out] Is set to 1 if HW cursor was enabled by OS at the time of the grab. */
|
||||||
|
BOOL bProtectedContent; /**< [out] Is set to 1 if protected content was active (DXVA encryption Session). */
|
||||||
|
DWORD dwDriverInternalError; /**< [out] Indicates the status code from lower layers. 0 or 0xFBCA11F9 indicates no error was returned. */
|
||||||
|
BOOL bStereoOn; /**< [out] Is set to 1 if stereo was on. */
|
||||||
|
BOOL bIGPUCapture; /**< [out] Is set to 1 if the captured frame is from iGPU. 0 if capture fails or if captured from dGPU*/
|
||||||
|
DWORD dwSourcePID; /**< [out] Indicates which process caused the last screen update that got grabbed*/
|
||||||
|
DWORD dwReserved3; /**< [in] Reserved, do not use. */
|
||||||
|
NvU32 dwReserved2[13]; /**< [in] Resereved, should be set to 0. */
|
||||||
|
} NvFBCFrameGrabInfo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_STRUCTS
|
||||||
|
* \brief Deines the parameters to be used with NvFBC_GetStatusEx API
|
||||||
|
*/
|
||||||
|
typedef struct _NvFBCStatusEx
|
||||||
|
{
|
||||||
|
NvU32 dwVersion; /**< [in] Struct version. Set to NVFBC_STATUS_VER. */
|
||||||
|
NvU32 bIsCapturePossible :1; /**< [out] Indicates if NvFBC feature is enabled. */
|
||||||
|
NvU32 bCurrentlyCapturing:1; /**< [out] Indicates if NVFBC is currently capturing for the Adapter ordinal specified in dwAdapterIdx. */
|
||||||
|
NvU32 bCanCreateNow :1; /**< [out] Deprecated. Do not use. */
|
||||||
|
NvU32 bSupportMultiHead :1; /**< [out] MultiHead grab supported. */
|
||||||
|
NvU32 bSupportMultiClient:1; /**< [out] Multiple capture clients on same display adapter supported. */
|
||||||
|
NvU32 bReservedBits :27; /**< [in] Reserved, do not use. */
|
||||||
|
NvU32 dwNvFBCVersion; /**< [out] Indicates the highest NvFBC interface version supported by the loaded NVFBC library. */
|
||||||
|
NvU32 dwAdapterIdx; /**< [in] Adapter Ordinal corresponding to the display to be grabbed. IGNORED if bCapturePID is set */
|
||||||
|
void* pPrivateData; /**< [in] optional **/
|
||||||
|
NvU32 dwPrivateDataSize; /**< [in] optional **/
|
||||||
|
NvU32 dwReserved[59]; /**< [in] Reserved. Should be set to 0. */
|
||||||
|
void* pReserved[31]; /**< [in] Reserved. Should be set to NULL. */
|
||||||
|
} NvFBCStatusEx;
|
||||||
|
#define NVFBC_STATUS_VER_1 NVFBC_STRUCT_VERSION(NvFBCStatusEx, 1)
|
||||||
|
#define NVFBC_STATUS_VER_2 NVFBC_STRUCT_VERSION(NvFBCStatusEx, 2)
|
||||||
|
#define NVFBC_STATUS_VER NVFBC_STATUS_VER_2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_STRUCTS
|
||||||
|
* \brief Defines the parameters to be used with NvFBC_CreateEx API.
|
||||||
|
*/
|
||||||
|
typedef struct _NvFBCCreateParams
|
||||||
|
{
|
||||||
|
NvU32 dwVersion; /**< [in] Struct version. Set to NVFBC_CREATE_PARAMS_VER. */
|
||||||
|
NvU32 dwInterfaceType; /**< [in] ID of the NVFBC interface Type being requested. */
|
||||||
|
NvU32 dwMaxDisplayWidth; /**< [out] Max. display width allowed. */
|
||||||
|
NvU32 dwMaxDisplayHeight; /**< [out] Max. display height allowed. */
|
||||||
|
void* pDevice; /**< [in] Device pointer. */
|
||||||
|
void* pPrivateData; /**< [in] Private data [optional]. */
|
||||||
|
NvU32 dwPrivateDataSize; /**< [in] Size of private data. */
|
||||||
|
NvU32 dwInterfaceVersion; /**< [in] Version of the capture interface. */
|
||||||
|
void* pNvFBC; /**< [out] A pointer to the requested NVFBC object. */
|
||||||
|
NvU32 dwAdapterIdx; /**< [in] Adapter Ordinal corresponding to the display to be grabbed. If pDevice is set, this parameter is ignored. */
|
||||||
|
NvU32 dwNvFBCVersion; /**< [out] Indicates the highest NvFBC interface version supported by the loaded NVFBC library. */
|
||||||
|
void* cudaCtx; /**< [in] CUDA context created using cuD3D9CtxCreate with the D3D9 device passed as pDevice. Only used for NvFBCCuda interface.
|
||||||
|
It is mandatory to pass a valid D3D9 device if cudaCtx is passed. The call will fail otherwise.
|
||||||
|
Client must release NvFBCCuda object before destroying the cudaCtx. */
|
||||||
|
void* pPrivateData2; /**< [in] Private data [optional]. */
|
||||||
|
NvU32 dwPrivateData2Size; /**< [in] Size of private data. */
|
||||||
|
NvU32 dwReserved[55]; /**< [in] Reserved. Should be set to 0. */
|
||||||
|
void* pReserved[27]; /**< [in] Reserved. Should be set to NULL. */
|
||||||
|
}NvFBCCreateParams;
|
||||||
|
#define NVFBC_CREATE_PARAMS_VER_1 NVFBC_STRUCT_VERSION(NvFBCCreateParams, 1)
|
||||||
|
#define NVFBC_CREATE_PARAMS_VER_2 NVFBC_STRUCT_VERSION(NvFBCCreateParams, 2)
|
||||||
|
#define NVFBC_CREATE_PARAMS_VER NVFBC_CREATE_PARAMS_VER_2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_STRUCTS
|
||||||
|
* \brief Defines parameters for a Grab\Capture call to get HW cursor data in the NVFBCToSys capture session.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
NvU32 dwVersion; /**< [in]: Struct version. Set to NVFBC_MOUSE_GRAB_INFO_VER.*/
|
||||||
|
NvU32 dwWidth; /**< [out]: Width of mouse glyph captured.*/
|
||||||
|
NvU32 dwHeight; /**< [out]: Height of mouse glyph captured.*/
|
||||||
|
NvU32 dwPitch; /**< [out]: Pitch of mouse glyph captured.*/
|
||||||
|
NvU32 bIsHwCursor : 1; /**< [out]: Tells if cursor is HW cursor or SW cursor. If set to 0, ignore height, width, pitch and pBits.*/
|
||||||
|
NvU32 bReserved : 32; /**< [in]: Reserved.*/
|
||||||
|
NvU32 dwPointerFlags; /**< [out]: Maps to DXGK_POINTERFLAGS::Value.*/
|
||||||
|
NvU32 dwXHotSpot; /**< [out]: Maps to DXGKARG_SETPOINTERSHAPE::XHot.*/
|
||||||
|
NvU32 dwYHotSpot; /**< [out]: Maps to DXGKARG_SETPOINTERSHAPE::YHot.*/
|
||||||
|
NvU32 dwUpdateCounter; /**< [out]: Cursor update Counter. */
|
||||||
|
NvU32 dwBufferSize; /**< [out]: Size of the buffer contaiing the captured cursor glyph. */
|
||||||
|
void * pBits; /**< [out]: pointer to buffer containing the captured cursor glyph.*/
|
||||||
|
NvU32 dwReservedA[22]; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
void * pReserved[15]; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
}NVFBC_CURSOR_CAPTURE_PARAMS;
|
||||||
|
#define NVFBC_CURSOR_CAPTURE_PARAMS_VER NVFBC_STRUCT_VERSION(NVFBC_CURSOR_CAPTURE_PARAMS, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENTRYPOINTS
|
||||||
|
* \brief NVFBC API to set global overrides
|
||||||
|
* \param [in] dwFlags Global overrides for NVFBC. Use ::NVFBC_GLOBAL_FLAGS value.
|
||||||
|
*/
|
||||||
|
void NVFBCAPI NvFBC_SetGlobalFlags(DWORD dwFlags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENTRYPOINTS
|
||||||
|
* \brief NVFBC API to create an NVFBC capture session.
|
||||||
|
* Instantiates an interface identified by NvFBCCreateParams::dwInterfaceType.
|
||||||
|
* \param [inout] pCreateParams Pointer to a struct of type ::NvFBCCreateParams, typecast to void*
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
NVFBCRESULT NVFBCAPI NvFBC_CreateEx(void * pCreateParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENTRYPOINTS
|
||||||
|
* \brief NVFBC API to query Current NVFBC status.
|
||||||
|
* Queries the status for the adapter pointed to by the NvFBCStatusEx::dwAdapterIdx parameter.
|
||||||
|
* \param [inout] pCreateParams Pointer to a struct of type ::NvFBCStatusEx.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
NVFBCRESULT NVFBCAPI NvFBC_GetStatusEx(NvFBCStatusEx *pNvFBCStatusEx);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENTRYPOINTS
|
||||||
|
* \brief NVFBC API to enable \ disable NVFBC feature.
|
||||||
|
* \param [in] nvFBCState Refer ::NVFBC_STATE
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
NVFBCRESULT NVFBCAPI NvFBC_Enable(NVFBC_STATE nvFBCState);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_ENTRYPOINTS
|
||||||
|
* \brief NVFBC API to query highest GRID SDK version supported by the loaded NVFBC library.
|
||||||
|
* \param [out] pVersion Pointer to a 32-bit integer to hold the supported GRID SDK version.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
NVFBCRESULT NVFBCAPI NvFBC_GetSDKVersion(NvU32 * pVersion);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \cond API_PFN
|
||||||
|
*/
|
||||||
|
typedef void (NVFBCAPI * NvFBC_SetGlobalFlagsType) (DWORD dwFlags);
|
||||||
|
typedef NVFBCRESULT (NVFBCAPI * NvFBC_CreateFunctionExType) (void * pCreateParams);
|
||||||
|
typedef NVFBCRESULT (NVFBCAPI * NvFBC_GetStatusExFunctionType) (void * pNvFBCStatus);
|
||||||
|
typedef NVFBCRESULT (NVFBCAPI * NvFBC_EnableFunctionType) (NVFBC_STATE nvFBCState);
|
||||||
|
typedef NVFBCRESULT (NVFBCAPI * NvFBC_GetSDKVersionFunctionType) (NvU32 * pVersion);
|
||||||
|
/**
|
||||||
|
* \endcond API_PFN
|
||||||
|
*/
|
||||||
Vendored
+176
@@ -0,0 +1,176 @@
|
|||||||
|
/**
|
||||||
|
* \file This file contains defintions for NVFBCToSys
|
||||||
|
*
|
||||||
|
* Copyright 1993-2016 NVIDIA Corporation. All rights reserved.
|
||||||
|
* NOTICE TO LICENSEE: This source code and/or documentation ("Licensed Deliverables")
|
||||||
|
* are subject to the applicable NVIDIA license agreement
|
||||||
|
* that governs the use of the Licensed Deliverables.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef NVFBC_TO_SYS_H_
|
||||||
|
#define NVFBC_TO_SYS_H_
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_TOSYS NVFBCToSys Interface
|
||||||
|
* \brief Interface for grabbing Desktop images and generating output in system memory.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_TOSYS_ENUMS Enums
|
||||||
|
* \ingroup NVFBC_TOSYS
|
||||||
|
* \brief Enumerations used with NVFBCToSys interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_TOSYS_STRUCTS Structs
|
||||||
|
* \ingroup NVFBC_TOSYS
|
||||||
|
* \brief Parameter Structs Defined for use with NVFBCToSys interface.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \defgroup NVFBC_TOSYS_INTERFACE Object Interface
|
||||||
|
* \ingroup NVFBC_TOSYS
|
||||||
|
* \brief Interface class definition for NVFBCToSys Capture API
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS
|
||||||
|
* \brief Macro to define the interface ID to be passed as NvFBCCreateParams::dwInterfaceType
|
||||||
|
* for creating an NVFBCToSys capture session object.
|
||||||
|
*/
|
||||||
|
#define NVFBC_TO_SYS (0x1204)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_ENUMS
|
||||||
|
* Enumerates output buffer pixel data formats supported by NVFBCToSys.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
NVFBC_TOSYS_ARGB = 0, /**< Output Pixels in ARGB format: 32bpp, one byte per channel. */
|
||||||
|
NVFBC_TOSYS_RGB , /**< Output Pixels in RGB format: 24bpp, one byte per channel. */
|
||||||
|
NVFBC_TOSYS_YYYYUV420p , /**< Output Pixels in YUV420 format: 12bpp,
|
||||||
|
the Y' channel at full resolution, U channel at half resolution (1 byte for four pixels), V channel at half resolution. */
|
||||||
|
NVFBC_TOSYS_RGB_PLANAR , /**< Output Pixels in planar RGB format: 24bpp,
|
||||||
|
stored sequentially in memory as complete red channel, complete green channel, complete blue channel. */
|
||||||
|
NVFBC_TOSYS_XOR , /**< Output Pixels in RGB format: 24bpp XOR'd with the prior frame. */
|
||||||
|
NVFBC_TOSYS_YUV444p , /**< Output Pixels in YUV444 planar format, i.e. separate 8-bpp Y, U, V planes with no subsampling.*/
|
||||||
|
NVFBC_TOSYS_BUF_FMT_LAST , /**< Sentinel value. Do not use.*/
|
||||||
|
} NVFBCToSysBufferFormat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_ENUMS
|
||||||
|
* Enumerates Capture\Grab modes supported by NVFBCToSys.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_FULL = 0, /**< Grab full res */
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_SCALE , /**< Will convert current res to supplied resolution (dwTargetWidth and dwTargetHeight) */
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_CROP , /**< Native res, crops a subwindow, of dwTargetWidth and dwTargetHeight sizes, starting at dwStartX and dwStartY */
|
||||||
|
NVFBC_TOSYS_SOURCEMODE_LAST , /**< Sentinel value. Do not use. */
|
||||||
|
}NVFBCToSysGrabMode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_ENUMS
|
||||||
|
* \enum NVFBC_TOSYS_GRAB_FLAGS Enumerates special commands for grab\capture supported by NVFBCToSys.
|
||||||
|
*/
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
NVFBC_TOSYS_NOFLAGS = 0x0, /**< Default (no flags set). Grabbing will wait for a new frame or HW mouse move. */
|
||||||
|
NVFBC_TOSYS_NOWAIT = 0x1, /**< Grabbing will not wait for a new frame nor a HW cursor move. */
|
||||||
|
NVFBC_TOSYS_WAIT_WITH_TIMEOUT = 0x10, /**< Grabbing will wait for a new frame or HW mouse move with a maximum wait time of NVFBC_TOSYS_GRAB_FRAME_PARAMS::dwWaitTime millisecond*/
|
||||||
|
} NVFBC_TOSYS_GRAB_FLAGS;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_STRUCTS
|
||||||
|
* \brief Defines parameters used to configure NVFBCToSys capture session.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
NvU32 dwVersion; /**< [in]: Struct version. Set to NVFBC_TOSYS_SETUP_PARAMS_VER.*/
|
||||||
|
NvU32 bWithHWCursor :1; /**< [in]: The client should set this to 1 if it requires the HW cursor to be composited on the captured image.*/
|
||||||
|
NvU32 bDiffMap :1; /**< [in]: The client should set this to use the DiffMap feature.*/
|
||||||
|
NvU32 bEnableSeparateCursorCapture : 1; /**< [in]: The client should set this to 1 if it wants to enable mouse capture in separate stream.*/
|
||||||
|
NvU32 bReservedBits :29; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
NVFBCToSysBufferFormat eMode; /**< [in]: Output image format.*/
|
||||||
|
NvU32 dwReserved1; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
void **ppBuffer; /**< [out]: Container to hold NvFBC output buffers.*/
|
||||||
|
void **ppDiffMap; /**< [out]: Container to hold NvFBC output diffmap buffers.*/
|
||||||
|
void *hCursorCaptureEvent; /**< [out]: Client should wait for mouseEventHandle event before calling MouseGrab function. */
|
||||||
|
NvU32 dwReserved[58]; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
void *pReserved[29]; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
} NVFBC_TOSYS_SETUP_PARAMS_V2;
|
||||||
|
#define NVFBC_TOSYS_SETUP_PARAMS_VER2 NVFBC_STRUCT_VERSION(NVFBC_TOSYS_SETUP_PARAMS, 2)
|
||||||
|
typedef NVFBC_TOSYS_SETUP_PARAMS_V2 NVFBC_TOSYS_SETUP_PARAMS;
|
||||||
|
#define NVFBC_TOSYS_SETUP_PARAMS_VER NVFBC_TOSYS_SETUP_PARAMS_VER2
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_STRUCTS
|
||||||
|
* \brief Defines parameters for a Grab\Capture call in the NVFBCToSys capture session.
|
||||||
|
* Also holds information regarding the grabbed data.
|
||||||
|
*/
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
NvU32 dwVersion; /**< [in]: Struct version. Set to NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER.*/
|
||||||
|
NvU32 dwFlags; /**< [in]: Special grabbing requests. This should be a bit-mask of NVFBC_TOSYS_GRAB_FLAGS values.*/
|
||||||
|
NvU32 dwTargetWidth; /**< [in]: Target image width. NvFBC will scale the captured image to fit taret width and height. Used with NVFBC_TOSYS_SOURCEMODE_SCALE and NVFBC_TOSYS_SOURCEMODE_CROP. */
|
||||||
|
NvU32 dwTargetHeight; /**< [in]: Target image height. NvFBC will scale the captured image to fit taret width and height. Used with NVFBC_TOSYS_SOURCEMODE_SCALE and NVFBC_TOSYS_SOURCEMODE_CROP. */
|
||||||
|
NvU32 dwStartX; /**< [in]: x-coordinate of starting pixel for cropping. Used with NVFBC_TOSYS_SOURCEMODE_CROP. */
|
||||||
|
NvU32 dwStartY; /**< [in]: y-coordinate of starting pixel for cropping. Used with NVFBC_TOSYS_SOURCEMODE_CROP. .*/
|
||||||
|
NVFBCToSysGrabMode eGMode; /**< [in]: Frame grab mode.*/
|
||||||
|
NvU32 dwWaitTime; /**< [in]: Time limit for NvFBCToSysGrabFrame() to wait until a new frame is available or a HW mouse moves. Use with NVFBC_TOSYS_WAIT_WITH_TIMEOUT */
|
||||||
|
NvFBCFrameGrabInfo *pNvFBCFrameGrabInfo; /**< [in/out]: Frame grab information and feedback from NvFBC driver.*/
|
||||||
|
NvU32 dwReserved[56]; /**< [in]: Reserved. Set to 0.*/
|
||||||
|
void *pReserved[31]; /**< [in]: Reserved. Set to NULL.*/
|
||||||
|
} NVFBC_TOSYS_GRAB_FRAME_PARAMS_V1;
|
||||||
|
#define NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER1 NVFBC_STRUCT_VERSION(NVFBC_TOSYS_GRAB_FRAME_PARAMS, 1)
|
||||||
|
typedef NVFBC_TOSYS_GRAB_FRAME_PARAMS_V1 NVFBC_TOSYS_GRAB_FRAME_PARAMS;
|
||||||
|
#define NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER NVFBC_TOSYS_GRAB_FRAME_PARAMS_VER1
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup NVFBC_TOSYS_INTERFACE
|
||||||
|
* Interface class definition for NVFBCToSys Capture API
|
||||||
|
*/
|
||||||
|
class INvFBCToSys_v3
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* \brief Sets up NVFBC System Memory capture according to the provided parameters.
|
||||||
|
* \param [in] pParam Pointer to a struct of type ::NVFBC_TOSYS_SETUP_PARAMS.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
virtual NVFBCRESULT NVFBCAPI NvFBCToSysSetUp (NVFBC_TOSYS_SETUP_PARAMS_V2 *pParam) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Captures the desktop and dumps the captured data to a System memory buffer.
|
||||||
|
* If the API returns a failure, the client should check the return codes and ::NvFBCFrameGrabInfo output fields to determine if the session needs to be re-created.
|
||||||
|
* \param [inout] pParam Pointer to a struct of type ::NVFBC_TOSYS_GRAB_FRAME_PARAMS.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
virtual NVFBCRESULT NVFBCAPI NvFBCToSysGrabFrame (NVFBC_TOSYS_GRAB_FRAME_PARAMS *pParam) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Captures HW cursor data whenever shape of mouse is changed
|
||||||
|
* \param [inout] pParam Pointer to a struct of type ::NVFBC_CURSOR_CAPTURE_PARAMS.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
virtual NVFBCRESULT NVFBCAPI NvFBCToSysCursorCapture (NVFBC_CURSOR_CAPTURE_PARAMS *pParam) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief A high precision implementation of Sleep().
|
||||||
|
* Can provide sub quantum (usually 16ms) sleep that does not burn CPU cycles.
|
||||||
|
* \param [in] qwMicroSeconds The number of microseconds that the thread should sleep for.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
virtual NVFBCRESULT NVFBCAPI NvFBCToSysGPUBasedCPUSleep (__int64 qwMicroSeconds) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Destroys the NVFBCToSys capture session.
|
||||||
|
* \return An applicable ::NVFBCRESULT value.
|
||||||
|
*/
|
||||||
|
virtual NVFBCRESULT NVFBCAPI NvFBCToSysRelease () = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef INvFBCToSys_v3 NvFBCToSys;
|
||||||
|
|
||||||
|
#endif // NVFBC_TO_SYS_H_
|
||||||
Vendored
+2
@@ -0,0 +1,2 @@
|
|||||||
|
/* Windows.h — case-compat shim for the vendor NvFBC header, not our API. */
|
||||||
|
#include <windows.h>
|
||||||
Reference in New Issue
Block a user