use std::sync::mpsc::{Receiver, SyncSender};
use std::sync::Mutex;
use std::time::Duration;
use windows::core::{IInspectable, Interface, Result as WResult};
use windows::Foundation::TypedEventHandler;
use windows::Graphics::Capture::{
Direct3D11CaptureFramePool, GraphicsCaptureItem, GraphicsCaptureSession,
};
use windows::Graphics::DirectX::Direct3D11::IDirect3DDevice;
use windows::Graphics::DirectX::DirectXPixelFormat;
use windows::Graphics::SizeInt32;
use windows::Win32::Foundation::HWND;
use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_FEATURE_LEVEL_11_0};
use windows::Win32::Graphics::Direct3D11::{
D3D11CreateDevice, ID3D11Device, ID3D11Texture2D, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
D3D11_CREATE_DEVICE_VIDEO_SUPPORT, D3D11_SDK_VERSION,
};
use windows::Win32::Graphics::Dxgi::IDXGIDevice;
use windows::Win32::Graphics::Gdi::{MonitorFromWindow, HMONITOR, MONITOR_DEFAULTTOPRIMARY};
use windows::Win32::System::WinRT::Direct3D11::{
CreateDirect3D11DeviceFromDXGIDevice, IDirect3DDxgiInterfaceAccess,
};
use windows::Win32::System::WinRT::Graphics::Capture::IGraphicsCaptureItemInterop;
use crate::{CaptureTarget, CapturedFrame, PipelineError, Result};
const FRAME_POOL_BUFFERS: i32 = 2;
pub struct CaptureSession {
_item: GraphicsCaptureItem,
frame_pool: Direct3D11CaptureFramePool,
session: GraphicsCaptureSession,
device: ID3D11Device,
_frame_arrived_token: i64,
}
impl CaptureSession {
pub fn device(&self) -> &ID3D11Device {
&self.device
}
}
impl Drop for CaptureSession {
fn drop(&mut self) {
let _ = self.session.Close();
let _ = self.frame_pool.Close();
}
}
#[derive(Clone, Copy, Debug)]
pub struct CaptureConfig {
pub target: CaptureTarget,
pub capture_cursor: bool,
}
pub fn start(
config: CaptureConfig,
bound: usize,
) -> Result<(CaptureSession, Receiver<CapturedFrame>)> {
if !GraphicsCaptureSession::IsSupported()? {
return Err(PipelineError::CaptureUnsupported);
}
let device = create_d3d_device()?;
let d3d_interop = create_winrt_device(&device)?;
let item = create_capture_item(config.target)?;
let size = item.Size()?;
let frame_pool = Direct3D11CaptureFramePool::CreateFreeThreaded(
&d3d_interop,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
FRAME_POOL_BUFFERS,
size,
)?;
let (tx, rx) = std::sync::mpsc::sync_channel::<CapturedFrame>(bound);
let state = std::sync::Arc::new(Mutex::new(FrameState {
tx,
device: AgileDevice(d3d_interop.clone()),
last_size: size,
start_ticks: None,
}));
let handler_state = state.clone();
let handler = TypedEventHandler::<Direct3D11CaptureFramePool, IInspectable>::new(
move |pool, _| -> WResult<()> {
let pool = pool.as_ref().expect("frame pool present in callback");
on_frame_arrived(pool, &handler_state);
Ok(())
},
);
let token = frame_pool.FrameArrived(&handler)?;
let session = frame_pool.CreateCaptureSession(&item)?;
let _ = session.SetIsCursorCaptureEnabled(config.capture_cursor);
session.StartCapture()?;
Ok((
CaptureSession {
_item: item,
frame_pool,
session,
device,
_frame_arrived_token: token,
},
rx,
))
}
struct FrameState {
tx: SyncSender<CapturedFrame>,
device: AgileDevice,
last_size: SizeInt32,
start_ticks: Option<i64>,
}
struct AgileDevice(IDirect3DDevice);
unsafe impl Send for AgileDevice {}
fn on_frame_arrived(
pool: &Direct3D11CaptureFramePool,
state: &std::sync::Arc<Mutex<FrameState>>,
) {
let mut guard = match state.lock() {
Ok(g) => g,
Err(_) => return,
};
let frame = match pool.TryGetNextFrame() {
Ok(f) => f,
Err(_) => return,
};
let content_size = match frame.ContentSize() {
Ok(s) => s,
Err(_) => return,
};
if content_size.Width != guard.last_size.Width
|| content_size.Height != guard.last_size.Height
{
let _ = pool.Recreate(
&guard.device.0,
DirectXPixelFormat::B8G8R8A8UIntNormalized,
FRAME_POOL_BUFFERS,
content_size,
);
guard.last_size = content_size;
return;
}
let ticks = match frame.SystemRelativeTime() {
Ok(t) => t.Duration, Err(_) => return,
};
let start = *guard.start_ticks.get_or_insert(ticks);
let rel_100ns = (ticks - start).max(0) as u64;
let timestamp = Duration::from_nanos(rel_100ns * 100);
let surface = match frame.Surface() {
Ok(s) => s,
Err(_) => return,
};
let texture: ID3D11Texture2D = match surface.cast::<IDirect3DDxgiInterfaceAccess>() {
Ok(access) => match unsafe { access.GetInterface::<ID3D11Texture2D>() } {
Ok(t) => t,
Err(_) => return,
},
Err(_) => return,
};
let width = content_size.Width.max(0) as u32;
let height = content_size.Height.max(0) as u32;
let _ = state_send_nonblocking(
&guard.tx,
CapturedFrame {
texture,
timestamp,
width,
height,
},
);
}
fn state_send_nonblocking(tx: &SyncSender<CapturedFrame>, f: CapturedFrame) -> Result<()> {
match tx.try_send(f) {
Ok(()) => Ok(()),
Err(std::sync::mpsc::TrySendError::Full(_)) => Ok(()), Err(std::sync::mpsc::TrySendError::Disconnected(_)) => Err(PipelineError::Stopped),
}
}
fn create_d3d_device() -> Result<ID3D11Device> {
use windows::Win32::Foundation::HMODULE;
let flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT | D3D11_CREATE_DEVICE_VIDEO_SUPPORT;
let mut device: Option<ID3D11Device> = None;
let feature_levels = [D3D_FEATURE_LEVEL_11_0];
unsafe {
D3D11CreateDevice(
None,
D3D_DRIVER_TYPE_HARDWARE,
HMODULE::default(),
flags,
Some(&feature_levels),
D3D11_SDK_VERSION,
Some(&mut device),
None,
None,
)?;
}
let device = device.expect("D3D11CreateDevice succeeded but returned null device");
use windows::Win32::Graphics::Direct3D11::ID3D11Multithread;
if let Ok(mt) = device.cast::<ID3D11Multithread>() {
unsafe { let _ = mt.SetMultithreadProtected(true); };
}
Ok(device)
}
fn create_winrt_device(device: &ID3D11Device) -> Result<IDirect3DDevice> {
let dxgi: IDXGIDevice = device.cast()?;
let inspectable = unsafe { CreateDirect3D11DeviceFromDXGIDevice(&dxgi)? };
let winrt: IDirect3DDevice = inspectable.cast()?;
Ok(winrt)
}
fn create_capture_item(target: CaptureTarget) -> Result<GraphicsCaptureItem> {
let interop: IGraphicsCaptureItemInterop =
windows::core::factory::<GraphicsCaptureItem, IGraphicsCaptureItemInterop>()?;
match target {
CaptureTarget::Monitor(index) => {
let hmon = monitor_at_index(index)?;
let item: GraphicsCaptureItem =
unsafe { interop.CreateForMonitor(hmon)? };
Ok(item)
}
CaptureTarget::Window(hwnd) => {
let item: GraphicsCaptureItem =
unsafe { interop.CreateForWindow(HWND(hwnd as *mut _))? };
Ok(item)
}
}
}
fn monitor_at_index(index: usize) -> Result<HMONITOR> {
use windows::Win32::Graphics::Gdi::{EnumDisplayMonitors, HDC};
use windows::core::BOOL;
use windows::Win32::Foundation::{LPARAM, RECT};
struct Collector {
monitors: Vec<HMONITOR>,
}
unsafe extern "system" fn cb(
hmon: HMONITOR,
_hdc: HDC,
_rect: *mut RECT,
data: LPARAM,
) -> BOOL {
let collector = &mut *(data.0 as *mut Collector);
collector.monitors.push(hmon);
BOOL(1) }
let mut collector = Collector {
monitors: Vec::new(),
};
unsafe {
let _ = EnumDisplayMonitors(
None,
None,
Some(cb),
LPARAM(&mut collector as *mut _ as isize),
);
}
collector
.monitors
.get(index)
.copied()
.ok_or(PipelineError::MonitorNotFound(index))
}
pub fn primary_monitor() -> HMONITOR {
unsafe { MonitorFromWindow(HWND(std::ptr::null_mut()), MONITOR_DEFAULTTOPRIMARY) }
}