]> git.eshelyaron.com Git - emacs.git/commitdiff
Implement double buffering on MS Windows
authorPo Lu <luangruo@yahoo.com>
Fri, 29 Apr 2022 03:33:41 +0000 (11:33 +0800)
committerPo Lu <luangruo@yahoo.com>
Sat, 30 Apr 2022 03:20:46 +0000 (11:20 +0800)
* etc/NEWS: Announce changes.
* src/w32fns.c (w32_set_inhibit_double_buffering): New function.
(w32_wnd_proc):
(Fx_create_frame):
(w32_create_tip_frame): Set `inhibit-double-buffering' parameter.
(w32_frame_parm_handlers): Add new handler.

* src/w32term.c (w32_show_back_buffer):
(w32_release_paint_buffer): New functions.
(w32_frame_up_to_date): Show back buffer if applicable.
(w32_buffer_flipping_unblocked_hook): New hook.
(w32_scroll_run): Use BitBlt to scroll instead of window
scrolling functions.
(w32_scroll_bar_clear): Don't clear scroll bars when double
buffered.
(w32_read_socket): Flip buffers after reading input events in
some cases.
(w32_free_frame_resources): Free back buffer.
(w32_create_terminal): Add new hook.

* src/w32term.h (struct w32_output): New fields for handling
back buffers.
* src/w32xfns.c (select_palette): Fix indentation.
(get_frame_dc, release_frame_dc): Return back buffer when
appropriate and set dirty flag.

etc/NEWS
src/w32fns.c
src/w32term.c
src/w32term.h
src/w32xfns.c

index c796f605b122f944d44d09f8bbe46ba2170997e9..5c2f152a127b329ebfb31720092773f165bb1636 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2146,6 +2146,13 @@ to preserve the old behavior, apply
 
 ** MS-Windows
 
+---
+*** Emacs now supports double buffering on MS Windows to reduce flicker.
+This leads to a noticable reduction in the amount of graphics flicker
+during redisplay on many systems, but can also make painting slower.
+If that happens, it can be disabled by setting the
+'inhibit-double-buffering' frame parameter.
+
 +++
 *** Emacs now supports system dark mode.
 On Windows 10 (version 1809 and higher) and Windows 11, Emacs will now
index a880136d0ac05818be1951531e960b4ade017525..d4e4b2a30bf4ff56b95513c7179ea2fe62a6c380 100644 (file)
@@ -1802,6 +1802,25 @@ w32_set_tool_bar_lines (struct frame *f, Lisp_Object value, Lisp_Object oldval)
   w32_change_tool_bar_height (f, nlines * FRAME_LINE_HEIGHT (f));
 }
 
+static void
+w32_set_inhibit_double_buffering (struct frame *f,
+                                 Lisp_Object new_value,
+                                 Lisp_Object old_value)
+{
+  block_input ();
+
+  if (NILP (new_value))
+    FRAME_OUTPUT_DATA (f)->want_paint_buffer = 1;
+  else
+    {
+      FRAME_OUTPUT_DATA (f)->want_paint_buffer = 0;
+      w32_release_paint_buffer (f);
+
+      SET_FRAME_GARBAGED (f);
+    }
+
+  unblock_input ();
+}
 
 /* Set the pixel height of the tool bar of frame F to HEIGHT.  */
 void
@@ -4093,7 +4112,9 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
     {
     case WM_ERASEBKGND:
       f = w32_window_to_frame (dpyinfo, hwnd);
-      if (f)
+
+      enter_crit ();
+      if (f && !FRAME_OUTPUT_DATA (f)->paint_buffer)
        {
          HDC hdc = get_frame_dc (f);
          GetUpdateRect (hwnd, &wmsg.rect, FALSE);
@@ -4107,6 +4128,7 @@ w32_wnd_proc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
                     wmsg.rect.right, wmsg.rect.bottom));
 #endif /* W32_DEBUG_DISPLAY */
        }
+      leave_crit ();
       return 1;
     case WM_PALETTECHANGED:
       /* ignore our own changes */
@@ -6080,6 +6102,10 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
                          ? make_fixnum (0) : make_fixnum (1),
                          NULL, NULL, RES_TYPE_NUMBER);
 
+  gui_default_parameter (f, parameters, Qinhibit_double_buffering, Qnil,
+                         "inhibitDoubleBuffering", "InhibitDoubleBuffering",
+                         RES_TYPE_BOOLEAN);
+
   gui_default_parameter (f, parameters, Qbuffer_predicate, Qnil,
                          "bufferPredicate", "BufferPredicate", RES_TYPE_SYMBOL);
   gui_default_parameter (f, parameters, Qtitle, Qnil,
@@ -7096,6 +7122,9 @@ w32_create_tip_frame (struct w32_display_info *dpyinfo, Lisp_Object parms)
                          "alpha", "Alpha", RES_TYPE_NUMBER);
   gui_default_parameter (f, parms, Qalpha_background, Qnil,
                          "alphaBackground", "AlphaBackground", RES_TYPE_NUMBER);
+  gui_default_parameter (f, parms, Qinhibit_double_buffering, Qnil,
+                         "inhibitDoubleBuffering", "InhibitDoubleBuffering",
+                         RES_TYPE_BOOLEAN);
 
   /* Add `tooltip' frame parameter's default value. */
   if (NILP (Fframe_parameter (frame, Qtooltip)))
