Skip to main content

dxgcap/
lib.rs

1//! Capture the screen with DXGI Desktop Duplication
2
3#![cfg(windows)]
4
5extern crate winapi;
6extern crate wio;
7
8use std::mem::zeroed;
9use std::{mem, ptr, slice};
10use winapi::shared::dxgi::{
11    CreateDXGIFactory1, IDXGIAdapter, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGISurface1,
12    IID_IDXGIFactory1, DXGI_MAP_READ, DXGI_OUTPUT_DESC, DXGI_RESOURCE_PRIORITY_MAXIMUM,
13};
14use winapi::shared::dxgi1_2::{IDXGIOutput1, IDXGIOutputDuplication};
15use winapi::shared::dxgitype::*;
16// use winapi::shared::ntdef::*;
17use winapi::shared::windef::*;
18use winapi::shared::winerror::*;
19use winapi::um::d3d11::*;
20use winapi::um::d3dcommon::*;
21use winapi::um::unknwnbase::*;
22use winapi::um::winuser::*;
23use wio::com::ComPtr;
24
25/// Color represented by additive channels: Blue (b), Green (g), Red (r), and Alpha (a).
26#[derive(Copy, Clone, Debug, PartialOrd, PartialEq, Eq, Ord)]
27pub struct BGRA8 {
28    pub b: u8,
29    pub g: u8,
30    pub r: u8,
31    pub a: u8,
32}
33
34/// Possible errors when capturing
35#[derive(Debug)]
36pub enum CaptureError {
37    /// Could not duplicate output, access denied. Might be in protected fullscreen.
38    AccessDenied,
39    /// Access to the duplicated output was lost. Likely, mode was changed e.g. window => full
40    AccessLost,
41    /// Error when trying to refresh outputs after some failure.
42    RefreshFailure,
43    /// AcquireNextFrame timed out.
44    Timeout,
45    /// General/Unexpected failure
46    Fail(&'static str),
47}
48
49/// Check whether the HRESULT represents a failure
50pub fn hr_failed(hr: HRESULT) -> bool {
51    hr < 0
52}
53
54fn create_dxgi_factory_1() -> ComPtr<IDXGIFactory1> {
55    unsafe {
56        let mut factory = ptr::null_mut();
57        let hr = CreateDXGIFactory1(&IID_IDXGIFactory1, &mut factory);
58        if hr_failed(hr) {
59            panic!("Failed to create DXGIFactory1, {:x}", hr)
60        } else {
61            ComPtr::from_raw(factory as *mut IDXGIFactory1)
62        }
63    }
64}
65
66fn d3d11_create_device(
67    adapter: *mut IDXGIAdapter,
68) -> (ComPtr<ID3D11Device>, ComPtr<ID3D11DeviceContext>) {
69    unsafe {
70        let (mut d3d11_device, mut device_context) = (ptr::null_mut(), ptr::null_mut());
71        let hr = D3D11CreateDevice(
72            adapter,
73            D3D_DRIVER_TYPE_UNKNOWN,
74            ptr::null_mut(),
75            0,
76            ptr::null_mut(),
77            0,
78            D3D11_SDK_VERSION,
79            &mut d3d11_device,
80            &mut D3D_FEATURE_LEVEL_9_1,
81            &mut device_context,
82        );
83        if hr_failed(hr) {
84            panic!("Failed to create d3d11 device and device context, {:x}", hr)
85        } else {
86            (
87                ComPtr::from_raw(d3d11_device as *mut ID3D11Device),
88                ComPtr::from_raw(device_context),
89            )
90        }
91    }
92}
93
94fn get_adapter_outputs(adapter: &IDXGIAdapter1) -> Vec<ComPtr<IDXGIOutput>> {
95    let mut outputs = Vec::new();
96    for i in 0.. {
97        unsafe {
98            let mut output = ptr::null_mut();
99            if hr_failed(adapter.EnumOutputs(i, &mut output)) {
100                break;
101            } else {
102                let mut out_desc = zeroed();
103                (*output).GetDesc(&mut out_desc);
104                if out_desc.AttachedToDesktop != 0 {
105                    outputs.push(ComPtr::from_raw(output))
106                } else {
107                    break;
108                }
109            }
110        }
111    }
112    outputs
113}
114
115fn output_is_primary(output: &ComPtr<IDXGIOutput1>) -> bool {
116    unsafe {
117        let mut output_desc = zeroed();
118        output.GetDesc(&mut output_desc);
119        let mut monitor_info: MONITORINFO = zeroed();
120        monitor_info.cbSize = mem::size_of::<MONITORINFO>() as u32;
121        GetMonitorInfoW(output_desc.Monitor, &mut monitor_info);
122        (monitor_info.dwFlags & 1) != 0
123    }
124}
125
126fn get_capture_source(
127    output_dups: Vec<(ComPtr<IDXGIOutputDuplication>, ComPtr<IDXGIOutput1>)>,
128    cs_index: usize,
129) -> Option<(ComPtr<IDXGIOutputDuplication>, ComPtr<IDXGIOutput1>)> {
130    if cs_index == 0 {
131        output_dups
132            .into_iter()
133            .find(|&(_, ref out)| output_is_primary(out))
134    } else {
135        output_dups
136            .into_iter()
137            .filter(|&(_, ref out)| !output_is_primary(out))
138            .nth(cs_index - 1)
139    }
140}
141
142fn duplicate_outputs(
143    mut device: ComPtr<ID3D11Device>,
144    outputs: Vec<ComPtr<IDXGIOutput>>,
145) -> Result<
146    (
147        ComPtr<ID3D11Device>,
148        Vec<(ComPtr<IDXGIOutputDuplication>, ComPtr<IDXGIOutput1>)>,
149    ),
150    HRESULT,
151> {
152    let mut out_dups = Vec::new();
153    for output in outputs
154        .into_iter()
155        .map(|out| out.cast::<IDXGIOutput1>().unwrap())
156    {
157        let dxgi_device = device.up::<IUnknown>();
158        let output_duplication = unsafe {
159            let mut output_duplication = ptr::null_mut();
160            let hr = output.DuplicateOutput(dxgi_device.as_raw(), &mut output_duplication);
161            if hr_failed(hr) {
162                return Err(hr);
163            }
164            ComPtr::from_raw(output_duplication)
165        };
166        device = dxgi_device.cast().unwrap();
167        out_dups.push((output_duplication, output));
168    }
169    Ok((device, out_dups))
170}
171
172struct DuplicatedOutput {
173    device: ComPtr<ID3D11Device>,
174    device_context: ComPtr<ID3D11DeviceContext>,
175    output: ComPtr<IDXGIOutput1>,
176    output_duplication: ComPtr<IDXGIOutputDuplication>,
177}
178impl DuplicatedOutput {
179    fn get_desc(&self) -> DXGI_OUTPUT_DESC {
180        unsafe {
181            let mut desc = zeroed();
182            self.output.GetDesc(&mut desc);
183            desc
184        }
185    }
186
187    fn capture_frame_to_surface(
188        &mut self,
189        timeout_ms: u32,
190    ) -> Result<ComPtr<IDXGISurface1>, HRESULT> {
191        let frame_resource = unsafe {
192            let mut frame_resource = ptr::null_mut();
193            let mut frame_info = zeroed();
194            let hr = self.output_duplication.AcquireNextFrame(
195                timeout_ms,
196                &mut frame_info,
197                &mut frame_resource,
198            );
199            if hr_failed(hr) {
200                return Err(hr);
201            }
202            ComPtr::from_raw(frame_resource)
203        };
204        let frame_texture = frame_resource.cast::<ID3D11Texture2D>().unwrap();
205        let mut texture_desc = unsafe {
206            let mut texture_desc = zeroed();
207            frame_texture.GetDesc(&mut texture_desc);
208            texture_desc
209        };
210        // Configure the description to make the texture readable
211        texture_desc.Usage = D3D11_USAGE_STAGING;
212        texture_desc.BindFlags = 0;
213        texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
214        texture_desc.MiscFlags = 0;
215        let readable_texture = unsafe {
216            let mut readable_texture = ptr::null_mut();
217            let hr =
218                self.device
219                    .CreateTexture2D(&mut texture_desc, ptr::null(), &mut readable_texture);
220            if hr_failed(hr) {
221                return Err(hr);
222            }
223            ComPtr::from_raw(readable_texture)
224        };
225        // Lower priorities causes stuff to be needlessly copied from gpu to ram,
226        // causing huge ram usage on some systems.
227        unsafe { readable_texture.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM) };
228        let readable_surface = readable_texture.up::<ID3D11Resource>();
229        unsafe {
230            self.device_context.CopyResource(
231                readable_surface.as_raw(),
232                frame_texture.up::<ID3D11Resource>().as_raw(),
233            );
234            self.output_duplication.ReleaseFrame();
235        }
236        readable_surface.cast()
237    }
238}
239
240/// Manager of DXGI duplicated outputs
241pub struct DXGIManager {
242    duplicated_output: Option<DuplicatedOutput>,
243    capture_source_index: usize,
244    timeout_ms: u32,
245}
246
247struct SharedPtr<T>(*const T);
248
249unsafe impl<T> Send for SharedPtr<T> {}
250
251unsafe impl<T> Sync for SharedPtr<T> {}
252
253impl DXGIManager {
254    /// Construct a new manager with capture timeout
255    pub fn new(timeout_ms: u32) -> Result<DXGIManager, &'static str> {
256        let mut manager = DXGIManager {
257            duplicated_output: None,
258            capture_source_index: 0,
259            timeout_ms: timeout_ms,
260        };
261
262        match manager.acquire_output_duplication() {
263            Ok(_) => Ok(manager),
264            Err(_) => Err("Failed to acquire output duplication"),
265        }
266    }
267
268    pub fn geometry(&self) -> (usize, usize) {
269        let output_desc = self.duplicated_output.as_ref().unwrap().get_desc();
270        let RECT {
271            left,
272            top,
273            right,
274            bottom,
275        } = output_desc.DesktopCoordinates;
276        ((right - left) as usize, (bottom - top) as usize)
277    }
278
279    /// Set index of capture source to capture from
280    pub fn set_capture_source_index(&mut self, cs: usize) {
281        self.capture_source_index = cs;
282        self.acquire_output_duplication().unwrap()
283    }
284
285    pub fn get_capture_source_index(&self) -> usize {
286        self.capture_source_index
287    }
288
289    /// Set timeout to use when capturing
290    pub fn set_timeout_ms(&mut self, timeout_ms: u32) {
291        self.timeout_ms = timeout_ms
292    }
293
294    /// Duplicate and acquire output selected by `capture_source_index`
295    pub fn acquire_output_duplication(&mut self) -> Result<(), ()> {
296        self.duplicated_output = None;
297        let factory = create_dxgi_factory_1();
298        for (outputs, adapter) in (0..)
299            .map(|i| {
300                let mut adapter = ptr::null_mut();
301                unsafe {
302                    if factory.EnumAdapters1(i, &mut adapter) != DXGI_ERROR_NOT_FOUND {
303                        Some(ComPtr::from_raw(adapter))
304                    } else {
305                        None
306                    }
307                }
308            })
309            .take_while(Option::is_some)
310            .map(Option::unwrap)
311            .map(|mut adapter| (get_adapter_outputs(&mut adapter), adapter))
312            .filter(|&(ref outs, _)| !outs.is_empty())
313        {
314            // Creating device for each adapter that has the output
315            let (d3d11_device, device_context) = d3d11_create_device(adapter.up().as_raw());
316            let (d3d11_device, output_duplications) =
317                duplicate_outputs(d3d11_device, outputs).map_err(|_| ())?;
318            if let Some((output_duplication, output)) =
319                get_capture_source(output_duplications, self.capture_source_index)
320            {
321                self.duplicated_output = Some(DuplicatedOutput {
322                    device: d3d11_device,
323                    device_context: device_context,
324                    output: output,
325                    output_duplication: output_duplication,
326                });
327                return Ok(());
328            }
329        }
330        Err(())
331    }
332
333    fn capture_frame_to_surface(&mut self) -> Result<ComPtr<IDXGISurface1>, CaptureError> {
334        if let None = self.duplicated_output {
335            if let Ok(_) = self.acquire_output_duplication() {
336                return Err(CaptureError::Fail("No valid duplicated output"));
337            } else {
338                return Err(CaptureError::RefreshFailure);
339            }
340        }
341        let timeout_ms = self.timeout_ms;
342        match self
343            .duplicated_output
344            .as_mut()
345            .unwrap()
346            .capture_frame_to_surface(timeout_ms)
347        {
348            Ok(surface) => Ok(surface),
349            Err(DXGI_ERROR_ACCESS_LOST) => {
350                if let Ok(_) = self.acquire_output_duplication() {
351                    Err(CaptureError::AccessLost)
352                } else {
353                    Err(CaptureError::RefreshFailure)
354                }
355            }
356            Err(E_ACCESSDENIED) => Err(CaptureError::AccessDenied),
357            Err(DXGI_ERROR_WAIT_TIMEOUT) => Err(CaptureError::Timeout),
358            Err(_) => {
359                if let Ok(_) = self.acquire_output_duplication() {
360                    Err(CaptureError::Fail("Failure when acquiring frame"))
361                } else {
362                    Err(CaptureError::RefreshFailure)
363                }
364            }
365        }
366    }
367
368    fn capture_frame_t<T: Copy + Send + Sync + Sized>(&mut self) -> Result<(Vec<T>, (usize, usize)), CaptureError> {
369        let frame_surface = match self.capture_frame_to_surface() {
370            Ok(surface) => surface,
371            Err(e) => return Err(e),
372        };
373        let mapped_surface = unsafe {
374            let mut mapped_surface = zeroed();
375            if hr_failed(frame_surface.Map(&mut mapped_surface, DXGI_MAP_READ)) {
376                frame_surface.Release();
377                return Err(CaptureError::Fail("Failed to map surface"));
378            }
379            mapped_surface
380        };
381        let byte_size = |x| x * mem::size_of::<BGRA8>() / mem::size_of::<T>();
382        let output_desc = self.duplicated_output.as_mut().unwrap().get_desc();
383        let stride = mapped_surface.Pitch as usize / mem::size_of::<BGRA8>();
384        let byte_stride = byte_size(stride);
385        let (output_width, output_height) = {
386            let RECT {
387                left,
388                top,
389                right,
390                bottom,
391            } = output_desc.DesktopCoordinates;
392            ((right - left) as usize, (bottom - top) as usize)
393        };
394        let mut pixel_buf = Vec::with_capacity(byte_size(output_width * output_height));
395        
396        let scan_lines =
397            match output_desc.Rotation {
398                DXGI_MODE_ROTATION_ROTATE90 | DXGI_MODE_ROTATION_ROTATE270 => output_width,
399                _ => output_height,
400            };
401
402        let mapped_pixels = unsafe {
403            slice::from_raw_parts(
404                mapped_surface.pBits as *const T,
405                byte_stride * scan_lines,
406            )
407        };
408        
409        match output_desc.Rotation {
410            DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED =>
411                pixel_buf.extend_from_slice(mapped_pixels),
412            DXGI_MODE_ROTATION_ROTATE90 => {
413                unsafe {
414                    let ptr = SharedPtr(pixel_buf.as_ptr() as *const BGRA8);
415                    mapped_pixels.chunks(byte_stride).rev().enumerate().for_each(|(column, chunk)| {
416                        let mut src = chunk.as_ptr() as *const BGRA8;
417                        let mut dst = ptr.0 as *mut BGRA8;
418                        dst = dst.add(column);
419                        let stop = src.add(output_height);
420                        while src != stop {
421                            dst.write(*src);
422                            src = src.add(1);
423                            dst = dst.add(output_width);
424                        }
425                    });
426                    pixel_buf.set_len(pixel_buf.capacity());
427                }
428            }
429            DXGI_MODE_ROTATION_ROTATE180 => {
430                unsafe {
431                    let ptr = SharedPtr(pixel_buf.as_ptr() as *const BGRA8);
432                    mapped_pixels.chunks(byte_stride).rev().enumerate().for_each(|(scan_line, chunk)| {
433                        let mut src = chunk.as_ptr() as *const BGRA8;
434                        let mut dst = ptr.0 as *mut BGRA8;
435                        dst = dst.add(scan_line * output_width);
436                        let stop = src;
437                        src = src.add(output_width);
438                        while src != stop {
439                            src = src.sub(1);
440                            dst.write(*src);
441                            dst = dst.add(1);
442                        }
443                    });
444                    pixel_buf.set_len(pixel_buf.capacity());
445                }
446            }
447            DXGI_MODE_ROTATION_ROTATE270 => {
448                unsafe {
449                    let ptr = SharedPtr(pixel_buf.as_ptr() as *const BGRA8);
450                    mapped_pixels.chunks(byte_stride).enumerate().for_each(|(column, chunk)| {
451                        let mut src = chunk.as_ptr() as *const BGRA8;
452                        let mut dst = ptr.0 as *mut BGRA8;
453                        dst = dst.add(column);
454                        let stop = src;
455                        src = src.add(output_height);
456                        while src != stop {
457                            src = src.sub(1);
458                            dst.write(*src);
459                            dst = dst.add(output_width);
460                        }
461                    });
462                    pixel_buf.set_len(pixel_buf.capacity());
463                }
464            }
465            n => unreachable!("Undefined DXGI_MODE_ROTATION: {}", n),
466        }
467        unsafe { frame_surface.Unmap() };
468        Ok((pixel_buf, (output_width, output_height)))
469    }
470
471    /// Capture a frame
472    ///
473    /// On success, return Vec with pixels and width and height of frame.
474    /// On failure, return CaptureError.
475    pub fn capture_frame(&mut self) -> Result<(Vec<BGRA8>, (usize, usize)), CaptureError> {
476        self.capture_frame_t()
477    }
478
479    /// Capture a frame
480    ///
481    /// On success, return Vec with pixel components and width and height of frame.
482    /// On failure, return CaptureError.
483    pub fn capture_frame_components(&mut self) -> Result<(Vec<u8>, (usize, usize)), CaptureError> {
484        self.capture_frame_t()
485    }
486}
487
488#[test]
489fn test() {
490    let mut manager = DXGIManager::new(300).unwrap();
491    for _ in 0..100 {
492        match manager.capture_frame() {
493            Ok((pixels, (_, _))) => {
494                let len = pixels.len() as u64;
495                let (r, g, b) = pixels.into_iter().fold((0u64, 0u64, 0u64), |(r, g, b), p| {
496                    (r + p.r as u64, g + p.g as u64, b + p.b as u64)
497                });
498                println!("avg: {} {} {}", r / len, g / len, b / len)
499            }
500            Err(e) => println!("error: {:?}", e),
501        }
502    }
503}
504
505#[test]
506fn compare_frame_dims() {
507    let mut manager = DXGIManager::new(300).unwrap();
508    let (frame, (fw, fh)) = manager.capture_frame().unwrap();
509    let (frame_u8, (fu8w, fu8h)) = manager.capture_frame_components().unwrap();
510    assert_eq!(fw, fu8w);
511    assert_eq!(fh, fu8h);
512    assert_eq!(4 * frame.len(), frame_u8.len());
513}