use crate::capture::Capture;
use crate::frame::Frame;
use crate::{Error, Result};
use windows::core::ComInterface;
use windows::Win32::Foundation::HMODULE;
use windows::Win32::Graphics::Direct3D::{D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_UNKNOWN};
use windows::Win32::Graphics::Direct3D11::{
D3D11CreateDevice, ID3D11Device, ID3D11DeviceContext, ID3D11Texture2D, D3D11_CPU_ACCESS_READ,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, D3D11_MAPPED_SUBRESOURCE, D3D11_MAP_READ,
D3D11_TEXTURE2D_DESC, D3D11_USAGE_STAGING,
};
use windows::Win32::Graphics::Dxgi::Common::{DXGI_FORMAT, DXGI_SAMPLE_DESC};
use windows::Win32::Graphics::Dxgi::{
CreateDXGIFactory1, IDXGIFactory2, IDXGIOutput1, IDXGIOutputDuplication, IDXGIResource,
DXGI_ERROR_WAIT_TIMEOUT, DXGI_OUTDUPL_DESC, DXGI_OUTDUPL_FRAME_INFO,
};
const ACQUIRE_TIMEOUT_MS: u32 = 33;
struct DxgiInner {
device: ID3D11Device,
context: ID3D11DeviceContext,
output1: IDXGIOutput1,
duplication: IDXGIOutputDuplication,
staging: ID3D11Texture2D,
w: usize,
h: usize,
have_frame: bool,
}
impl DxgiInner {
fn try_new() -> windows::core::Result<DxgiInner> {
unsafe {
let factory: IDXGIFactory2 = CreateDXGIFactory1()?;
let adapter = factory.EnumAdapters(0)?;
let mut device_opt: Option<ID3D11Device> = None;
let mut context_opt: Option<ID3D11DeviceContext> = None;
D3D11CreateDevice(
&adapter,
D3D_DRIVER_TYPE_UNKNOWN,
HMODULE::default(),
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
None,
7,
Some(&mut device_opt),
None,
Some(&mut context_opt),
)?;
let device = device_opt.ok_or_else(windows::core::Error::from_win32)?;
let context = context_opt.ok_or_else(windows::core::Error::from_win32)?;
let _ = D3D_DRIVER_TYPE_HARDWARE;
let output = adapter.EnumOutputs(0)?;
let output1: IDXGIOutput1 = output.cast()?;
let (duplication, staging, w, h) = Self::build_duplication(&output1, &device)?;
Ok(DxgiInner {
device,
context,
output1,
duplication,
staging,
w,
h,
have_frame: false,
})
}
}
fn build_duplication(
output1: &IDXGIOutput1,
device: &ID3D11Device,
) -> windows::core::Result<(IDXGIOutputDuplication, ID3D11Texture2D, usize, usize)> {
unsafe {
let duplication = output1.DuplicateOutput(device)?;
let mut od = std::mem::zeroed::<DXGI_OUTDUPL_DESC>();
duplication.GetDesc(&mut od);
let w = od.ModeDesc.Width as usize;
let h = od.ModeDesc.Height as usize;
let fmt: DXGI_FORMAT = od.ModeDesc.Format;
let mut desc = std::mem::zeroed::<D3D11_TEXTURE2D_DESC>();
desc.Width = w as u32;
desc.Height = h as u32;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = fmt;
desc.SampleDesc = DXGI_SAMPLE_DESC {
Count: 1,
Quality: 0,
};
desc.Usage = D3D11_USAGE_STAGING;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ.0 as u32;
let mut staging_opt: Option<ID3D11Texture2D> = None;
device.CreateTexture2D(&desc, None, Some(&mut staging_opt))?;
let staging = staging_opt.ok_or_else(windows::core::Error::from_win32)?;
Ok((duplication, staging, w, h))
}
}
fn rebuild_duplication(&mut self) -> windows::core::Result<()> {
let (duplication, staging, w, h) = Self::build_duplication(&self.output1, &self.device)?;
self.duplication = duplication;
self.staging = staging;
self.w = w;
self.h = h;
self.have_frame = false;
Ok(())
}
fn grab_into(&mut self, dst: &mut Frame) -> Result<bool> {
unsafe {
let mut res: Option<IDXGIResource> = None;
let mut fi = std::mem::zeroed::<DXGI_OUTDUPL_FRAME_INFO>();
let mut acquired = false;
let fresh =
match self
.duplication
.AcquireNextFrame(ACQUIRE_TIMEOUT_MS, &mut fi, &mut res)
{
Ok(()) => {
acquired = true;
res.is_some()
}
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => false,
Err(_) => {
res = None;
self.rebuild_duplication().map_err(|e| {
Error::capture(format!("dxgi duplication rebuild failed: {e}"))
})?;
match self.duplication.AcquireNextFrame(
ACQUIRE_TIMEOUT_MS,
&mut fi,
&mut res,
) {
Ok(()) => {
acquired = true;
res.is_some()
}
Err(_) => false,
}
}
};
if acquired {
if let Some(r) = res.take() {
if let Ok(tex) = r.cast::<ID3D11Texture2D>() {
self.context.CopyResource(&self.staging, &tex);
}
}
let _ = self.duplication.ReleaseFrame();
}
let need = fresh || !self.have_frame;
if need {
let mut mapped = std::mem::zeroed::<D3D11_MAPPED_SUBRESOURCE>();
if self
.context
.Map(&self.staging, 0, D3D11_MAP_READ, 0, Some(&mut mapped))
.is_ok()
{
dst.prepare_bgra(self.w, self.h);
let src = mapped.pData as *const u8;
let row = self.w * 4;
let dst_ptr = dst.pixels.as_mut_ptr();
for y in 0..self.h {
std::ptr::copy_nonoverlapping(
src.add(y * mapped.RowPitch as usize),
dst_ptr.add(y * row),
row,
);
}
self.context.Unmap(&self.staging, 0);
self.have_frame = true;
}
}
Ok(fresh)
}
}
}
pub struct DxgiCapture {
inner: DxgiInner,
}
impl DxgiCapture {
pub fn new_primary() -> Option<Self> {
DxgiInner::try_new().ok().map(|inner| DxgiCapture { inner })
}
}
impl Capture for DxgiCapture {
fn grab(&mut self) -> Result<Frame> {
let mut frame = Frame::bgra8(0, 0, Vec::new());
self.inner.grab_into(&mut frame)?;
if frame.pixels.is_empty() {
return Err(Error::capture("dxgi returned empty frame"));
}
Ok(frame)
}
fn grab_into(&mut self, dst: &mut Frame) -> Result<bool> {
let changed = self.inner.grab_into(dst)?;
if dst.pixels.is_empty() {
return Err(Error::capture("dxgi returned empty frame"));
}
Ok(changed)
}
fn backend(&self) -> &'static str {
"dxgi"
}
}