@@ -10432,7 +10461,7 @@ frame_parm_handler w32_frame_parm_handlers[] =
   gui_set_alpha,
   0, /* x_set_sticky */
   0, /* x_set_tool_bar_position */
-  0, /* x_set_inhibit_double_buffering */
+  w32_set_inhibit_double_buffering,
   w32_set_undecorated,
   w32_set_parent_frame,
   w32_set_skip_taskbar,
index 7837032304c4d23cfc4f147719b0e202628e607b..ca96320a5ef8823164aedaddbbc7619df7fc18fb 100644 (file)
@@ -275,6 +275,57 @@ XGetGCValues (void *ignore, XGCValues *gc,
 }
 #endif
 
+static void
+w32_show_back_buffer (struct frame *f)
+{
+  struct w32_output *output;
+  HDC raw_dc;
+
+  output = FRAME_OUTPUT_DATA (f);
+
+  enter_crit ();
+
+  if (output->paint_buffer)
+    {
+      raw_dc = GetDC (output->window_desc);
+
+      if (!raw_dc)
+       emacs_abort ();
+
+      BitBlt (raw_dc, 0, 0, FRAME_PIXEL_WIDTH (f),
+             FRAME_PIXEL_HEIGHT (f),
+             output->paint_dc, 0, 0, SRCCOPY);
+      ReleaseDC (output->window_desc, raw_dc);
+
+      output->paint_buffer_dirty = 0;
+    }
+
+  leave_crit ();
+}
+
+void
+w32_release_paint_buffer (struct frame *f)
+{
+  /* Delete the back buffer so it gets created
+     again the next time we ask for the DC.  */
+
+  enter_crit ();
+  if (FRAME_OUTPUT_DATA (f)->paint_buffer)
+    {
+      SelectObject (FRAME_OUTPUT_DATA (f)->paint_dc,
+                   FRAME_OUTPUT_DATA (f)->paint_dc_object);
+      ReleaseDC (FRAME_OUTPUT_DATA (f)->window_desc,
+                FRAME_OUTPUT_DATA (f)->paint_buffer_handle);
+      DeleteDC (FRAME_OUTPUT_DATA (f)->paint_dc);
+      DeleteObject (FRAME_OUTPUT_DATA (f)->paint_buffer);
+
+      FRAME_OUTPUT_DATA (f)->paint_buffer = NULL;
+      FRAME_OUTPUT_DATA (f)->paint_dc = NULL;
+      FRAME_OUTPUT_DATA (f)->paint_buffer_handle = NULL;
+    }
+  leave_crit ();
+}
+
 static void
 w32_get_mouse_wheel_vertical_delta (void)
 {
@@ -704,10 +755,19 @@ w32_update_end (struct frame *f)
 static void
 w32_frame_up_to_date (struct frame *f)
 {
-  if (FRAME_W32_P (f))
-    FRAME_MOUSE_UPDATE (f);
+  FRAME_MOUSE_UPDATE (f);
+
+  if (!buffer_flipping_blocked_p ()
+      && FRAME_OUTPUT_DATA (f)->paint_buffer_dirty)
+    w32_show_back_buffer (f);
 }
 
+static void
+w32_buffer_flipping_unblocked_hook (struct frame *f)
+{
+  if (FRAME_OUTPUT_DATA (f)->paint_buffer_dirty)
+    w32_show_back_buffer (f);
+}
 
 /* Draw truncation mark bitmaps, continuation mark bitmaps, overlay
    arrow bitmaps, or clear the fringes if no bitmaps are required
@@ -2872,8 +2932,7 @@ w32_scroll_run (struct window *w, struct run *run)
 {
   struct frame *f = XFRAME (w->frame);
   int x, y, width, height, from_y, to_y, bottom_y;
-  HWND hwnd = FRAME_W32_WINDOW (f);
-  HRGN expect_dirty;
+  HDC hdc;
 
   /* Get frame-relative bounding box of the text display area of W,
      without mode lines.  Include in this box the left and right
@@ -2892,7 +2951,6 @@ w32_scroll_run (struct window *w, struct run *run)
        height = bottom_y - from_y;
       else
        height = run->height;
-      expect_dirty = CreateRectRgn (x, y + height, x + width, bottom_y);
     }
   else
     {
@@ -2902,44 +2960,15 @@ w32_scroll_run (struct window *w, struct run *run)
        height = bottom_y - to_y;
       else
        height = run->height;
-      expect_dirty = CreateRectRgn (x, y, x + width, to_y);
     }
 
   block_input ();
-
   /* Cursor off.  Will be switched on again in gui_update_window_end.  */
   gui_clear_cursor (w);
-
-  {
-    RECT from;
-    RECT to;
-    HRGN dirty = CreateRectRgn (0, 0, 0, 0);
-    HRGN combined = CreateRectRgn (0, 0, 0, 0);
-
-    from.left = to.left = x;
-    from.right = to.right = x + width;
-    from.top = from_y;
-    from.bottom = from_y + height;
-    to.top = y;
-    to.bottom = bottom_y;
-
-    ScrollWindowEx (hwnd, 0, to_y - from_y, &from, &to, dirty,
-                   NULL, SW_INVALIDATE);
-
-    /* Combine this with what we expect to be dirty. This covers the
-       case where not all of the region we expect is actually dirty.  */
-    CombineRgn (combined, dirty, expect_dirty, RGN_OR);
-
-    /* If the dirty region is not what we expected, redraw the entire frame.  */
-    if (!EqualRgn (combined, expect_dirty))
-      SET_FRAME_GARBAGED (f);
-
-    DeleteObject (dirty);
-    DeleteObject (combined);
-  }
-
+  hdc = get_frame_dc (f);
+  BitBlt (hdc, x, to_y, width, height, hdc, x, from_y, SRCCOPY);
+  release_frame_dc (f, hdc);
   unblock_input ();
-  DeleteObject (expect_dirty);
 }
 
 
@@ -4809,6 +4838,9 @@ w32_scroll_bar_clear (struct frame *f)
 {
   Lisp_Object bar;
 
+  if (FRAME_OUTPUT_DATA (f)->paint_buffer)
+    return;
+
   /* We can have scroll bars even if this is 0,
      if we just turned off scroll bar mode.
      But in that case we should not clear them.  */
@@ -4928,6 +4960,8 @@ w32_read_socket (struct terminal *terminal,
       /*            w32_name_of_message (msg.msg.message), */
       /*            msg.msg.time)); */
 
+      f = NULL;
+
       EVENT_INIT (inev);
       inev.kind = NO_EVENT;
       inev.arg = Qnil;
@@ -4969,24 +5003,33 @@ w32_read_socket (struct terminal *terminal,
                }
              else
                {
-                 /* Erase background again for safety.  But don't do
-                    that if the frame's 'garbaged' flag is set, since
-                    in that case expose_frame will do nothing, and if
-                    the various redisplay flags happen to be unset,
-                    we are left with a blank frame.  */
-                 if (!FRAME_GARBAGED_P (f) || FRAME_PARENT_FRAME (f))
+                 enter_crit ();
+                 if (!FRAME_OUTPUT_DATA (f)->paint_buffer)
                    {
-                     HDC hdc = get_frame_dc (f);
-
-                     w32_clear_rect (f, hdc, &msg.rect);
-                     release_frame_dc (f, hdc);
+                     /* Erase background again for safety.  But don't do
+                        that if the frame's 'garbaged' flag is set, since
+                        in that case expose_frame will do nothing, and if
+                        the various redisplay flags happen to be unset,
+                        we are left with a blank frame.  */
+
+                     if (!FRAME_GARBAGED_P (f) || FRAME_PARENT_FRAME (f))
+                       {
+                         HDC hdc = get_frame_dc (f);
+
+                         w32_clear_rect (f, hdc, &msg.rect);
+                         release_frame_dc (f, hdc);
+                       }
+
+                     expose_frame (f,
+                                   msg.rect.left,
+                                   msg.rect.top,
+                                   msg.rect.right - msg.rect.left,
+                                   msg.rect.bottom - msg.rect.top);
+                     w32_clear_under_internal_border (f);
                    }
-                 expose_frame (f,
-                               msg.rect.left,
-                               msg.rect.top,
-                               msg.rect.right - msg.rect.left,
-                               msg.rect.bottom - msg.rect.top);
-                 w32_clear_under_internal_border (f);
+                 else
+                   w32_show_back_buffer (f);
+                 leave_crit ();
                }
            }
          break;
@@ -5659,6 +5702,8 @@ w32_read_socket (struct terminal *terminal,
                  if (width != FRAME_PIXEL_WIDTH (f)
                      || height != FRAME_PIXEL_HEIGHT (f))
                    {
+                     w32_release_paint_buffer (f);
+
                      change_frame_size
                        (f, width, height, false, true, false);
                      SET_FRAME_GARBAGED (f);
@@ -5840,6 +5885,17 @@ w32_read_socket (struct terminal *terminal,
            }
          count++;
        }
+
+      /* Event processing might have drawn to F outside redisplay.  If
+         that is the case, flush any changes that have been made to
+         the front buffer.  */
+
+      if (f && FRAME_OUTPUT_DATA (f)->paint_buffer_dirty
+         /* WM_WINDOWPOSCHANGED makes the buffer dirty, but there's
+            no reason to flush it here, and that also causes
+            flicker.  */
+         && !f->garbaged && msg.msg.message != WM_WINDOWPOSCHANGED)
+       w32_show_back_buffer (f);
     }
 
   /* If the focus was just given to an autoraising frame,
@@ -7054,6 +7110,9 @@ w32_free_frame_resources (struct frame *f)
      face.  */
   free_frame_faces (f);
 
+  /* Now release the back buffer if any exists.  */
+  w32_release_paint_buffer (f);
+
   if (FRAME_W32_WINDOW (f))
     my_destroy_window (f, FRAME_W32_WINDOW (f));
 
@@ -7350,6 +7409,7 @@ w32_create_terminal (struct w32_display_info *dpyinfo)
   terminal->update_end_hook = w32_update_end;
   terminal->read_socket_hook = w32_read_socket;
   terminal->frame_up_to_date_hook = w32_frame_up_to_date;
+  terminal->buffer_flipping_unblocked_hook = w32_buffer_flipping_unblocked_hook;
   terminal->defined_color_hook = w32_defined_color;
   terminal->query_frame_background_color = w32_query_frame_background_color;
   terminal->query_colors = w32_query_colors;
@@ -7505,6 +7565,7 @@ w32_delete_display (struct w32_display_info *dpyinfo)
     if (dpyinfo->palette)
       DeleteObject (dpyinfo->palette);
   }
