fix: recover DXGI capture after access loss (#16024)

* fix: recover DXGI capture after access loss

* fix: track DXGI frame ownership

* fix: only recover DXGI access loss

* fix: stabilize DXGI recovery after mode switches

* fix: preserve DXGI recovery budget

* refactor: model DXGI frame lifecycle as enum

* docs: clarify DXGI frame cleanup order

* fix: scope DXGI frame grace to access loss recovery

* fix: preserve DXGI recovery across display changes

* chore: log DXGI recovery attempts at debug level
This commit is contained in:
Joss Gray
2026-09-15 22:18:44 -07:00
committed by GitHub
parent 8154c026a4
commit f9c2f16857
2 changed files with 156 additions and 17 deletions

View File

@@ -41,6 +41,13 @@ impl<T> Drop for ComPtr<T> {
}
}
#[derive(Clone, Copy, PartialEq)]
enum FrameState {
Idle,
Acquired,
Mapped,
}
pub struct Capturer {
device: ComPtr<ID3D11Device>,
display: Display,
@@ -58,6 +65,7 @@ pub struct Capturer {
output_texture: bool,
adapter_desc1: DXGI_ADAPTER_DESC1,
rotate: Rotate,
frame_state: FrameState,
}
impl Capturer {
@@ -174,6 +182,7 @@ impl Capturer {
output_texture: false,
adapter_desc1,
rotate,
frame_state: FrameState::Idle,
})
}
@@ -335,6 +344,7 @@ impl Capturer {
let mut info = mem::MaybeUninit::uninit().assume_init();
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
self.frame_state = FrameState::Acquired;
let frame = ComPtr(frame);
if *info.LastPresentTime.QuadPart() == 0 {
@@ -345,9 +355,11 @@ impl Capturer {
let mut rect = mem::MaybeUninit::uninit().assume_init();
if self.fastlane {
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
self.frame_state = FrameState::Mapped;
} else {
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
self.frame_state = FrameState::Mapped;
}
Ok((rect.pBits, rect.Pitch))
}
@@ -424,7 +436,7 @@ impl Capturer {
}
}
} else {
self.unmap();
self.release_frame()?;
let r = self.load_frame(timeout)?;
let rotate = match self.display.rotation() {
DXGI_MODE_ROTATION_IDENTITY | DXGI_MODE_ROTATION_UNSPECIFIED => kRotate0,
@@ -472,12 +484,13 @@ impl Capturer {
if self.duplication.0.is_null() {
return Err(std::io::ErrorKind::AddrNotAvailable.into());
}
(*self.duplication.0).ReleaseFrame();
self.release_frame()?;
let mut frame = ptr::null_mut();
#[allow(invalid_value)]
let mut info = mem::MaybeUninit::uninit().assume_init();
wrap_hresult((*self.duplication.0).AcquireNextFrame(timeout, &mut info, &mut frame))?;
self.frame_state = FrameState::Acquired;
let frame = ComPtr(frame);
if info.AccumulatedFrames == 0 || *info.LastPresentTime.QuadPart() == 0 {
@@ -574,16 +587,42 @@ impl Capturer {
}
}
fn unmap(&self) {
fn release_frame(&mut self) -> io::Result<()> {
if self.duplication.is_null() {
return Ok(());
}
let mut first_error = None;
// Unmap before ReleaseFrame invalidates the desktop surface; use the same
// order for staging surfaces. Cleanup advances Mapped -> Acquired -> Idle,
// while Idle is a no-op. Advance state even on errors to avoid retrying
// cleanup, but still attempt ReleaseFrame if unmapping fails.
unsafe {
(*self.duplication.0).ReleaseFrame();
if self.fastlane {
(*self.duplication.0).UnMapDesktopSurface();
} else {
if !self.surface.is_null() {
(*self.surface.0).Unmap();
if self.frame_state == FrameState::Mapped {
let result = if self.fastlane {
wrap_hresult((*self.duplication.0).UnMapDesktopSurface())
} else if !self.surface.is_null() {
wrap_hresult((*self.surface.0).Unmap())
} else {
Ok(())
};
self.frame_state = FrameState::Acquired;
if let Err(err) = result {
first_error = Some(err);
}
}
if self.frame_state == FrameState::Acquired {
let result = wrap_hresult((*self.duplication.0).ReleaseFrame());
self.frame_state = FrameState::Idle;
if first_error.is_none() {
if let Err(err) = result {
first_error = Some(err);
}
}
}
}
match first_error {
Some(err) => Err(err),
None => Ok(()),
}
}
@@ -599,8 +638,8 @@ impl Capturer {
impl Drop for Capturer {
fn drop(&mut self) {
if !self.duplication.is_null() {
self.unmap();
if let Err(err) = self.release_frame() {
eprintln!("DXGI frame cleanup failed: {err}");
}
}
}