160 lines
6.3 KiB
Rust
160 lines
6.3 KiB
Rust
#[cfg(target_os = "windows")]
|
|
use windows::{
|
|
core::Interface,
|
|
Win32::Foundation::*,
|
|
Win32::Graphics::Direct3D::*,
|
|
Win32::Graphics::Direct3D11::*,
|
|
Win32::Graphics::Dxgi::Common::*,
|
|
Win32::Graphics::Dxgi::*,
|
|
Win32::System::Com::*,
|
|
};
|
|
use crate::capture::{Frame, FrameSource};
|
|
|
|
#[cfg(target_os = "windows")]
|
|
pub struct WindowsCapture {
|
|
device: ID3D11Device,
|
|
context: ID3D11DeviceContext,
|
|
duplication: IDXGIOutputDuplication,
|
|
gpu_texture: ID3D11Texture2D,
|
|
width: u32,
|
|
height: u32,
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
unsafe impl Send for WindowsCapture {}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
impl WindowsCapture {
|
|
pub fn new() -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
|
|
unsafe {
|
|
// Initialize COM library on capture thread
|
|
let _ = CoInitializeEx(None, COINIT_MULTITHREADED);
|
|
|
|
// Create D3D11 hardware device
|
|
let mut device: Option<ID3D11Device> = None;
|
|
let mut context: Option<ID3D11DeviceContext> = None;
|
|
let mut feature_level = D3D_FEATURE_LEVEL_11_0;
|
|
|
|
D3D11CreateDevice(
|
|
None,
|
|
D3D_DRIVER_TYPE_HARDWARE,
|
|
None,
|
|
D3D11_CREATE_DEVICE_BGRA_SUPPORT,
|
|
Some(&[D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0]),
|
|
D3D11_SDK_VERSION,
|
|
Some(&mut device),
|
|
Some(&mut feature_level),
|
|
Some(&mut context),
|
|
)?;
|
|
|
|
let device = device.ok_or("Failed to create D3D11 device")?;
|
|
let context = context.ok_or("Failed to create D3D11 context")?;
|
|
|
|
// Retrieve DXGI Output for the primary display
|
|
let dxgi_device: IDXGIDevice = device.cast()?;
|
|
let adapter = dxgi_device.GetAdapter()?;
|
|
let output = adapter.EnumOutputs(0)?;
|
|
let output1: IDXGIOutput1 = output.cast()?;
|
|
|
|
let desc = output.GetDesc()?;
|
|
let width = (desc.DesktopCoordinates.right - desc.DesktopCoordinates.left) as u32;
|
|
let height = (desc.DesktopCoordinates.bottom - desc.DesktopCoordinates.top) as u32;
|
|
|
|
// Initialize Desktop Duplication
|
|
let duplication = output1.DuplicateOutput(&device)?;
|
|
|
|
// Allocate a dedicated VRAM-resident GPU texture for 100% zero-copy capture
|
|
// (Frame stays on GPU die, zero PCIe bus readback, 0% CPU consumption)
|
|
let gpu_desc = D3D11_TEXTURE2D_DESC {
|
|
Width: width,
|
|
Height: height,
|
|
MipLevels: 1,
|
|
ArraySize: 1,
|
|
Format: DXGI_FORMAT_B8G8R8A8_UNORM,
|
|
SampleDesc: DXGI_SAMPLE_DESC { Count: 1, Quality: 0 },
|
|
Usage: D3D11_USAGE_DEFAULT,
|
|
BindFlags: (D3D11_BIND_RENDER_TARGET.0 | D3D11_BIND_SHADER_RESOURCE.0) as u32,
|
|
CPUAccessFlags: 0,
|
|
MiscFlags: 0,
|
|
};
|
|
|
|
let mut gpu_texture: Option<ID3D11Texture2D> = None;
|
|
device.CreateTexture2D(&gpu_desc, None, Some(&mut gpu_texture))?;
|
|
let gpu_texture = gpu_texture.ok_or("Failed to create GPU VRAM texture")?;
|
|
|
|
println!("Windows DXGI Hardware Capture active (Pure GPU Zero-Copy): {}x{}", width, height);
|
|
|
|
Ok(Self {
|
|
device,
|
|
context,
|
|
duplication,
|
|
gpu_texture,
|
|
width,
|
|
height,
|
|
})
|
|
}
|
|
}
|
|
|
|
fn reinit_duplication(&mut self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
unsafe {
|
|
let dxgi_device: IDXGIDevice = self.device.cast()?;
|
|
let adapter = dxgi_device.GetAdapter()?;
|
|
let output = adapter.EnumOutputs(0)?;
|
|
let output1: IDXGIOutput1 = output.cast()?;
|
|
self.duplication = output1.DuplicateOutput(&self.device)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(target_os = "windows")]
|
|
impl FrameSource for WindowsCapture {
|
|
fn next_frame(&mut self) -> Result<Frame, Box<dyn std::error::Error + Send + Sync>> {
|
|
unsafe {
|
|
let mut frame_info = DXGI_OUTDUPL_FRAME_INFO::default();
|
|
let mut desktop_resource: Option<IDXGIResource> = None;
|
|
|
|
for _ in 0..5 {
|
|
match self.duplication.AcquireNextFrame(150, &mut frame_info, &mut desktop_resource) {
|
|
Ok(()) => {
|
|
if let Some(resource) = desktop_resource {
|
|
let texture: ID3D11Texture2D = resource.cast()?;
|
|
|
|
// Copy directly on the GPU from the desktop buffer to our persistent VRAM texture
|
|
self.context.CopyResource(&self.gpu_texture, &texture);
|
|
|
|
// Immediately release the desktop frame back to the DWM compositor
|
|
let _ = self.duplication.ReleaseFrame();
|
|
|
|
// Pass the VRAM texture handle directly to the hardware encoder (Zero CPU copy!)
|
|
return Ok(Frame::D3D11Texture {
|
|
handle: self.gpu_texture.as_raw() as usize,
|
|
});
|
|
}
|
|
let _ = self.duplication.ReleaseFrame();
|
|
}
|
|
Err(e) if e.code() == DXGI_ERROR_WAIT_TIMEOUT => {
|
|
// Display is idle (no new pixels), yield current texture without spinning CPU
|
|
std::thread::sleep(std::time::Duration::from_millis(8));
|
|
return Ok(Frame::D3D11Texture {
|
|
handle: self.gpu_texture.as_raw() as usize,
|
|
});
|
|
}
|
|
Err(e) if e.code() == DXGI_ERROR_ACCESS_LOST => {
|
|
println!("DXGI access lost (display mode or fullscreen switch), reacquiring...");
|
|
let _ = self.reinit_duplication();
|
|
std::thread::sleep(std::time::Duration::from_millis(50));
|
|
continue;
|
|
}
|
|
Err(e) => {
|
|
return Err(Box::new(e));
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Frame::D3D11Texture {
|
|
handle: self.gpu_texture.as_raw() as usize,
|
|
})
|
|
}
|
|
}
|
|
}
|