perf(resize): dedupe the live-resize watcher, early-out DX11 resize

The live-resize event watcher redrew for every resize event, and each
resize tore down and recreated the DX11 render target even when the
pixel size had not actually changed. A single drag fires both a RESIZED
and a PIXEL_SIZE_CHANGED event reporting the same final size, so the
work ran twice.

- The watcher keys off the real pixel size and returns early when it has
  not moved since the last pass, collapsing the duplicate event pair into
  one redraw. It also bails out when called from inside an open ImGui
  frame, which the caption buttons can trigger.
- The DX11 resize now checks the swap chain's current dimensions up
  front and does nothing when the size already matches and the target is
  live, skipping the destroy/recreate churn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-10-13 18:16:50 -05:00
co-authored by Claude Opus 4.8
parent 13059412ce
commit 49240facff
2 changed files with 30 additions and 11 deletions
+16 -11
View File
@@ -252,16 +252,6 @@ void DX11Context::resize(int width, int height)
(void)height;
if (!swapChain_ || !deviceContext_) return;
cleanupRenderTarget();
// Unbind the render target from the pipeline — ResizeBuffers requires
// ALL outstanding references to back-buffer resources to be released.
// Without this, ResizeBuffers fails with DXGI_ERROR_INVALID_CALL when
// growing the window (shrinking appears fine because the old larger
// buffer simply gets cropped by DWM).
deviceContext_->OMSetRenderTargets(0, nullptr, nullptr);
deviceContext_->Flush();
// For composition swap chains (CreateSwapChainForComposition),
// passing 0,0 means "keep current size" — NOT auto-detect from HWND.
// We must pass the actual pixel dimensions from GetClientRect.
@@ -272,7 +262,22 @@ void DX11Context::resize(int width, int height)
w = static_cast<UINT>(rc.right - rc.left);
h = static_cast<UINT>(rc.bottom - rc.top);
}
if (w == 0 || h == 0) return;
if (w == 0 || h == 0) return; // minimized / zero-area: keep the current RTV, do nothing
// Already this size with a live RTV: nothing to do (skips the destroy/recreate churn).
DXGI_SWAP_CHAIN_DESC1 d;
if (SUCCEEDED(swapChain_->GetDesc1(&d)) && d.Width == w && d.Height == h && renderTargetView_)
return;
cleanupRenderTarget();
// Unbind the render target from the pipeline — ResizeBuffers requires
// ALL outstanding references to back-buffer resources to be released.
// Without this, ResizeBuffers fails with DXGI_ERROR_INVALID_CALL when
// growing the window (shrinking appears fine because the old larger
// buffer simply gets cropped by DWM).
deviceContext_->OMSetRenderTargets(0, nullptr, nullptr);
deviceContext_->Flush();
HRESULT hr = swapChain_->ResizeBuffers(0, w, h, DXGI_FORMAT_UNKNOWN, 0);
if (FAILED(hr)) {