Metal: defer presenting clear-only update cycles (first tab-switch flash)
Switching to a tab whose buffer faces are not realized yet garbages the frame: redisplay first runs an update cycle that only calls clear_frame, and Metal presented that blank frame, flashing white for the ~30 ms the follow-up cycle needs to realize faces and repaint. NS does not flash because AppKit coalesces backing-store flushes within the event cycle. Track per-cycle whether clear_frame ran and whether any glyph string was drawn; a clear-only cycle commits to the static texture but defers the present (needsPresent), which the follow-up content cycle or flush_display then picks up. Also adds MTL_LOG_SEQ=1 tracing of the begin/clear/draw/end/present sequence, which is what exposed the blank present.
This commit is contained in:
parent
748a2cd107
commit
93aa0e5d32
2 changed files with 41 additions and 1 deletions
|
|
@ -179,6 +179,14 @@ typedef struct mtl_spring {
|
|||
drawRect: expose path for the uncovered pixels; Metal has none). */
|
||||
@property (nonatomic, assign) int lastMiniHeight;
|
||||
|
||||
/* Within the current update cycle: clear_frame ran / real content was drawn.
|
||||
A cycle that only cleared (a garbaged frame, e.g. the first switch to a tab
|
||||
whose faces are not realized yet) must NOT present, or the user sees a blank
|
||||
flash before the follow-up cycle paints the actual content. NS does not
|
||||
flash there because AppKit coalesces the backing-store flushes. */
|
||||
@property (nonatomic, assign) BOOL cycleSawClear;
|
||||
@property (nonatomic, assign) BOOL cycleSawDraw;
|
||||
|
||||
/* Main Emacs render cycle (renders to staticTexture) */
|
||||
- (void)beginFrame;
|
||||
- (void)endFrame;
|
||||
|
|
|
|||
|
|
@ -1400,6 +1400,20 @@ easing_apply (MtlScrollEasing mode, float t)
|
|||
self.needsPresent = YES;
|
||||
}
|
||||
|
||||
/* Sequence tracing for present-flow debugging (MTL_LOG_SEQ=1). */
|
||||
static BOOL
|
||||
mtl_log_seq_p (void)
|
||||
{
|
||||
static int on = -1;
|
||||
if (on < 0) on = getenv ("MTL_LOG_SEQ") != NULL;
|
||||
return on > 0;
|
||||
}
|
||||
|
||||
#define MTL_SEQ(fmt, ...) \
|
||||
do { if (mtl_log_seq_p ()) \
|
||||
fprintf (stderr, "[mtlseq %.3f] " fmt "\n", \
|
||||
CACurrentMediaTime (), ##__VA_ARGS__); } while (0)
|
||||
|
||||
- (void)compositeToScreen
|
||||
{
|
||||
if (!self.staticTexture || !g_blit_pipeline) return;
|
||||
|
|
@ -1407,6 +1421,8 @@ easing_apply (MtlScrollEasing mode, float t)
|
|||
id<CAMetalDrawable> drawable = [self.metalLayer nextDrawable];
|
||||
if (!drawable) return;
|
||||
|
||||
MTL_SEQ ("PRESENT");
|
||||
|
||||
self.needsPresent = NO; /* about to present whatever is in the static texture */
|
||||
|
||||
NSSize sz = self.metalLayer.frame.size;
|
||||
|
|
@ -2709,12 +2725,14 @@ mtl_draw_glyph_string (struct glyph_string *s)
|
|||
|
||||
if (fd.encoder)
|
||||
{
|
||||
fd.cycleSawDraw = YES;
|
||||
[fd applyClipRect:clip];
|
||||
mtl_draw_glyph_string_impl (s);
|
||||
[fd clearClipRect];
|
||||
}
|
||||
else
|
||||
{
|
||||
MTL_SEQ ("immediate glyph draw x=%d y=%d w=%d", s->x, s->y, s->width);
|
||||
[fd beginFrame];
|
||||
[fd applyClipRect:clip];
|
||||
mtl_draw_glyph_string_impl (s);
|
||||
|
|
@ -2737,6 +2755,8 @@ mtl_clear_frame (struct frame *f)
|
|||
committed immediate frame with a deferred present, like
|
||||
mtl_draw_glyph_string. */
|
||||
BOOL immediate = (fd.encoder == nil);
|
||||
MTL_SEQ ("clear_frame (immediate=%d)", immediate);
|
||||
fd.cycleSawClear = YES;
|
||||
if (immediate) [fd beginFrame];
|
||||
|
||||
NSRect bounds = fd.metalLayer ? CGRectMake (0, 0,
|
||||
|
|
@ -2828,6 +2848,7 @@ mtl_flush_display (struct frame *f)
|
|||
encoder that never got endEncoding, causing an assertion failure. */
|
||||
MtlFrameData *fd = mtl_get_frame_data (f);
|
||||
if (!fd) return;
|
||||
MTL_SEQ ("flush_display (encoder=%d needsPresent=%d)", fd.encoder != nil, (int) fd.needsPresent);
|
||||
if (fd.encoder)
|
||||
[fd endFrame];
|
||||
else if (fd.needsPresent)
|
||||
|
|
@ -2844,6 +2865,9 @@ mtl_update_begin (struct frame *f)
|
|||
/* Guard: if encoder already active (e.g. from a re-entrant redisplay),
|
||||
end it cleanly before starting a new frame. */
|
||||
if (fd.encoder) [fd endFrame];
|
||||
MTL_SEQ ("update_begin");
|
||||
fd.cycleSawClear = NO;
|
||||
fd.cycleSawDraw = NO;
|
||||
[fd beginFrame];
|
||||
|
||||
/* Expose substitute: when the minibuffer (echo area) changes height, the
|
||||
|
|
@ -2874,7 +2898,15 @@ static void
|
|||
mtl_update_end (struct frame *f)
|
||||
{
|
||||
MtlFrameData *fd = mtl_get_frame_data (f);
|
||||
if (fd) [fd endFrame];
|
||||
if (!fd) return;
|
||||
/* A cycle that only cleared the garbaged frame (no content drawn) commits
|
||||
to the static texture but defers the present: presenting it would flash
|
||||
a blank frame for the tens of ms the follow-up cycle needs to realize
|
||||
faces/fonts and repaint (seen on the first switch to a new tab). The
|
||||
deferred present is picked up by the next cycle or by flush_display. */
|
||||
BOOL clearOnly = fd.cycleSawClear && !fd.cycleSawDraw;
|
||||
MTL_SEQ ("update_end%s", clearOnly ? " (clear-only, present deferred)" : "");
|
||||
[fd endFramePresent:!clearOnly];
|
||||
}
|
||||
|
||||
static void
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue