Skip to content

🐛 fix(activex): compose frames offscreen to remove pan flicker - #61

Merged
pmeems merged 6 commits into
MapWindow:developmentfrom
Julian-MW:fix/flicker-free-frame-painting
Nov 6, 2026
Merged

pmeems merged 6 commits into
MapWindow:developmentfrom
Julian-MW:fix/flicker-free-frame-painting

Conversation

@Julian-MW

Copy link
Copy Markdown

Phase 1 of the flicker/diagnostic plan: double-buffered OnDraw, WM_ERASEBKGND suppression, and a reworded OpenGL fallback diagnostic. Phase 2 (pan-by-blit layer cache) and Phase 3 (Core tessellation for OpenGL) are deliberately not part of this PR.

Motivation

Panning a vector map in the C# test host (samples/CSharp/ActiveXTestHost) flickered heavily on every mouse move, unlike MapWinGIS 5. Two causes:

  1. OnDraw painted directly to the container's DC, so every redraw showed the background erase and the partially drawn frame.
  2. The control did not handle WM_ERASEBKGND, so the windowed control erased its background to the host color before each redraw, flashing through the geometry.

Separately, the OpenGL preview backend's rejection message ("The scene uses geometry that the OpenGL backend does not support.") read like a geometry corruption error even though the GDI fallback renders the frame correctly. The status bar now reports the actual situation.

Changes

  • FrameBuffer (new, mwg::activex) — a resizable offscreen DC backed by a 32-bit top-down DIB section (CreateDIBSection, BI_RGB). Resize allocates the replacement before releasing the current buffer, so a failed resize keeps a presentable buffer; non-positive sizes and allocation failures return false.
  • CMapWinGISControl::OnDraw (rewritten) — composes the frame in the FrameBuffer when the target size is drawable and presents it with a single BitBlt (SRCCOPY). Drawing straight to drawInfo.hdcDraw remains the fallback for sizes the buffer cannot hold. Backend contract unchanged: both backends take any HDC, so no backend code was touched for buffering.
  • WM_ERASEBKGND suppressed (OnEraseBkgnd returns 1, handled) — OnDraw paints an opaque frame over the whole bounds, so the erase pass only ever produced a visible flash.
  • OpenGL fallback diagnostic reworded (OpenGlVectorLayerRenderer::ValidateTarget) — "The OpenGL preview backend supports only convex, hole-free polygons; this frame was rendered with GDI." The OpenGL convex-only GL_POLYGON limitation itself is unchanged; tessellating concave/holed polygons in Core is Phase 3.
  • Tests — new FrameBufferTests.cpp (8 tests: allocation, resize, same-size reuse, failed-resize keeps buffer, pixel round-trip, release/reuse, non-positive sizes); VectorLayerRendererTests.cpp assertions updated to the new diagnostic string. FrameBuffer is compiled into MapWinGIS.ActiveX.Impl.lib so the native tests exercise it without COM activation; registered in all three .vcxproj files.
  • Memory — one sentence added to .ai/memory/architecture/activex.md documenting the offscreen composition contract.

Design decisions

  • Buffering lives in the OCX layer, not the backends: the coordinator/backend split (VectorRenderBackend taking any HDC) is preserved, and the Core stays pure.
  • A failed Resize never leaves the control without a buffer: the previous DIB is released only after the replacement is fully selected.
  • No public automation API changed; the only observable behavior differences are the absence of flicker and the reworded RenderingStatus diagnostic string.

Build and test status

  • Not built locally — this tree requires Windows/MSBuild (Debug|x64 per AGENTS.md) and the authoring environment is Linux-only. Validation is delegated to the build.yml workflow (windows-2025, Debug x64), which runs on PRs.
  • The new/changed code follows the existing style (4-space, Allman, [[nodiscard]], repo commit conventions); no new warnings are expected, but the compiler pass on CI is the authoritative check.

Out of scope (future phases)

  • Phase 2: MapWinGIS 5-style pan-by-blit layer cache (_bufferBitmap/_layerBitmap, RedrawSkipDataLayers) for panning without re-rendering layers.
  • Phase 3: Core tessellation so the OpenGL backend can draw concave and holed polygons instead of falling back to GDI.

- Compose OnDraw frames in a resizable DIB-section frame buffer and present them with a single BitBlt, so the visible surface never shows the background erase or a partially drawn frame
- Suppress WM_ERASEBKGND because OnDraw paints an opaque frame over the whole bounds
- Reword the OpenGL fallback diagnostic to name the preview limitation and the GDI fallback
- Add FrameBuffer unit tests and update the diagnostic assertions

@pmeems pmeems left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated review. Final decision is with the human reviewer.

- Compose the frame through a renderFrame lambda used for both the buffered and direct paths
- On failure of BitBlt from the offscreen memory DC (cross-device targets such as printers or metafiles), redraw the frame directly to drawInfo.hdcDraw instead of returning E_FAIL
@pmeems
pmeems merged commit 4758642 into MapWindow:development Nov 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants