Opened 4 weeks ago
Last modified 3 weeks ago
#24842 new enhancement
[patch] Reuse the previous frame when panning at constant zoom
| Reported by: | mistenman | Owned by: | team |
|---|---|---|---|
| Priority: | major | Milestone: | 26.09 |
| Component: | Core mappaint | Version: | |
| Keywords: | render rendering performance optimization panning graphics | Cc: |
Description
Marked as priority: major due to being a massive improvement to the responsiveness of the panning action.
Right-button dragging currently re-renders the whole map on every mouse move. On dense, low-zoom views one frame takes 200–300 ms to render even on my powerful gaming computer, and because the render happens on the UI thread, panning stutters and the editor freezes while a frame is drawn.
The map view already keeps the unchanged layers nonChangedLayersBuffer and reuses it while the view stays the same (lastViewID). The idea is to also reuse it for a panned view, by shifting the old pixels by the pan distance and only redrawing the strip that was exposed. Copying pixels is much cheaper than re-rasterizing the whole map.
The buffer is kept as a complete composite of all visible layers, so a panned view can always be built by shifting it. The reuse has to be skipped, falling back to the current full redraw, in situations such as a zoom, resize,
layer changes, paint preference changes, or a pan larger than the viewport.
One limitation is that while panning, layers invalidated mid-drag (for example freshly loaded imagery tiles) are only redrawn in the exposed strip, so their changes show up once the pan stops. When the view is still, each tile arrival still causes a full redraw of the layer, as before.
The main change is in MapView.drawMapContent. The imagery attribution is fixed to the viewport and would smear if it stayed in the reused composite, so it needs to be painted on top per frame instead.
Related: #11487 "added an optional asynchronous tiled renderer". This proposal targets the default synchronous renderer (used when tiled rendering is disabled and at zoom levels above mappaint.fast_render.zlevel), reducing per-frame work by reusing the previous frame instead of re-rasterizing the whole map. The two approaches are complementary.
Summary of attached patch (implementation example against r19616)
The map view already keeps a composite of the unchanged layers and reuses it while the view stays the same. This patch extends that to panned views. During a drag at constant zoom, it copies the previous frame shifted by the pan distance and redraws only the strip that was exposed (which I will refer to as pan-reuse). Copying pixels is far cheaper than re-rasterizing the whole map, so pan frames drop from roughly 200-300 ms to 1-20 ms (on my setup), depending on the complexity of the frame.
Changes by file:
- MapView.java: pan-reuse in
drawMapContent. The buffer is copied back after every frame, so it always holds a complete composite of all visible layers and a pan frame can be built by shifting it and repainting the exposed strips. Reuse requires the layer set to be unchanged, the scale unchanged, the last frame to have covered the whole viewport, and the movement to be between 0.5 px and the viewport size. Layers invalidated during the pan (e.g. imagery tiles arriving) are painted strip-only, and the rest shows up when the pan stops. Also addsisPanning()and a per-frame imagery attribution pass. With the config preferencemappaint.debug.pan-reuse=true/false, panning actions print debug information about which pan-reuse logic triggers into stdout. - AbstractTileSourceLayer.java: the imagery attribution is fixed to the viewport, so it would smear if left in the reused composite. It is now painted on top of the frame each paint via a new
paintAttribution(Graphics2D, MapView). - SelectAction.java, OsmDataLayer.java: hovering no longer invalidates the data layer. Previously every crossing of a feature caused a full re-render (~200-300 ms) and broke the reuse state. The hover highlight is now drawn by SelectAction as a temporary overlay layer,
OsmDataLayer.primitiveHoveredno longer resets the render cache or invalidates, andgiveUserFeedbackis suppressed while panning. - MapMover.java:
endMovementno longer invalidates all layers. A pan changes nothing but the view, so the last composite stays valid; the old invalidation caused a full re-render right after every drag and at the start of the next one.
Reuse falls back to the normal full repaint whenever the view did more than translate, i.e. zoom, resize, layer changes, or a pan larger than the viewport. When the view is still, invalidated layers repaint in full as before.
Testing: set mappaint.debug.pan-reuse=true (optionally mappaint.render.benchmark=true) and drag at constant zoom on a dense view. each drag frame should log PANREUSE reuse and the benchmark's phase 2's render time should drop to a fraction of a full frame render.
I will attach a video demonstrating panning in r19555 vs r19616 with the patch applied.
Attachments (2)
Change History (4)
by , 4 weeks ago
| Attachment: | pan-reuse.patch added |
|---|
by , 4 weeks ago
| Attachment: | r19555 (left) vs patched r19616 (right).mp4 added |
|---|
comment:1 by , 4 weeks ago
| Milestone: | → 26.09 |
|---|
comment:2 by , 3 weeks ago
| Summary: | Reuse the previous frame when panning at constant zoom → [patch] Reuse the previous frame when panning at constant zoom |
|---|



a demonstration of the patch in action