diff --git a/Cargo.lock b/Cargo.lock index 29557a4..edc6f11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -125,6 +125,24 @@ dependencies = [ "thiserror", ] +[[package]] +name = "cbindgen" +version = "0.29.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ecb53484c9c167ba674026b656d8a27d7657a58e6066aa902bfb1a4aa00ae20" +dependencies = [ + "heck", + "indexmap", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn", + "tempfile", + "toml", +] + [[package]] name = "cc" version = "1.2.67" @@ -223,6 +241,12 @@ version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + [[package]] name = "errno" version = "0.3.14" @@ -233,6 +257,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -249,18 +279,45 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + [[package]] name = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -344,6 +401,15 @@ dependencies = [ "windows 0.61.3", ] +[[package]] +name = "luminal-vgd-ffi" +version = "0.1.0" +dependencies = [ + "cbindgen", + "luminal-driver-proto", + "luminal-vgd-host", +] + [[package]] name = "luminal-vgd-host" version = "0.1.0" @@ -444,6 +510,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + [[package]] name = "regex" version = "1.13.1" @@ -557,6 +629,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "sharded-slab" version = "0.1.7" @@ -607,6 +688,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom", + "once_cell", + "rustix", + "windows-sys 0.60.2", +] + [[package]] name = "thiserror" version = "2.0.18" @@ -636,6 +730,45 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tracelogging" version = "1.2.4" @@ -1140,6 +1273,18 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" + [[package]] name = "zmij" version = "1.0.23" diff --git a/Cargo.toml b/Cargo.toml index 0802a4b..960c825 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = ["crates/luminal-driver-proto", "crates/luminal-vgd-core", "crates/luminal-vgd-host", "crates/luminal-vgd-driver", "crates/vgd-probe"] +members = ["crates/luminal-driver-proto", "crates/luminal-vgd-core", "crates/luminal-vgd-host", "crates/luminal-vgd-driver", "crates/vgd-probe", "crates/luminal-vgd-ffi"] [workspace.package] edition = "2021" diff --git a/crates/luminal-vgd-ffi/Cargo.toml b/crates/luminal-vgd-ffi/Cargo.toml new file mode 100644 index 0000000..a8d9c50 --- /dev/null +++ b/crates/luminal-vgd-ffi/Cargo.toml @@ -0,0 +1,18 @@ +# SPDX-License-Identifier: AGPL-3.0-only +[package] +name = "luminal-vgd-ffi" +version = "0.1.0" +description = "C ABI over luminal-vgd-host for the LuminalShine (C++) integration: control-device ops and ring consumption. Built as a staticlib for the MSYS2 UCRT64 toolchain (target x86_64-pc-windows-gnullvm)." +edition.workspace = true +license.workspace = true +repository.workspace = true + +[lib] +crate-type = ["staticlib", "lib"] + +[dependencies] +luminal-driver-proto = { path = "../luminal-driver-proto" } +luminal-vgd-host = { path = "../luminal-vgd-host" } + +[build-dependencies] +cbindgen = { version = "0.29", default-features = false } diff --git a/crates/luminal-vgd-ffi/build.rs b/crates/luminal-vgd-ffi/build.rs new file mode 100644 index 0000000..cf63750 --- /dev/null +++ b/crates/luminal-vgd-ffi/build.rs @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: AGPL-3.0-only +//! Generates include/luminal_vgd.h from the FFI surface. The header is +//! committed (LuminalShine's CMake includes it without running cargo); +//! this regenerates it on every build so drift is impossible to miss in +//! review. + +fn main() { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let config = cbindgen::Config { + language: cbindgen::Language::C, + include_guard: Some("LUMINAL_VGD_H".into()), + header: Some( + "/* SPDX-License-Identifier: AGPL-3.0-only\n * Generated by luminal-vgd-ffi's build.rs (cbindgen) — do not edit.\n *\n * Link (MSYS2 UCRT64):\n * cargo build -p luminal-vgd-ffi --release --target x86_64-pc-windows-gnullvm\n * ... -lluminal_vgd_ffi -lcfgmgr32 -lws2_32 -luserenv -lntdll -lbcrypt\n */" + .into(), + ), + cpp_compat: true, + ..Default::default() + }; + match cbindgen::Builder::new() + .with_crate(&crate_dir) + .with_config(config) + .generate() + { + Ok(bindings) => { + bindings.write_to_file(format!("{crate_dir}/include/luminal_vgd.h")); + } + // Never fail the build over header generation (e.g. during a + // syntax-error iteration); the committed header stays. + Err(e) => println!("cargo:warning=cbindgen skipped: {e}"), + } + println!("cargo:rerun-if-changed=src/lib.rs"); +} diff --git a/crates/luminal-vgd-ffi/include/luminal_vgd.h b/crates/luminal-vgd-ffi/include/luminal_vgd.h new file mode 100644 index 0000000..e30b263 --- /dev/null +++ b/crates/luminal-vgd-ffi/include/luminal_vgd.h @@ -0,0 +1,172 @@ +/* SPDX-License-Identifier: AGPL-3.0-only + * Generated by luminal-vgd-ffi's build.rs (cbindgen) — do not edit. + * + * Link (MSYS2 UCRT64): + * cargo build -p luminal-vgd-ffi --release --target x86_64-pc-windows-gnullvm + * ... -lluminal_vgd_ffi -lcfgmgr32 -lws2_32 -luserenv -lntdll -lbcrypt + */ + +#ifndef LUMINAL_VGD_H +#define LUMINAL_VGD_H + +#include +#include +#include +#include + +/** + * OS-level failure (I/O error, device unreachable) as opposed to a + * negative proto `err::*` code from the driver. + */ +#define VGD_ERR_IO -1000 + +typedef struct VgdDeviceHandle VgdDeviceHandle; + +typedef struct VgdRingHandle VgdRingHandle; + +/** + * Handshake results the backend needs for capability gating. + */ +typedef struct VgdCaps { + uint16_t proto_major; + uint16_t proto_minor; + uint32_t driver_build; + uint32_t caps; + uint32_t max_monitors; + uint32_t watchdog_secs; +} VgdCaps; + +/** + * One display mode; `modes[0]` is preferred. Mirrors proto `ModeSpec`. + */ +typedef struct VgdModeSpec { + uint32_t width; + uint32_t height; + uint32_t refresh_millihz; +} VgdModeSpec; + +/** + * Monitor creation parameters (see proto `CreateMonitorRequest` for full + * field semantics; zero means "driver default" where the proto says so). + */ +typedef struct VgdCreateRequest { + uint64_t session_id; + uint64_t display_id; + uint64_t adapter_luid; + uint32_t lease_timeout_ms; + uint32_t bit_depth; + uint32_t hdr; + uint32_t flags; + uint32_t mode_count; + struct VgdModeSpec modes[4]; + uint32_t physical_width_mm; + uint32_t physical_height_mm; + /** + * NUL-padded UTF-16LE. + */ + uint16_t friendly_name[32]; +} VgdCreateRequest; + +typedef struct VgdCreateReply { + uint64_t session_id; + uint64_t display_id; + /** + * `0` or a negative proto `err::*` code. + */ + int32_t result; + uint32_t ring_slots; + uint32_t connector_index; +} VgdCreateReply; + +/** + * Snapshot of the ring header for health/fallback decisions. + */ +typedef struct VgdRingStatus { + uint32_t generation; + /** + * proto `ring_state::*` (1 ACTIVE, 2 REBUILDING, 3 DEAD, 0 uninit). + */ + uint32_t state; + uint64_t latest_sequence; + uint64_t frames_published; + uint64_t frames_dropped; + uint64_t heartbeat_qpc; + uint64_t qpc_frequency; +} VgdRingStatus; + +/** + * A claimed (checked-out) frame. The driver will not overwrite this slot + * until `vgd_ring_release`. + */ +typedef struct VgdFrame { + uint32_t index; + /** + * Bake into the texture name; re-claim if the header generation has + * moved on by the time the texture is opened. + */ + uint32_t generation; + uint64_t sequence; + uint64_t present_qpc; +} VgdFrame; + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Open the LuminalVGD control device. NULL when the driver is absent — + * the caller falls back to another backend. + */ +struct VgdDeviceHandle *vgd_device_open(void); + +void vgd_device_close(struct VgdDeviceHandle *dev); + +int32_t vgd_handshake(struct VgdDeviceHandle *dev, struct VgdCaps *out); + +int32_t vgd_create_monitor(struct VgdDeviceHandle *dev, + const struct VgdCreateRequest *req, + struct VgdCreateReply *out); + +int32_t vgd_destroy_monitor(struct VgdDeviceHandle *dev, uint64_t session_id); + +/** + * Feed the per-lease watchdog; call at least once per `watchdog_secs`. + */ +int32_t vgd_ping(struct VgdDeviceHandle *dev, uint64_t session_id); + +/** + * Map the frame ring for a created monitor. NULL until the driver has + * created the section (retry briefly after create). + */ +struct VgdRingHandle *vgd_ring_open(uint64_t session_id, uint32_t ring_slots); + +void vgd_ring_close(struct VgdRingHandle *ring); + +int32_t vgd_ring_status(struct VgdRingHandle *ring, struct VgdRingStatus *out); + +/** + * Claim the freshest published frame. Returns `true` and fills `out` + * when a frame was checked out; `false` when nothing is published. + */ +bool vgd_ring_claim(struct VgdRingHandle *ring, struct VgdFrame *out); + +/** + * Release a claimed frame back to the driver. Exactly once per claim. + */ +void vgd_ring_release(struct VgdRingHandle *ring, uint32_t index); + +/** + * Compose the named shared-texture name for (session, generation, slot) + * into `out` (capacity 96 u16s, NUL-padded). Returns the char count. + * Open with `ID3D11Device1::OpenSharedResourceByName`. + */ +uint32_t vgd_slot_texture_name(uint64_t session_id, + uint32_t generation, + uint32_t slot, + uint16_t *out); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus + +#endif /* LUMINAL_VGD_H */ diff --git a/crates/luminal-vgd-ffi/src/lib.rs b/crates/luminal-vgd-ffi/src/lib.rs new file mode 100644 index 0000000..20d7fe7 --- /dev/null +++ b/crates/luminal-vgd-ffi/src/lib.rs @@ -0,0 +1,312 @@ +// SPDX-License-Identifier: AGPL-3.0-only +//! C ABI for LuminalShine (C++): control-device operations and ring +//! consumption, wrapping `luminal-vgd-host`. This is a *conversion* +//! boundary — the host↔driver wire ABI still lives only in +//! `luminal-driver-proto`; the `Vgd*` structs here exist so cbindgen can +//! emit a self-contained C header. +//! +//! Conventions: +//! - Handles are opaque pointers (`VgdDeviceHandle`, `VgdRingHandle`); +//! every `*_open` has exactly one `*_close`. +//! - Functions return `0` on success, a negative `err::*` proto code on +//! driver-refused, or [`VGD_ERR_IO`] on OS-level failure. +//! - All entry points are panic-proof (`catch_unwind`): a bug in this +//! layer degrades to an error code, never unwinds into C++. +//! +//! Texture access stays on the C++ side: claim a frame, compose the +//! texture name with [`vgd_slot_texture_name`], `OpenSharedResourceByName` +//! on the encoder's D3D11 device, keyed-mutex acquire key 1 (bounded!), +//! use, release to key 1, then [`vgd_ring_release`]. + +#![cfg(windows)] +#![allow(clippy::missing_safety_doc)] + +use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::ptr::null_mut; + +use luminal_driver_proto::{names, CreateMonitorRequest, ModeSpec, MAX_MODES_PER_MONITOR}; +use luminal_vgd_host::device::{RingView, VgdDevice}; + +/// OS-level failure (I/O error, device unreachable) as opposed to a +/// negative proto `err::*` code from the driver. +pub const VGD_ERR_IO: i32 = -1000; + +pub struct VgdDeviceHandle(VgdDevice); +pub struct VgdRingHandle(RingView); + +/// Handshake results the backend needs for capability gating. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdCaps { + pub proto_major: u16, + pub proto_minor: u16, + pub driver_build: u32, + pub caps: u32, + pub max_monitors: u32, + pub watchdog_secs: u32, +} + +/// One display mode; `modes[0]` is preferred. Mirrors proto `ModeSpec`. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdModeSpec { + pub width: u32, + pub height: u32, + pub refresh_millihz: u32, +} + +/// Monitor creation parameters (see proto `CreateMonitorRequest` for full +/// field semantics; zero means "driver default" where the proto says so). +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdCreateRequest { + pub session_id: u64, + pub display_id: u64, + pub adapter_luid: u64, + pub lease_timeout_ms: u32, + pub bit_depth: u32, + pub hdr: u32, + pub flags: u32, + pub mode_count: u32, + pub modes: [VgdModeSpec; 4], + pub physical_width_mm: u32, + pub physical_height_mm: u32, + /// NUL-padded UTF-16LE. + pub friendly_name: [u16; 32], +} + +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdCreateReply { + pub session_id: u64, + pub display_id: u64, + /// `0` or a negative proto `err::*` code. + pub result: i32, + pub ring_slots: u32, + pub connector_index: u32, +} + +/// Snapshot of the ring header for health/fallback decisions. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdRingStatus { + pub generation: u32, + /// proto `ring_state::*` (1 ACTIVE, 2 REBUILDING, 3 DEAD, 0 uninit). + pub state: u32, + pub latest_sequence: u64, + pub frames_published: u64, + pub frames_dropped: u64, + pub heartbeat_qpc: u64, + pub qpc_frequency: u64, +} + +/// A claimed (checked-out) frame. The driver will not overwrite this slot +/// until `vgd_ring_release`. +#[repr(C)] +#[derive(Clone, Copy)] +pub struct VgdFrame { + pub index: u32, + /// Bake into the texture name; re-claim if the header generation has + /// moved on by the time the texture is opened. + pub generation: u32, + pub sequence: u64, + pub present_qpc: u64, +} + +fn guarded(default: T, f: impl FnOnce() -> T) -> T { + catch_unwind(AssertUnwindSafe(f)).unwrap_or(default) +} + +/// Open the LuminalVGD control device. NULL when the driver is absent — +/// the caller falls back to another backend. +#[no_mangle] +pub extern "C" fn vgd_device_open() -> *mut VgdDeviceHandle { + guarded(null_mut(), || match VgdDevice::open_first() { + Ok(dev) => Box::into_raw(Box::new(VgdDeviceHandle(dev))), + Err(_) => null_mut(), + }) +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_device_close(dev: *mut VgdDeviceHandle) { + if !dev.is_null() { + drop(Box::from_raw(dev)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_handshake(dev: *mut VgdDeviceHandle, out: *mut VgdCaps) -> i32 { + if dev.is_null() || out.is_null() { + return VGD_ERR_IO; + } + guarded(VGD_ERR_IO, || match (*dev).0.handshake() { + Ok(h) => { + *out = VgdCaps { + proto_major: h.driver_proto_major, + proto_minor: h.driver_proto_minor, + driver_build: h.driver_build, + caps: h.caps, + max_monitors: h.max_monitors, + watchdog_secs: h.watchdog_secs, + }; + 0 + } + Err(_) => VGD_ERR_IO, + }) +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_create_monitor( + dev: *mut VgdDeviceHandle, + req: *const VgdCreateRequest, + out: *mut VgdCreateReply, +) -> i32 { + if dev.is_null() || req.is_null() || out.is_null() { + return VGD_ERR_IO; + } + guarded(VGD_ERR_IO, || { + let r = &*req; + let mut modes = [ModeSpec::default(); MAX_MODES_PER_MONITOR as usize]; + for (dst, src) in modes.iter_mut().zip(r.modes.iter()) { + *dst = ModeSpec { + width: src.width, + height: src.height, + refresh_millihz: src.refresh_millihz, + }; + } + let proto_req = CreateMonitorRequest { + session_id: r.session_id, + display_id: r.display_id, + adapter_luid: r.adapter_luid, + lease_timeout_ms: r.lease_timeout_ms, + bit_depth: r.bit_depth, + hdr: r.hdr, + edid_serial: 0, + flags: r.flags, + mode_count: r.mode_count, + modes, + physical_width_mm: r.physical_width_mm, + physical_height_mm: r.physical_height_mm, + friendly_name: r.friendly_name, + }; + match (*dev).0.create_monitor(&proto_req) { + Ok(reply) => { + *out = VgdCreateReply { + session_id: reply.session_id, + display_id: reply.display_id, + result: reply.result, + ring_slots: reply.ring_slots, + connector_index: reply.connector_index, + }; + 0 + } + Err(_) => VGD_ERR_IO, + } + }) +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_destroy_monitor(dev: *mut VgdDeviceHandle, session_id: u64) -> i32 { + if dev.is_null() { + return VGD_ERR_IO; + } + guarded(VGD_ERR_IO, || { + (*dev).0.destroy_monitor(session_id).unwrap_or(VGD_ERR_IO) + }) +} + +/// Feed the per-lease watchdog; call at least once per `watchdog_secs`. +#[no_mangle] +pub unsafe extern "C" fn vgd_ping(dev: *mut VgdDeviceHandle, session_id: u64) -> i32 { + if dev.is_null() { + return VGD_ERR_IO; + } + guarded(VGD_ERR_IO, || (*dev).0.ping(session_id).unwrap_or(VGD_ERR_IO)) +} + +/// Map the frame ring for a created monitor. NULL until the driver has +/// created the section (retry briefly after create). +#[no_mangle] +pub extern "C" fn vgd_ring_open(session_id: u64, ring_slots: u32) -> *mut VgdRingHandle { + guarded(null_mut(), || match RingView::open(session_id, ring_slots) { + Ok(view) => Box::into_raw(Box::new(VgdRingHandle(view))), + Err(_) => null_mut(), + }) +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_ring_close(ring: *mut VgdRingHandle) { + if !ring.is_null() { + drop(Box::from_raw(ring)); + } +} + +#[no_mangle] +pub unsafe extern "C" fn vgd_ring_status(ring: *mut VgdRingHandle, out: *mut VgdRingStatus) -> i32 { + if ring.is_null() || out.is_null() { + return VGD_ERR_IO; + } + guarded(VGD_ERR_IO, || { + let h = (*ring).0.header(); + *out = VgdRingStatus { + generation: h.ring_generation, + state: h.state, + latest_sequence: h.latest_sequence, + frames_published: h.frames_published, + frames_dropped: h.frames_dropped, + heartbeat_qpc: h.driver_heartbeat_qpc, + qpc_frequency: h.qpc_frequency, + }; + 0 + }) +} + +/// Claim the freshest published frame. Returns `true` and fills `out` +/// when a frame was checked out; `false` when nothing is published. +#[no_mangle] +pub unsafe extern "C" fn vgd_ring_claim(ring: *mut VgdRingHandle, out: *mut VgdFrame) -> bool { + if ring.is_null() || out.is_null() { + return false; + } + guarded(false, || match (*ring).0.claim_latest() { + Some(frame) => { + *out = VgdFrame { + index: frame.index, + generation: frame.generation, + sequence: frame.sequence, + present_qpc: frame.present_qpc, + }; + true + } + None => false, + }) +} + +/// Release a claimed frame back to the driver. Exactly once per claim. +#[no_mangle] +pub unsafe extern "C" fn vgd_ring_release(ring: *mut VgdRingHandle, index: u32) { + if !ring.is_null() { + guarded((), || (*ring).0.release(index)); + } +} + +/// Compose the named shared-texture name for (session, generation, slot) +/// into `out` (capacity 96 u16s, NUL-padded). Returns the char count. +/// Open with `ID3D11Device1::OpenSharedResourceByName`. +#[no_mangle] +pub unsafe extern "C" fn vgd_slot_texture_name( + session_id: u64, + generation: u32, + slot: u32, + out: *mut u16, +) -> u32 { + if out.is_null() { + return 0; + } + guarded(0, || { + let mut buf = [0u16; 96]; + let len = names::slot_texture_name(session_id, generation, slot, &mut buf); + std::ptr::copy_nonoverlapping(buf.as_ptr(), out, 96); + len as u32 + }) +} diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 5858b2f..cccbcd4 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -80,6 +80,23 @@ and runs Inf2Cat. Environment knobs: `EWDK_ROOT` (default `C:\`), - **cargo-wdk 0.1.1** installed for scaffolding reference; the actual build is plain cargo + wdk-build. +## FFI staticlib (LuminalShine integration) + +LuminalShine (C++, MSYS2 UCRT64 clang) links `luminal-vgd-ffi` as a +static library. Build with the UCRT-compatible GNU target and link the +Rust runtime's system deps: + +``` +rustup target add x86_64-pc-windows-gnullvm # one-time +cargo build -p luminal-vgd-ffi --release --target x86_64-pc-windows-gnullvm +# link: -lluminal_vgd_ffi -lcfgmgr32 -lws2_32 -luserenv -lntdll -lbcrypt +``` + +The C header is generated by cbindgen into +`crates/luminal-vgd-ffi/include/luminal_vgd.h` (committed; regenerated on +every build). Do NOT use the MSVC-target `.lib` for LuminalShine — the +CRTs differ. + ## Installing (dev) After signing (below): `scripts\install-driver.ps1` from an elevated