glterm: throttle animation presents and clamp scroll copies
The cursor-effect and cross-fade pumps presented on top of redisplay's own presents, pushing the window surface well past the refresh rate; under that pressure Mesa juggles extra swapchain buffers and radeonsi was caught presenting a stale one, an ancient frame flashing for one vblank on buffer switches. Animation ticks now skip their present when a swap happened less than 12 ms ago; the time-based animations lose nothing. Also clamp copy_region to the framebuffer: a scroll computed against a mid-resize frame size could blit from outside the FBO, which writes undefined (recycled) VRAM into visible content. GL_LOG_PRESENT now prints a present sequence number, timestamp and cross-fade phase, and GL_PRESENT_FINISH=1 drains the GPU before each swap as a diagnostic knob.
This commit is contained in:
parent
f3a1554765
commit
dfb6186811
1 changed files with 54 additions and 6 deletions
60
src/glterm.c
60
src/glterm.c
|
|
@ -510,6 +510,14 @@ struct gl_frame_data
|
|||
GLuint trans_tex;
|
||||
int trans_w, trans_h;
|
||||
double trans_start, trans_dur;
|
||||
/* When the last successful swap happened. The animation pumps (cursor
|
||||
effects, cross-fade) throttle on it: their ticks interleave with
|
||||
redisplay's own presents, and burst-presenting the surface well above
|
||||
the refresh rate makes Mesa juggle extra swapchain buffers -- under
|
||||
which radeonsi has been seen presenting a stale one (an ancient frame
|
||||
flashing for one vblank). Time-based animations lose nothing by
|
||||
skipping a tick that lands right after a present. */
|
||||
double last_swap;
|
||||
/* Inline video, or NULL. Composited over the FBO blit on every present
|
||||
(see gl_present_to_window); the MtlVideoPlayer analogue. */
|
||||
struct gl_video *video;
|
||||
|
|
@ -1029,6 +1037,8 @@ gl_present_to_window (struct gl_frame_data *fd)
|
|||
&& !getenv ("GL_NO_DAMAGE")) ? 1 : 0;
|
||||
log_present = getenv ("GL_LOG_PRESENT") ? 1 : 0;
|
||||
}
|
||||
static unsigned long present_seq;
|
||||
present_seq++;
|
||||
|
||||
/* Buffer age: how many swaps ago this back buffer was last presented
|
||||
(0 = new/reallocated/unknown). Queried after make-current, before
|
||||
|
|
@ -1085,9 +1095,12 @@ gl_present_to_window (struct gl_frame_data *fd)
|
|||
}
|
||||
|
||||
if (log_present)
|
||||
fprintf (stderr, "[glpresent] fbo=%dx%d window=%dx%d age=%d %s boxes=%d\n",
|
||||
fd->w, fd->h, sw, sh, (int) age, full ? "full" : "partial",
|
||||
full ? 1 : repair.n);
|
||||
fprintf (stderr, "[glpresent] #%lu t=%.4f fbo=%dx%d window=%dx%d age=%d %s"
|
||||
" boxes=%d trans=%.3f cycle=%d\n",
|
||||
present_seq, gl_now (), fd->w, fd->h, sw, sh, (int) age,
|
||||
full ? "full" : "partial", full ? 1 : repair.n,
|
||||
fd->trans_dur > 0 ? gl_now () - fd->trans_start : -1.0,
|
||||
(int) fd->in_cycle);
|
||||
|
||||
/* Both the FBO and the window's default framebuffer use GL bottom-left
|
||||
origin, so a straight (unflipped) blit lands right-side-up on screen.
|
||||
|
|
@ -1198,7 +1211,13 @@ gl_present_to_window (struct gl_frame_data *fd)
|
|||
screen (the current frame's changes, not the age repairs: those match
|
||||
the screen already). The compositor then recomposites only those
|
||||
bands. Zero rects means full damage, which is also the fallback
|
||||
without the extension. */
|
||||
without the extension. GL_PRESENT_FINISH=1 drains the GPU first
|
||||
(diagnostic knob for present-order races in the GL stack). */
|
||||
{
|
||||
static int finish = -1;
|
||||
if (finish == -1) finish = getenv ("GL_PRESENT_FINISH") ? 1 : 0;
|
||||
if (finish) glFinish ();
|
||||
}
|
||||
EGLBoolean swapped;
|
||||
if (partial_enabled && g_swap_damage && !overlay && !fd->dirty.all
|
||||
&& fd->dirty.n > 0 && sw == fd->w && sh == fd->h)
|
||||
|
|
@ -1236,6 +1255,7 @@ gl_present_to_window (struct gl_frame_data *fd)
|
|||
fd->swap_dirty[fd->swap_head].all = true;
|
||||
fd->swap_head = (fd->swap_head + 1) % GL_SWAP_RING;
|
||||
gl_dirty_clear (&fd->dirty);
|
||||
fd->last_swap = gl_now ();
|
||||
|
||||
/* Post-swap size recheck (see the comment at retry_present): if the
|
||||
surface turns out to have resized under this present, what just went
|
||||
|
|
@ -1364,6 +1384,22 @@ gl_drv_copy_region (struct frame *f, int x, int y, int w, int h,
|
|||
int sx = (int) (x * s), sy = (int) (y * s);
|
||||
int dx = (int) (dst_x * s), dy = (int) (dst_y * s);
|
||||
int sw = (int) (w * s), sh = (int) (h * s);
|
||||
/* Clamp the move to the FBO. During a resize race Emacs can ask for a
|
||||
scroll computed against the old frame size; a blit whose SOURCE rect
|
||||
leaves the read buffer writes UNDEFINED pixels (on radeonsi: recycled
|
||||
VRAM, i.e. fragments of old frames) into the destination. Trim both
|
||||
rects by the same amount so the copy stays a pure translation. */
|
||||
{
|
||||
int lo = sx < dx ? sx : dx; /* leftmost edge of either rect */
|
||||
if (lo < 0) { sx -= lo; dx -= lo; sw += lo; }
|
||||
lo = sy < dy ? sy : dy;
|
||||
if (lo < 0) { sy -= lo; dy -= lo; sh += lo; }
|
||||
int hi = sx > dx ? sx : dx; /* rightmost start of either rect */
|
||||
if (hi + sw > fd->w) sw = fd->w - hi;
|
||||
hi = sy > dy ? sy : dy;
|
||||
if (hi + sh > fd->h) sh = fd->h - hi;
|
||||
if (sw <= 0 || sh <= 0) return;
|
||||
}
|
||||
int sy0 = fd->h - (sy + sh), dy0 = fd->h - (dy + sh);
|
||||
glDisable (GL_SCISSOR_TEST);
|
||||
if (dx >= sx + sw || sx >= dx + sw || dy0 >= sy0 + sh || sy0 >= dy0 + sh)
|
||||
|
|
@ -2266,7 +2302,11 @@ gl_anim_tick (struct frame *f, double dt)
|
|||
a->n_particles = alive;
|
||||
}
|
||||
|
||||
if (moved && !fd->in_cycle)
|
||||
/* Present only when something moved AND redisplay has not just swapped:
|
||||
piling animation presents on top of redisplay's own floods the
|
||||
swapchain (see last_swap in gl_frame_data). The skipped motion still
|
||||
reaches the screen with the next tick or present. */
|
||||
if (moved && !fd->in_cycle && gl_now () - fd->last_swap >= 0.012)
|
||||
{ gl_present_to_window (fd); fd->needs_present = false; }
|
||||
return g_gl_animations_enabled;
|
||||
}
|
||||
|
|
@ -2605,6 +2645,9 @@ gl_transition_start (struct frame *f, double duration)
|
|||
|
||||
fd->trans_start = gl_now ();
|
||||
fd->trans_dur = duration;
|
||||
if (getenv ("GL_LOG_PRESENT"))
|
||||
fprintf (stderr, "[gltrans] arm t=%.4f dur=%.0fms fbo=%dx%d\n",
|
||||
fd->trans_start, duration * 1000, fd->w, fd->h);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2616,7 +2659,12 @@ gl_transition_tick (struct frame *f)
|
|||
{
|
||||
struct gl_frame_data *fd = gl_get_frame_data (f);
|
||||
if (!fd || fd->trans_dur <= 0) return false;
|
||||
gl_present_to_window (fd);
|
||||
/* The fade is drawn on EVERY present while it runs, so a tick that
|
||||
lands right after a redisplay present has nothing to add; skipping
|
||||
it keeps the present rate at the refresh rate instead of flooding
|
||||
the swapchain (see last_swap in gl_frame_data). */
|
||||
if (gl_now () - fd->last_swap >= 0.012)
|
||||
gl_present_to_window (fd);
|
||||
return fd->trans_dur > 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue