feat(vegas): render every plugin's ticker content off the render thread

The plugin-facing canvas (DisplayManager.image, draw, matrix) was one
shared object, so any plugin whose Vegas content needed it -- display
capture, scroll-content generation, narrowed rendering -- was deferred to
the render thread and fetched there one at a time. On hdpi that is most
plugins, and each fetch stalled the scroll: news ~320ms, hockey ~660ms,
in bursts whenever the strip extended.

DisplayManager.offscreen() gives the calling thread a canvas of its own.
image, draw and matrix are now properties that resolve to the thread's
surface while it is inside the block and to the shared canvas otherwise,
so the ~100 existing uses become thread-correct unchanged. Inside,
update_display(), the hardware half of clear(), and set_scrolling_state()/
set_frame_hold() are inert, so a plugin drawn for Vegas can neither reach
the panel nor re-pace the live scroll. render_size() is rebuilt on it.
capture_mode() now restores the previous state instead of clearing it,
so it cannot end suppression inside an offscreen block.

The adapter draws every path on its own canvas (_isolated_canvas) and
drops the copy-and-restore of the shared image, which from a background
thread would have written a stale frame back over the render loop's.
Background fetches take the plugin's update/display lock, waiting up to
2s for a running update() and skipping the plugin that round otherwise;
Vegas never took that lock, so render-thread captures already raced
update(). A background fetch that comes back empty is no longer queued
for the render thread.

vegas_scroll.offscreen_prefetch (default true) restores the old deferred
path when false. See docs/OFFSCREEN_RENDERING.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Chuck
2026-09-24 09:31:27 -04:00
co-authored by Claude Opus 5.5
parent 430e2312f8
commit 13b5264d11
9 changed files with 690 additions and 89 deletions
+10
View File
@@ -74,6 +74,14 @@ class VegasModeConfig:
# precedence over smooth_scroll's whole-pixel pacing when on.
sub_pixel_blend: bool = False
# Render every plugin's ticker content on the background prefetch thread,
# each on a canvas of its own (DisplayManager.offscreen), instead of
# handing plugins that draw on the display canvas to the render thread one
# at a time. Each of those cost the scroll a 40-600ms pause. False restores
# that path; it is kept for one release in case a plugin misbehaves when
# drawn off the render thread. See docs/OFFSCREEN_RENDERING.md.
offscreen_prefetch: bool = True
# Keep one continuous strip, extending it with the next group of plugins as
# the scroll approaches the end, instead of composing a fresh strip and
# swapping it in. A swap stops the motion, substitutes every pixel at once
@@ -203,6 +211,7 @@ class VegasModeConfig:
smooth_scroll=vegas_config.get('smooth_scroll', True),
sub_pixel_blend=bool(vegas_config.get('sub_pixel_blend', False)),
continuous_scroll=vegas_config.get('continuous_scroll', True),
offscreen_prefetch=bool(vegas_config.get('offscreen_prefetch', True)),
extend_threshold_screens=float(
vegas_config.get('extend_threshold_screens', 2.0)),
auto_trim=vegas_config.get('auto_trim', True),
@@ -244,6 +253,7 @@ class VegasModeConfig:
'smooth_scroll': self.smooth_scroll,
'sub_pixel_blend': self.sub_pixel_blend,
'continuous_scroll': self.continuous_scroll,
'offscreen_prefetch': self.offscreen_prefetch,
'extend_threshold_screens': self.extend_threshold_screens,
'auto_trim': self.auto_trim,
'trim_threshold': self.trim_threshold,