fix: reuse the dxgi staging texture instead of one per frame

ohgodwhat() created a full screen D3D11_USAGE_STAGING texture for every
captured frame and pinned each one with SetEvictionPriority(MAXIMUM).
Because D3D11 resource destruction may be deferred, that per-frame churn
can accumulate a large amount of graphics kernel paged pool on affected
drivers. Keep a single staging texture and rebuild it only when the
desktop image changes shape.

Also check the IDXGISurface QueryInterface result, so a failure can no
longer leave surface null while readable holds a valid texture.

Reported in #15945.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011sw75MSAz7PTqrSALdStXe
This commit is contained in:
rustdesk
2026-08-27 15:02:31 +08:00
co-authored by Claude Opus 5
parent 1fe451c2e8
commit 7c830e76c6
+32 -17
View File
@@ -48,6 +48,7 @@ pub struct Capturer {
duplication: ComPtr<IDXGIOutputDuplication>,
fastlane: bool,
surface: ComPtr<IDXGISurface>,
readable: ComPtr<ID3D11Texture2D>,
texture: ComPtr<ID3D11Texture2D>,
width: usize,
height: usize,
@@ -163,6 +164,7 @@ impl Capturer {
duplication: ComPtr(duplication),
fastlane: desc.DesktopImageInSystemMemory == TRUE,
surface: ComPtr(ptr::null_mut()),
readable: ComPtr(ptr::null_mut()),
texture: ComPtr(ptr::null_mut()),
width: display.width() as usize,
height: display.height() as usize,
@@ -346,14 +348,14 @@ impl Capturer {
if self.fastlane {
wrap_hresult((*self.duplication.0).MapDesktopSurface(&mut rect))?;
} else {
self.surface = ComPtr(self.ohgodwhat(frame.0)?);
self.ohgodwhat(frame.0)?;
wrap_hresult((*self.surface.0).Map(&mut rect, DXGI_MAP_READ))?;
}
Ok((rect.pBits, rect.Pitch))
}
// copy from GPU memory to system memory
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<*mut IDXGISurface> {
unsafe fn ohgodwhat(&mut self, frame: *mut IDXGIResource) -> io::Result<()> {
let mut texture: *mut ID3D11Texture2D = ptr::null_mut();
(*frame).QueryInterface(
&IID_ID3D11Texture2D,
@@ -370,24 +372,37 @@ impl Capturer {
texture_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
texture_desc.MiscFlags = 0;
let mut readable = ptr::null_mut();
wrap_hresult((*self.device.0).CreateTexture2D(
&mut texture_desc,
ptr::null(),
&mut readable,
))?;
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
let readable = ComPtr(readable);
// Avoid per-frame staging texture allocation and the kernel allocation churn it causes.
let mut current: D3D11_TEXTURE2D_DESC = mem::zeroed();
if !self.surface.is_null() {
(*self.readable.0).GetDesc(&mut current);
}
if current.Width != texture_desc.Width
|| current.Height != texture_desc.Height
|| current.Format != texture_desc.Format
{
let mut readable = ptr::null_mut();
wrap_hresult((*self.device.0).CreateTexture2D(
&mut texture_desc,
ptr::null(),
&mut readable,
))?;
(*readable).SetEvictionPriority(DXGI_RESOURCE_PRIORITY_MAXIMUM);
let readable = ComPtr(readable);
let mut surface = ptr::null_mut();
(*readable.0).QueryInterface(
&IID_IDXGISurface,
&mut surface as *mut *mut _ as *mut *mut _,
);
let mut surface = ptr::null_mut();
wrap_hresult((*readable.0).QueryInterface(
&IID_IDXGISurface,
&mut surface as *mut *mut _ as *mut *mut _,
))?;
(*self.context.0).CopyResource(readable.0 as *mut _, texture.0 as *mut _);
self.readable = readable;
self.surface = ComPtr(surface);
}
Ok(surface)
(*self.context.0).CopyResource(self.readable.0 as *mut _, texture.0 as *mut _);
Ok(())
}
pub fn frame<'a>(&'a mut self, timeout: UINT) -> io::Result<Frame<'a>> {