Subject: Re: bug#32932: 27.0.50; render bugs on macOS Mojave

From: Alan Third <[email-masked]>
Date: Thu, 1 Nov 2018 22:55:19 +0000

On Thu, Nov 01, 2018 at 08:10:53PM +0200, Eli Zaretskii wrote:
> > From: Aaron Jensen <[email-masked]>
> > Date: Wed, 31 Oct 2018 23:51:42 -0700
> > Cc: Alan Third <[email-masked]>, [email-masked]
> > 
> > getting set causes erase_phys_cursor to get called, which ultimately
> > calls draw_phys_cursor_glyph, which calls draw_glyphs, which I believe
> > is what is blanking the line. It appears to be more than just
> > redrawing the glyph under the cursor.
> > 
> > Another clue is that it appear to only blank from where the cursor is
> > to the end of the line. Anything before that isn’t cleared.
> 
> Can you find the reason for that?  In general, redrawing the cursor
> should only redraw a single character, and sometimes the two adjacent
> ones.  It shouldn't redraw more than that.
> 
> From what you describe, it sounds like the problem is in the logic
> that determines which parts to redraw, see update_text_area in
> dispnew.c.

I’ve done some digging, and I’m pretty tired right now so apologies if
this makes no sense, but it looks as though when Emacs is clearing the
cursor it redraws the entire line that contains the cursor.

This is something being done to a line with the cursor on it:

New dirty rect:(X:10 Y:380)/(W:560 H:14)
Process 40552 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
    frame #0: 0x00000001003ae24a Emacs`ns_clip_to_rect(f=0x00000001030527b0, r=(origin = (x = 10, y = 380), size = (width = 560, height = 14)), n=1) at nsterm.m:1214
   1211	            {
   1212	              fprintf (stderr, "New dirty rect:" NSTRACE_FMT_RECT "\n",
   1213	                         NSTRACE_ARG_RECT(r[i]));
-> 1214	              [view setNeedsDisplayInRect:r[i]];
   1215	            }
   1216	        }
   1217	    }
Target 0: (Emacs) stopped.

I’m printing out the area that Emacs wants to draw (New dirty rect).
It has a width of 560, which is, I think, the full width of the text
area.

The interesting bit of the backtrace:

* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
  * frame #0: 0x00000001003ae24a Emacs`ns_clip_to_rect(f=0x00000001030527b0, r=(origin = (x = 10, y = 380), size = (width = 560, height = 14)), n=1) at nsterm.m:1214
    frame #1: 0x00000001003e29bf Emacs`ns_draw_glyph_string(s=0x00007ffeefbfbf10) at nsterm.m:4096
    frame #2: 0x000000010006f742 Emacs`draw_glyphs(w=0x0000000103051630, x=388, row=0x00000001030fb100, area=TEXT_AREA, start=53, end=54, hl=DRAW_NORMAL_TEXT, overlaps=0) at xdisp.c:26878
    frame #3: 0x0000000100070c2e Emacs`draw_phys_cursor_glyph(w=0x0000000103051630, row=0x00000001030fb100, hl=DRAW_NORMAL_TEXT) at xdisp.c:29434
    frame #4: 0x0000000100071369 Emacs`erase_phys_cursor(w=0x0000000103051630) at xdisp.c:29570
    frame #5: 0x0000000100071884 Emacs`display_and_set_cursor(w=0x0000000103051630, on=true, hpos=53, vpos=27, x=371, y=378) at xdisp.c:29659
    frame #6: 0x0000000100072243 Emacs`update_window_cursor(w=0x0000000103051630, on=true) at xdisp.c:29714
    frame #7: 0x000000010007204d Emacs`update_cursor_in_window_tree(w=0x0000000103051630, on_p=true) at xdisp.c:29732
    frame #8: 0x0000000100071fd2 Emacs`x_update_cursor(f=0x00000001030527b0, on_p=true) at xdisp.c:29746
    frame #9: 0x00000001003c3b0e Emacs`ns_frame_rehighlight(frame=0x00000001030527b0) at nsterm.m:1508
    frame #10: 0x00000001003c3607 Emacs`-[EmacsView windowDidBecomeKey](self=0x00000001022a8b20, _cmd="windowDidBecomeKey") at nsterm.m:7159
    frame #11: 0x00000001003c3428 Emacs`-[EmacsView windowDidBecomeKey:](self=0x00000001022a8b20, _cmd="windowDidBecomeKey:", notification=@"NSWindowDidBecomeKeyNotification") at nsterm.m:7145

You can see that ‘draw_glyphs’ is being called with start=53 and
end=54, which sounds, to me, like it’s wanting to draw one glyph: the
one under the cursor.

However when it gets round to asking to clip to an area
(ns_clip_to_rect) it’s wanting the entire row.

The parameter s in ns_draw_glyph_string looks right to me (i.e. x, y, width
and height look right for a single glyph):

(lldb) p *s
(glyph_string) $1 = {
  x = 381
  y = 380
  ybase = 391
  width = 7
  background_width = 7
  height = 14
  left_overhang = 0
  right_overhang = 0
  f = 0x00000001030527b0
  w = 0x0000000103051630
  display = 0x0000000000000000
  row = 0x00000001030fb100
  area = TEXT_AREA
  char2b = 0x00007ffeefbfbf00 u"'"
  nchars = 1
  hl = DRAW_NORMAL_TEXT
  face = 0x000000010229e130
  font = 0x000000010427c848
  cmp = 0x0000000000000000
  cmp_id = 0
  cmp_from = 0
  cmp_to = 0
  extends_to_end_of_line_p = false
  background_filled_p = false
  font_not_found_p = false
  stippled_p = false
  for_overlaps = 0
  padding_p = false
  first_glyph = 0x00000001030ed9f0
  img = 0x0000000000000000
  xwidget = 0x0000000000000000
  slice = (x = 0, y = 0, width = 0, height = 0)
  clip_head = 0x0000000000000000
  clip_tail = 0x0000000000000000
  clip = ([0] = (origin = (x = 0, y = 0), size = (width = 0, height = 0)), [1] = (origin = (x = 0, y = 0), size = (width = 0, height = 0)))
  num_clips = 0
  underline_position = 0
  underline_thickness = 0
  next = 0x0000000000000000
  prev = 0x0000000000000000
}

Here’s where it’s working out the clipping rectangle:

(lldb) f 1
frame #1: 0x00000001003e29bf Emacs`ns_draw_glyph_string(s=0x00007ffeefbfbf10) at nsterm.m:4096
   4093	    case CHAR_GLYPH:
   4094	    case COMPOSITE_GLYPH:
   4095	      n = ns_get_glyph_string_clip_rect (s, r);
-> 4096	      if (ns_clip_to_rect (s->f, r, n))
   4097	        {
   4098	          if (s->for_overlaps || (s->cmp_from > 0
   4099	                                  && ! s->first_glyph->u.cmp.automatic))

ns_get_glyph_string_clip_rect is a simple wrapper round
get_glyph_string_clip_rects, so when asked for the clipping rectangle
for a single glyph, it returns a rectangle covering the entire row.

Because we just mark it as dirty and come back to draw it later we do
end up redrawing the entire row.

I don’t know if this is a bug in get_glyph_string_clip_rects, or if
we’re misusing it here and should work out our own clipping rectangles.

I still don’t know why this results in the row being blanked out,
though.
-- 
Alan Third