Skip to content

Render a synchronized-output frame on close, not on the next debounce - #6073

Open
s-celles wants to merge 2 commits into
xtermjs:masterfrom
s-celles:fix/dec2026-sync-render
Open

Render a synchronized-output frame on close, not on the next debounce#6073
s-celles wants to merge 2 commits into
xtermjs:masterfrom
s-celles:fix/dec2026-sync-render

Conversation

@s-celles

Copy link
Copy Markdown

Fixes #6071.

Problem

Under a continuous full-screen animation that wraps every frame in a DEC
private mode 2026 (synchronized output) block, the display is capped at ~1 fps
however fast frames arrive and however idle the renderer is — completed frames
only paint when the 1000 ms synchronized-output timeout expires.

When a 2026 block closes, the input handler requests a refresh.
RenderService.refreshRows flushes the buffered rows but schedules the paint
through the render debouncer (requestAnimationFrame). Under a continuous
animation the next frame opens a new 2026 block before that rAF fires, and
_renderRows skips while synchronized output is on — so the debounced paint is
dropped and the frame only appears on the sync timeout.

Fix

Render synchronously when a synchronized-output buffer was just flushed
(buffered is truthy). At that point sync is already false — the flush
runs from the mode-reset that turned sync off — so the completed frame paints
before the next frame can reopen the mode. No partial-frame tearing, since the
frame is complete by construction.

Testing

Measured against a 30 fps animated-background TUI (TryIt.jl): renders went from
~1/s to the frame-arrival rate, with no tearing.

Assisted-by: AI

…e next debounce

When DEC private mode 2026 (synchronized output) closes, the input handler
requests a refresh. RenderService flushes the buffered rows but schedules the
paint through the render debouncer (requestAnimationFrame). Under a continuous
full-screen animation the next frame opens a new 2026 block before that rAF
fires, and `_renderRows` skips while synchronized output is on — so the
debounced paint is dropped and the frame only appears when the 1000ms
synchronized-output timeout expires. The display is then capped at ~1fps
however fast frames arrive and however idle the renderer is.

Render synchronously when a synchronized-output buffer was just flushed
(`buffered` is truthy). At that point the mode is already off — the flush runs
from the mode reset that turned it off — so the completed frame paints before
the next frame can reopen the mode.

Measured against a 30fps animated-background TUI: renders went from ~1/s to the
frame arrival rate, with no partial-frame tearing.

Assisted by AI.
@jerch

jerch commented Aug 1, 2026

Copy link
Copy Markdown
Member

Hmm to be honest - I don't like the idea to introduce synchronous rendering. I think a better solution would be to introduce double buffering, where DECRST 2026 triggers a viewport buffer copy to be shown on next requestAnimationFrame (with an upper time limit).

@Tyriar Your thoughts on this?

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.

DEC 2026 synchronized output renders at ~1 fps under continuous full-screen animation

2 participants