1use std::path::Path;
30use std::{fs, io};
31
32use rayon::iter::{IntoParallelIterator, ParallelIterator};
33use windows::Win32::Foundation::E_ACCESSDENIED;
34use windows::Win32::Graphics::Direct3D11::{
35 D3D11_BOX, D3D11_TEXTURE2D_DESC, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
36};
37use windows::Win32::Graphics::Dxgi::Common::{
38 DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_R16G16B16A16_FLOAT,
39};
40use windows::Win32::Graphics::Dxgi::{
41 DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_NOT_FOUND, DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO,
42 IDXGIDevice4, IDXGIOutput6, IDXGIOutputDuplication,
43};
44use windows::Win32::UI::HiDpi::{DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, SetProcessDpiAwarenessContext};
45use windows::core::Interface;
46
47use crate::d3d11::{MappedStagingTexture, StagingTexture, create_d3d_device, unmap_staging_texture};
48use crate::encoder::{ImageEncoder, ImageEncoderError, ImageEncoderPixelFormat, ImageFormat};
49use crate::monitor::Monitor;
50
51#[derive(thiserror::Error, Debug)]
53pub enum Error {
54 #[error("Invalid crop size")]
56 InvalidSize,
57 #[error("Failed to find DXGI output for the specified monitor")]
59 OutputNotFound,
60 #[error("AcquireNextFrame timed out")]
62 Timeout,
63 #[error("Duplication access lost; the duplication must be recreated")]
65 AccessLost,
66 #[error("DirectX error: {0}")]
68 DirectXError(#[from] crate::d3d11::Error),
69 #[error("Invalid staging texture: {0}")]
71 InvalidStagingTexture(&'static str),
72 #[error("Windows API succeeded but did not return {0}")]
74 UnexpectedNullResult(&'static str),
75 #[error("Failed to encode the image buffer to image bytes with the specified format: {0}")]
79 ImageEncoderError(#[from] crate::encoder::ImageEncoderError),
80 #[error("I/O error: {0}")]
84 IoError(#[from] io::Error),
85 #[error("Windows API error: {0}")]
87 WindowsError(#[from] windows::core::Error),
88}
89
90#[derive(Eq, PartialEq, Clone, Copy, Debug)]
92pub enum DxgiDuplicationFormat {
93 Rgba16F,
95 Rgba8,
97 Bgra8,
99}
100
101const DEFAULT_DUPLICATION_FORMATS: [DXGI_FORMAT; 3] =
102 [DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM];
103
104pub struct DxgiDuplicationApi {
109 d3d_device: ID3D11Device,
111 d3d_device_context: ID3D11DeviceContext,
113 duplication: IDXGIOutputDuplication,
115 duplication_desc: DXGI_OUTDUPL_DESC,
117 dxgi_device: IDXGIDevice4,
119 output: IDXGIOutput6,
121 is_holding_frame: bool,
123}
124
125fn enable_per_monitor_dpi_awareness() -> Result<(), Error> {
126 match unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) } {
127 Ok(()) => Ok(()),
128 Err(error) if error.code() == E_ACCESSDENIED => Ok(()),
129 Err(error) => Err(Error::WindowsError(error)),
130 }
131}
132
133fn find_output_for_monitor(dxgi_device: &IDXGIDevice4, monitor: Monitor) -> Result<IDXGIOutput6, Error> {
134 let adapter = unsafe { dxgi_device.GetAdapter()? };
135 let mut index = 0u32;
136
137 loop {
138 match unsafe { adapter.EnumOutputs(index) } {
139 Ok(output) => {
140 let desc = unsafe { output.GetDesc()? };
141 if desc.Monitor.0 == monitor.as_raw_hmonitor() {
142 return Ok(output.cast::<IDXGIOutput6>()?);
143 }
144 index += 1;
145 }
146 Err(error) if error.code() == DXGI_ERROR_NOT_FOUND => return Err(Error::OutputNotFound),
147 Err(error) => return Err(Error::WindowsError(error)),
148 }
149 }
150}
151
152fn map_supported_formats(supported_formats: &[DxgiDuplicationFormat]) -> Vec<DXGI_FORMAT> {
153 let mut supported_formats = supported_formats
154 .iter()
155 .map(|format| match format {
156 DxgiDuplicationFormat::Rgba16F => DXGI_FORMAT_R16G16B16A16_FLOAT,
157 DxgiDuplicationFormat::Rgba8 => DXGI_FORMAT_R8G8B8A8_UNORM,
158 DxgiDuplicationFormat::Bgra8 => DXGI_FORMAT_B8G8R8A8_UNORM,
159 })
160 .collect::<Vec<_>>();
161
162 if !supported_formats.contains(&DXGI_FORMAT_B8G8R8A8_UNORM) {
163 supported_formats.push(DXGI_FORMAT_B8G8R8A8_UNORM);
164 }
165
166 supported_formats
167}
168
169impl DxgiDuplicationApi {
170 fn release_frame_if_needed(&mut self) -> Result<(), Error> {
171 if !self.is_holding_frame {
172 return Ok(());
173 }
174
175 match unsafe { self.duplication.ReleaseFrame() } {
176 Ok(()) => {
177 self.is_holding_frame = false;
178 Ok(())
179 }
180 Err(error) if error.code() == DXGI_ERROR_ACCESS_LOST => Err(Error::AccessLost),
181 Err(error) => Err(Error::WindowsError(error)),
182 }
183 }
184
185 fn recreate_with_formats(mut self, supported_formats: &[DXGI_FORMAT]) -> Result<Self, Error> {
186 let _ = self.release_frame_if_needed();
187
188 let d3d_device = self.d3d_device.clone();
192 let d3d_device_context = self.d3d_device_context.clone();
193 let dxgi_device = self.dxgi_device.clone();
194 let output = self.output.clone();
195 drop(self);
196
197 let duplication = unsafe { output.DuplicateOutput1(&d3d_device, 0, supported_formats)? };
198 let duplication_desc = unsafe { duplication.GetDesc() };
199
200 Ok(Self {
201 d3d_device,
202 d3d_device_context,
203 duplication,
204 duplication_desc,
205 dxgi_device,
206 output,
207 is_holding_frame: false,
208 })
209 }
210
211 pub fn new(monitor: Monitor) -> Result<Self, Error> {
216 let (d3d_device, d3d_device_context) = create_d3d_device()?;
218
219 let dxgi_device = d3d_device.cast::<IDXGIDevice4>()?;
220 let output = find_output_for_monitor(&dxgi_device, monitor)?;
221 enable_per_monitor_dpi_awareness()?;
222
223 let duplication = unsafe { output.DuplicateOutput1(&d3d_device, 0, &DEFAULT_DUPLICATION_FORMATS)? };
225
226 let duplication_desc = unsafe { duplication.GetDesc() };
228
229 Ok(Self {
230 d3d_device,
231 d3d_device_context,
232 duplication,
233 duplication_desc,
234 dxgi_device,
235 output,
236 is_holding_frame: false,
237 })
238 }
239
240 pub fn new_options(monitor: Monitor, supported_formats: &[DxgiDuplicationFormat]) -> Result<Self, Error> {
246 let (d3d_device, d3d_device_context) = create_d3d_device()?;
248
249 let dxgi_device = d3d_device.cast::<IDXGIDevice4>()?;
250 let output = find_output_for_monitor(&dxgi_device, monitor)?;
251 let supported_formats = map_supported_formats(supported_formats);
252 enable_per_monitor_dpi_awareness()?;
253
254 let duplication = unsafe { output.DuplicateOutput1(&d3d_device, 0, &supported_formats)? };
256
257 let duplication_desc = unsafe { duplication.GetDesc() };
259
260 Ok(Self {
261 d3d_device,
262 d3d_device_context,
263 duplication,
264 duplication_desc,
265 dxgi_device,
266 output,
267 is_holding_frame: false,
268 })
269 }
270
271 pub fn recreate(self) -> Result<Self, Error> {
274 self.recreate_with_formats(&DEFAULT_DUPLICATION_FORMATS)
275 }
276
277 pub fn recreate_options(self, supported_formats: &[DxgiDuplicationFormat]) -> Result<Self, Error> {
281 let supported_formats = map_supported_formats(supported_formats);
282 self.recreate_with_formats(&supported_formats)
283 }
284
285 #[inline]
288 #[must_use]
289 pub const fn device(&self) -> &ID3D11Device {
290 &self.d3d_device
291 }
292
293 #[inline]
296 #[must_use]
297 pub const fn device_context(&self) -> &ID3D11DeviceContext {
298 &self.d3d_device_context
299 }
300
301 #[inline]
303 #[must_use]
304 pub const fn duplication(&self) -> &IDXGIOutputDuplication {
305 &self.duplication
306 }
307
308 #[inline]
310 #[must_use]
311 pub const fn duplication_desc(&self) -> &DXGI_OUTDUPL_DESC {
312 &self.duplication_desc
313 }
314
315 #[inline]
317 #[must_use]
318 pub const fn dxgi_device(&self) -> &IDXGIDevice4 {
319 &self.dxgi_device
320 }
321
322 #[inline]
324 #[must_use]
325 pub const fn output(&self) -> &IDXGIOutput6 {
326 &self.output
327 }
328
329 #[inline]
331 #[must_use]
332 pub const fn width(&self) -> u32 {
333 self.duplication_desc.ModeDesc.Width
334 }
335
336 #[inline]
338 #[must_use]
339 pub const fn height(&self) -> u32 {
340 self.duplication_desc.ModeDesc.Height
341 }
342
343 #[inline]
345 #[must_use]
346 pub const fn format(&self) -> DxgiDuplicationFormat {
347 match self.duplication_desc.ModeDesc.Format {
348 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
349 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
350 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
351 _ => unreachable!(),
352 }
353 }
354
355 #[inline]
357 #[must_use]
358 pub const fn refresh_rate(&self) -> (u32, u32) {
359 (self.duplication_desc.ModeDesc.RefreshRate.Numerator, self.duplication_desc.ModeDesc.RefreshRate.Denominator)
360 }
361
362 #[inline]
382 pub fn acquire_next_frame(&mut self, timeout_ms: u32) -> Result<DxgiDuplicationFrame<'_>, Error> {
383 let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
384 let mut resource = None;
385
386 self.release_frame_if_needed()?;
388
389 match unsafe { self.duplication.AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource) } {
391 Ok(()) => (),
392 Err(e) => {
393 if e.code() == DXGI_ERROR_WAIT_TIMEOUT {
394 return Err(Error::Timeout);
395 } else if e.code() == DXGI_ERROR_ACCESS_LOST {
396 return Err(Error::AccessLost);
397 } else {
398 return Err(Error::WindowsError(e));
399 }
400 }
401 }
402 self.is_holding_frame = true;
403
404 let resource = resource.ok_or(Error::UnexpectedNullResult("an acquired DXGI frame resource"))?;
405
406 let frame_texture = resource.cast::<ID3D11Texture2D>()?;
408
409 let mut frame_desc = D3D11_TEXTURE2D_DESC::default();
411 unsafe { frame_texture.GetDesc(&mut frame_desc) };
412
413 Ok(DxgiDuplicationFrame {
414 d3d_device: &self.d3d_device,
415 d3d_device_context: &self.d3d_device_context,
416 duplication: &self.duplication,
417 texture: frame_texture,
418 texture_desc: frame_desc,
419 frame_info,
420 })
421 }
422}
423
424impl Drop for DxgiDuplicationApi {
425 fn drop(&mut self) {
426 let _ = self.release_frame_if_needed();
427 }
428}
429
430pub struct DxgiDuplicationFrame<'a> {
434 d3d_device: &'a ID3D11Device,
435 d3d_device_context: &'a ID3D11DeviceContext,
436 duplication: &'a IDXGIOutputDuplication,
437 texture: ID3D11Texture2D,
438 texture_desc: D3D11_TEXTURE2D_DESC,
439 frame_info: DXGI_OUTDUPL_FRAME_INFO,
440}
441
442impl<'a> DxgiDuplicationFrame<'a> {
443 #[inline]
445 #[must_use]
446 pub const fn width(&self) -> u32 {
447 self.texture_desc.Width
448 }
449
450 #[inline]
452 #[must_use]
453 pub const fn height(&self) -> u32 {
454 self.texture_desc.Height
455 }
456
457 #[inline]
459 #[must_use]
460 pub const fn format(&self) -> DxgiDuplicationFormat {
461 match self.texture_desc.Format {
462 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
463 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
464 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
465 _ => unreachable!(),
466 }
467 }
468
469 #[inline]
471 #[must_use]
472 pub const fn device(&self) -> &ID3D11Device {
473 self.d3d_device
474 }
475
476 #[inline]
478 #[must_use]
479 pub const fn device_context(&self) -> &ID3D11DeviceContext {
480 self.d3d_device_context
481 }
482
483 #[inline]
485 #[must_use]
486 pub const fn duplication(&self) -> &IDXGIOutputDuplication {
487 self.duplication
488 }
489
490 #[inline]
492 #[must_use]
493 pub const fn texture(&self) -> &ID3D11Texture2D {
494 &self.texture
495 }
496
497 #[inline]
500 #[must_use]
501 pub const fn texture_desc(&self) -> &D3D11_TEXTURE2D_DESC {
502 &self.texture_desc
503 }
504
505 #[inline]
507 #[must_use]
508 pub const fn frame_info(&self) -> &DXGI_OUTDUPL_FRAME_INFO {
509 &self.frame_info
510 }
511
512 #[inline]
520 pub fn buffer<'b>(&'b mut self) -> Result<DxgiDuplicationFrameBuffer<'b>, Error> {
521 let staging = StagingTexture::new(
522 self.d3d_device,
523 self.texture_desc.Width,
524 self.texture_desc.Height,
525 self.texture_desc.Format,
526 )?;
527
528 unsafe {
530 self.d3d_device_context.CopyResource(staging.texture(), &self.texture);
531 }
532
533 let mapped_texture = MappedStagingTexture::map_owned(self.d3d_device_context, staging)?;
534
535 Ok(DxgiDuplicationFrameBuffer::from_mapped(
536 mapped_texture,
537 self.texture_desc.Width,
538 self.texture_desc.Height,
539 self.format(),
540 ))
541 }
542
543 #[inline]
545 pub fn buffer_crop<'b>(
546 &'b mut self,
547 start_x: u32,
548 start_y: u32,
549 end_x: u32,
550 end_y: u32,
551 ) -> Result<DxgiDuplicationFrameBuffer<'b>, Error> {
552 if start_x >= end_x || start_y >= end_y {
553 return Err(Error::InvalidSize);
554 }
555
556 let texture_width = end_x - start_x;
557 let texture_height = end_y - start_y;
558
559 let staging = StagingTexture::new(self.d3d_device, texture_width, texture_height, self.texture_desc.Format)?;
560
561 let src_box = D3D11_BOX { left: start_x, top: start_y, front: 0, right: end_x, bottom: end_y, back: 1 };
563
564 unsafe {
566 self.d3d_device_context.CopySubresourceRegion(
567 staging.texture(),
568 0,
569 0,
570 0,
571 0,
572 &self.texture,
573 0,
574 Some(&src_box),
575 );
576 }
577
578 let mapped_texture = MappedStagingTexture::map_owned(self.d3d_device_context, staging)?;
579
580 Ok(DxgiDuplicationFrameBuffer::from_mapped(mapped_texture, texture_width, texture_height, self.format()))
581 }
582
583 #[inline]
589 pub fn buffer_with<'s>(
590 &'s mut self,
591 staging: &'s mut StagingTexture,
592 ) -> Result<DxgiDuplicationFrameBuffer<'s>, Error> {
593 let desc = staging.desc();
595 if desc.Width != self.texture_desc.Width || desc.Height != self.texture_desc.Height {
596 return Err(Error::InvalidStagingTexture("geometry must match the frame"));
597 }
598 if desc.Format != self.texture_desc.Format {
599 return Err(Error::InvalidStagingTexture("format must match the frame"));
600 }
601
602 unmap_staging_texture(self.d3d_device_context, staging);
603
604 unsafe {
606 self.d3d_device_context.CopyResource(staging.texture(), &self.texture);
607 }
608
609 let mapped_texture = MappedStagingTexture::map_borrowed(self.d3d_device_context, staging)?;
610
611 Ok(DxgiDuplicationFrameBuffer::from_mapped(
612 mapped_texture,
613 self.texture_desc.Width,
614 self.texture_desc.Height,
615 self.format(),
616 ))
617 }
618
619 #[inline]
624 pub fn buffer_crop_with<'s>(
625 &'s mut self,
626 staging: &'s mut StagingTexture,
627 start_x: u32,
628 start_y: u32,
629 end_x: u32,
630 end_y: u32,
631 ) -> Result<DxgiDuplicationFrameBuffer<'s>, Error> {
632 if start_x >= end_x || start_y >= end_y {
634 return Err(Error::InvalidSize);
635 }
636
637 let crop_width = end_x - start_x;
638 let crop_height = end_y - start_y;
639
640 let desc = staging.desc();
642 if desc.Format != self.texture_desc.Format {
643 return Err(Error::InvalidStagingTexture("format must match the frame"));
644 }
645 if desc.Width < crop_width || desc.Height < crop_height {
646 return Err(Error::InvalidStagingTexture("staging texture too small for crop region"));
647 }
648
649 unmap_staging_texture(self.d3d_device_context, staging);
650
651 let src_box = D3D11_BOX { left: start_x, top: start_y, front: 0, right: end_x, bottom: end_y, back: 1 };
653
654 unsafe {
656 self.d3d_device_context.CopySubresourceRegion(
657 staging.texture(),
658 0,
659 0,
660 0,
661 0,
662 &self.texture,
663 0,
664 Some(&src_box),
665 );
666 }
667
668 let mapped_texture = MappedStagingTexture::map_borrowed(self.d3d_device_context, staging)?;
669
670 Ok(DxgiDuplicationFrameBuffer::from_mapped(mapped_texture, crop_width, crop_height, self.format()))
671 }
672
673 #[inline]
675 pub fn save_as_image<T: AsRef<Path>>(&mut self, path: T, format: ImageFormat) -> Result<(), Error> {
676 let mut frame_buffer = self.buffer()?;
677
678 frame_buffer.save_as_image(path, format)?;
679
680 Ok(())
681 }
682}
683
684enum DxgiDuplicationFrameBufferBacking<'a> {
693 Borrowed(&'a mut [u8]),
694 Mapped(MappedStagingTexture<'a>),
695}
696
697impl DxgiDuplicationFrameBufferBacking<'_> {
698 const fn as_slice(&self, height: u32) -> &[u8] {
699 match self {
700 Self::Borrowed(buffer) => buffer,
701 Self::Mapped(texture) => texture.as_slice(height),
702 }
703 }
704
705 const fn as_mut_slice(&mut self, height: u32) -> &mut [u8] {
706 match self {
707 Self::Borrowed(buffer) => buffer,
708 Self::Mapped(texture) => texture.as_mut_slice(height),
709 }
710 }
711}
712
713pub struct DxgiDuplicationFrameBuffer<'a> {
715 backing: DxgiDuplicationFrameBufferBacking<'a>,
716 width: u32,
717 height: u32,
718 row_pitch: u32,
719 depth_pitch: u32,
720 format: DxgiDuplicationFormat,
721}
722
723impl<'a> DxgiDuplicationFrameBuffer<'a> {
724 #[inline]
726 #[must_use]
727 pub const fn new(
728 raw_buffer: &'a mut [u8],
729 width: u32,
730 height: u32,
731 row_pitch: u32,
732 depth_pitch: u32,
733 format: DxgiDuplicationFormat,
734 ) -> Self {
735 Self {
736 backing: DxgiDuplicationFrameBufferBacking::Borrowed(raw_buffer),
737 width,
738 height,
739 row_pitch,
740 depth_pitch,
741 format,
742 }
743 }
744
745 const fn from_mapped(
746 mapped_texture: MappedStagingTexture<'a>,
747 width: u32,
748 height: u32,
749 format: DxgiDuplicationFormat,
750 ) -> Self {
751 let row_pitch = mapped_texture.row_pitch();
752 let depth_pitch = mapped_texture.depth_pitch();
753
754 Self {
755 backing: DxgiDuplicationFrameBufferBacking::Mapped(mapped_texture),
756 width,
757 height,
758 row_pitch,
759 depth_pitch,
760 format,
761 }
762 }
763
764 #[inline]
766 #[must_use]
767 pub const fn width(&self) -> u32 {
768 self.width
769 }
770
771 #[inline]
773 #[must_use]
774 pub const fn height(&self) -> u32 {
775 self.height
776 }
777
778 #[inline]
780 #[must_use]
781 pub const fn row_pitch(&self) -> u32 {
782 self.row_pitch
783 }
784
785 #[inline]
787 #[must_use]
788 pub const fn depth_pitch(&self) -> u32 {
789 self.depth_pitch
790 }
791
792 #[inline]
794 #[must_use]
795 pub const fn format(&self) -> DxgiDuplicationFormat {
796 self.format
797 }
798
799 #[inline]
801 #[must_use]
802 pub const fn has_padding(&self) -> bool {
803 self.width * self.bytes_per_pixel() != self.row_pitch
804 }
805
806 #[inline]
808 #[must_use]
809 pub fn as_nopadding_buffer<'b>(&'b self, buffer: &'b mut Vec<u8>) -> &'b [u8] {
810 let raw_buffer = self.backing.as_slice(self.height);
811
812 if !self.has_padding() {
813 return raw_buffer;
814 }
815
816 let width = self.width;
817 let height = self.height;
818 let row_pitch = self.row_pitch;
819 let multiplier = self.bytes_per_pixel();
820 let frame_size = (width * height * multiplier) as usize;
821 if buffer.len() < frame_size {
822 buffer.resize(frame_size, 0);
823 }
824
825 let width_size = (width * multiplier) as usize;
826 let buffer_address = buffer.as_mut_ptr() as usize;
827 let raw_buffer_address = raw_buffer.as_ptr() as usize;
828 (0..height).into_par_iter().for_each(|y| {
829 let index = (y * row_pitch) as usize;
830 let src = raw_buffer_address as *const u8;
831 let dst = buffer_address as *mut u8;
832
833 unsafe {
834 std::ptr::copy_nonoverlapping(src.add(index), dst.add(y as usize * width_size), width_size);
835 }
836 });
837
838 &buffer[0..frame_size]
839 }
840
841 #[inline]
843 #[must_use]
844 pub const fn as_raw_buffer(&mut self) -> &mut [u8] {
845 self.backing.as_mut_slice(self.height)
846 }
847
848 #[inline]
850 pub fn save_as_image<T: AsRef<Path>>(&mut self, path: T, format: ImageFormat) -> Result<(), Error> {
851 let width = self.width;
852 let height = self.height;
853
854 let pixel_format = match self.format {
855 DxgiDuplicationFormat::Rgba8 => ImageEncoderPixelFormat::Rgba8,
856 DxgiDuplicationFormat::Bgra8 => ImageEncoderPixelFormat::Bgra8,
857 _ => return Err(ImageEncoderError::UnsupportedFormat.into()),
858 };
859
860 let mut buffer = Vec::new();
861 let bytes =
862 ImageEncoder::new(format, pixel_format)?.encode(self.as_nopadding_buffer(&mut buffer), width, height)?;
863
864 fs::write(path, bytes)?;
865
866 Ok(())
867 }
868
869 #[inline]
870 #[must_use]
871 const fn bytes_per_pixel(&self) -> u32 {
872 match self.format {
873 DxgiDuplicationFormat::Rgba16F => 8,
874 DxgiDuplicationFormat::Rgba8 | DxgiDuplicationFormat::Bgra8 => 4,
875 }
876 }
877}