use std::{
cell::RefCell,
error::Error,
sync::{
atomic::{self, AtomicBool},
Arc,
},
};
use log::{info, trace};
use parking_lot::Mutex;
use windows::{
core::{ComInterface, IInspectable, HSTRING},
Foundation::{Metadata::ApiInformation, TypedEventHandler},
Graphics::{
Capture::{Direct3D11CaptureFramePool, GraphicsCaptureItem, GraphicsCaptureSession},
DirectX::{Direct3D11::IDirect3DDevice, DirectXPixelFormat},
},
Win32::{
Graphics::{
Direct3D11::{
ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_CPU_ACCESS_READ,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING,
},
Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC},
},
System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess,
UI::WindowsAndMessaging::PostQuitMessage,
},
};
use crate::{
capture::WindowsCaptureHandler,
d3d11::{create_d3d_device, create_direct3d_device, SendDirectX},
frame::Frame,
settings::ColorFormat,
};
thread_local! {
pub static RESULT: RefCell<Option<Result<(), Box<dyn Error + Send + Sync>>>> = RefCell::new(Some(Ok(())));
}
#[derive(thiserror::Error, Eq, PartialEq, Clone, Copy, Debug)]
pub enum WindowsCaptureError {
#[error("Graphics Capture API Is Not Supported")]
Unsupported,
#[error("Graphics Capture API Toggling Cursor Capture Is Not Supported")]
CursorUnsupported,
#[error("Graphics Capture API Toggling Border Capture Is Not Supported")]
BorderUnsupported,
#[error("Already Started")]
AlreadyStarted,
}
pub struct InternalCaptureControl {
stop: Arc<AtomicBool>,
}
impl InternalCaptureControl {
#[must_use]
pub fn new(stop: Arc<AtomicBool>) -> Self {
Self { stop }
}
pub fn stop(self) {
self.stop.store(true, atomic::Ordering::Relaxed);
}
}
pub struct GraphicsCaptureApi {
_item: GraphicsCaptureItem,
_d3d_device: ID3D11Device,
_direct3d_device: IDirect3DDevice,
_d3d_device_context: ID3D11DeviceContext,
frame_pool: Option<Arc<Direct3D11CaptureFramePool>>,
session: Option<GraphicsCaptureSession>,
active: bool,
capture_cursor: Option<bool>,
draw_border: Option<bool>,
}
impl GraphicsCaptureApi {
pub fn new<T: WindowsCaptureHandler + std::marker::Send + 'static>(
item: GraphicsCaptureItem,
callback: T,
capture_cursor: Option<bool>,
draw_border: Option<bool>,
color_format: ColorFormat,
) -> Result<Self, Box<dyn Error + Send + Sync>> {
if !ApiInformation::IsApiContractPresentByMajor(
&HSTRING::from("Windows.Foundation.UniversalApiContract"),
8,
)? {
return Err(Box::new(WindowsCaptureError::Unsupported));
}
trace!("Creating DirectX Devices");
let (d3d_device, d3d_device_context) = create_d3d_device()?;
let direct3d_device = create_direct3d_device(&d3d_device)?;
let pixel_format = if color_format == ColorFormat::Rgba8 {
DirectXPixelFormat::R8G8B8A8UIntNormalized
} else {
DirectXPixelFormat::B8G8R8A8UIntNormalized
};
trace!("Creating Frame Pool");
let frame_pool =
Direct3D11CaptureFramePool::Create(&direct3d_device, pixel_format, 2, item.Size()?)?;
let frame_pool = Arc::new(frame_pool);
trace!("Creating Capture Session");
let session = frame_pool.CreateCaptureSession(&item)?;
let callback = Arc::new(Mutex::new(callback));
let closed = Arc::new(AtomicBool::new(false));
item.Closed(
&TypedEventHandler::<GraphicsCaptureItem, IInspectable>::new({
let callback_closed = callback.clone();
let closed_item = closed.clone();
move |_, _| {
closed_item.store(true, atomic::Ordering::Relaxed);
unsafe { PostQuitMessage(0) };
let result = callback_closed.lock().on_closed();
let _ = RESULT
.replace(Some(result))
.expect("Failed To Replace RESULT");
Result::Ok(())
}
}),
)?;
frame_pool.FrameArrived(
&TypedEventHandler::<Direct3D11CaptureFramePool, IInspectable>::new({
let frame_pool_recreate = frame_pool.clone();
let closed_frame_pool = closed;
let d3d_device_frame_pool = d3d_device.clone();
let context = d3d_device_context.clone();
let mut last_size = item.Size()?;
let callback_frame_pool = callback;
let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone());
move |frame, _| {
if closed_frame_pool.load(atomic::Ordering::Relaxed) {
return Ok(());
}
let frame = frame.as_ref().unwrap().TryGetNextFrame()?;
let frame_content_size = frame.ContentSize()?;
let frame_surface = frame.Surface()?;
let frame_surface = frame_surface.cast::<IDirect3DDxgiInterfaceAccess>()?;
let frame_surface = unsafe { frame_surface.GetInterface::<ID3D11Texture2D>()? };
let mut desc = D3D11_TEXTURE2D_DESC::default();
unsafe { frame_surface.GetDesc(&mut desc) }
if desc.Format.0 as i32 == pixel_format.0 {
if frame_content_size.Width != last_size.Width
|| frame_content_size.Height != last_size.Height
{
info!(
"Size Changed From {}x{} to {}x{} -> Recreating Device",
last_size.Width,
last_size.Height,
frame_content_size.Width,
frame_content_size.Height,
);
let direct3d_device_recreate = &direct3d_device_recreate;
frame_pool_recreate
.Recreate(
&direct3d_device_recreate.0,
pixel_format,
2,
frame_content_size,
)
.unwrap();
last_size = frame_content_size;
return Ok(());
}
let texture_width = desc.Width;
let texture_height = desc.Height;
let texture_desc = D3D11_TEXTURE2D_DESC {
Width: texture_width,
Height: texture_height,
MipLevels: 1,
ArraySize: 1,
Format: DXGI_FORMAT(pixel_format.0 as u32),
SampleDesc: DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
},
Usage: D3D11_USAGE_STAGING,
BindFlags: 0,
CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32,
MiscFlags: 0,
};
let mut texture = None;
unsafe {
d3d_device_frame_pool.CreateTexture2D(
&texture_desc,
None,
Some(&mut texture),
)?;
};
let texture = texture.unwrap();
let frame = Frame::new(
texture,
frame_surface,
&context,
texture_width,
texture_height,
color_format,
);
let stop = Arc::new(AtomicBool::new(false));
let internal_capture_control = InternalCaptureControl::new(stop.clone());
let result = callback_frame_pool
.lock()
.on_frame_arrived(frame, internal_capture_control);
if stop.load(atomic::Ordering::Relaxed) || result.is_err() {
let _ = RESULT
.replace(Some(result))
.expect("Failed To Replace RESULT");
closed_frame_pool.store(true, atomic::Ordering::Relaxed);
unsafe { PostQuitMessage(0) };
}
} else {
closed_frame_pool.store(true, atomic::Ordering::Relaxed);
unsafe { PostQuitMessage(0) };
let result = callback_frame_pool.lock().on_closed();
let _ = RESULT
.replace(Some(result))
.expect("Failed To Replace RESULT");
}
Result::Ok(())
}
}),
)?;
Ok(Self {
_item: item,
_d3d_device: d3d_device,
_direct3d_device: direct3d_device,
_d3d_device_context: d3d_device_context,
frame_pool: Some(frame_pool),
session: Some(session),
active: false,
capture_cursor,
draw_border,
})
}
pub fn start_capture(&mut self) -> Result<(), Box<dyn Error + Send + Sync>> {
if self.active {
return Err(Box::new(WindowsCaptureError::AlreadyStarted));
}
if self.capture_cursor.is_some() {
if ApiInformation::IsPropertyPresent(
&HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
&HSTRING::from("IsCursorCaptureEnabled"),
)? {
self.session
.as_ref()
.unwrap()
.SetIsCursorCaptureEnabled(self.capture_cursor.unwrap())?;
} else {
return Err(Box::new(WindowsCaptureError::CursorUnsupported));
}
}
if self.draw_border.is_some() {
if ApiInformation::IsPropertyPresent(
&HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
&HSTRING::from("IsBorderRequired"),
)? {
self.session
.as_ref()
.unwrap()
.SetIsBorderRequired(self.draw_border.unwrap())?;
} else {
return Err(Box::new(WindowsCaptureError::BorderUnsupported));
}
}
self.session.as_ref().unwrap().StartCapture()?;
self.active = true;
Ok(())
}
pub fn stop_capture(mut self) {
if let Some(frame_pool) = self.frame_pool.take() {
frame_pool.Close().expect("Failed to Close Frame Pool");
}
if let Some(session) = self.session.take() {
session.Close().expect("Failed to Close Capture Session");
}
}
pub fn is_supported() -> Result<bool, Box<dyn Error + Send + Sync>> {
Ok(ApiInformation::IsApiContractPresentByMajor(
&HSTRING::from("Windows.Foundation.UniversalApiContract"),
8,
)?)
}
pub fn is_cursor_toggle_supported() -> Result<bool, Box<dyn Error + Send + Sync>> {
Ok(ApiInformation::IsPropertyPresent(
&HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
&HSTRING::from("IsCursorCaptureEnabled"),
)?)
}
pub fn is_border_toggle_supported() -> Result<bool, Box<dyn Error + Send + Sync>> {
Ok(ApiInformation::IsPropertyPresent(
&HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
&HSTRING::from("IsBorderRequired"),
)?)
}
}
impl Drop for GraphicsCaptureApi {
fn drop(&mut self) {
if let Some(frame_pool) = self.frame_pool.take() {
frame_pool.Close().expect("Failed to Close Frame Pool");
}
if let Some(session) = self.session.take() {
session.Close().expect("Failed to Close Capture Session");
}
}
}