Skip to content

Commit b3b9c3e

Browse files
committed
fix(gui): resolve D3D11 image preview jitter during horizontal resize
- Remove Flush() before ResizeBuffers — eliminates CPU stall that widens the window where DWM composites stale front buffer - Use event-provided pixel dimensions directly instead of querying SDL_GetWindowSizeInPixels() which may return stale values - Skip vsync (Present(0,0)) on resize frames so new content reaches DWM before next composition cycle - Add same-size early return to avoid redundant ResizeBuffers calls - Only handle SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED (physical pixels), drop SDL_EVENT_WINDOW_RESIZED (logical size, wrong on HiDPI) Root cause: DXGI_SCALING_NONE + ResizeBuffers destroys all back buffers, leaving a timing gap where DWM composites the stale front buffer at the old size, top-left aligned in the new window — visible as a per-frame position oscillation. OpenGL unaffected (glViewport never invalidates framebuffer content).
1 parent 4f61441 commit b3b9c3e

3 files changed

Lines changed: 21 additions & 14 deletions

File tree

src/gui/backend/d3d11_backend.cpp

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -403,21 +403,26 @@ void D3D11Backend::end_frame() {
403403
}
404404

405405
void D3D11Backend::present() {
406-
// Present with vsync
407-
m_swap_chain->Present(1, 0);
406+
// During resize: present immediately (sync=0) to push frame to DWM before
407+
// it composites stale content. Normal frames use vsync (sync=1).
408+
UINT sync_interval = m_in_resize ? 0 : 1;
409+
m_swap_chain->Present(sync_interval, 0);
410+
m_in_resize = false;
408411
}
409412

410413
void D3D11Backend::on_resize(int width, int height) {
411414
if (!m_initialized || width <= 0 || height <= 0) return;
415+
if (m_window_width == width && m_window_height == height) return;
412416

413-
// Get actual drawable size
414-
SDL_GetWindowSizeInPixels(m_window, &m_window_width, &m_window_height);
417+
m_window_width = width;
418+
m_window_height = height;
415419

416-
// Release render target before resize
420+
// Release render target before resize (required by DXGI)
417421
cleanup_render_target();
418-
m_context->Flush();
419422

420-
// Resize swap chain buffers — flags = 0 (cooperative, no mode switch)
423+
// Resize swap chain buffers — no Flush() needed; ResizeBuffers handles
424+
// outstanding GPU work internally. Removing Flush() avoids a CPU stall
425+
// that widens the window where DWM composites stale content.
421426
HRESULT hr = m_swap_chain->ResizeBuffers(
422427
0, // Keep current buffer count
423428
m_window_width,
@@ -434,6 +439,10 @@ void D3D11Backend::on_resize(int width, int height) {
434439

435440
// Recreate render target
436441
create_render_target();
442+
443+
// Signal present() to use immediate mode (skip vsync) so the resized
444+
// frame reaches DWM before it composites the stale front buffer.
445+
m_in_resize = true;
437446
}
438447

439448
// =============================================================================

src/gui/backend/d3d11_backend.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ class D3D11Backend final : public IRenderBackend {
124124
int m_window_height{0};
125125
bool m_initialized{false};
126126
bool m_using_warp{false};
127+
bool m_in_resize{false}; // Set during resize → present without vsync
127128

128129
// Feature level info
129130
D3D_FEATURE_LEVEL m_feature_level{D3D_FEATURE_LEVEL_11_0};

src/gui/gui_app.cpp

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -384,13 +384,11 @@ int run(int argc, char** argv) {
384384
auto event_watch = [](void* userdata, SDL_Event* event) -> bool {
385385
auto* ctx = static_cast<RenderContext*>(userdata);
386386

387-
if (event->type == SDL_EVENT_WINDOW_RESIZED ||
388-
event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
387+
if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED ||
389388
event->type == SDL_EVENT_WINDOW_EXPOSED) {
390389

391-
// Update backend size
392-
if (event->type == SDL_EVENT_WINDOW_RESIZED ||
393-
event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
390+
// Update backend with pixel-accurate size (not logical size from RESIZED)
391+
if (event->type == SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED) {
394392
ctx->backend->on_resize(event->window.data1, event->window.data2);
395393
}
396394

@@ -444,9 +442,8 @@ int run(int argc, char** argv) {
444442
}
445443
break;
446444

447-
case SDL_EVENT_WINDOW_RESIZED:
448445
case SDL_EVENT_WINDOW_PIXEL_SIZE_CHANGED:
449-
// Resize is handled in event watch callback
446+
// Sync backend to current pixel size
450447
backend->on_resize(event.window.data1, event.window.data2);
451448
break;
452449

0 commit comments

Comments
 (0)