[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
master bc44455f77 1/3: Implement double buffering on MS Windows
From: |
Po Lu |
Subject: |
master bc44455f77 1/3: Implement double buffering on MS Windows |
Date: |
Sat, 30 Apr 2022 01:43:14 -0400 (EDT) |
branch: master
commit bc44455f778a256a861c8063e87a662a13f603e1
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>
Implement double buffering on MS Windows
* 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 | 7 +++
src/w32fns.c | 33 +++++++++++-
src/w32term.c | 167 +++++++++++++++++++++++++++++++++++++++-------------------
src/w32term.h | 22 ++++++++
src/w32xfns.c | 76 ++++++++++++++++++++++----
5 files changed, 240 insertions(+), 65 deletions(-)
diff --git a/etc/NEWS b/etc/NEWS
index c796f605b1..5c2f152a12 100644
--- 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
diff --git a/src/w32fns.c b/src/w32fns.c
index a880136d0a..d4e4b2a30b 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -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,
diff --git a/src/w32term.c b/src/w32term.c
index 7837032304..ca96320a5e 100644
--- a/src/w32term.c
+++ b/src/w32term.c
@@ -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 ();
}
diff --git a/src/w32term.h b/src/w32term.h
index 6c48323651..2dcc43fc59 100644
--- a/src/w32term.h
+++ b/src/w32term.h
@@ -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);
diff --git a/src/w32xfns.c b/src/w32xfns.c
index d5974b906e..139985c5bd 100644
--- a/src/w32xfns.c
+++ b/src/w32xfns.c
@@ -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 ();