Files
fluxer-desktop-patched/fluxer_desktop/native/win-game-capture/src/dxgi_capture.rs
T
brenden 682afacd30 Add native self-hosted instance connection to fluxer_desktop
Trimmed monorepo checkout (fluxer_desktop + packages/voice_engine_v2 +
tools/ci) with a "Connect to a Different Server" menu item and popout
that lets the desktop app switch to any self-hosted Fluxer instance,
plus fixes for well-known discovery on single-domain self-hosted
deployments and a false-positive ERR_ABORTED on same-origin client
redirects during the switch. Defaults to chat.fluxr.chat and uses an
isolated userData directory from the official build.
2026-07-01 18:22:43 -04:00

1106 lines
36 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-or-later
#[cfg(target_os = "windows")]
use windows::{
Win32::{
Foundation::{HMODULE, HWND, RECT},
Graphics::{
Direct3D::D3D_DRIVER_TYPE_UNKNOWN,
Direct3D11::{
D3D11_BOX, D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_RESOURCE_MISC_SHARED,
D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_DEFAULT, D3D11CreateDevice,
ID3D11Device, ID3D11DeviceContext, ID3D11Resource, ID3D11Texture2D,
},
Dxgi::{
Common::{
DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_MODE_ROTATION, DXGI_MODE_ROTATION_IDENTITY,
DXGI_MODE_ROTATION_UNSPECIFIED, DXGI_SAMPLE_DESC,
},
CreateDXGIFactory1, DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_MORE_DATA,
DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_FRAME_INFO, DXGI_OUTDUPL_POINTER_SHAPE_INFO,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR,
DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME, IDXGIAdapter, IDXGIAdapter1,
IDXGIDevice, IDXGIFactory1, IDXGIOutput, IDXGIOutput1, IDXGIOutputDuplication,
IDXGIResource,
},
Gdi::{HMONITOR, MONITOR_DEFAULTTONULL, MonitorFromWindow},
},
UI::WindowsAndMessaging::{GetWindowRect, IsWindow},
},
core::Interface,
};
#[cfg(target_os = "windows")]
use crate::{
CaptureInner, emit_lifecycle, emit_shared_texture_frame, note_media_frame_without_sink,
resolve_frame_sink,
};
#[cfg(target_os = "windows")]
use std::sync::{Arc, atomic::Ordering};
#[cfg(target_os = "windows")]
pub fn parse_window_source_id(source_id: &str, source_kind: &str) -> Option<HWND> {
if source_kind != "window" {
return None;
}
let hwnd_str = source_id.strip_prefix("window:")?;
let hwnd_token = hwnd_str.split(':').next()?;
let hwnd_num = parse_hwnd_token(hwnd_token)?;
let hwnd = HWND(hwnd_num as *mut _);
if unsafe { IsWindow(Some(hwnd)) }.as_bool() {
Some(hwnd)
} else {
None
}
}
#[cfg(target_os = "windows")]
fn parse_hwnd_token(token: &str) -> Option<isize> {
if let Some(hex) = token
.strip_prefix("0x")
.or_else(|| token.strip_prefix("0X"))
{
return isize::from_str_radix(hex, 16).ok();
}
token.parse().ok()
}
#[cfg(target_os = "windows")]
fn validate_output_rotation(rotation: DXGI_MODE_ROTATION) -> Result<(), String> {
if rotation.0 == DXGI_MODE_ROTATION_UNSPECIFIED.0 || rotation.0 == DXGI_MODE_ROTATION_IDENTITY.0
{
return Ok(());
}
Err(format!(
"Rotated DXGI outputs are not supported by this fallback (rotation={})",
rotation.0
))
}
#[cfg(target_os = "windows")]
pub struct DxgiCaptureSession {
device: ID3D11Device,
context: ID3D11DeviceContext,
hwnd: HWND,
capture_width: u32,
capture_height: u32,
requested_width: Option<u32>,
requested_height: Option<u32>,
}
#[cfg(target_os = "windows")]
unsafe impl Send for DxgiCaptureSession {}
#[cfg(target_os = "windows")]
#[derive(Default)]
struct PointerShapeCache {
buffer: Vec<u8>,
info: Option<DXGI_OUTDUPL_POINTER_SHAPE_INFO>,
}
#[cfg(target_os = "windows")]
enum PointerShapeUpdate {
Ok,
AccessLost,
Error(String),
}
#[cfg(target_os = "windows")]
struct DuplicationState {
duplication: IDXGIOutputDuplication,
monitor: HMONITOR,
monitor_rect: RECT,
shared_output: Option<SharedTextureOutput>,
cap_w: u32,
cap_h: u32,
out_w: u32,
out_h: u32,
}
#[cfg(target_os = "windows")]
pub(crate) const SHARED_OUTPUT_SLOT_COUNT: usize = 3;
#[cfg(target_os = "windows")]
pub(crate) struct SharedOutputSlot {
pub(crate) texture: ID3D11Texture2D,
pub(crate) handle: u64,
}
#[cfg(target_os = "windows")]
pub(crate) struct SharedTextureOutput {
pub(crate) slots: [SharedOutputSlot; SHARED_OUTPUT_SLOT_COUNT],
pub(crate) slot_cursor: usize,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) dxgi_format: u32,
}
#[cfg(target_os = "windows")]
impl SharedTextureOutput {
pub(crate) fn next_slot_index(&mut self) -> usize {
assert!(
self.slot_cursor < SHARED_OUTPUT_SLOT_COUNT,
"slot cursor in range"
);
assert!(self.width > 0, "shared output width positive");
let slot_index = self.slot_cursor;
self.slot_cursor = (slot_index + 1) % SHARED_OUTPUT_SLOT_COUNT;
slot_index
}
}
#[cfg(target_os = "windows")]
impl DxgiCaptureSession {
pub fn new(
hwnd: HWND,
requested_width: Option<u32>,
requested_height: Option<u32>,
) -> Result<Self, String> {
let adapter = find_dxgi_adapter_for_window(hwnd)?;
let (device, context) = create_d3d11_device(&adapter)?;
let mut window_rect = RECT::default();
unsafe {
GetWindowRect(hwnd, &mut window_rect).map_err(|e| format!("GetWindowRect: {e}"))?;
}
let width = (window_rect.right - window_rect.left).max(1) as u32;
let height = (window_rect.bottom - window_rect.top).max(1) as u32;
let (capture_width, capture_height) =
resolve_output_size(width, height, requested_width, requested_height);
Ok(Self {
device,
context,
hwnd,
capture_width,
capture_height,
requested_width,
requested_height,
})
}
pub fn capture_width(&self) -> u32 {
self.capture_width
}
pub fn capture_height(&self) -> u32 {
self.capture_height
}
}
#[cfg(target_os = "windows")]
fn find_dxgi_adapter_for_window(hwnd: HWND) -> Result<IDXGIAdapter1, String> {
let target_monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL) };
if target_monitor.is_invalid() {
return Err("MonitorFromWindow returned null".into());
}
let factory = unsafe { CreateDXGIFactory1::<IDXGIFactory1>() }
.map_err(|e| format!("CreateDXGIFactory1: {e}"))?;
let mut adapter_index = 0u32;
loop {
let adapter = match unsafe { factory.EnumAdapters1(adapter_index) } {
Ok(adapter) => adapter,
Err(_) => break,
};
adapter_index += 1;
let mut output_index = 0u32;
loop {
let output: IDXGIOutput = match unsafe { adapter.EnumOutputs(output_index) } {
Ok(output) => output,
Err(_) => break,
};
output_index += 1;
let desc = unsafe { output.GetDesc().map_err(|e| format!("GetDesc: {e}"))? };
if desc.Monitor == target_monitor {
validate_output_rotation(desc.Rotation)?;
return Ok(adapter);
}
}
}
Err("Could not find DXGI adapter for the target window's monitor".into())
}
#[cfg(target_os = "windows")]
fn create_d3d11_device(
adapter: &IDXGIAdapter1,
) -> Result<(ID3D11Device, ID3D11DeviceContext), String> {
let adapter: IDXGIAdapter = adapter
.cast()
.map_err(|e| format!("IDXGIAdapter cast: {e}"))?;
let mut device = None;
let mut context = None;
unsafe {
D3D11CreateDevice(
Some(&adapter),
D3D_DRIVER_TYPE_UNKNOWN,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
D3D11_SDK_VERSION,
Some(&mut device),
None,
Some(&mut context),
)
.map_err(|e| format!("D3D11CreateDevice: {e}"))?;
}
let device = device.ok_or("D3D11 device was None")?;
let context = context.ok_or("D3D11 context was None")?;
if let Ok(dxgi_device) = device.cast::<IDXGIDevice>() {
let _ = unsafe { dxgi_device.SetGPUThreadPriority(7) };
}
Ok((device, context))
}
#[cfg(target_os = "windows")]
fn create_output_duplication(
device: &ID3D11Device,
hwnd: HWND,
) -> Result<(IDXGIOutputDuplication, RECT, HMONITOR), String> {
let dxgi_device: IDXGIDevice = device
.cast()
.map_err(|e| format!("IDXGIDevice cast: {e}"))?;
let adapter: IDXGIAdapter1 = unsafe { dxgi_device.GetAdapter() }
.map_err(|e| format!("GetAdapter: {e}"))?
.cast()
.map_err(|e| format!("IDXGIAdapter1 cast: {e}"))?;
let target_monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL) };
if target_monitor.is_invalid() {
return Err("MonitorFromWindow returned null".into());
}
let mut output_index = 0u32;
loop {
let output: IDXGIOutput = match unsafe { adapter.EnumOutputs(output_index) } {
Ok(o) => o,
Err(_) => break,
};
output_index += 1;
let desc = unsafe { output.GetDesc().map_err(|e| format!("GetDesc: {e}"))? };
if desc.Monitor == target_monitor {
validate_output_rotation(desc.Rotation)?;
let output1: IDXGIOutput1 = output
.cast()
.map_err(|e| format!("IDXGIOutput1 cast: {e}"))?;
let duplication = unsafe {
output1
.DuplicateOutput(device)
.map_err(|e| format!("DuplicateOutput: {e}"))?
};
return Ok((duplication, desc.DesktopCoordinates, target_monitor));
}
}
Err("Could not find DXGI output for the target window's monitor".into())
}
#[cfg(target_os = "windows")]
pub(crate) fn create_shared_output_texture(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<SharedTextureOutput, String> {
assert!(width > 0, "shared output width positive");
assert!(height > 0, "shared output height positive");
let mut slots = Vec::with_capacity(SHARED_OUTPUT_SLOT_COUNT);
for _ in 0..SHARED_OUTPUT_SLOT_COUNT {
slots.push(create_shared_output_slot(device, width, height)?);
}
assert_eq!(
slots.len(),
SHARED_OUTPUT_SLOT_COUNT,
"all shared output slots created"
);
let slots = <[SharedOutputSlot; SHARED_OUTPUT_SLOT_COUNT]>::try_from(slots)
.map_err(|_| "shared output slot count mismatch".to_string())?;
Ok(SharedTextureOutput {
slots,
slot_cursor: 0,
width,
height,
dxgi_format: DXGI_FORMAT_B8G8R8A8_UNORM.0 as u32,
})
}
#[cfg(target_os = "windows")]
fn create_shared_output_slot(
device: &ID3D11Device,
width: u32,
height: u32,
) -> Result<SharedOutputSlot, String> {
assert!(width > 0, "shared output slot width positive");
assert!(height > 0, "shared output slot height positive");
let desc = D3D11_TEXTURE2D_DESC {
Width: width,
Height: height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_DEFAULT,
BindFlags: Default::default(),
CPUAccessFlags: 0,
MiscFlags: D3D11_RESOURCE_MISC_SHARED.0 as u32,
};
let mut texture = None;
let texture = unsafe {
device
.CreateTexture2D(&desc, None, Some(&mut texture))
.map_err(|e| format!("CreateTexture2D shared output: {e}"))?;
texture.ok_or("D3D11 shared output texture was None")?
};
let resource: IDXGIResource = texture
.cast()
.map_err(|e| format!("IDXGIResource shared output cast: {e}"))?;
let handle = unsafe { resource.GetSharedHandle() }
.map_err(|e| format!("GetSharedHandle shared output: {e}"))?;
if handle.is_invalid() {
return Err("GetSharedHandle shared output returned an invalid handle".into());
}
Ok(SharedOutputSlot {
texture,
handle: handle.0 as usize as u64,
})
}
#[cfg(target_os = "windows")]
pub(crate) fn resolve_output_size(
src_width: u32,
src_height: u32,
requested_width: Option<u32>,
requested_height: Option<u32>,
) -> (u32, u32) {
let max_width = requested_width.unwrap_or(src_width).max(1);
let max_height = requested_height.unwrap_or(src_height).max(1);
let scale = (max_width as f64 / src_width.max(1) as f64)
.min(max_height as f64 / src_height.max(1) as f64)
.min(1.0);
if scale >= 1.0 {
return (src_width.max(1), src_height.max(1));
}
(
((src_width as f64 * scale).floor() as u32).max(1),
((src_height as f64 * scale).floor() as u32).max(1),
)
}
#[cfg(target_os = "windows")]
pub(crate) fn wall_clock_us() -> i64 {
use std::time::{SystemTime, UNIX_EPOCH};
SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_micros() as i64)
.unwrap_or(0)
}
#[cfg(target_os = "windows")]
pub(crate) fn capture_timestamp_us(capture_start: std::time::Instant) -> i64 {
let elapsed_us = capture_start.elapsed().as_micros();
assert!(elapsed_us < i64::MAX as u128);
elapsed_us as i64
}
pub(crate) fn pacing_sleep_and_next_deadline(
now: std::time::Instant,
deadline: std::time::Instant,
frame_interval: std::time::Duration,
) -> (std::time::Duration, std::time::Instant) {
assert!(frame_interval > std::time::Duration::ZERO);
if now < deadline {
let sleep_duration = deadline - now;
assert!(sleep_duration <= frame_interval);
(sleep_duration, deadline + frame_interval)
} else {
(std::time::Duration::ZERO, now + frame_interval)
}
}
#[cfg(target_os = "windows")]
pub fn capture_loop(inner: &Arc<CaptureInner>, frame_interval: std::time::Duration) {
let hwnd;
let device;
let context;
let requested_width;
let requested_height;
{
let guard = inner.session.lock();
let session = match guard.as_ref() {
Some(s) => s,
None => {
emit_lifecycle(inner, "closed-clean", "no session");
return;
}
};
hwnd = session.hwnd;
device = session.device.clone();
context = session.context.clone();
requested_width = session.requested_width;
requested_height = session.requested_height;
}
let capture_id = inner.capture_id.lock().clone();
let mut duplication_state: Option<DuplicationState> = None;
let mut pointer_shape = PointerShapeCache::default();
let mut recreate_backoff = std::time::Duration::from_millis(100);
let capture_start = std::time::Instant::now();
let mut next_frame_deadline = capture_start + frame_interval;
while inner.running.load(Ordering::Acquire) {
if !unsafe { IsWindow(Some(hwnd)) }.as_bool() {
emit_lifecycle(inner, "closed", "window closed");
break;
}
if duplication_state.is_none() {
match setup_duplication(&device, hwnd, requested_width, requested_height) {
Ok(state) => {
duplication_state = Some(state);
pointer_shape = PointerShapeCache::default();
}
Err(e) => {
if !inner.running.load(Ordering::Acquire) {
break;
}
emit_lifecycle(
inner,
"error",
&format!("Failed to create DXGI output duplication: {e}"),
);
std::thread::sleep(recreate_backoff);
recreate_backoff =
(recreate_backoff * 2).min(std::time::Duration::from_secs(2));
continue;
}
}
}
let Some(state) = duplication_state.as_mut() else {
continue;
};
match acquire_and_emit_frame(
inner,
&context,
state,
hwnd,
capture_id.as_deref(),
&mut pointer_shape,
capture_start,
) {
FrameResult::Ok => {
recreate_backoff = std::time::Duration::from_millis(100);
}
FrameResult::Timeout => {}
FrameResult::Resized { width, height } => {
state.cap_w = width;
state.cap_h = height;
let (out_w, out_h) =
resolve_output_size(width, height, requested_width, requested_height);
state.out_w = out_w;
state.out_h = out_h;
state.shared_output = create_shared_output_texture(&device, width, height).ok();
}
FrameResult::AccessLost => {
duplication_state = None;
pointer_shape = PointerShapeCache::default();
std::thread::sleep(recreate_backoff);
recreate_backoff = (recreate_backoff * 2).min(std::time::Duration::from_secs(2));
continue;
}
FrameResult::WindowGone => {
emit_lifecycle(inner, "closed", "window closed during capture");
break;
}
FrameResult::Error(e) => {
emit_lifecycle(inner, "error", &e);
{
let mut guard = inner.fallback.lock();
if let Some(tracker) = guard.as_mut() {
let _ = tracker.observe(crate::fallback::FailureSignature::DeviceLost);
}
}
duplication_state = None;
pointer_shape = PointerShapeCache::default();
continue;
}
}
let now = std::time::Instant::now();
let (sleep_duration, deadline) =
pacing_sleep_and_next_deadline(now, next_frame_deadline, frame_interval);
next_frame_deadline = deadline;
if sleep_duration > std::time::Duration::ZERO {
std::thread::sleep(sleep_duration);
}
}
inner.running.store(false, Ordering::Release);
emit_lifecycle(inner, "closed-clean", "capture stopped");
}
#[cfg(target_os = "windows")]
fn setup_duplication(
device: &ID3D11Device,
hwnd: HWND,
requested_width: Option<u32>,
requested_height: Option<u32>,
) -> Result<DuplicationState, String> {
let (duplication, monitor_rect, monitor) = create_output_duplication(device, hwnd)?;
let mut window_rect = RECT::default();
unsafe {
GetWindowRect(hwnd, &mut window_rect).map_err(|e| format!("GetWindowRect: {e}"))?;
}
let crop_left = (window_rect.left - monitor_rect.left).max(0) as u32;
let crop_top = (window_rect.top - monitor_rect.top).max(0) as u32;
let monitor_width = (monitor_rect.right - monitor_rect.left) as u32;
let monitor_height = (monitor_rect.bottom - monitor_rect.top) as u32;
let crop_right = ((window_rect.right - monitor_rect.left) as u32).min(monitor_width);
let crop_bottom = ((window_rect.bottom - monitor_rect.top) as u32).min(monitor_height);
let cap_w = crop_right.saturating_sub(crop_left).max(1);
let cap_h = crop_bottom.saturating_sub(crop_top).max(1);
let (out_w, out_h) = resolve_output_size(cap_w, cap_h, requested_width, requested_height);
let shared_output = create_shared_output_texture(device, cap_w, cap_h).ok();
Ok(DuplicationState {
duplication,
monitor,
monitor_rect,
shared_output,
cap_w,
cap_h,
out_w,
out_h,
})
}
#[cfg(target_os = "windows")]
enum FrameResult {
Ok,
Timeout,
Resized { width: u32, height: u32 },
AccessLost,
WindowGone,
Error(String),
}
#[cfg(target_os = "windows")]
fn acquire_and_emit_frame(
inner: &Arc<CaptureInner>,
context: &ID3D11DeviceContext,
state: &mut DuplicationState,
hwnd: HWND,
capture_id: Option<&str>,
_pointer_shape: &mut PointerShapeCache,
capture_start: std::time::Instant,
) -> FrameResult {
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
let mut resource = None;
match unsafe {
state
.duplication
.AcquireNextFrame(50, &mut frame_info, &mut resource)
} {
Ok(()) => {}
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => return FrameResult::Timeout,
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => return FrameResult::AccessLost,
Err(e) => return FrameResult::Error(format!("AcquireNextFrame: {e}")),
}
let desktop_texture: ID3D11Texture2D = match resource
.as_ref()
.and_then(|r| r.cast::<ID3D11Texture2D>().ok())
{
Some(t) => t,
None => {
let _ = unsafe { state.duplication.ReleaseFrame() };
return FrameResult::Error("Failed to cast desktop resource to ID3D11Texture2D".into());
}
};
if !unsafe { IsWindow(Some(hwnd)) }.as_bool() {
let _ = unsafe { state.duplication.ReleaseFrame() };
return FrameResult::WindowGone;
}
let mut window_rect = RECT::default();
if unsafe { GetWindowRect(hwnd, &mut window_rect) }.is_err() {
let _ = unsafe { state.duplication.ReleaseFrame() };
return FrameResult::WindowGone;
}
let monitor_rect = &state.monitor_rect;
let crop_left = (window_rect.left - monitor_rect.left).max(0) as u32;
let crop_top = (window_rect.top - monitor_rect.top).max(0) as u32;
let monitor_width = (monitor_rect.right - monitor_rect.left) as u32;
let monitor_height = (monitor_rect.bottom - monitor_rect.top) as u32;
let crop_right = ((window_rect.right - monitor_rect.left) as u32).min(monitor_width);
let crop_bottom = ((window_rect.bottom - monitor_rect.top) as u32).min(monitor_height);
let cur_w = crop_right.saturating_sub(crop_left).max(1);
let cur_h = crop_bottom.saturating_sub(crop_top).max(1);
if cur_w != state.cap_w || cur_h != state.cap_h {
let current_monitor = unsafe { MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL) };
let _ = unsafe { state.duplication.ReleaseFrame() };
if current_monitor != state.monitor {
return FrameResult::AccessLost;
}
return FrameResult::Resized {
width: cur_w,
height: cur_h,
};
}
let src_box = D3D11_BOX {
left: crop_left,
top: crop_top,
front: 0,
right: crop_left + state.cap_w,
bottom: crop_top + state.cap_h,
back: 1,
};
let desktop_resource: ID3D11Resource = match desktop_texture.cast() {
Ok(resource) => resource,
Err(e) => {
let _ = unsafe { state.duplication.ReleaseFrame() };
return FrameResult::Error(format!("ID3D11Resource desktop cast: {e}"));
}
};
let result = emit_acquired_frame(
inner,
context,
state,
capture_id,
&desktop_resource,
&src_box,
capture_start,
);
let _ = unsafe { state.duplication.ReleaseFrame() };
result
}
#[cfg(target_os = "windows")]
fn emit_acquired_frame(
inner: &Arc<CaptureInner>,
context: &ID3D11DeviceContext,
state: &mut DuplicationState,
capture_id: Option<&str>,
desktop_resource: &ID3D11Resource,
src_box: &D3D11_BOX,
capture_start: std::time::Instant,
) -> FrameResult {
assert!(src_box.right > src_box.left, "source box width positive");
assert!(src_box.bottom > src_box.top, "source box height positive");
let Some(frame_sink) = resolve_frame_sink(inner, capture_id) else {
note_media_frame_without_sink(
inner,
"DXGI frame dropped because no native frame sink is registered",
);
return FrameResult::Ok;
};
if state.out_w != state.cap_w || state.out_h != state.cap_h {
return FrameResult::Error(
"DXGI native bus scaling requires a GPU scaler; refusing CPU readback fallback".into(),
);
}
let Some(shared_output) = state.shared_output.as_mut() else {
return FrameResult::Error("DXGI shared texture output unavailable".into());
};
let slot_index = shared_output.next_slot_index();
let slot = &shared_output.slots[slot_index];
let output_resource: ID3D11Resource = match slot.texture.cast() {
Ok(resource) => resource,
Err(e) => {
return FrameResult::Error(format!("ID3D11Resource shared output cast: {e}"));
}
};
unsafe {
context.CopySubresourceRegion(
&output_resource,
0,
0,
0,
0,
desktop_resource,
0,
Some(src_box),
);
context.Flush();
}
let _ = emit_shared_texture_frame(
inner,
&frame_sink,
slot.handle,
shared_output.width,
shared_output.height,
shared_output.dxgi_format,
capture_timestamp_us(capture_start),
);
FrameResult::Ok
}
#[cfg(target_os = "windows")]
fn update_pointer_shape(
duplication: &IDXGIOutputDuplication,
frame_info: &DXGI_OUTDUPL_FRAME_INFO,
cache: &mut PointerShapeCache,
) -> PointerShapeUpdate {
if frame_info.PointerShapeBufferSize == 0 {
return PointerShapeUpdate::Ok;
}
let buffer_size = frame_info.PointerShapeBufferSize as usize;
if cache.buffer.len() < buffer_size {
cache.buffer.resize(buffer_size, 0);
}
let mut required_size = 0u32;
let mut shape_info = DXGI_OUTDUPL_POINTER_SHAPE_INFO::default();
let result = unsafe {
duplication.GetFramePointerShape(
cache.buffer.len() as u32,
cache.buffer.as_mut_ptr().cast(),
&mut required_size,
&mut shape_info,
)
};
match result {
Ok(()) => {
cache.buffer.truncate(required_size as usize);
cache.info = Some(shape_info);
PointerShapeUpdate::Ok
}
Err(e) if e.code() == DXGI_ERROR_MORE_DATA && required_size > cache.buffer.len() as u32 => {
cache.buffer.resize(required_size as usize, 0);
let result = unsafe {
duplication.GetFramePointerShape(
cache.buffer.len() as u32,
cache.buffer.as_mut_ptr().cast(),
&mut required_size,
&mut shape_info,
)
};
match result {
Ok(()) => {
cache.buffer.truncate(required_size as usize);
cache.info = Some(shape_info);
PointerShapeUpdate::Ok
}
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => PointerShapeUpdate::AccessLost,
Err(e) => PointerShapeUpdate::Error(format!("GetFramePointerShape: {e}")),
}
}
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => PointerShapeUpdate::AccessLost,
Err(e) => PointerShapeUpdate::Error(format!("GetFramePointerShape: {e}")),
}
}
#[cfg(target_os = "windows")]
#[allow(clippy::too_many_arguments)]
fn composite_pointer_shape(
frame: &mut [u8],
width: u32,
height: u32,
stride: u32,
frame_info: &DXGI_OUTDUPL_FRAME_INFO,
crop_left: u32,
crop_top: u32,
cache: &PointerShapeCache,
) {
if !frame_info.PointerPosition.Visible.as_bool() {
return;
}
let Some(shape_info) = cache.info else {
return;
};
if shape_info.Width == 0 || shape_info.Height == 0 || shape_info.Pitch == 0 {
return;
}
let pointer_x = frame_info.PointerPosition.Position.x - crop_left as i32;
let pointer_y = frame_info.PointerPosition.Position.y - crop_top as i32;
if pointer_x >= width as i32 || pointer_y >= height as i32 {
return;
}
if shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_COLOR.0 as u32 {
composite_color_pointer(
frame,
width,
height,
stride,
pointer_x,
pointer_y,
&shape_info,
&cache.buffer,
);
} else if shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MASKED_COLOR.0 as u32 {
composite_masked_color_pointer(
frame,
width,
height,
stride,
pointer_x,
pointer_y,
&shape_info,
&cache.buffer,
);
} else if shape_info.Type == DXGI_OUTDUPL_POINTER_SHAPE_TYPE_MONOCHROME.0 as u32 {
composite_monochrome_pointer(
frame,
width,
height,
stride,
pointer_x,
pointer_y,
&shape_info,
&cache.buffer,
);
}
}
#[cfg(target_os = "windows")]
#[allow(clippy::too_many_arguments)]
fn composite_color_pointer(
frame: &mut [u8],
width: u32,
height: u32,
stride: u32,
pointer_x: i32,
pointer_y: i32,
shape_info: &DXGI_OUTDUPL_POINTER_SHAPE_INFO,
shape: &[u8],
) {
let pitch = shape_info.Pitch as usize;
for y in 0..shape_info.Height as i32 {
let dst_y = pointer_y + y;
if dst_y < 0 || dst_y >= height as i32 {
continue;
}
for x in 0..shape_info.Width as i32 {
let dst_x = pointer_x + x;
if dst_x < 0 || dst_x >= width as i32 {
continue;
}
let src_offset = y as usize * pitch + x as usize * 4;
let dst_offset = dst_y as usize * stride as usize + dst_x as usize * 4;
if src_offset + 4 > shape.len() || dst_offset + 4 > frame.len() {
continue;
}
blend_bgra_pixel(frame, dst_offset, shape, src_offset);
}
}
}
#[cfg(target_os = "windows")]
#[allow(clippy::too_many_arguments)]
fn composite_masked_color_pointer(
frame: &mut [u8],
width: u32,
height: u32,
stride: u32,
pointer_x: i32,
pointer_y: i32,
shape_info: &DXGI_OUTDUPL_POINTER_SHAPE_INFO,
shape: &[u8],
) {
let pitch = shape_info.Pitch as usize;
for y in 0..shape_info.Height as i32 {
let dst_y = pointer_y + y;
if dst_y < 0 || dst_y >= height as i32 {
continue;
}
for x in 0..shape_info.Width as i32 {
let dst_x = pointer_x + x;
if dst_x < 0 || dst_x >= width as i32 {
continue;
}
let src_offset = y as usize * pitch + x as usize * 4;
let dst_offset = dst_y as usize * stride as usize + dst_x as usize * 4;
if src_offset + 4 > shape.len() || dst_offset + 4 > frame.len() {
continue;
}
let mask = shape[src_offset + 3];
if mask == 0 {
frame[dst_offset..dst_offset + 3]
.copy_from_slice(&shape[src_offset..src_offset + 3]);
frame[dst_offset + 3] = 255;
} else if mask == 0xff {
frame[dst_offset] ^= shape[src_offset];
frame[dst_offset + 1] ^= shape[src_offset + 1];
frame[dst_offset + 2] ^= shape[src_offset + 2];
frame[dst_offset + 3] = 255;
} else {
blend_bgra_pixel(frame, dst_offset, shape, src_offset);
}
}
}
}
#[cfg(target_os = "windows")]
#[allow(clippy::too_many_arguments)]
fn composite_monochrome_pointer(
frame: &mut [u8],
width: u32,
height: u32,
stride: u32,
pointer_x: i32,
pointer_y: i32,
shape_info: &DXGI_OUTDUPL_POINTER_SHAPE_INFO,
shape: &[u8],
) {
let pitch = shape_info.Pitch as usize;
let full_height = shape_info.Height as usize;
if pitch == 0 || full_height == 0 {
return;
}
let visible_height = if shape.len() >= pitch * full_height * 2 {
full_height
} else {
full_height / 2
};
if visible_height == 0 || shape.len() < pitch * visible_height * 2 {
return;
}
let xor_mask_offset = pitch * visible_height;
for y in 0..visible_height as i32 {
let dst_y = pointer_y + y;
if dst_y < 0 || dst_y >= height as i32 {
continue;
}
for x in 0..shape_info.Width as i32 {
let dst_x = pointer_x + x;
if dst_x < 0 || dst_x >= width as i32 {
continue;
}
let byte_index = y as usize * pitch + x as usize / 8;
let mask = 0x80 >> (x as usize % 8);
if byte_index >= xor_mask_offset || xor_mask_offset + byte_index >= shape.len() {
continue;
}
let and_set = shape[byte_index] & mask != 0;
let xor_set = shape[xor_mask_offset + byte_index] & mask != 0;
let dst_offset = dst_y as usize * stride as usize + dst_x as usize * 4;
if dst_offset + 4 > frame.len() {
continue;
}
match (and_set, xor_set) {
(true, false) => {}
(true, true) => {
frame[dst_offset] ^= 0xff;
frame[dst_offset + 1] ^= 0xff;
frame[dst_offset + 2] ^= 0xff;
frame[dst_offset + 3] = 255;
}
(false, false) => {
frame[dst_offset] = 0;
frame[dst_offset + 1] = 0;
frame[dst_offset + 2] = 0;
frame[dst_offset + 3] = 255;
}
(false, true) => {
frame[dst_offset] = 255;
frame[dst_offset + 1] = 255;
frame[dst_offset + 2] = 255;
frame[dst_offset + 3] = 255;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::pacing_sleep_and_next_deadline;
use std::time::{Duration, Instant};
const TEST_FRAME_INTERVAL: Duration = Duration::from_millis(33);
#[test]
fn pacing_sleeps_remaining_time_and_advances_deadline_by_interval() {
let start = Instant::now();
let deadline = start + TEST_FRAME_INTERVAL;
let now = start + Duration::from_millis(10);
let (sleep_duration, next_deadline) =
pacing_sleep_and_next_deadline(now, deadline, TEST_FRAME_INTERVAL);
assert_eq!(sleep_duration, Duration::from_millis(23));
assert_eq!(next_deadline, deadline + TEST_FRAME_INTERVAL);
}
#[test]
fn pacing_does_not_drift_across_iterations_with_varying_work() {
let start = Instant::now();
let mut deadline = start + TEST_FRAME_INTERVAL;
let mut now = start;
for iteration in 1..=100u32 {
now += Duration::from_millis(7);
let (sleep_duration, next_deadline) =
pacing_sleep_and_next_deadline(now, deadline, TEST_FRAME_INTERVAL);
assert!(sleep_duration <= TEST_FRAME_INTERVAL);
now += sleep_duration;
deadline = next_deadline;
assert_eq!(now, start + TEST_FRAME_INTERVAL * iteration);
}
}
#[test]
fn pacing_resets_deadline_without_sleeping_when_behind() {
let start = Instant::now();
let deadline = start + TEST_FRAME_INTERVAL;
let now = deadline + Duration::from_millis(50);
let (sleep_duration, next_deadline) =
pacing_sleep_and_next_deadline(now, deadline, TEST_FRAME_INTERVAL);
assert_eq!(sleep_duration, Duration::ZERO);
assert_eq!(next_deadline, now + TEST_FRAME_INTERVAL);
}
#[test]
fn pacing_at_exact_deadline_resets_without_sleeping() {
let start = Instant::now();
let deadline = start + TEST_FRAME_INTERVAL;
let (sleep_duration, next_deadline) =
pacing_sleep_and_next_deadline(deadline, deadline, TEST_FRAME_INTERVAL);
assert_eq!(sleep_duration, Duration::ZERO);
assert_eq!(next_deadline, deadline + TEST_FRAME_INTERVAL);
}
}
#[cfg(target_os = "windows")]
fn blend_bgra_pixel(frame: &mut [u8], dst_offset: usize, shape: &[u8], src_offset: usize) {
let alpha = shape[src_offset + 3] as u32;
if alpha == 0 {
return;
}
if alpha == 255 {
frame[dst_offset..dst_offset + 4].copy_from_slice(&shape[src_offset..src_offset + 4]);
return;
}
let inv_alpha = 255 - alpha;
for channel in 0..3 {
let src = shape[src_offset + channel] as u32;
let dst = frame[dst_offset + channel] as u32;
frame[dst_offset + channel] = ((src * alpha + dst * inv_alpha + 127) / 255) as u8;
}
frame[dst_offset + 3] = 255;
}