1use std::sync::Arc;
2use std::sync::atomic::{self, AtomicBool};
3
4use parking_lot::Mutex;
5use windows::Foundation::Metadata::ApiInformation;
6use windows::Foundation::TypedEventHandler;
7use windows::Graphics::Capture::{
8 Direct3D11CaptureFramePool, GraphicsCaptureDirtyRegionMode, GraphicsCaptureItem,
9 GraphicsCaptureSession,
10};
11use windows::Graphics::DirectX::Direct3D11::IDirect3DDevice;
12use windows::Graphics::DirectX::DirectXPixelFormat;
13use windows::Win32::Foundation::{LPARAM, WPARAM};
14use windows::Win32::Graphics::Direct3D11::{
15 D3D11_TEXTURE2D_DESC, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
16};
17use windows::Win32::System::WinRT::Direct3D11::IDirect3DDxgiInterfaceAccess;
18use windows::Win32::UI::WindowsAndMessaging::{PostThreadMessageW, WM_QUIT};
19use windows::core::{HSTRING, IInspectable, Interface};
20
21use crate::capture::GraphicsCaptureApiHandler;
22use crate::d3d11::{self, SendDirectX, create_direct3d_device};
23use crate::frame::Frame;
24use crate::settings::{
25 CaptureItemTypes, ColorFormat, CursorCaptureSettings, DirtyRegionSettings, DrawBorderSettings,
26 MinimumUpdateIntervalSettings, SecondaryWindowSettings,
27};
28
29#[derive(thiserror::Error, Eq, PartialEq, Clone, Debug)]
30pub enum Error {
31 #[error("The Graphics Capture API is not supported on this platform.")]
32 Unsupported,
33 #[error(
34 "Toggling cursor capture is not supported by the Graphics Capture API on this platform."
35 )]
36 CursorConfigUnsupported,
37 #[error(
38 "Toggling the capture border is not supported by the Graphics Capture API on this platform."
39 )]
40 BorderConfigUnsupported,
41 #[error(
42 "Capturing secondary windows is not supported by the Graphics Capture API on this platform."
43 )]
44 SecondaryWindowsUnsupported,
45 #[error(
46 "Setting a minimum update interval is not supported by the Graphics Capture API on this platform."
47 )]
48 MinimumUpdateIntervalUnsupported,
49 #[error("Dirty region tracking is not supported by the Graphics Capture API on this platform.")]
50 DirtyRegionUnsupported,
51 #[error("The capture has already been started.")]
52 AlreadyStarted,
53 #[error("DirectX error: {0}")]
54 DirectXError(#[from] d3d11::Error),
55 #[error("Window error: {0}")]
56 WindowError(#[from] crate::window::Error),
57 #[error("Windows API error: {0}")]
58 WindowsError(#[from] windows::core::Error),
59}
60
61pub struct InternalCaptureControl {
63 stop: Arc<AtomicBool>,
64}
65
66impl InternalCaptureControl {
67 #[must_use]
77 #[inline]
78 pub const fn new(stop: Arc<AtomicBool>) -> Self {
79 Self { stop }
80 }
81
82 #[inline]
84 pub fn stop(self) {
85 self.stop.store(true, atomic::Ordering::Relaxed);
86 }
87}
88
89pub struct GraphicsCaptureApi {
91 item: GraphicsCaptureItem,
93 _d3d_device: ID3D11Device,
95 _direct3d_device: IDirect3DDevice,
97 _d3d_device_context: ID3D11DeviceContext,
99 frame_pool: Option<Arc<Direct3D11CaptureFramePool>>,
101 session: Option<GraphicsCaptureSession>,
103 halt: Arc<AtomicBool>,
105 active: bool,
107 capture_closed_event_token: i64,
109 frame_arrived_event_token: i64,
111}
112
113impl GraphicsCaptureApi {
114 #[allow(clippy::too_many_arguments)]
137 #[inline]
138 pub fn new<
139 T: GraphicsCaptureApiHandler<Error = E> + Send + 'static,
140 E: Send + Sync + 'static,
141 >(
142 d3d_device: ID3D11Device,
143 d3d_device_context: ID3D11DeviceContext,
144 item: GraphicsCaptureItem,
145 item_type: CaptureItemTypes,
146 callback: Arc<Mutex<T>>,
147 cursor_capture_settings: CursorCaptureSettings,
148 draw_border_settings: DrawBorderSettings,
149 secondary_window_settings: SecondaryWindowSettings,
150 minimum_update_interval_settings: MinimumUpdateIntervalSettings,
151 dirty_region_settings: DirtyRegionSettings,
152 color_format: ColorFormat,
153 thread_id: u32,
154 result: Arc<Mutex<Option<E>>>,
155 ) -> Result<Self, Error> {
156 if !Self::is_supported()? {
158 return Err(Error::Unsupported);
159 }
160
161 if cursor_capture_settings != CursorCaptureSettings::Default
162 && !Self::is_cursor_settings_supported()?
163 {
164 return Err(Error::CursorConfigUnsupported);
165 }
166
167 if draw_border_settings != DrawBorderSettings::Default
168 && !Self::is_border_settings_supported()?
169 {
170 return Err(Error::BorderConfigUnsupported);
171 }
172
173 if secondary_window_settings != SecondaryWindowSettings::Default
174 && !Self::is_secondary_windows_supported()?
175 {
176 return Err(Error::SecondaryWindowsUnsupported);
177 }
178
179 if minimum_update_interval_settings != MinimumUpdateIntervalSettings::Default
180 && !Self::is_minimum_update_interval_supported()?
181 {
182 return Err(Error::MinimumUpdateIntervalUnsupported);
183 }
184
185 if dirty_region_settings != DirtyRegionSettings::Default
186 && !Self::is_dirty_region_supported()?
187 {
188 return Err(Error::DirtyRegionUnsupported);
189 }
190
191 let title_bar_height = match item_type {
193 CaptureItemTypes::Window(window) => Some(window.title_bar_height()?),
194 CaptureItemTypes::Monitor(_) => None,
195 };
196
197 let direct3d_device = create_direct3d_device(&d3d_device)?;
199
200 let pixel_format = DirectXPixelFormat(color_format as i32);
201
202 let frame_pool =
204 Direct3D11CaptureFramePool::Create(&direct3d_device, pixel_format, 1, item.Size()?)?;
205 let frame_pool = Arc::new(frame_pool);
206
207 let session = frame_pool.CreateCaptureSession(&item)?;
209
210 let mut buffer = vec![0u8; 3840 * 2160 * 4];
213
214 let halt = Arc::new(AtomicBool::new(false));
216
217 let capture_closed_event_token = item.Closed(&TypedEventHandler::<
219 GraphicsCaptureItem,
220 IInspectable,
221 >::new({
222 let callback_closed = callback.clone();
224 let halt_closed = halt.clone();
225 let result_closed = result.clone();
226
227 move |_, _| {
228 halt_closed.store(true, atomic::Ordering::Relaxed);
229
230 let callback_closed = callback_closed.lock().on_closed();
232 if let Err(e) = callback_closed {
233 *result_closed.lock() = Some(e);
234 }
235
236 unsafe {
238 PostThreadMessageW(thread_id, WM_QUIT, WPARAM::default(), LPARAM::default())?;
239 };
240
241 Result::Ok(())
242 }
243 }))?;
244
245 let frame_arrived_event_token = frame_pool.FrameArrived(&TypedEventHandler::<
247 Direct3D11CaptureFramePool,
248 IInspectable,
249 >::new({
250 let frame_pool_recreate = frame_pool.clone();
252 let halt_frame_pool = halt.clone();
253 let d3d_device_frame_pool = d3d_device.clone();
254 let context = d3d_device_context.clone();
255 let result_frame_pool = result;
256
257 let mut last_size = item.Size()?;
258 let callback_frame_pool = callback;
259 let direct3d_device_recreate = SendDirectX::new(direct3d_device.clone());
260
261 move |frame, _| {
262 if halt_frame_pool.load(atomic::Ordering::Relaxed) {
264 return Ok(());
265 }
266
267 let frame = frame
269 .as_ref()
270 .expect("FrameArrived parameter was None this should never happen.")
271 .TryGetNextFrame()?;
272 let timestamp = frame.SystemRelativeTime()?;
273
274 let frame_content_size = frame.ContentSize()?;
276
277 let frame_surface = frame.Surface()?;
279
280 let frame_dxgi_interface = frame_surface.cast::<IDirect3DDxgiInterfaceAccess>()?;
282 let frame_texture =
283 unsafe { frame_dxgi_interface.GetInterface::<ID3D11Texture2D>()? };
284
285 let mut desc = D3D11_TEXTURE2D_DESC::default();
287 unsafe { frame_texture.GetDesc(&mut desc) }
288
289 if frame_content_size.Width != last_size.Width
291 || frame_content_size.Height != last_size.Height
292 {
293 let direct3d_device_recreate = &direct3d_device_recreate;
294 frame_pool_recreate.Recreate(
295 &direct3d_device_recreate.0,
296 pixel_format,
297 1,
298 frame_content_size,
299 )?;
300
301 last_size = frame_content_size;
302
303 return Ok(());
304 }
305
306 let texture_width = desc.Width;
308 let texture_height = desc.Height;
309
310 let mut frame = Frame::new(
312 &d3d_device_frame_pool,
313 frame_surface,
314 frame_texture,
315 timestamp,
316 &context,
317 &mut buffer,
318 texture_width,
319 texture_height,
320 color_format,
321 title_bar_height,
322 );
323
324 let stop = Arc::new(AtomicBool::new(false));
326 let internal_capture_control = InternalCaptureControl::new(stop.clone());
327
328 let result = callback_frame_pool
330 .lock()
331 .on_frame_arrived(&mut frame, internal_capture_control);
332
333 if stop.load(atomic::Ordering::Relaxed) || result.is_err() {
335 if let Err(e) = result {
336 *result_frame_pool.lock() = Some(e);
337 }
338
339 halt_frame_pool.store(true, atomic::Ordering::Relaxed);
340
341 unsafe {
343 PostThreadMessageW(
344 thread_id,
345 WM_QUIT,
346 WPARAM::default(),
347 LPARAM::default(),
348 )?;
349 };
350 }
351
352 Result::Ok(())
353 }
354 }))?;
355
356 if cursor_capture_settings != CursorCaptureSettings::Default {
357 if Self::is_cursor_settings_supported()? {
358 match cursor_capture_settings {
359 CursorCaptureSettings::Default => (),
360 CursorCaptureSettings::WithCursor => session.SetIsCursorCaptureEnabled(true)?,
361 CursorCaptureSettings::WithoutCursor => {
362 session.SetIsCursorCaptureEnabled(false)?
363 }
364 };
365 } else {
366 return Err(Error::CursorConfigUnsupported);
367 }
368 }
369
370 if draw_border_settings != DrawBorderSettings::Default {
371 if Self::is_border_settings_supported()? {
372 match draw_border_settings {
373 DrawBorderSettings::Default => (),
374 DrawBorderSettings::WithBorder => {
375 session.SetIsBorderRequired(true)?;
376 }
377 DrawBorderSettings::WithoutBorder => session.SetIsBorderRequired(false)?,
378 }
379 } else {
380 return Err(Error::BorderConfigUnsupported);
381 }
382 }
383
384 if secondary_window_settings != SecondaryWindowSettings::Default {
385 if Self::is_secondary_windows_supported()? {
386 match secondary_window_settings {
387 SecondaryWindowSettings::Default => (),
388 SecondaryWindowSettings::Include => session.SetIncludeSecondaryWindows(true)?,
389 SecondaryWindowSettings::Exclude => {
390 session.SetIncludeSecondaryWindows(false)?
391 }
392 }
393 } else {
394 return Err(Error::SecondaryWindowsUnsupported);
395 }
396 }
397
398 if minimum_update_interval_settings != MinimumUpdateIntervalSettings::Default {
399 if Self::is_minimum_update_interval_supported()? {
400 match minimum_update_interval_settings {
401 MinimumUpdateIntervalSettings::Default => (),
402 MinimumUpdateIntervalSettings::Custom(duration) => {
403 session.SetMinUpdateInterval(duration.into())?;
404 }
405 }
406 } else {
407 return Err(Error::MinimumUpdateIntervalUnsupported);
408 }
409 }
410
411 if dirty_region_settings != DirtyRegionSettings::Default {
412 if Self::is_dirty_region_supported()? {
413 match dirty_region_settings {
414 DirtyRegionSettings::Default => (),
415 DirtyRegionSettings::ReportOnly => {
416 session.SetDirtyRegionMode(GraphicsCaptureDirtyRegionMode::ReportOnly)?
417 }
418 DirtyRegionSettings::ReportAndRender => session
419 .SetDirtyRegionMode(GraphicsCaptureDirtyRegionMode::ReportAndRender)?,
420 }
421 } else {
422 return Err(Error::DirtyRegionUnsupported);
423 }
424 }
425
426 Ok(Self {
427 item,
428 _d3d_device: d3d_device,
429 _direct3d_device: direct3d_device,
430 _d3d_device_context: d3d_device_context,
431 frame_pool: Some(frame_pool),
432 session: Some(session),
433 halt,
434 active: false,
435 frame_arrived_event_token,
436 capture_closed_event_token,
437 })
438 }
439
440 #[inline]
447 pub fn start_capture(&mut self) -> Result<(), Error> {
448 if self.active {
449 return Err(Error::AlreadyStarted);
450 }
451
452 self.session.as_ref().unwrap().StartCapture()?;
453 self.active = true;
454
455 Ok(())
456 }
457
458 #[inline]
460 pub fn stop_capture(mut self) {
461 if let Some(frame_pool) = self.frame_pool.take() {
462 frame_pool
463 .RemoveFrameArrived(self.frame_arrived_event_token)
464 .expect("Failed to remove Frame Arrived event handler");
465
466 frame_pool.Close().expect("Failed to Close Frame Pool");
467 }
468
469 if let Some(session) = self.session.take() {
470 session.Close().expect("Failed to Close Capture Session");
471 }
472
473 self.item
474 .RemoveClosed(self.capture_closed_event_token)
475 .expect("Failed to remove Capture Session Closed event handler");
476 }
477
478 #[must_use]
484 #[inline]
485 pub fn halt_handle(&self) -> Arc<AtomicBool> {
486 self.halt.clone()
487 }
488
489 #[inline]
495 pub fn is_supported() -> Result<bool, Error> {
496 Ok(ApiInformation::IsApiContractPresentByMajor(
497 &HSTRING::from("Windows.Foundation.UniversalApiContract"),
498 8,
499 )? && GraphicsCaptureSession::IsSupported()?)
500 }
501
502 #[inline]
508 pub fn is_cursor_settings_supported() -> Result<bool, Error> {
509 Ok(ApiInformation::IsPropertyPresent(
510 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
511 &HSTRING::from("IsCursorCaptureEnabled"),
512 )? && Self::is_supported()?)
513 }
514
515 #[inline]
521 pub fn is_border_settings_supported() -> Result<bool, Error> {
522 Ok(ApiInformation::IsPropertyPresent(
523 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
524 &HSTRING::from("IsBorderRequired"),
525 )? && Self::is_supported()?)
526 }
527
528 #[inline]
534 pub fn is_secondary_windows_supported() -> Result<bool, Error> {
535 Ok(ApiInformation::IsPropertyPresent(
536 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
537 &HSTRING::from("IncludeSecondaryWindows"),
538 )? && Self::is_supported()?)
539 }
540
541 #[inline]
547 pub fn is_minimum_update_interval_supported() -> Result<bool, Error> {
548 Ok(ApiInformation::IsPropertyPresent(
549 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
550 &HSTRING::from("MinUpdateInterval"),
551 )? && Self::is_supported()?)
552 }
553
554 #[inline]
560 pub fn is_dirty_region_supported() -> Result<bool, Error> {
561 Ok(ApiInformation::IsPropertyPresent(
562 &HSTRING::from("Windows.Graphics.Capture.GraphicsCaptureSession"),
563 &HSTRING::from("DirtyRegionMode"),
564 )? && Self::is_supported()?)
565 }
566}
567
568impl Drop for GraphicsCaptureApi {
569 fn drop(&mut self) {
570 if let Some(frame_pool) = self.frame_pool.take() {
571 let _ = frame_pool.RemoveFrameArrived(self.frame_arrived_event_token);
572 let _ = frame_pool.Close();
573 }
574
575 if let Some(session) = self.session.take() {
576 let _ = session.Close();
577 }
578
579 let _ = self.item.RemoveClosed(self.capture_closed_event_token);
580 }
581}