1use std::sync::Arc;
2use std::sync::atomic::{self, AtomicBool, AtomicI32};
3
4use parking_lot::Mutex;
5use windows::Foundation::Metadata::ApiInformation;
6use windows::Foundation::TypedEventHandler;
7use windows::Graphics::Capture::{
8 Direct3D11CaptureFramePool, GraphicsCaptureDirtyRegionMode, GraphicsCaptureItem, GraphicsCaptureSession,
9};
10use windows::Graphics::DirectX::Direct3D11::IDirect3DDevice;
11use windows::Graphics::DirectX::DirectXPixelFormat;
12use windows::Win32::Foundation::{LPARAM, WPARAM};
13use windows::Win32::Graphics::Direct3D11::{D3D11_TEXTURE2D_DESC, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D};
14use windows::Win32::System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess;
15use windows::Win32::UI::WindowsAndMessaging::{PostThreadMessageW, WM_QUIT};
16use windows::core::{HSTRING, IInspectable, Interface};
17
18use crate::capture::GraphicsCaptureApiHandler;
19use crate::d3d11::{self, SendDirectX, create_direct3d_device};
20use crate::frame::Frame;
21use crate::settings::{
22 ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings, GraphicsCaptureItemType,
23 MinimumUpdateIntervalSettings, SecondaryWindowSettings,
24};
25
26#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
27pub enum Error {
29 #[error("The Graphics Capture API is not supported on this platform.")]
31 Unsupported,
32 #[error("Toggling cursor capture is not supported by the Graphics Capture API on this platform.")]
34 CursorConfigUnsupported,
35 #[error("Toggling the capture border is not supported by the Graphics Capture API on this platform.")]
37 BorderConfigUnsupported,
38 #[error("Capturing secondary windows is not supported by the Graphics Capture API on this platform.")]
40 SecondaryWindowsUnsupported,
41 #[error("Setting a minimum update interval is not supported by the Graphics Capture API on this platform.")]
43 MinimumUpdateIntervalUnsupported,
44 #[error("Dirty region tracking is not supported by the Graphics Capture API on this platform.")]
46 DirtyRegionUnsupported,
47 #[error("The capture has already been started.")]
49 AlreadyStarted,
50 #[error("DirectX error: {0}")]
54 DirectXError(#[from] d3d11::Error),
55 #[error("Window error: {0}")]
59 WindowError(#[from] crate::window::Error),
60 #[error("Windows API error: {0}")]
64 WindowsError(#[from] windows::core::Error),
65}
66
67pub struct InternalCaptureControl {
69 stop: Arc<AtomicBool>,
70}
71
72impl InternalCaptureControl {
73 #[inline]
75 #[must_use]
76 pub const fn new(stop: Arc<AtomicBool>) -> Self {
77 Self { stop }
78 }
79
80 #[inline]
82 pub fn stop(self) {
83 self.stop.store(true, atomic::Ordering::Relaxed);
84 }
85}
86
87pub struct GraphicsCaptureApi {
89 item_with_details: GraphicsCaptureItemType,
92 _d3d_device: ID3D11Device,
94 _direct3d_device: IDirect3DDevice,
96 _d3d_device_context: ID3D11DeviceContext,
98 frame_pool: Option<Arc<Direct3D11CaptureFramePool>>,
100 session: Option<GraphicsCaptureSession>,
102 halt: Arc<AtomicBool>,
104 active: bool,
106 capture_closed_event_token: i64,
108 frame_arrived_event_token: i64,
110}
111
112impl GraphicsCaptureApi {
113 #[allow(clippy::too_many_arguments)]
118 #[inline]
119 pub fn new<T: GraphicsCaptureApiHandler<Error = E> + Send + 'static, E: Send + Sync + 'static>(
120 d3d_device: ID3D11Device,
121 d3d_device_context: ID3D11DeviceContext,
122 item_with_details: GraphicsCaptureItemType,
123 callback: Arc<Mutex<T>>,
124 cursor_capture_settings: CursorCaptureSettings,
125 draw_border_settings: DrawBorderSettings,
126 secondary_window_settings: SecondaryWindowSettings,
127 minimum_update_interval_settings: MinimumUpdateIntervalSettings,
128 dirty_region_settings: DirtyRegionSettings,
129 color_format: ColorFormat,
130 thread_id: u32,
131 result: Arc<Mutex<Option<E>>>,
132 ) -> Result<Self, Error> {
133 if !Self::is_supported()? {
135 return Err(Error::Unsupported);
136 }
137
138 if cursor_capture_settings != CursorCaptureSettings::Default && !Self::is_cursor_settings_supported()? {
139 return Err(Error::CursorConfigUnsupported);
140 }
141
142 if draw_border_settings != DrawBorderSettings::Default && !Self::is_border_settings_supported()? {
143 return Err(Error::BorderConfigUnsupported);
144 }
145
146 if secondary_window_settings != SecondaryWindowSettings::Default && !Self::is_secondary_windows_supported()? {
147 return Err(Error::SecondaryWindowsUnsupported);
148 }
149
150 if minimum_update_interval_settings != MinimumUpdateIntervalSettings::Default
151 && !Self::is_minimum_update_interval_supported()?
152 {
153 return Err(Error::MinimumUpdateIntervalUnsupported);
154 }
155
156 if dirty_region_settings != DirtyRegionSettings::Default && !Self::is_dirty_region_supported()? {
157 return Err(Error::DirtyRegionUnsupported);
158 }
159
160 let title_bar_height = match item_with_details {
162 GraphicsCaptureItemType::Window((_, window)) => Some(window.title_bar_height()?),
163 GraphicsCaptureItemType::Monitor(_) => None,
164 GraphicsCaptureItemType::Unknown(_) => None,
165 };
166
167 let item = match &item_with_details {
168 GraphicsCaptureItemType::Window((item, _)) => item,
169 GraphicsCaptureItemType::Monitor((item, _)) => item,
170 GraphicsCaptureItemType::Unknown((item, _)) => item,
171 };
172
173 let direct3d_device = create_direct3d_device(&d3d_device)?;
175
176 let pixel_format = DirectXPixelFormat(color_format as i32);
177
178 let frame_pool = Direct3D11CaptureFramePool::Create(&direct3d_device, pixel_format, 1, item.Size()?)?;
180 let frame_pool = Arc::new(frame_pool);
181
182 let session = frame_pool.CreateCaptureSession(item)?;
184
185 let halt = Arc::new(AtomicBool::new(false));
187
188 let capture_closed_event_token =
190 item.Closed(&TypedEventHandler::<GraphicsCaptureItem, IInspectable>::new({
191 let callback_closed = callback.clone();
193 let halt_closed = halt.clone();
194 let result_closed = result.clone();
195
196 move |_, _| {
197 halt_closed.store(true, atomic::Ordering::Relaxed);
198
199 let callback_closed = callback_closed.lock().on_closed();
201 if let Err(e) = callback_closed {
202 *result_closed.lock() = Some(e);
203 }
204
205 unsafe {
207 PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default())?;
208 };
209
210 Result::Ok(())
211 }
212 }))?;
213
214 let frame_arrived_event_token = frame_pool.FrameArrived(&TypedEventHandler::<
216 Direct3D11CaptureFramePool,
217 IInspectable,
218 >::new({
219 let frame_pool_recreate = frame_pool.clone();
221 let halt_frame_pool = halt.clone();
222 let d3d_device_frame_pool = d3d_device.clone();
223 let context = d3d_device_context.clone();
224 let result_frame_pool = result;
225
226 let last_size = item.Size()?;
227 let last_size = Arc::new((AtomicI32::new(last_size.Width), AtomicI32::new(last_size.Height)));
228 let callback_frame_pool = callback;
229 let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone());
230
231 move |frame, _| {
232 if halt_frame_pool.load(atomic::Ordering::Relaxed) {
234 return Ok(());
235 }
236
237 let frame = frame
239 .as_ref()
240 .expect("FrameArrived parameter was None this should never happen.")
241 .TryGetNextFrame()?;
242
243 let frame_content_size = frame.ContentSize()?;
245
246 let frame_surface = frame.Surface()?;
248
249 let frame_dxgi_interface = frame_surface.cast::<IDirect3DDxgiInterfaceAccess>()?;
251 let frame_texture = unsafe { frame_dxgi_interface.GetInterface::<ID3D11Texture2D>()? };
252
253 let mut desc = D3D11_TEXTURE2D_DESC::default();
255 unsafe { frame_texture.GetDesc(&mut desc) }
256
257 if frame_content_size.Width != last_size.0.load(atomic::Ordering::Relaxed)
259 || frame_content_size.Height != last_size.1.load(atomic::Ordering::Relaxed)
260 {
261 let direct3d_device_recreate = &direct3d_device_recreate;
262 frame_pool_recreate.Recreate(&direct3d_device_recreate.0, pixel_format, 1, frame_content_size)?;
263
264 last_size.0.store(frame_content_size.Width, atomic::Ordering::Relaxed);
265 last_size.1.store(frame_content_size.Height, atomic::Ordering::Relaxed);
266 }
267
268 let mut frame = Frame::new(
270 frame,
271 &d3d_device_frame_pool,
272 frame_surface,
273 frame_texture,
274 &context,
275 desc,
276 color_format,
277 title_bar_height,
278 );
279
280 let stop = Arc::new(AtomicBool::new(false));
282 let internal_capture_control = InternalCaptureControl::new(stop.clone());
283
284 let result = callback_frame_pool.lock().on_frame_arrived(&mut frame, internal_capture_control);
286
287 if stop.load(atomic::Ordering::Relaxed) || result.is_err() {
289 if let Err(e) = result {
290 *result_frame_pool.lock() = Some(e);
291 }
292
293 halt_frame_pool.store(true, atomic::Ordering::Relaxed);
294
295 unsafe {
297 PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default())?;
298 };
299 }
300
301 Result::Ok(())
302 }
303 }))?;
304
305 if cursor_capture_settings != CursorCaptureSettings::Default {
306 if Self::is_cursor_settings_supported()? {
307 match cursor_capture_settings {
308 CursorCaptureSettings::Default => (),
309 CursorCaptureSettings::WithCursor => session.SetIsCursorCaptureEnabled(true)?,
310 CursorCaptureSettings::WithoutCursor => session.SetIsCursorCaptureEnabled(false)?,
311 };
312 } else {
313 return Err(Error::CursorConfigUnsupported);
314 }
315 }
316
317 if draw_border_settings != DrawBorderSettings::Default {
318 if Self::is_border_settings_supported()? {
319 match draw_border_settings {
320 DrawBorderSettings::Default => (),
321 DrawBorderSettings::WithBorder => {
322 session.SetIsBorderRequired(true)?;
323 }
324 DrawBorderSettings::WithoutBorder => session.SetIsBorderRequired(false)?,
325 }
326 } else {
327 return Err(Error::BorderConfigUnsupported);
328 }
329 }
330
331 if secondary_window_settings != SecondaryWindowSettings::Default {
332 if Self::is_secondary_windows_supported()? {
333 match secondary_window_settings {
334 SecondaryWindowSettings::Default => (),
335 SecondaryWindowSettings::Include => session.SetIncludeSecondaryWindows(true)?,
336 SecondaryWindowSettings::Exclude => session.SetIncludeSecondaryWindows(false)?,
337 }
338 } else {
339 return Err(Error::SecondaryWindowsUnsupported);
340 }
341 }
342
343 if minimum_update_interval_settings != MinimumUpdateIntervalSettings::Default {
344 if Self::is_minimum_update_interval_supported()? {
345 match minimum_update_interval_settings {
346 MinimumUpdateIntervalSettings::Default => (),
347 MinimumUpdateIntervalSettings::Custom(duration) => {
348 session.SetMinUpdateInterval(duration.into())?;
349 }
350 }
351 } else {
352 return Err(Error::MinimumUpdateIntervalUnsupported);
353 }
354 }
355
356 if dirty_region_settings != DirtyRegionSettings::Default {
357 if Self::is_dirty_region_supported()? {
358 match dirty_region_settings {
359 DirtyRegionSettings::Default => (),
360 DirtyRegionSettings::ReportOnly => {
361 session.SetDirtyRegionMode(GraphicsCaptureDirtyRegionMode::ReportOnly)?
362 }
363 DirtyRegionSettings::ReportAndRender => {
364 session.SetDirtyRegionMode(GraphicsCaptureDirtyRegionMode::ReportAndRender)?
365 }
366 }
367 } else {
368 return Err(Error::DirtyRegionUnsupported);
369 }
370 }
371
372 Ok(Self {
373 item_with_details,
374 _d3d_device: d3d_device,
375 _direct3d_device: direct3d_device,
376 _d3d_device_context: d3d_device_context,
377 frame_pool: Some(frame_pool),
378 session: Some(session),
379 halt,
380 active: false,
381 frame_arrived_event_token,
382 capture_closed_event_token,
383 })
384 }
385
386 #[inline]
393 pub fn start_capture(&mut self) -> Result<(), Error> {
394 if self.active {
395 return Err(Error::AlreadyStarted);
396 }
397
398 self.session.as_ref().unwrap().StartCapture()?;
399 self.active = true;
400
401 Ok(())
402 }
403
404 #[inline]
406 pub fn stop_capture(mut self) {
407 if let Some(frame_pool) = self.frame_pool.take() {
408 frame_pool
409 .RemoveFrameArrived(self.frame_arrived_event_token)
410 .expect("Failed to remove Frame Arrived event handler");
411
412 frame_pool.Close().expect("Failed to Close Frame Pool");
413 }
414
415 if let Some(session) = self.session.take() {
416 session.Close().expect("Failed to Close Capture Session");
417 }
418
419 let item = match &self.item_with_details {
420 GraphicsCaptureItemType::Window((item, _)) => item,
421 GraphicsCaptureItemType::Monitor((item, _)) => item,
422 GraphicsCaptureItemType::Unknown((item, _)) => item,
423 };
424
425 item.RemoveClosed(self.capture_closed_event_token)
426 .expect("Failed to remove Capture Session Closed event handler");
427 }
428
429 #[inline]
435 #[must_use]
436 pub fn halt_handle(&self) -> Arc<AtomicBool> {
437 self.halt.clone()
438 }
439
440 #[inline]
442 pub fn is_supported() -> Result<bool, Error> {
443 Ok(ApiInformation::IsApiContractPresentByMajor(&HSTRING::from("Windows.Foundation.UniversalApiContract"), 8)?
444 && GraphicsCaptureSession::IsSupported()?)
445 }
446
447 #[inline]
449 pub fn is_cursor_settings_supported() -> Result<bool, Error> {
450 Ok(ApiInformation::IsPropertyPresent(
451 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
452 &HSTRING::from("IsCursorCaptureEnabled"),
453 )? && Self::is_supported()?)
454 }
455
456 #[inline]
458 pub fn is_border_settings_supported() -> Result<bool, Error> {
459 Ok(ApiInformation::IsPropertyPresent(
460 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
461 &HSTRING::from("IsBorderRequired"),
462 )? && Self::is_supported()?)
463 }
464
465 #[inline]
467 pub fn is_secondary_windows_supported() -> Result<bool, Error> {
468 Ok(ApiInformation::IsPropertyPresent(
469 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
470 &HSTRING::from("IncludeSecondaryWindows"),
471 )? && Self::is_supported()?)
472 }
473
474 #[inline]
476 pub fn is_minimum_update_interval_supported() -> Result<bool, Error> {
477 Ok(ApiInformation::IsPropertyPresent(
478 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
479 &HSTRING::from("MinUpdateInterval"),
480 )? && Self::is_supported()?)
481 }
482
483 #[inline]
485 pub fn is_dirty_region_supported() -> Result<bool, Error> {
486 Ok(ApiInformation::IsPropertyPresent(
487 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
488 &HSTRING::from("DirtyRegionMode"),
489 )? && Self::is_supported()?)
490 }
491}
492
493impl Drop for GraphicsCaptureApi {
494 fn drop(&mut self) {
495 if let Some(frame_pool) = self.frame_pool.take() {
496 let _ = frame_pool.RemoveFrameArrived(self.frame_arrived_event_token);
497 let _ = frame_pool.Close();
498 }
499
500 if let Some(session) = self.session.take() {
501 let _ = session.Close();
502 }
503
504 let item = match &self.item_with_details {
505 GraphicsCaptureItemType::Window((item, _)) => item,
506 GraphicsCaptureItemType::Monitor((item, _)) => item,
507 GraphicsCaptureItemType::Unknown((item, _)) => item,
508 };
509
510 let _ = item.RemoveClosed(self.capture_closed_event_token);
511 }
512}