+
   w32_reset_fringes ();
 }
 
index 6c48323651f5da29c1e95b5e9f6a91fd126e5e4a..2dcc43fc59f71637cf6fb823a67e8a6100028114 100644 (file)
@@ -412,6 +412,27 @@ struct w32_output
      geometry when 'fullscreen' is reset to nil.  */
   WINDOWPLACEMENT normal_placement;
   int prev_fsmode;
+
+  /* The back buffer if there is an ongoing double-buffered drawing
+     operation.  */
+  HBITMAP paint_buffer;
+
+  /* The handle of the back buffer and a DC that ought to be released
+     alongside the back buffer.  */
+  HDC paint_dc, paint_buffer_handle;
+
+  /* The object previously selected into `paint_dc'.  */
+  HGDIOBJ paint_dc_object;
+
+  /* The width and height of `paint_buffer'.  */
+  int paint_buffer_width, paint_buffer_height;
+
+  /* Whether or not some painting was done to this window that has not
+     yet been drawn.  */
+  unsigned paint_buffer_dirty : 1;
+
+  /* Whether or not this frame should be double buffered.  */
+  unsigned want_paint_buffer : 1;
 };
 
 extern struct w32_output w32term_display;
@@ -876,6 +897,7 @@ typedef char guichar_t;
 
 extern Lisp_Object w32_popup_dialog (struct frame *, Lisp_Object, Lisp_Object);
 extern void w32_arrow_cursor (void);
