//! DXGI Desktop Duplication screen capture //! //! Uses the Windows Desktop Duplication API (available on Windows 8+) for //! high-performance, low-latency screen capture with hardware acceleration. //! //! Reference: RustDesk's scrap library implementation use super::{CapturedFrame, Capturer, DirtyRect, Display}; use anyhow::{Context, Result}; use std::ptr; use std::time::Instant; use windows::core::Interface; use windows::Win32::Graphics::Direct3D::D3D_DRIVER_TYPE_UNKNOWN; use windows::Win32::Graphics::Direct3D11::{ D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ, D3D11_SDK_VERSION, D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING, }; use windows::Win32::Graphics::Dxgi::{ CreateDXGIFactory1, IDXGIAdapter1, IDXGIFactory1, IDXGIOutput, IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource, DXGI_ERROR_ACCESS_LOST, DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO, DXGI_RESOURCE_PRIORITY_MAXIMUM, }; /// DXGI Desktop Duplication capturer pub struct DxgiCapturer { display: Display, device: ID3D11Device, context: ID3D11DeviceContext, duplication: IDXGIOutputDuplication, staging_texture: Option, width: u32, height: u32, // Future use: frame diffing against the previously captured frame. #[allow(dead_code)] last_frame: Option>, } impl DxgiCapturer { /// Create a new DXGI capturer for the specified display pub fn new(display: Display) -> Result { let (device, context, duplication, desc) = Self::create_duplication(&display)?; Ok(Self { display, device, context, duplication, staging_texture: None, width: desc.ModeDesc.Width, height: desc.ModeDesc.Height, last_frame: None, }) } /// Create D3D device and output duplication fn create_duplication( target_display: &Display, ) -> Result<( ID3D11Device, ID3D11DeviceContext, IDXGIOutputDuplication, DXGI_OUTDUPL_DESC, )> { unsafe { // Create DXGI factory let factory: IDXGIFactory1 = CreateDXGIFactory1().context("Failed to create DXGI factory")?; // Find the adapter and output for this display let (adapter, output) = Self::find_adapter_output(&factory, target_display)?; // Create D3D11 device let mut device: Option = None; let mut context: Option = None; D3D11CreateDevice( &adapter, D3D_DRIVER_TYPE_UNKNOWN, None, Default::default(), None, D3D11_SDK_VERSION, Some(&mut device), None, Some(&mut context), ) .context("Failed to create D3D11 device")?; let device = device.context("D3D11 device is None")?; let context = context.context("D3D11 context is None")?; // Get IDXGIOutput1 interface let output1: IDXGIOutput1 = output .cast() .context("Failed to get IDXGIOutput1 interface")?; // Create output duplication let duplication = output1 .DuplicateOutput(&device) .context("Failed to create output duplication")?; // Get duplication description let desc = duplication.GetDesc(); tracing::info!( "Created DXGI duplication: {}x{}, display: {}", desc.ModeDesc.Width, desc.ModeDesc.Height, target_display.name ); Ok((device, context, duplication, desc)) } } /// Find the adapter and output for the specified display fn find_adapter_output( factory: &IDXGIFactory1, display: &Display, ) -> Result<(IDXGIAdapter1, IDXGIOutput)> { unsafe { let mut adapter_idx = 0u32; loop { // Enumerate adapters let adapter: IDXGIAdapter1 = match factory.EnumAdapters1(adapter_idx) { Ok(a) => a, Err(_) => break, }; let mut output_idx = 0u32; loop { // Enumerate outputs for this adapter let output: IDXGIOutput = match adapter.EnumOutputs(output_idx) { Ok(o) => o, Err(_) => break, }; // Check if this is the display we want let desc = output.GetDesc()?; let name = String::from_utf16_lossy( &desc.DeviceName[..desc .DeviceName .iter() .position(|&c| c == 0) .unwrap_or(desc.DeviceName.len())], ); if name == display.name || desc.Monitor.0 as isize == display.handle { return Ok((adapter, output)); } output_idx += 1; } adapter_idx += 1; } // If we didn't find the specific display, use the first one let adapter: IDXGIAdapter1 = factory.EnumAdapters1(0).context("No adapters found")?; let output: IDXGIOutput = adapter.EnumOutputs(0).context("No outputs found")?; Ok((adapter, output)) } } /// Create or get the staging texture for CPU access fn get_staging_texture(&mut self, src_texture: &ID3D11Texture2D) -> Result<&ID3D11Texture2D> { if self.staging_texture.is_none() { unsafe { let mut desc = D3D11_TEXTURE2D_DESC::default(); src_texture.GetDesc(&mut desc); desc.Usage = D3D11_USAGE_STAGING; desc.BindFlags = Default::default(); desc.CPUAccessFlags = 0x20000; // D3D11_CPU_ACCESS_READ desc.MiscFlags = Default::default(); let mut staging: Option = None; self.device .CreateTexture2D(&desc, None, Some(&mut staging)) .context("Failed to create staging texture")?; let staging = staging.context("Staging texture is None")?; // Set high priority let resource: IDXGIResource = staging.cast()?; resource.SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM)?; self.staging_texture = Some(staging); } } Ok(self.staging_texture.as_ref().unwrap()) } /// Acquire the next frame from the desktop fn acquire_frame( &mut self, timeout_ms: u32, ) -> Result> { unsafe { let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default(); let mut desktop_resource: Option = None; let result = self.duplication.AcquireNextFrame( timeout_ms, &mut frame_info, &mut desktop_resource, ); match result { Ok(_) => { let resource = desktop_resource.context("Desktop resource is None")?; // Check if there's actually a new frame if frame_info.LastPresentTime == 0 { self.duplication.ReleaseFrame().ok(); return Ok(None); } let texture: ID3D11Texture2D = resource .cast() .context("Failed to cast to ID3D11Texture2D")?; Ok(Some((texture, frame_info))) } Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => { // No new frame available Ok(None) } Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => { // Desktop duplication was invalidated, need to recreate tracing::warn!("Desktop duplication access lost, will need to recreate"); Err(anyhow::anyhow!("Access lost")) } Err(e) => Err(e).context("Failed to acquire frame"), } } } /// Copy frame data to CPU-accessible memory fn copy_frame_data(&mut self, texture: &ID3D11Texture2D) -> Result> { unsafe { // Get or create staging texture let staging = self.get_staging_texture(texture)?.clone(); // Copy from GPU texture to staging texture self.context.CopyResource(&staging, texture); // Map the staging texture for CPU read let mut mapped = D3D11_MAPPED_SUBRESOURCE::default(); self.context .Map(&staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped)) .context("Failed to map staging texture")?; // Copy pixel data let src_pitch = mapped.RowPitch as usize; let dst_pitch = (self.width * 4) as usize; let height = self.height as usize; let mut data = vec![0u8; dst_pitch * height]; let src_ptr = mapped.pData as *const u8; for y in 0..height { let src_row = src_ptr.add(y * src_pitch); let dst_row = data.as_mut_ptr().add(y * dst_pitch); ptr::copy_nonoverlapping(src_row, dst_row, dst_pitch); } // Unmap self.context.Unmap(&staging, 0); Ok(data) } } /// Extract dirty rectangles from frame info fn extract_dirty_rects(&self, _frame_info: &DXGI_OUTDUPL_FRAME_INFO) -> Option> { // TODO: Implement dirty rectangle extraction using // IDXGIOutputDuplication::GetFrameDirtyRects and GetFrameMoveRects // For now, return None to indicate full frame update None } } impl Capturer for DxgiCapturer { fn capture(&mut self) -> Result> { // Try to acquire a frame with 100ms timeout let frame_result = self.acquire_frame(100)?; let (texture, frame_info) = match frame_result { Some((t, f)) => (t, f), None => return Ok(None), // No new frame }; // Copy frame data to CPU memory let data = self.copy_frame_data(&texture)?; // Release the frame unsafe { self.duplication.ReleaseFrame().ok(); } // Extract dirty rectangles if available let dirty_rects = self.extract_dirty_rects(&frame_info); Ok(Some(CapturedFrame { width: self.width, height: self.height, data, timestamp: Instant::now(), display_id: self.display.id, dirty_rects, })) } fn display(&self) -> &Display { &self.display } fn is_valid(&self) -> bool { // Could check if duplication is still valid true } } impl Drop for DxgiCapturer { fn drop(&mut self) { // Release any held frame unsafe { self.duplication.ReleaseFrame().ok(); } } }