From: Owen W. Taylor Date: Wed, 14 Nov 2012 17:23:41 +0000 (-0500) Subject: GdkWindowX11: Only start a frame when we emit damage X-Git-Url: http://pileus.org/git/?p=~andy%2Fgtk;a=commitdiff_plain;h=d761df7e0c73341a191b45ac5c30c44eaf31e305 GdkWindowX11: Only start a frame when we emit damage Instead of communicating the start of a frame to the window manager as soon as we begin a frame, start a frame only when we know we've actually created damage to the contents of a window. (This uses cairo_set_mime_data() as a notification mechanism - a clever suggestion from Uli Schlachter.) The advantage of this is that we aren't forcing the compositor to do a frame cycle and send _NET_WM_FRAME_DRAWN - depending on how the compositor is structured that might either cause it to do extra work or it might send _NET_WM_FRAME_DRAWN early and upset frame timing. https://bugzilla.gnome.org/show_bug.cgi?id=685460 --- diff --git a/gdk/x11/gdkwindow-x11.c b/gdk/x11/gdkwindow-x11.c index fbc13b565..f2c5735e7 100644 --- a/gdk/x11/gdkwindow-x11.c +++ b/gdk/x11/gdkwindow-x11.c @@ -213,6 +213,70 @@ set_sync_counter(Display *display, XSyncSetCounter(display, counter, sync_value); } +static void +window_pre_damage (GdkWindow *window) +{ + GdkWindow *toplevel_window = gdk_window_get_toplevel (window); + GdkWindowImplX11 *impl; + + if (!toplevel_window || !WINDOW_IS_TOPLEVEL (toplevel_window)) + return; + + impl = GDK_WINDOW_IMPL_X11 (toplevel_window->impl); + + if (impl->toplevel->in_frame && + impl->toplevel->current_counter_value % 2 == 0) + { + impl->toplevel->current_counter_value += 1; + set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper), + impl->toplevel->extended_update_counter, + impl->toplevel->current_counter_value); + } +} + +static void +on_surface_changed (void *data) +{ + GdkWindow *window = data; + + window_pre_damage (window); +} + +/* We want to know when cairo drawing causes damage to the window, + * so we engage in the _NET_WM_FRAME_DRAWN protocol with the + * window only when there actually is drawing. To do that we use + * a technique (hack) suggested by Uli Schlachter - if we set + * a dummy "mime data" on the cairo surface (this facility is + * used to attach JPEG data to an imager), then cairo wil flush + * and remove the mime data before making any changes to the window. + */ + +static void +hook_surface_changed (GdkWindow *window) +{ + GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl); + + if (impl->cairo_surface) + cairo_surface_set_mime_data (impl->cairo_surface, + "x-gdk/change-notify", + (unsigned char *)"X", + 1, + on_surface_changed, + window); +} + +static void +unhook_surface_changed (GdkWindow *window) +{ + GdkWindowImplX11 *impl = GDK_WINDOW_IMPL_X11 (window->impl); + + if (impl->cairo_surface) + cairo_surface_set_mime_data (impl->cairo_surface, + "x-gdk/change-notify", + NULL, 0, + NULL, NULL); +} + static void gdk_x11_window_begin_frame (GdkWindow *window) { @@ -226,13 +290,9 @@ gdk_x11_window_begin_frame (GdkWindow *window) impl->toplevel->extended_update_counter == None) return; - if (impl->toplevel->current_counter_value % 2 == 0) - { - impl->toplevel->current_counter_value += 1; - set_sync_counter(GDK_WINDOW_XDISPLAY (impl->wrapper), - impl->toplevel->extended_update_counter, - impl->toplevel->current_counter_value); - } + impl->toplevel->in_frame = TRUE; + + hook_surface_changed (window); } static void @@ -245,9 +305,12 @@ gdk_x11_window_end_frame (GdkWindow *window) impl = GDK_WINDOW_IMPL_X11 (window->impl); if (!WINDOW_IS_TOPLEVEL (window) || - impl->toplevel->extended_update_counter == None) + impl->toplevel->extended_update_counter == None || + !impl->toplevel->in_frame) return; + impl->toplevel->in_frame = FALSE; + if (impl->toplevel->current_counter_value % 2 == 1) { impl->toplevel->current_counter_value += 1; @@ -262,6 +325,8 @@ gdk_x11_window_end_frame (GdkWindow *window) gdk_frame_clock_freeze (gdk_window_get_frame_clock (window)); } } + + unhook_surface_changed (window); } /***************************************************** @@ -307,6 +372,9 @@ gdk_x11_ref_cairo_surface (GdkWindow *window) if (impl->cairo_surface) cairo_surface_set_user_data (impl->cairo_surface, &gdk_x11_cairo_key, impl, gdk_x11_cairo_surface_destroy); + + if (WINDOW_IS_TOPLEVEL (window) && impl->toplevel->in_frame) + hook_surface_changed (window); } else cairo_surface_reference (impl->cairo_surface); @@ -326,6 +394,9 @@ gdk_window_impl_x11_finalize (GObject *object) wrapper = impl->wrapper; + if (WINDOW_IS_TOPLEVEL (wrapper) && impl->toplevel->in_frame) + unhook_surface_changed (wrapper); + _gdk_x11_window_grab_check_destroy (wrapper); if (!GDK_WINDOW_DESTROYED (wrapper)) @@ -1569,6 +1640,8 @@ window_x11_move (GdkWindow *window, if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD) { + /* The window isn't actually damaged, but it's parent is */ + window_pre_damage (window); _gdk_x11_window_move_resize_child (window, x, y, window->width, window->height); @@ -1598,6 +1671,8 @@ window_x11_resize (GdkWindow *window, if (height < 1) height = 1; + window_pre_damage (window); + if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD) { _gdk_x11_window_move_resize_child (window, @@ -1641,6 +1716,8 @@ window_x11_move_resize (GdkWindow *window, if (height < 1) height = 1; + window_pre_damage (window); + if (GDK_WINDOW_TYPE (window) == GDK_WINDOW_CHILD) { _gdk_x11_window_move_resize_child (window, x, y, width, height); diff --git a/gdk/x11/gdkwindow-x11.h b/gdk/x11/gdkwindow-x11.h index c42365222..8dde33501 100644 --- a/gdk/x11/gdkwindow-x11.h +++ b/gdk/x11/gdkwindow-x11.h @@ -127,6 +127,8 @@ struct _GdkToplevelX11 */ guint have_focused : 1; + guint in_frame : 1; + /* If we're expecting a response from the compositor after painting a frame */ guint frame_pending : 1;