From 49240facff5a2e49b4190a7044798ab136b0e8ac Mon Sep 17 00:00:00 2001 From: DanS Date: Tue, 13 Oct 2026 18:16:50 -0500 Subject: [PATCH] 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 --- src/main.cpp | 14 ++++++++++++++ src/platform/dx11_context.cpp | 27 ++++++++++++++++----------- 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index eb723a8..0278d10 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1527,6 +1527,20 @@ int main(int argc, char* argv[]) if (event->window.windowID != SDL_GetWindowID(ctx->window)) return true; + // Never nest NewFrame() inside an already-open frame — Windows min/max/restore buttons + // (renderBorderlessControls) push SDL window events synchronously from inside app.render(). (C146) + ImGuiContext* gctx = ImGui::GetCurrentContext(); + if (!gctx || gctx->WithinFrameScope) return true; + + // Dedupe the RESIZED + PIXEL_SIZE_CHANGED pair: both report the same final pixel size, so render + // once. SDL_GetWindowSizeInPixels is a consistent key across both event types (their data1/data2 + // differ — points vs pixels — and cannot be compared directly). + int pw = 0, ph = 0; + SDL_GetWindowSizeInPixels(ctx->window, &pw, &ph); + static int lastW = 0, lastH = 0; + if (pw == lastW && ph == lastH) return true; + lastW = pw; lastH = ph; + // Avoid re-entrancy static bool in_resize_render = false; if (in_resize_render) return true; diff --git a/src/platform/dx11_context.cpp b/src/platform/dx11_context.cpp index 822527c..e562b56 100644 --- a/src/platform/dx11_context.cpp +++ b/src/platform/dx11_context.cpp @@ -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(rc.right - rc.left); h = static_cast(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)) {