Skip to content

fix(colormap2): follow pan via GPU translation instead of freezing#36

Merged
jeandet merged 1 commit into
SciQLop:mainfrom
jeandet:fix/colormap2-pan-translation
Jun 18, 2026
Merged

fix(colormap2): follow pan via GPU translation instead of freezing#36
jeandet merged 1 commit into
SciQLop:mainfrom
jeandet:fix/colormap2-pan-translation

Conversation

@jeandet

@jeandet jeandet commented Jun 18, 2026

Copy link
Copy Markdown
Member

Problem

A spectrogram (QCPColorMap2) stays stuck in place while the axes scroll during a pan, snapping to the right place only when panning stops. Worst on large / log-Y energy spectrograms. QCPHistogram2D (ViewportIndependent) is unaffected.

Root cause (two parts)

QCPColorMap2 is ViewportDependent: each range change kicks an async resample, and the layer was only redrawn on the pipeline's finished signal.

  1. Layer never dirtied mid-drag in the sync flow. Interactive drags call markAffectedLayersDirty(), but a pan driven by set_range (axis synchronisation across stacked plots) does not — so the synced spectrogram's layer was never marked dirty during the drag → frozen until the resample landed. (A tracer showed 0 draw() calls during a fast drag vs 40 for the histogram.)
  2. Even when dirtied, it would repaint+re-upload. Just marking dirty repaints the CPU staging buffer and re-uploads the viewport-sized texture every frame — the exact cost the skip-on-translate / Metal pan work removed for line graphs.

Fix

  • Mark the layer dirty in onViewportChanged() so a set_range-driven pan engages it.
  • Add QCPColorMap2::stallPixelOffset() so the compositor translates the existing texture by the pan delta (no repaint, no re-upload) — the same skip-on-translate fast path QCPGraph2/QCPMultiGraph use. A fresh resample/data/gradient change forces a real redraw (mapImageInvalidated guard).
  • The zoom-rejection is scale-aware (new qcp::axisRangeSizeRatio): a pure pan on a log axis changes the linear range size and would otherwise be misread as a zoom and rejected — which is why log-Y spectrograms were worst hit.

Net: colormaps now pan as cheaply as line graphs (GPU texture shift), honoring the macOS Metal pan optimisation rather than re-uploading each frame.

Tests (test-paintbuffer)

  • colormap2_panDirtiesLayerBuffer{,LogY} — a pan dirties the colormap's layer (fail before this fix, pass after).
  • colormap2_stallOffsetOnPan / …OnLogYPan / …NullOnZoom — offset is non-null on a pan (incl. log-Y) and null on a genuine zoom.

No new regressions in the auto-test suite (the 4 pre-existing RHI-context failures are unrelated and present on main).

🤖 Generated with Claude Code

@jeandet jeandet force-pushed the fix/colormap2-pan-translation branch from da44d56 to e10d34c Compare June 18, 2026 10:50
@jeandet jeandet changed the title fix(colormap2): follow pan smoothly instead of freezing during async resample fix(colormap2): follow pan via GPU translation instead of freezing Jun 18, 2026
@jeandet jeandet requested a review from Copilot June 18, 2026 10:52

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Fixes QCPColorMap2 panning so viewport-dependent spectrograms no longer “freeze” during axis-synchronized pans, by dirtying the layer on viewport changes and enabling the existing skip-on-translate compositor path (GPU texture translation), including correct behavior on log axes.

Changes:

  • Mark QCPColorMap2’s layer dirty in onViewportChanged() so setRange-driven pans repaint/compose during drags.
  • Implement QCPColorMap2::stallPixelOffset() (with rendered-range tracking) to translate the last-rendered texture on pure pans instead of repainting/reuploading.
  • Add log-scale-aware zoom-vs-pan detection (qcp::axisRangeSizeRatio) and new test-paintbuffer coverage for pan dirtying and translation behavior.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/auto/test-paintbuffer/test-paintbuffer.h Adds new test declarations for colormap2 pan dirtying and stall offset behavior.
tests/auto/test-paintbuffer/test-paintbuffer.cpp Adds regression tests validating layer dirtying on pan and GPU translation offsets (linear + log Y).
src/plottables/plottable-colormap2.h Adds stallPixelOffset() override and rendered-range state used for translation.
src/plottables/plottable-colormap2.cpp Dirts layer on viewport changes; implements translation offset logic; records rendered ranges after draw.
src/painting/viewport-offset.h Adds axisRangeSizeRatio helper for scale-aware (log) pan/zoom discrimination.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1 to +3
#include <vector>
#include "test-paintbuffer.h"
#include <painting/viewport-offset.h>
A spectrogram (QCPColorMap2) froze in place while the axes scrolled during a
pan, snapping only when panning stopped — worst on large / log-Y energy
spectrograms. QCPHistogram2D (ViewportIndependent) was unaffected.

Two causes, two parts to the fix:

1. Engage the layer on pan. QCPColorMap2 is ViewportDependent: a range change
   kicks an async resample and the layer was only redrawn on the pipeline's
   finished signal. Interactive drags call markAffectedLayersDirty(), but a pan
   driven by set_range (axis synchronisation) does not — so the synced
   spectrogram was never marked dirty mid-drag and stayed frozen. Mark the
   layer dirty in onViewportChanged().

2. Translate, don't repaint. Marking dirty alone would repaint the CPU staging
   buffer and re-upload the (viewport-sized) texture every frame — exactly what
   the skip-on-translate / Metal pan optimisation avoids for line graphs. Give
   QCPColorMap2 a stallPixelOffset() override so the compositor shifts the
   existing texture by the pan delta instead (no repaint, no re-upload), the
   same fast path QCPGraph2/QCPMultiGraph use. A fresh resample / data /
   gradient change forces a real redraw (guards on mapImageInvalidated).

The zoom-rejection in stallPixelOffset is scale-aware (new
qcp::axisRangeSizeRatio): a pure pan on a log axis changes the *linear* range
size and would otherwise be misread as a zoom and rejected — which is why log-Y
spectrograms were the worst affected.

Tests (test-paintbuffer): pan dirties the colormap layer (fail before, pass
after); stallPixelOffset is non-null on a pan incl. log-Y, and null on a zoom.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jeandet jeandet force-pushed the fix/colormap2-pan-translation branch from e10d34c to c97003d Compare June 18, 2026 11:05
@jeandet

jeandet commented Jun 18, 2026

Copy link
Copy Markdown
Member Author

Addressed: reordered test-paintbuffer.cpp includes so the unit's own header (test-paintbuffer.h) comes first, matching the rest of the suite.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated no new comments.

@jeandet jeandet merged commit 104f5f4 into SciQLop:main Jun 18, 2026
5 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