1use std::path::Path;
30use std::{fs, io, slice};
31
32use rayon::iter::{IntoParallelIterator, ParallelIterator};
33use windows::Win32::Foundation::E_ACCESSDENIED;
34use windows::Win32::Graphics::Direct3D11::{
35 D3D11_BOX, D3D11_CPU_ACCESS_READ, D3D11_CPU_ACCESS_WRITE, D3D11_MAP_READ_WRITE, D3D11_MAPPED_SUBRESOURCE,
36 D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D,
37};
38use windows::Win32::Graphics::Dxgi::Common::{
39 DXGI_FORMAT, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM_SRGB, DXGI_FORMAT_R8G8B8A8_UNORM,
40 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM,
41 DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_SAMPLE_DESC,
42};
43use windows::Win32::Graphics::Dxgi::{
44 DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO, IDXGIDevice4,
45 IDXGIOutput6, IDXGIOutputDuplication,
46};
47use windows::Win32::UI::HiDpi::{DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2, SetProcessDpiAwarenessContext};
48use windows::core::Interface;
49
50use crate::d3d11::{StagingTexture, create_d3d_device};
51use crate::encoder::{ImageEncoder, ImageEncoderError, ImageEncoderPixelFormat, ImageFormat};
52use crate::monitor::Monitor;
53
54#[derive(thiserror::Error, Debug)]
56pub enum Error {
57 #[error("Invalid crop size")]
59 InvalidSize,
60 #[error("Failed to find DXGI output for the specified monitor")]
62 OutputNotFound,
63 #[error("AcquireNextFrame timed out")]
65 Timeout,
66 #[error("Duplication access lost; the duplication must be recreated")]
68 AccessLost,
69 #[error("DirectX error: {0}")]
71 DirectXError(#[from] crate::d3d11::Error),
72 #[error("Invalid staging texture: {0}")]
74 InvalidStagingTexture(&'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 Rgb10A2,
97 Rgb10XrA2,
99 Rgba8,
101 Rgba8Srgb,
103 Bgra8,
105 Bgra8Srgb,
107}
108
109pub struct DxgiDuplicationApi {
114 d3d_device: ID3D11Device,
116 d3d_device_context: ID3D11DeviceContext,
118 duplication: IDXGIOutputDuplication,
120 duplication_desc: DXGI_OUTDUPL_DESC,
122 dxgi_device: IDXGIDevice4,
124 output: IDXGIOutput6,
126 is_holding_frame: bool,
128}
129
130impl DxgiDuplicationApi {
131 pub fn new(monitor: Monitor) -> Result<Self, Error> {
136 let (d3d_device, d3d_device_context) = create_d3d_device()?;
138
139 let dxgi_device = d3d_device.cast::<IDXGIDevice4>()?;
141 let adapter = unsafe { dxgi_device.GetAdapter()? };
142
143 let found_output;
145 let mut index = 0u32;
146 loop {
147 let output = unsafe { adapter.EnumOutputs(index) }?;
148 let desc = unsafe { output.GetDesc()? };
149 if desc.Monitor.0 == monitor.as_raw_hmonitor() {
150 found_output = Some(output);
151 break;
152 }
153 index += 1;
154 }
155
156 let Some(output) = found_output else {
157 return Err(Error::OutputNotFound);
158 };
159
160 let output = output.cast::<IDXGIOutput6>()?;
162
163 match unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) } {
165 Ok(()) => (),
166 Err(e) => {
167 if e.code() != E_ACCESSDENIED {
170 return Err(Error::WindowsError(e));
171 }
172 }
173 }
174
175 let duplication = unsafe { output.DuplicateOutput(&d3d_device)? };
177
178 let duplication_desc = unsafe { duplication.GetDesc() };
180
181 Ok(Self {
182 d3d_device,
183 d3d_device_context,
184 duplication,
185 duplication_desc,
186 dxgi_device,
187 output,
188 is_holding_frame: false,
189 })
190 }
191
192 pub fn new_options(monitor: Monitor, supported_formats: &[DxgiDuplicationFormat]) -> Result<Self, Error> {
200 let (d3d_device, d3d_device_context) = create_d3d_device()?;
202
203 let dxgi_device = d3d_device.cast::<IDXGIDevice4>()?;
205 let adapter = unsafe { dxgi_device.GetAdapter()? };
206
207 let found_output;
209 let mut index = 0u32;
210 loop {
211 let output = unsafe { adapter.EnumOutputs(index) }?;
212 let desc = unsafe { output.GetDesc()? };
213 if desc.Monitor.0 == monitor.as_raw_hmonitor() {
214 found_output = Some(output);
215 break;
216 }
217 index += 1;
218 }
219
220 let Some(output) = found_output else {
221 return Err(Error::OutputNotFound);
222 };
223
224 let output = output.cast::<IDXGIOutput6>()?;
226
227 let mut supported_formats = supported_formats
229 .iter()
230 .map(|f| match f {
231 DxgiDuplicationFormat::Rgba16F => DXGI_FORMAT_R16G16B16A16_FLOAT,
232 DxgiDuplicationFormat::Rgb10A2 => DXGI_FORMAT_R10G10B10A2_UNORM,
233 DxgiDuplicationFormat::Rgb10XrA2 => DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
234 DxgiDuplicationFormat::Rgba8 => DXGI_FORMAT_R8G8B8A8_UNORM,
235 DxgiDuplicationFormat::Rgba8Srgb => DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
236 DxgiDuplicationFormat::Bgra8 => DXGI_FORMAT_B8G8R8A8_UNORM,
237 DxgiDuplicationFormat::Bgra8Srgb => DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
238 })
239 .collect::<Vec<DXGI_FORMAT>>();
240
241 if !supported_formats.contains(&DXGI_FORMAT_B8G8R8A8_UNORM) {
242 supported_formats.push(DXGI_FORMAT_B8G8R8A8_UNORM);
243 }
244
245 match unsafe { SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2) } {
247 Ok(()) => (),
248 Err(e) => {
249 if e.code() != E_ACCESSDENIED {
252 return Err(Error::WindowsError(e));
253 }
254 }
255 }
256
257 let duplication = unsafe { output.DuplicateOutput1(&d3d_device, 0, &supported_formats)? };
259
260 let duplication_desc = unsafe { duplication.GetDesc() };
262
263 Ok(Self {
264 d3d_device,
265 d3d_device_context,
266 duplication,
267 duplication_desc,
268 dxgi_device,
269 output,
270 is_holding_frame: false,
271 })
272 }
273
274 pub fn recreate(self) -> Result<Self, Error> {
277 let Self {
278 d3d_device,
279 d3d_device_context,
280 duplication,
281 duplication_desc: _,
282 dxgi_device,
283 output,
284 is_holding_frame: _,
285 } = self;
286
287 drop(duplication);
288
289 let duplication = unsafe { output.DuplicateOutput(&d3d_device)? };
290 let duplication_desc = unsafe { duplication.GetDesc() };
291
292 Ok(Self {
293 d3d_device,
294 d3d_device_context,
295 duplication,
296 duplication_desc,
297 dxgi_device,
298 output,
299 is_holding_frame: false,
300 })
301 }
302
303 pub fn recreate_options(self, supported_formats: &[DxgiDuplicationFormat]) -> Result<Self, Error> {
307 let mut supported_formats = supported_formats
309 .iter()
310 .map(|f| match f {
311 DxgiDuplicationFormat::Rgba16F => DXGI_FORMAT_R16G16B16A16_FLOAT,
312 DxgiDuplicationFormat::Rgb10A2 => DXGI_FORMAT_R10G10B10A2_UNORM,
313 DxgiDuplicationFormat::Rgb10XrA2 => DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM,
314 DxgiDuplicationFormat::Rgba8 => DXGI_FORMAT_R8G8B8A8_UNORM,
315 DxgiDuplicationFormat::Rgba8Srgb => DXGI_FORMAT_R8G8B8A8_UNORM_SRGB,
316 DxgiDuplicationFormat::Bgra8 => DXGI_FORMAT_B8G8R8A8_UNORM,
317 DxgiDuplicationFormat::Bgra8Srgb => DXGI_FORMAT_B8G8R8A8_UNORM_SRGB,
318 })
319 .collect::<Vec<DXGI_FORMAT>>();
320
321 if !supported_formats.contains(&DXGI_FORMAT_B8G8R8A8_UNORM) {
322 supported_formats.push(DXGI_FORMAT_B8G8R8A8_UNORM);
323 }
324
325 let Self {
326 d3d_device,
327 d3d_device_context,
328 duplication,
329 duplication_desc: _,
330 dxgi_device,
331 output,
332 is_holding_frame: _,
333 } = self;
334
335 drop(duplication);
336
337 let duplication = unsafe { output.DuplicateOutput1(&d3d_device, 0, &supported_formats)? };
338 let duplication_desc = unsafe { duplication.GetDesc() };
339
340 Ok(Self {
341 d3d_device,
342 d3d_device_context,
343 duplication,
344 duplication_desc,
345 dxgi_device,
346 output,
347 is_holding_frame: false,
348 })
349 }
350
351 #[inline]
354 #[must_use]
355 pub const fn device(&self) -> &ID3D11Device {
356 &self.d3d_device
357 }
358
359 #[inline]
362 #[must_use]
363 pub const fn device_context(&self) -> &ID3D11DeviceContext {
364 &self.d3d_device_context
365 }
366
367 #[inline]
369 #[must_use]
370 pub const fn duplication(&self) -> &IDXGIOutputDuplication {
371 &self.duplication
372 }
373
374 #[inline]
376 #[must_use]
377 pub const fn duplication_desc(&self) -> &DXGI_OUTDUPL_DESC {
378 &self.duplication_desc
379 }
380
381 #[inline]
383 #[must_use]
384 pub const fn dxgi_device(&self) -> &IDXGIDevice4 {
385 &self.dxgi_device
386 }
387
388 #[inline]
390 #[must_use]
391 pub const fn output(&self) -> &IDXGIOutput6 {
392 &self.output
393 }
394
395 #[inline]
397 #[must_use]
398 pub const fn width(&self) -> u32 {
399 self.duplication_desc.ModeDesc.Width
400 }
401
402 #[inline]
404 #[must_use]
405 pub const fn height(&self) -> u32 {
406 self.duplication_desc.ModeDesc.Height
407 }
408
409 #[inline]
411 #[must_use]
412 pub const fn format(&self) -> DxgiDuplicationFormat {
413 match self.duplication_desc.ModeDesc.Format {
414 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
415 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
416 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
417 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
418 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
419 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
420 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
421 _ => unreachable!(),
422 }
423 }
424
425 #[inline]
427 #[must_use]
428 pub const fn refresh_rate(&self) -> (u32, u32) {
429 (self.duplication_desc.ModeDesc.RefreshRate.Numerator, self.duplication_desc.ModeDesc.RefreshRate.Denominator)
430 }
431
432 #[inline]
452 pub fn acquire_next_frame(&mut self, timeout_ms: u32) -> Result<DxgiDuplicationFrame<'_>, Error> {
453 let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
454 let mut resource = None;
455
456 if self.is_holding_frame {
458 match unsafe { self.duplication.ReleaseFrame() } {
459 Ok(()) => (),
460 Err(e) => {
461 if e.code() == DXGI_ERROR_ACCESS_LOST {
462 return Err(Error::AccessLost);
463 } else {
464 return Err(Error::WindowsError(e));
465 }
466 }
467 }
468 self.is_holding_frame = false;
469 }
470
471 match unsafe { self.duplication.AcquireNextFrame(timeout_ms, &mut frame_info, &mut resource) } {
473 Ok(()) => (),
474 Err(e) => {
475 if e.code() == DXGI_ERROR_WAIT_TIMEOUT {
476 return Err(Error::Timeout);
477 } else if e.code() == DXGI_ERROR_ACCESS_LOST {
478 return Err(Error::AccessLost);
479 } else {
480 return Err(Error::WindowsError(e));
481 }
482 }
483 }
484 self.is_holding_frame = true;
485
486 let resource = resource.unwrap();
487
488 let frame_texture = resource.cast::<ID3D11Texture2D>()?;
490
491 let mut frame_desc = D3D11_TEXTURE2D_DESC::default();
493 unsafe { frame_texture.GetDesc(&mut frame_desc) };
494
495 Ok(DxgiDuplicationFrame {
496 d3d_device: &self.d3d_device,
497 d3d_device_context: &self.d3d_device_context,
498 duplication: &self.duplication,
499 texture: frame_texture,
500 texture_desc: frame_desc,
501 frame_info,
502 })
503 }
504}
505
506pub struct DxgiDuplicationFrame<'a> {
510 d3d_device: &'a ID3D11Device,
511 d3d_device_context: &'a ID3D11DeviceContext,
512 duplication: &'a IDXGIOutputDuplication,
513 texture: ID3D11Texture2D,
514 texture_desc: D3D11_TEXTURE2D_DESC,
515 frame_info: DXGI_OUTDUPL_FRAME_INFO,
516}
517
518impl<'a> DxgiDuplicationFrame<'a> {
519 #[inline]
521 #[must_use]
522 pub const fn width(&self) -> u32 {
523 self.texture_desc.Width
524 }
525
526 #[inline]
528 #[must_use]
529 pub const fn height(&self) -> u32 {
530 self.texture_desc.Height
531 }
532
533 #[inline]
535 #[must_use]
536 pub const fn format(&self) -> DxgiDuplicationFormat {
537 match self.texture_desc.Format {
538 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
539 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
540 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
541 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
542 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
543 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
544 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
545 _ => unreachable!(),
546 }
547 }
548
549 #[inline]
551 #[must_use]
552 pub const fn device(&self) -> &ID3D11Device {
553 self.d3d_device
554 }
555
556 #[inline]
558 #[must_use]
559 pub const fn device_context(&self) -> &ID3D11DeviceContext {
560 self.d3d_device_context
561 }
562
563 #[inline]
565 #[must_use]
566 pub const fn duplication(&self) -> &IDXGIOutputDuplication {
567 self.duplication
568 }
569
570 #[inline]
572 #[must_use]
573 pub const fn texture(&self) -> &ID3D11Texture2D {
574 &self.texture
575 }
576
577 #[inline]
580 #[must_use]
581 pub const fn texture_desc(&self) -> &D3D11_TEXTURE2D_DESC {
582 &self.texture_desc
583 }
584
585 #[inline]
587 #[must_use]
588 pub const fn frame_info(&self) -> &DXGI_OUTDUPL_FRAME_INFO {
589 &self.frame_info
590 }
591
592 #[inline]
600 pub fn buffer<'b>(&'b mut self) -> Result<DxgiDuplicationFrameBuffer<'b>, Error> {
601 let texture_desc = D3D11_TEXTURE2D_DESC {
603 Width: self.texture_desc.Width,
604 Height: self.texture_desc.Height,
605 MipLevels: 1,
606 ArraySize: 1,
607 Format: self.texture_desc.Format,
608 SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
609 Usage: D3D11_USAGE_STAGING,
610 BindFlags: 0,
611 CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32 | D3D11_CPU_ACCESS_WRITE.0 as u32,
612 MiscFlags: 0,
613 };
614
615 let mut staging = None;
617 unsafe {
618 self.d3d_device.CreateTexture2D(&texture_desc, None, Some(&mut staging))?;
619 };
620 let staging = staging.unwrap();
621
622 unsafe {
624 self.d3d_device_context.CopyResource(&staging, &self.texture);
625 };
626
627 let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
629 unsafe {
630 self.d3d_device_context.Map(&staging, 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
631 };
632
633 let mapped_frame_data = unsafe {
635 slice::from_raw_parts_mut(mapped.pData.cast(), (self.texture_desc.Height * mapped.RowPitch) as usize)
636 };
637
638 let format = match self.texture_desc.Format {
639 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
640 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
641 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
642 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
643 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
644 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
645 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
646 _ => unreachable!(),
647 };
648
649 Ok(DxgiDuplicationFrameBuffer::new(
650 mapped_frame_data,
651 self.texture_desc.Width,
652 self.texture_desc.Height,
653 mapped.RowPitch,
654 mapped.DepthPitch,
655 format,
656 ))
657 }
658
659 #[inline]
661 pub fn buffer_crop<'b>(
662 &'b mut self,
663 start_x: u32,
664 start_y: u32,
665 end_x: u32,
666 end_y: u32,
667 ) -> Result<DxgiDuplicationFrameBuffer<'b>, Error> {
668 if start_x >= end_x || start_y >= end_y {
669 return Err(Error::InvalidSize);
670 }
671
672 let texture_width = end_x - start_x;
673 let texture_height = end_y - start_y;
674
675 let texture_desc = D3D11_TEXTURE2D_DESC {
677 Width: texture_width,
678 Height: texture_height,
679 MipLevels: 1,
680 ArraySize: 1,
681 Format: self.texture_desc.Format,
682 SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
683 Usage: D3D11_USAGE_STAGING,
684 BindFlags: 0,
685 CPUAccessFlags: D3D11_CPU_ACCESS_READ.0 as u32 | D3D11_CPU_ACCESS_WRITE.0 as u32,
686 MiscFlags: 0,
687 };
688
689 let mut staging = None;
691 unsafe {
692 self.d3d_device.CreateTexture2D(&texture_desc, None, Some(&mut staging))?;
693 };
694 let staging = staging.unwrap();
695
696 let src_box = D3D11_BOX { left: start_x, top: start_y, front: 0, right: end_x, bottom: end_y, back: 1 };
698
699 unsafe {
701 self.d3d_device_context.CopySubresourceRegion(&staging, 0, 0, 0, 0, &self.texture, 0, Some(&src_box));
702 }
703
704 let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
706 unsafe {
707 self.d3d_device_context.Map(&staging, 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
708 }
709
710 let mapped_frame_data =
712 unsafe { slice::from_raw_parts_mut(mapped.pData.cast(), (texture_height * mapped.RowPitch) as usize) };
713
714 let format = match self.texture_desc.Format {
715 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
716 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
717 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
718 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
719 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
720 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
721 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
722 _ => unreachable!(),
723 };
724
725 Ok(DxgiDuplicationFrameBuffer::new(
726 mapped_frame_data,
727 texture_width,
728 texture_height,
729 mapped.RowPitch,
730 mapped.DepthPitch,
731 format,
732 ))
733 }
734
735 #[inline]
741 pub fn buffer_with<'s>(
742 &'s mut self,
743 staging: &'s mut StagingTexture,
744 ) -> Result<DxgiDuplicationFrameBuffer<'s>, Error> {
745 let desc = staging.desc();
747 if desc.Width != self.texture_desc.Width || desc.Height != self.texture_desc.Height {
748 return Err(Error::InvalidStagingTexture("geometry must match the frame"));
749 }
750 if desc.Format != self.texture_desc.Format {
751 return Err(Error::InvalidStagingTexture("format must match the frame"));
752 }
753
754 if staging.is_mapped() {
756 unsafe { self.d3d_device_context.Unmap(staging.texture(), 0) };
757 staging.set_mapped(false);
758 }
759
760 unsafe {
762 self.d3d_device_context.CopyResource(staging.texture(), &self.texture);
763 }
764
765 let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
767 unsafe {
768 self.d3d_device_context.Map(staging.texture(), 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
769 }
770 staging.set_mapped(true);
771
772 let mapped_frame_data = unsafe {
774 slice::from_raw_parts_mut(mapped.pData.cast(), (self.texture_desc.Height * mapped.RowPitch) as usize)
775 };
776
777 let format = match self.texture_desc.Format {
778 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
779 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
780 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
781 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
782 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
783 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
784 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
785 _ => unreachable!(),
786 };
787
788 Ok(DxgiDuplicationFrameBuffer::new(
789 mapped_frame_data,
790 self.texture_desc.Width,
791 self.texture_desc.Height,
792 mapped.RowPitch,
793 mapped.DepthPitch,
794 format,
795 ))
796 }
797
798 #[inline]
803 pub fn buffer_crop_with<'s>(
804 &'s mut self,
805 staging: &'s mut StagingTexture,
806 start_x: u32,
807 start_y: u32,
808 end_x: u32,
809 end_y: u32,
810 ) -> Result<DxgiDuplicationFrameBuffer<'s>, Error> {
811 if start_x >= end_x || start_y >= end_y {
813 return Err(Error::InvalidSize);
814 }
815
816 let crop_width = end_x - start_x;
817 let crop_height = end_y - start_y;
818
819 let desc = staging.desc();
821 if desc.Format != self.texture_desc.Format {
822 return Err(Error::InvalidStagingTexture("format must match the frame"));
823 }
824 if desc.Width < crop_width || desc.Height < crop_height {
825 return Err(Error::InvalidStagingTexture("staging texture too small for crop region"));
826 }
827
828 if staging.is_mapped() {
830 unsafe { self.d3d_device_context.Unmap(staging.texture(), 0) };
831 staging.set_mapped(false);
832 }
833
834 let src_box = D3D11_BOX { left: start_x, top: start_y, front: 0, right: end_x, bottom: end_y, back: 1 };
836
837 unsafe {
839 self.d3d_device_context.CopySubresourceRegion(
840 staging.texture(),
841 0,
842 0,
843 0,
844 0,
845 &self.texture,
846 0,
847 Some(&src_box),
848 );
849 }
850
851 let mut mapped = D3D11_MAPPED_SUBRESOURCE::default();
853 unsafe {
854 self.d3d_device_context.Map(staging.texture(), 0, D3D11_MAP_READ_WRITE, 0, Some(&mut mapped))?;
855 }
856 staging.set_mapped(true);
857
858 let mapped_frame_data =
860 unsafe { slice::from_raw_parts_mut(mapped.pData.cast(), (crop_height * mapped.RowPitch) as usize) };
861
862 let format = match self.texture_desc.Format {
863 DXGI_FORMAT_R16G16B16A16_FLOAT => DxgiDuplicationFormat::Rgba16F,
864 DXGI_FORMAT_R10G10B10A2_UNORM => DxgiDuplicationFormat::Rgb10A2,
865 DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM => DxgiDuplicationFormat::Rgb10XrA2,
866 DXGI_FORMAT_R8G8B8A8_UNORM => DxgiDuplicationFormat::Rgba8,
867 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => DxgiDuplicationFormat::Rgba8Srgb,
868 DXGI_FORMAT_B8G8R8A8_UNORM => DxgiDuplicationFormat::Bgra8,
869 DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => DxgiDuplicationFormat::Bgra8Srgb,
870 _ => unreachable!(),
871 };
872
873 Ok(DxgiDuplicationFrameBuffer::new(
874 mapped_frame_data,
875 crop_width,
876 crop_height,
877 mapped.RowPitch,
878 mapped.DepthPitch,
879 format,
880 ))
881 }
882
883 #[inline]
885 pub fn save_as_image<T: AsRef<Path>>(&mut self, path: T, format: ImageFormat) -> Result<(), Error> {
886 let mut frame_buffer = self.buffer()?;
887
888 frame_buffer.save_as_image(path, format)?;
889
890 Ok(())
891 }
892}
893
894pub struct DxgiDuplicationFrameBuffer<'a> {
903 raw_buffer: &'a mut [u8],
904 width: u32,
905 height: u32,
906 row_pitch: u32,
907 depth_pitch: u32,
908 format: DxgiDuplicationFormat,
909}
910
911impl<'a> DxgiDuplicationFrameBuffer<'a> {
912 #[inline]
914 #[must_use]
915 pub const fn new(
916 raw_buffer: &'a mut [u8],
917 width: u32,
918 height: u32,
919 row_pitch: u32,
920 depth_pitch: u32,
921 format: DxgiDuplicationFormat,
922 ) -> Self {
923 Self { raw_buffer, width, height, row_pitch, depth_pitch, format }
924 }
925
926 #[inline]
928 #[must_use]
929 pub const fn width(&self) -> u32 {
930 self.width
931 }
932
933 #[inline]
935 #[must_use]
936 pub const fn height(&self) -> u32 {
937 self.height
938 }
939
940 #[inline]
942 #[must_use]
943 pub const fn row_pitch(&self) -> u32 {
944 self.row_pitch
945 }
946
947 #[inline]
949 #[must_use]
950 pub const fn depth_pitch(&self) -> u32 {
951 self.depth_pitch
952 }
953
954 #[inline]
956 #[must_use]
957 pub const fn format(&self) -> DxgiDuplicationFormat {
958 self.format
959 }
960
961 #[inline]
963 #[must_use]
964 pub const fn has_padding(&self) -> bool {
965 self.width * 4 != self.row_pitch
966 }
967
968 #[inline]
970 #[must_use]
971 pub fn as_nopadding_buffer<'b>(&'b self, buffer: &'b mut Vec<u8>) -> &'b [u8] {
972 if !self.has_padding() {
973 return self.raw_buffer;
974 }
975
976 let multiplier = match self.format {
977 DxgiDuplicationFormat::Rgba16F => 8,
978 DxgiDuplicationFormat::Rgb10A2 => 4,
979 DxgiDuplicationFormat::Rgb10XrA2 => 4,
980 DxgiDuplicationFormat::Rgba8 => 4,
981 DxgiDuplicationFormat::Rgba8Srgb => 4,
982 DxgiDuplicationFormat::Bgra8 => 4,
983 DxgiDuplicationFormat::Bgra8Srgb => 4,
984 };
985
986 let frame_size = (self.width * self.height * multiplier) as usize;
987 if buffer.capacity() < frame_size {
988 buffer.resize(frame_size, 0);
989 }
990
991 let width_size = (self.width * multiplier) as usize;
992 let buffer_address = buffer.as_mut_ptr() as isize;
993 (0..self.height).into_par_iter().for_each(|y| {
994 let index = (y * self.row_pitch) as usize;
995 let ptr = buffer_address as *mut u8;
996
997 unsafe {
998 std::ptr::copy_nonoverlapping(
999 self.raw_buffer.as_ptr().add(index),
1000 ptr.add(y as usize * width_size),
1001 width_size,
1002 );
1003 }
1004 });
1005
1006 &buffer[0..frame_size]
1007 }
1008
1009 #[inline]
1011 #[must_use]
1012 pub const fn as_raw_buffer(&mut self) -> &mut [u8] {
1013 self.raw_buffer
1014 }
1015
1016 #[inline]
1018 pub fn save_as_image<T: AsRef<Path>>(&mut self, path: T, format: ImageFormat) -> Result<(), Error> {
1019 let width = self.width;
1020 let height = self.height;
1021
1022 let pixel_format = match self.format {
1023 DxgiDuplicationFormat::Rgba8 => ImageEncoderPixelFormat::Rgba8,
1024 DxgiDuplicationFormat::Bgra8 => ImageEncoderPixelFormat::Bgra8,
1025 _ => return Err(ImageEncoderError::UnsupportedFormat.into()),
1026 };
1027
1028 let mut buffer = Vec::new();
1029 let bytes =
1030 ImageEncoder::new(format, pixel_format)?.encode(self.as_nopadding_buffer(&mut buffer), width, height)?;
1031
1032 fs::write(path, bytes)?;
1033
1034 Ok(())
1035 }
1036}