Skip to content

Viewer: replace GDI window presentation with a DXGI flip-model swap chain #90

Description

@chammoru

Context

Issue #88 exposed horizontal seams that mix adjacent video frames while the Viewer copies a large GDI back buffer into the window. The symptom is most visible with tall/portrait video in a large window on a 4K display and does not appear while paused.

Commit 6a50e71 is the currently verified mitigation: during playback it performs a second GdiFlush() + DwmFlush() after the final window BitBlt(). This stopped the artifact on the affected 4K system, but it deliberately waits for an additional desktop-composition interval. On a 60 Hz display that extra pacing can reduce high-frame-rate presentation throughput and cause playback frame drops.

The long-term fix should stop copying a large, changing GDI surface into a compositor-owned window surface and present completed frames through a DXGI flip-model swap chain instead.

Goal

Use a complete swap-chain buffer as the atomic unit of window presentation so that:

  • adjacent video frames cannot be mixed by DWM sampling during a large GDI BitBlt();
  • 24, 30, and 60 fps playback can use one synchronized present per displayed frame;
  • the double-DwmFlush() mitigation can be removed;
  • existing Viewer visuals and interactions remain unchanged.

Non-goals for the first phase

  • Rewriting scaling, colour conversion, overlays, or text rendering as GPU shaders.
  • Replacing every GDI drawing helper with Direct2D immediately.
  • Enabling unsynchronized DXGI_PRESENT_ALLOW_TEARING.
  • Changing playback scheduling, decoding, or audio synchronization except where required to integrate presentation.

Proposed architecture

1. Preserve the existing off-screen renderer

Keep the current OnDraw() composition order and GDI helpers for the first phase: canvas background, scaled image, selection rectangles, coordinates, box information, progress/volume UI, and empty state.

Replace the compatible CBitmap back buffer with a top-down 32-bit BGRA DIB section. This provides stable CPU-visible pixels and allows existing GDI operations to continue drawing into the memory DC.

No GDI drawing should target the Viewer HWND once flip-model presentation is active. Microsoft explicitly requires a flip-model HWND not to mix GDI and DXGI updates.

2. Add a D3D11/DXGI presentation backend

Create one presentation backend owned by CViewerView and used only on the UI thread:

  • D3D11 device with D3D11_CREATE_DEVICE_BGRA_SUPPORT;
  • DXGI factory and one swap chain for the Viewer HWND;
  • DXGI_FORMAT_B8G8R8A8_UNORM;
  • DXGI_SWAP_EFFECT_FLIP_DISCARD where supported, with FLIP_SEQUENTIAL as the compatibility alternative;
  • two or three buffers, sample count 1, windowed mode;
  • a CPU-uploadable texture matching the current client size.

The project already targets Windows 10, so DXGI 1.2 flip-model APIs are within the supported platform baseline.

3. Present a completed frame

The playback paint path should be:

  1. Adopt the newest queued RGB frame using the current buffer-ownership rules.
  2. Render the complete Viewer frame into the BGRA DIB section using the existing off-screen GDI path.
  3. Map/update the D3D11 upload texture and copy the complete BGRA image.
  4. Copy that texture into the current DXGI back buffer.
  5. Call Present(1, 0).
  6. Return the previously displayed decoded buffer to the pool only according to the existing stable-buffer lifetime rules.

A swap-chain buffer is not reused until DXGI owns and schedules the completed buffer. This removes the large window-DC BitBlt(), GdiFlush(), and DwmFlush() calls from the normal DXGI path.

4. Resize and lifecycle

  • Create device-independent resources once and size-dependent buffers lazily.
  • On WM_SIZE, release references to swap-chain buffers, call ResizeBuffers(), and recreate the upload texture/back-buffer references.
  • Skip presentation for zero-sized/minimized clients.
  • Release all device and swap-chain resources from the view lifecycle on the UI thread.
  • Keep printing or non-window OnDraw() targets on the existing GDI path.

5. Device loss and fallback

Handle DXGI_ERROR_DEVICE_REMOVED, DXGI_ERROR_DEVICE_RESET, resize failures, unsupported adapters, Remote Desktop limitations, and initialization failures.

If DXGI cannot be initialized or recovered, fall back to the current verified GDI presentation path including the #88 double-composition-wait mitigation. The fallback should be visible in debug logging so test reports identify which backend was active.

6. Optional Direct2D follow-up

Direct2D is not required for the first phase. After DXGI presentation is proven, Direct2D can render text and vector overlays directly into the DXGI surface, and image scaling can later move to a GPU texture/shader path. Those optimizations should be separate changes so the initial migration remains visually comparable and easy to revert.

Implementation phases

Phase A: backend and guarded integration

  • Introduce a small DXGI presenter class with RAII-managed COM resources.
  • Add required d3d11.lib and dxgi.lib project dependencies.
  • Add the BGRA DIB-section back buffer.
  • Integrate DXGI presentation behind a runtime/developer switch.
  • Preserve the GDI fallback.

Phase B: validation and default enablement

Phase C: cleanup

  • Remove the double-DwmFlush() workaround from the default DXGI path.
  • Retain it only in the GDI fallback.
  • Consider Direct2D overlays and GPU scaling as follow-up work based on profiling.

Acceptance criteria

  • No horizontal seam on the original Viewer: possible horizontal tearing during video playback after back-buffer reuse #88 tall/portrait 4K reproduction case.
  • No seam in large-window and full-screen playback at 24, 30, and 60 fps.
  • 60 fps content is not systematically limited to roughly 30 displayed frames per second.
  • Pause, resume, end-of-stream, seeking, and playback-rate changes remain correct.
  • Image scaling, progress/volume UI, selection rectangles, coordinates, box information, and empty-state rendering match the current Viewer.
  • Repeated resize, minimize/restore, full-screen transitions, DPI changes, and movement between monitors do not produce stale frames, flashes, device errors, or crashes.
  • DXGI device loss either recovers cleanly or activates the verified GDI fallback.
  • Core regression tests pass; Viewer and Comparator Release x64 builds remain clean.
  • A final artifact is verified on the affected company 4K system before merging.

References

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions