You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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:
Adopt the newest queued RGB frame using the current buffer-ownership rules.
Render the complete Viewer frame into the BGRA DIB section using the existing off-screen GDI path.
Map/update the D3D11 upload texture and copy the complete BGRA image.
Copy that texture into the current DXGI back buffer.
Call Present(1, 0).
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.
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.
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.
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
6a50e71is the currently verified mitigation: during playback it performs a secondGdiFlush() + DwmFlush()after the final windowBitBlt(). 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:
BitBlt();DwmFlush()mitigation can be removed;Non-goals for the first phase
DXGI_PRESENT_ALLOW_TEARING.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
CBitmapback 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
CViewerViewand used only on the UI thread:D3D11_CREATE_DEVICE_BGRA_SUPPORT;DXGI_FORMAT_B8G8R8A8_UNORM;DXGI_SWAP_EFFECT_FLIP_DISCARDwhere supported, withFLIP_SEQUENTIALas the compatibility alternative;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:
Present(1, 0).A swap-chain buffer is not reused until DXGI owns and schedules the completed buffer. This removes the large window-DC
BitBlt(),GdiFlush(), andDwmFlush()calls from the normal DXGI path.4. Resize and lifecycle
WM_SIZE, release references to swap-chain buffers, callResizeBuffers(), and recreate the upload texture/back-buffer references.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
d3d11.libanddxgi.libproject dependencies.Phase B: validation and default enablement
6a50e71build.Phase C: cleanup
DwmFlush()workaround from the default DXGI path.Acceptance criteria
References
6a50e71cf9b2bf26ec52c29bc4eb89ebfccc2e85