? metacity-caching.patch
Index: frames.c
===================================================================
RCS file: /cvs/gnome/metacity/src/frames.c,v
retrieving revision 1.75
diff -u -p -r1.75 frames.c
--- frames.c	24 Nov 2003 18:09:47 -0000	1.75
+++ frames.c	6 Jul 2004 12:50:55 -0000
@@ -62,7 +62,9 @@ static gboolean meta_frames_leave_notify
 static void meta_frames_paint_to_drawable (MetaFrames   *frames,
                                            MetaUIFrame  *frame,
                                            GdkDrawable  *drawable,
-                                           GdkRegion    *region);
+                                           GdkRegion    *region,
+					   int           x_offset,
+					   int           y_offset);
 
 static void meta_frames_calc_geometry (MetaFrames        *frames,
                                        MetaUIFrame         *frame,
@@ -257,6 +259,91 @@ meta_frames_finalize (GObject *object)
   G_OBJECT_CLASS (parent_class)->finalize (object);
 }
 
+static int invalidate_cache_timeout_id = 0;
+static GList *invalidate_frames = NULL;
+static GHashTable *cache;
+
+typedef struct
+{
+  GdkPixmap *top;
+  gint top_x, top_y;
+  
+  GdkPixmap *left;
+  gint left_x, left_y;
+  
+  GdkPixmap *right;
+  gint right_y, right_x;
+  
+  GdkPixmap *bottom;
+  gint bottom_x, bottom_y;
+  
+} CachedPixels;
+
+static CachedPixels *
+get_cache (MetaUIFrame *frame)
+{
+  CachedPixels *pixels;
+  
+  if (!cache)
+    cache = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free);
+
+  pixels = g_hash_table_lookup (cache, frame);
+
+  if (!pixels)
+    {
+      pixels = g_new0 (CachedPixels, 1);
+      g_hash_table_insert (cache, frame, pixels);
+    }
+
+  return pixels;
+}
+
+static void
+invalidate_cache (MetaUIFrame *frame)
+{
+  CachedPixels *pixels = get_cache (frame);
+  
+  if (pixels->top)
+    g_object_unref (pixels->top);
+  if (pixels->left)
+    g_object_unref (pixels->left);
+  if (pixels->right)
+    g_object_unref (pixels->right);
+  if (pixels->bottom)
+    g_object_unref (pixels->bottom);
+  
+  pixels->top = NULL;
+  pixels->bottom = NULL;
+  pixels->right = NULL;
+  pixels->left = NULL;
+
+  g_hash_table_remove (cache, frame);
+}
+
+static void
+invalidate_all_caches (void)
+{
+  GList *l;
+
+  for (l = invalidate_frames; l; l = l->next)
+    {
+      MetaUIFrame *frame = l->data;
+
+      invalidate_cache (frame);
+    }
+  
+  g_list_free (invalidate_frames);
+  invalidate_frames = NULL;
+}
+
+static gboolean
+invalidate_cache_timeout (gpointer data)
+{
+  invalidate_all_caches ();
+  invalidate_cache_timeout_id = 0;
+  return FALSE;
+}
+
 static void
 queue_recalc_func (gpointer key, gpointer value, gpointer data)
 {
@@ -273,6 +360,7 @@ queue_recalc_func (gpointer key, gpointe
   gtk_style_set_background (GTK_WIDGET (frames)->style,
                             frame->window, GTK_STATE_NORMAL);
   gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+  invalidate_cache (frame);
   meta_core_queue_frame_resize (gdk_display,
                                 frame->xwindow);
   if (frame->layout)
@@ -318,6 +406,7 @@ queue_draw_func (gpointer key, gpointer 
   gtk_style_set_background (GTK_WIDGET (frames)->style,
                             frame->window, GTK_STATE_NORMAL);
   gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+  invalidate_cache (frame);
 }
 
 static void
@@ -508,6 +597,11 @@ meta_frames_unmanage_window (MetaFrames 
 
   if (frame)
     {
+      /* invalidating all caches ensures the frame
+       * is not actually referenced anymore
+       */
+      invalidate_all_caches();
+      
       /* restore the cursor */
       meta_core_set_screen_cursor (gdk_display,
 				   frame->xwindow,
@@ -918,14 +1012,18 @@ meta_frames_move_resize_frame (MetaFrame
 			       int         height)
 {
   MetaUIFrame *frame = meta_frames_lookup_window (frames, xwindow);
-  int old_width, old_height;
+  int old_x, old_y, old_width, old_height;
   
   gdk_drawable_get_size (frame->window, &old_width, &old_height);
+  gdk_window_get_position (frame->window, &old_x, &old_y);
 
   gdk_window_move_resize (frame->window, x, y, width, height);
 
   if (old_width != width || old_height != height)
-    gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+    {
+      gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+      invalidate_cache (frame);
+    }
 }
 
 void
@@ -940,6 +1038,7 @@ meta_frames_queue_draw (MetaFrames *fram
   frame = meta_frames_lookup_window (frames, xwindow);
 
   gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+  invalidate_cache (frame);
 }
 
 void
@@ -966,6 +1065,7 @@ meta_frames_set_title (MetaFrames *frame
     }
   
   gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+  invalidate_cache (frame);
 }
 
 void
@@ -981,7 +1081,10 @@ meta_frames_repaint_frame (MetaFrames *f
 
   g_assert (frame);
 
-  gdk_window_process_updates (frame->window, TRUE);
+  /* repaint everything, so the other frame don't
+   * lag behind if they are exposed
+   */
+  gdk_window_process_all_updates ();
 }
 
 static void
@@ -1120,6 +1223,7 @@ redraw_control (MetaFrames *frames,
   rect = control_rect (control, &fgeom);
 
   gdk_window_invalidate_rect (frame->window, rect, FALSE);
+  invalidate_cache (frame);
 }
 
 static gboolean
@@ -1685,12 +1789,217 @@ meta_frames_destroy_event           (Gtk
   return TRUE;
 }
 
+/* Cut and paste from GDK */
+static GdkGC *
+get_bg_gc (GdkWindow *window, int x_offset, int y_offset)
+{
+  GdkWindowObject *private = (GdkWindowObject *)window;
+  guint gc_mask = 0;
+  GdkGCValues gc_values;
+
+  if (private->bg_pixmap == GDK_PARENT_RELATIVE_BG && private->parent)
+    {
+      return get_bg_gc (GDK_WINDOW (private->parent),
+			x_offset + private->x,
+			y_offset + private->y);
+    }
+  else if (private->bg_pixmap && 
+           private->bg_pixmap != GDK_PARENT_RELATIVE_BG && 
+           private->bg_pixmap != GDK_NO_BG)
+    {
+      gc_values.fill = GDK_TILED;
+      gc_values.tile = private->bg_pixmap;
+      gc_values.ts_x_origin = x_offset;
+      gc_values.ts_y_origin = y_offset;
+      
+      gc_mask = GDK_GC_FILL | GDK_GC_TILE | GDK_GC_TS_X_ORIGIN | GDK_GC_TS_Y_ORIGIN;
+
+      return gdk_gc_new_with_values (window, &gc_values, gc_mask);
+    }
+  else
+    {
+      GdkGC *gc = gdk_gc_new (window);
+
+      gdk_gc_set_foreground (gc, &(private->bg_color));
+
+      return gc;
+    }
+}
+
+static void
+clear_backing (GdkPixmap *pixmap,
+	       GdkWindow *window,
+	       int xoffset, int yoffset)
+{
+  GdkGC *tmp_gc = get_bg_gc (window, xoffset, yoffset);
+
+  gdk_draw_rectangle (pixmap, tmp_gc, TRUE,
+		      0, 0, -1, -1);
+  
+  g_object_unref (tmp_gc);
+}
+
+static GdkPixmap *
+generate_pixmap (MetaFrames *frames,
+		 MetaUIFrame *frame,
+		 int x,
+		 int y,
+		 int width,
+		 int height)
+{
+  GdkRectangle rectangle;
+  GdkRegion *region;
+  GdkPixmap *result;
+
+  rectangle.x = x;
+  rectangle.y = y;
+  rectangle.width = MAX (width, 1);
+  rectangle.height = MAX (height, 1);
+  
+  result = gdk_pixmap_new (frame->window, rectangle.width, rectangle.height, -1);
+  
+  clear_backing (result, frame->window, rectangle.x, rectangle.y);
+
+  region = gdk_region_rectangle (&rectangle);
+
+  meta_frames_paint_to_drawable (frames, frame, result, region,
+				 - rectangle.x, - rectangle.y);
+
+  gdk_region_destroy (region);
+
+  return result;
+}
+
+static void
+populate_cache (MetaFrames *frames,
+		MetaUIFrame *frame)
+{
+  int top, bottom, left, right;
+  int width, height;
+  int frame_width, frame_height, screen_width, screen_height;
+  CachedPixels *pixels;
+
+  meta_core_get_frame_extents (gdk_display,
+                               frame->xwindow,
+                               NULL, NULL,
+                               &frame_width, &frame_height);
+
+  meta_core_get_screen_size (gdk_display,
+                             frame->xwindow,
+                             &screen_width, &screen_height);
+
+  /* don't cache extremely large windows */
+  if (frame_width > 2 * screen_width ||
+      frame_height > 2 * screen_height)
+    {
+      return;
+    }
+  
+  meta_theme_get_frame_borders (meta_theme_get_current (),
+                                meta_core_get_frame_type (gdk_display, frame->xwindow),
+				frame->text_height,
+				meta_core_get_frame_flags (gdk_display, frame->xwindow),
+                                &top, &bottom, &left, &right);
+
+  meta_core_get_client_size (gdk_display, frame->xwindow, &width, &height);
+
+  pixels = get_cache (frame);
+  
+  if (!pixels->top)
+    {
+      pixels->top = generate_pixmap (frames, frame,
+				    0, 0, left + width + height, top);
+      pixels->top_x = 0;
+      pixels->top_y = 0;
+    }
+  
+  if (!pixels->left)
+    {
+      pixels->left = generate_pixmap (frames, frame,
+				     0, top, left, height);
+      pixels->left_x = 0;
+      pixels->left_y = top;
+    }
+  
+  if (!pixels->right)
+    {
+      pixels->right = generate_pixmap (frames, frame,
+				      left + width, top, right, height);
+      pixels->right_x = left + width;
+      pixels->right_y = top;
+    }
+  
+  if (!pixels->bottom)
+    {
+      pixels->bottom = generate_pixmap (frames, frame,
+				       0, top + height, left + width + height, bottom);
+      pixels->bottom_x = 0;
+      pixels->bottom_y = top + height;
+    }
+  
+  if (invalidate_cache_timeout_id)
+    g_source_remove (invalidate_cache_timeout_id);
+  
+  invalidate_cache_timeout_id = g_timeout_add (1000, invalidate_cache_timeout, NULL);
+
+  if (!g_list_find (invalidate_frames, frame))
+    invalidate_frames = g_list_prepend (invalidate_frames, frame);
+}
+
+static void
+clip_to_screen (GdkRegion *region, MetaUIFrame *frame)
+{
+  GdkRectangle frame_area;
+  GdkRectangle screen_area = { 0, 0, 0, 0 };
+  GdkRegion *tmp_region;
+  
+  /* Chop off stuff outside the screen; this optimization
+   * is crucial to handle huge client windows,
+   * like "xterm -geometry 1000x1000"
+   */
+  meta_core_get_frame_extents (gdk_display,
+                               frame->xwindow,
+                               &frame_area.x, &frame_area.y,
+                               &frame_area.width, &frame_area.height);
+
+  meta_core_get_screen_size (gdk_display,
+                             frame->xwindow,
+                             &screen_area.width, &screen_area.height);
+
+  gdk_region_offset (region, frame_area.x, frame_area.y);
+
+  tmp_region = gdk_region_rectangle (&frame_area);
+  gdk_region_intersect (region, tmp_region);
+  gdk_region_destroy (tmp_region);
+
+  gdk_region_offset (region, - frame_area.x, - frame_area.y);
+}
+
+static void
+subtract_from_region (GdkRegion *region, GdkDrawable *drawable,
+		      gint x, gint y)
+{
+  GdkRectangle rect;
+  GdkRegion *reg_rect;
+
+  gdk_drawable_get_size (drawable, &rect.width, &rect.height);
+  rect.x = x;
+  rect.y = y;
+
+  reg_rect = gdk_region_rectangle (&rect);
+  gdk_region_subtract (region, reg_rect);
+  gdk_region_destroy (reg_rect);
+}
+
 static gboolean
 meta_frames_expose_event (GtkWidget           *widget,
                           GdkEventExpose      *event)
 {
   MetaUIFrame *frame;
   MetaFrames *frames;
+  GdkGC *gc;
+  GdkRegion *region;
+  CachedPixels *pixels;
 
   frames = META_FRAMES (widget);
 
@@ -1705,8 +2014,57 @@ meta_frames_expose_event (GtkWidget     
       return TRUE;
     }
 
-  meta_frames_paint_to_drawable (frames, frame, frame->window, event->region);
+  populate_cache (frames, frame);
+
+  region = gdk_region_copy (event->region);
+  
+  gc = gdk_gc_new (frame->window);
+
+  pixels = get_cache (frame);
+  
+  if (pixels->top)
+    {
+      gdk_draw_drawable (frame->window, gc, pixels->top,
+			 0, 0,
+			 pixels->top_x, pixels->top_y,
+			 -1, -1);
+      subtract_from_region (region, pixels->top, pixels->top_x, pixels->top_y);
+    }
+
+  if (pixels->left)
+    {
+      gdk_draw_drawable (frame->window, gc, pixels->left,
+			 0, 0,
+			 pixels->left_x, pixels->left_y,
+			 -1, -1);
+      subtract_from_region (region, pixels->left, pixels->left_x, pixels->left_y);
+    }
+
+  if (pixels->right)
+    {
+      gdk_draw_drawable (frame->window, gc, pixels->right,
+			 0, 0,
+			 pixels->right_x, pixels->right_y,
+			 -1, -1);
+      subtract_from_region (region, pixels->right, pixels->right_x, pixels->right_y);
+    }
+  
+  if (pixels->bottom)
+    {
+      gdk_draw_drawable (frame->window, gc, pixels->bottom,
+			 0, 0,
+			 pixels->bottom_x, pixels->bottom_y,
+			 -1, -1);
+      subtract_from_region (region, pixels->bottom, pixels->bottom_x, pixels->bottom_y);
+    }
 
+  clip_to_screen (region, frame);
+  meta_frames_paint_to_drawable (frames, frame,
+				 frame->window, region, 0, 0);
+  
+  g_object_unref (gc);
+  gdk_region_destroy (region);
+  
   return TRUE;
 }
 
@@ -1714,7 +2072,9 @@ static void
 meta_frames_paint_to_drawable (MetaFrames   *frames,
                                MetaUIFrame  *frame,
                                GdkDrawable  *drawable,
-                               GdkRegion    *region)
+                               GdkRegion    *region,
+			       int           x_offset,
+			       int           y_offset)
 {
   GtkWidget *widget;
   MetaFrameFlags flags;
@@ -1731,7 +2091,6 @@ meta_frames_paint_to_drawable (MetaFrame
   GdkRectangle area;
   GdkRectangle *areas;
   int n_areas;
-  int screen_width, screen_height;
   MetaButtonLayout button_layout;
   MetaGrabOp grab_op;
   
@@ -1828,36 +2187,6 @@ meta_frames_paint_to_drawable (MetaFrame
   gdk_region_subtract (edges, tmp_region);
   gdk_region_destroy (tmp_region);
 
-  /* Chop off stuff outside the screen; this optimization
-   * is crucial to handle huge client windows,
-   * like "xterm -geometry 1000x1000"
-   */
-  meta_core_get_frame_extents (gdk_display,
-                               frame->xwindow,
-                               &area.x, &area.y,
-                               &area.width, &area.height);
-
-  meta_core_get_screen_size (gdk_display,
-                             frame->xwindow,
-                             &screen_width, &screen_height);
-
-  if ((area.x + area.width) > screen_width)
-    area.width = screen_width - area.x;
-  if (area.width < 0)
-    area.width = 0;
-  
-  if ((area.y + area.height) > screen_height)
-    area.height = screen_height - area.y;
-  if (area.height < 0)
-    area.height = 0;
-
-  area.x = 0; /* make relative to frame rather than screen */
-  area.y = 0;
-  
-  tmp_region = gdk_region_rectangle (&area);
-  gdk_region_intersect (edges, tmp_region);
-  gdk_region_destroy (tmp_region);
-
   /* Now draw remaining portion of region */
   gdk_region_get_rectangles (edges, &areas, &n_areas);
 
@@ -1872,8 +2201,8 @@ meta_frames_paint_to_drawable (MetaFrame
       meta_theme_draw_frame (meta_theme_get_current (),
                              widget,
                              drawable,
-                             &areas[i],
-                             0, 0,
+                             NULL, /* &areas[i], */
+                             x_offset, y_offset,
                              type,
                              flags,
                              w, h,
@@ -2136,6 +2465,7 @@ queue_pending_exposes_func (gpointer key
   if (frame->expose_delayed)
     {
       gdk_window_invalidate_rect (frame->window, NULL, FALSE);
+      invalidate_cache (frame);
       frame->expose_delayed = FALSE;
     }
 }