+extern void w32_release_paint_buffer (struct frame *);
 
 extern void syms_of_w32term (void);
 extern void syms_of_w32menu (void);
index d5974b906e87cf390e087ad2b2418d1c083ebaa6..139985c5bdbb14c38ebf1859afcb344571eea3e7 100644 (file)
@@ -136,13 +136,13 @@ select_palette (struct frame *f, HDC hdc)
     f->output_data.w32->old_palette = NULL;
 
   if (RealizePalette (hdc) != GDI_ERROR)
-  {
-    Lisp_Object frame, framelist;
-    FOR_EACH_FRAME (framelist, frame)
     {
-      SET_FRAME_GARBAGED (XFRAME (frame));
+      Lisp_Object frame, framelist;
+      FOR_EACH_FRAME (framelist, frame)
+       {
+         SET_FRAME_GARBAGED (XFRAME (frame));
+       }
     }
-  }
 }
 
 void
@@ -157,19 +157,68 @@ deselect_palette (struct frame *f, HDC hdc)
 HDC
 get_frame_dc (struct frame *f)
 {
-  HDC hdc;
+  HDC hdc, paint_dc;
+  HBITMAP back_buffer;
+  HGDIOBJ obj;
+  struct w32_output *output;
 
   if (f->output_method != output_w32)
     emacs_abort ();
 
   enter_crit ();
+  output = FRAME_OUTPUT_DATA (f);
+
+  if (output->paint_dc)
+    {
+      if (output->paint_buffer_width != FRAME_PIXEL_WIDTH (f)
+         || output->paint_buffer_height != FRAME_PIXEL_HEIGHT (f))
+       w32_release_paint_buffer (f);
+      else
+       {
+         output->paint_buffer_dirty = 1;
+         return output->paint_dc;
+       }
+    }
 
-  hdc = GetDC (f->output_data.w32->window_desc);
+  hdc = GetDC (output->window_desc);
 
   /* If this gets called during startup before the frame is valid,
      there is a chance of corrupting random data or crashing. */
   if (hdc)
-    select_palette (f, hdc);
+    {
+      select_palette (f, hdc);
+
+      if (FRAME_OUTPUT_DATA (f)->want_paint_buffer)
+       {
+         back_buffer
+           = CreateCompatibleBitmap (hdc, FRAME_PIXEL_WIDTH (f),
+                                     FRAME_PIXEL_HEIGHT (f));
+
+         if (back_buffer)
+           {
+             paint_dc = CreateCompatibleDC (hdc);
+
+             if (!paint_dc)
+               DeleteObject (back_buffer);
+             else
+               {
+                 obj = SelectObject (paint_dc, back_buffer);
+
+                 output->paint_dc_object = obj;
+                 output->paint_dc = paint_dc;
+                 output->paint_buffer_handle = hdc;
+                 output->paint_buffer = back_buffer;
+                 output->paint_buffer_width = FRAME_PIXEL_WIDTH (f);
+                 output->paint_buffer_height = FRAME_PIXEL_HEIGHT (f);
+                 output->paint_buffer_dirty = 1;
+
+                 SET_FRAME_GARBAGED (f);
+
+                 return paint_dc;
+               }
+           }
+       }
+    }
 
   return hdc;
 }
@@ -179,8 +228,15 @@ release_frame_dc (struct frame *f, HDC hdc)
 {
   int ret;
 
-  deselect_palette (f, hdc);
-  ret = ReleaseDC (f->output_data.w32->window_desc, hdc);
+  /* Avoid releasing the double-buffered DC here, since it'll be
+     released upon the next buffer flip instead.  */
+  if (hdc != FRAME_OUTPUT_DATA (f)->paint_dc)
+    {
+      deselect_palette (f, hdc);
+      ret = ReleaseDC (f->output_data.w32->window_desc, hdc);
+    }
+  else
+    ret = 0;
 
   leave_crit ();