gtkpixelcache.c 15.1 KB
Newer Older
Alexander Larsson's avatar
Alexander Larsson committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/* GTK - The GIMP Toolkit
 * Copyright (C) 2013 Red Hat, Inc.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

20
#include "gtkdebug.h"
21
#include "gtkprivate.h"
Alexander Larsson's avatar
Alexander Larsson committed
22
#include "gtkpixelcacheprivate.h"
23
#include "gtkrenderbackgroundprivate.h"
24
#include "gtkstylecontextprivate.h"
Alexander Larsson's avatar
Alexander Larsson committed
25

26 27
#define BLOW_CACHE_TIMEOUT_SEC 20

Alexander Larsson's avatar
Alexander Larsson committed
28 29
/* The extra size of the offscreen surface we allocate
   to make scrolling more efficient */
30
#define DEFAULT_EXTRA_SIZE 64
Alexander Larsson's avatar
Alexander Larsson committed
31

32
/* When resizing viewport we allow this extra
Alexander Larsson's avatar
Alexander Larsson committed
33
   size to avoid constantly reallocating when resizing */
34 35
#define ALLOW_SMALLER_SIZE_FACTOR 0.5
#define ALLOW_LARGER_SIZE_FACTOR  1.25
Alexander Larsson's avatar
Alexander Larsson committed
36 37 38

struct _GtkPixelCache {
  cairo_surface_t *surface;
39
  cairo_content_t content;
Alexander Larsson's avatar
Alexander Larsson committed
40 41 42 43 44 45

  /* Valid if surface != NULL */
  int surface_x;
  int surface_y;
  int surface_w;
  int surface_h;
46
  double surface_scale;
Alexander Larsson's avatar
Alexander Larsson committed
47 48 49

  /* may be null if not dirty */
  cairo_region_t *surface_dirty;
50

51
  GSource *timeout_source;
52 53 54

  guint extra_width;
  guint extra_height;
55 56

  guint always_cache : 1;
57
  guint is_opaque : 1;
Alexander Larsson's avatar
Alexander Larsson committed
58 59 60 61 62 63 64 65
};

GtkPixelCache *
_gtk_pixel_cache_new ()
{
  GtkPixelCache *cache;

  cache = g_new0 (GtkPixelCache, 1);
66 67
  cache->extra_width = DEFAULT_EXTRA_SIZE;
  cache->extra_height = DEFAULT_EXTRA_SIZE;
Alexander Larsson's avatar
Alexander Larsson committed
68 69 70 71 72 73 74 75 76 77

  return cache;
}

void
_gtk_pixel_cache_free (GtkPixelCache *cache)
{
  if (cache == NULL)
    return;

78
  if (cache->timeout_source ||
79 80 81
      cache->surface ||
      cache->surface_dirty)
    {
82
      g_warning ("pixel cache freed that wasn't unmapped: tag %u surface %p dirty %p",
83
                 g_source_get_id (cache->timeout_source), cache->surface, cache->surface_dirty);
84 85
    }

86 87 88
  g_clear_pointer (&cache->timeout_source, g_source_destroy);
  g_clear_pointer (&cache->surface, cairo_surface_destroy);
  g_clear_pointer (&cache->surface_dirty, cairo_region_destroy);
Alexander Larsson's avatar
Alexander Larsson committed
89 90 91 92

  g_free (cache);
}

93 94 95 96 97 98 99 100 101
void
_gtk_pixel_cache_set_extra_size (GtkPixelCache *cache,
                                 guint          extra_width,
                                 guint          extra_height)
{
  cache->extra_width = extra_width ? extra_width : DEFAULT_EXTRA_SIZE;
  cache->extra_height = extra_height ? extra_height : DEFAULT_EXTRA_SIZE;
}

102 103 104 105 106 107 108 109 110 111 112 113
void
_gtk_pixel_cache_get_extra_size (GtkPixelCache *cache,
                                 guint         *extra_width,
                                 guint         *extra_height)
{
  if (extra_width)
    *extra_width = cache->extra_width;

  if (extra_height)
    *extra_height = cache->extra_height;
}

114 115
void
_gtk_pixel_cache_set_content (GtkPixelCache   *cache,
116
                              cairo_content_t  content)
117 118 119 120 121
{
  cache->content = content;
  _gtk_pixel_cache_invalidate (cache, NULL);
}

Alexander Larsson's avatar
Alexander Larsson committed
122 123
/* Region is in canvas coordinates */
void
124 125
_gtk_pixel_cache_invalidate (GtkPixelCache  *cache,
                             cairo_region_t *region)
Alexander Larsson's avatar
Alexander Larsson committed
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
{
  cairo_rectangle_int_t r;
  cairo_region_t *free_region = NULL;

  if (cache->surface == NULL ||
      (region != NULL && cairo_region_is_empty (region)))
    return;

  if (region == NULL)
    {
      r.x = cache->surface_x;
      r.y = cache->surface_y;
      r.width = cache->surface_w;
      r.height = cache->surface_h;

      free_region = region =
142
        cairo_region_create_rectangle (&r);
Alexander Larsson's avatar
Alexander Larsson committed
143 144 145 146 147 148
    }

  if (cache->surface_dirty == NULL)
    {
      cache->surface_dirty = cairo_region_copy (region);
      cairo_region_translate (cache->surface_dirty,
149 150
                              -cache->surface_x,
                              -cache->surface_y);
Alexander Larsson's avatar
Alexander Larsson committed
151 152 153 154
    }
  else
    {
      cairo_region_translate (region,
155 156
                              -cache->surface_x,
                              -cache->surface_y);
Alexander Larsson's avatar
Alexander Larsson committed
157 158
      cairo_region_union (cache->surface_dirty, region);
      cairo_region_translate (region,
159 160
                              cache->surface_x,
                              cache->surface_y);
Alexander Larsson's avatar
Alexander Larsson committed
161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
    }

  if (free_region)
    cairo_region_destroy (free_region);

  r.x = 0;
  r.y = 0;
  r.width = cache->surface_w;
  r.height = cache->surface_h;

  cairo_region_intersect_rectangle (cache->surface_dirty, &r);
}

static void
_gtk_pixel_cache_create_surface_if_needed (GtkPixelCache         *cache,
176 177 178
                                           GdkWindow             *window,
                                           cairo_rectangle_int_t *view_rect,
                                           cairo_rectangle_int_t *canvas_rect)
Alexander Larsson's avatar
Alexander Larsson committed
179 180 181 182 183
{
  cairo_rectangle_int_t rect;
  int surface_w, surface_h;
  cairo_content_t content;

184
#ifdef G_ENABLE_DEBUG
185
  if (GTK_DISPLAY_DEBUG_CHECK (gdk_window_get_display (window), NO_PIXEL_CACHE))
186 187 188
    return;
#endif

189 190 191
  content = cache->content;
  if (!content)
    {
192
      if (cache->is_opaque)
193
        content = CAIRO_CONTENT_COLOR;
194 195
      else
        content = CAIRO_CONTENT_COLOR_ALPHA;
196
    }
Alexander Larsson's avatar
Alexander Larsson committed
197 198 199

  surface_w = view_rect->width;
  if (canvas_rect->width > surface_w)
200
    surface_w = MIN (surface_w + cache->extra_width, canvas_rect->width);
Alexander Larsson's avatar
Alexander Larsson committed
201 202 203

  surface_h = view_rect->height;
  if (canvas_rect->height > surface_h)
204
    surface_h = MIN (surface_h + cache->extra_height, canvas_rect->height);
Alexander Larsson's avatar
Alexander Larsson committed
205 206 207 208

  /* If current surface can't fit view_rect or is too large, kill it */
  if (cache->surface != NULL &&
      (cairo_surface_get_content (cache->surface) != content ||
209 210 211 212
       cache->surface_w < MAX(view_rect->width, surface_w * ALLOW_SMALLER_SIZE_FACTOR) ||
       cache->surface_w > surface_w * ALLOW_LARGER_SIZE_FACTOR ||
       cache->surface_h < MAX(view_rect->height, surface_h * ALLOW_SMALLER_SIZE_FACTOR) ||
       cache->surface_h > surface_h * ALLOW_LARGER_SIZE_FACTOR ||
213
       cache->surface_scale != gdk_window_get_scale_factor (window)))
Alexander Larsson's avatar
Alexander Larsson committed
214 215 216 217
    {
      cairo_surface_destroy (cache->surface);
      cache->surface = NULL;
      if (cache->surface_dirty)
218
        cairo_region_destroy (cache->surface_dirty);
Alexander Larsson's avatar
Alexander Larsson committed
219 220 221 222
      cache->surface_dirty = NULL;
    }

  /* Don't allocate a surface if view >= canvas, as we won't
223 224
   * be scrolling then anyway, unless the widget requested it.
   */
Alexander Larsson's avatar
Alexander Larsson committed
225
  if (cache->surface == NULL &&
226 227 228
      (cache->always_cache ||
       (view_rect->width < canvas_rect->width ||
        view_rect->height < canvas_rect->height)))
Alexander Larsson's avatar
Alexander Larsson committed
229 230 231 232 233
    {
      cache->surface_x = -canvas_rect->x;
      cache->surface_y = -canvas_rect->y;
      cache->surface_w = surface_w;
      cache->surface_h = surface_h;
234
      cache->surface_scale = gdk_window_get_scale_factor (window);
Alexander Larsson's avatar
Alexander Larsson committed
235 236

      cache->surface =
237 238
        gdk_window_create_similar_surface (window, content,
                                           surface_w, surface_h);
Alexander Larsson's avatar
Alexander Larsson committed
239 240 241 242 243
      rect.x = 0;
      rect.y = 0;
      rect.width = surface_w;
      rect.height = surface_h;
      cache->surface_dirty =
244
        cairo_region_create_rectangle (&rect);
Alexander Larsson's avatar
Alexander Larsson committed
245 246 247
    }
}

248
static void
Alexander Larsson's avatar
Alexander Larsson committed
249
_gtk_pixel_cache_set_position (GtkPixelCache         *cache,
250 251
                               cairo_rectangle_int_t *view_rect,
                               cairo_rectangle_int_t *canvas_rect)
Alexander Larsson's avatar
Alexander Larsson committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
{
  cairo_rectangle_int_t r, view_pos;
  cairo_region_t *copy_region;
  int new_surf_x, new_surf_y;
  cairo_t *backing_cr;

  if (cache->surface == NULL)
    return;

  /* Position of view inside canvas */
  view_pos.x = -canvas_rect->x;
  view_pos.y = -canvas_rect->y;
  view_pos.width = view_rect->width;
  view_pos.height = view_rect->height;

  /* Reposition so all is visible */
  if (view_pos.x < cache->surface_x ||
269
      view_pos.x + view_pos.width > cache->surface_x + cache->surface_w ||
Alexander Larsson's avatar
Alexander Larsson committed
270
      view_pos.y < cache->surface_y ||
271
      view_pos.y + view_pos.height > cache->surface_y + cache->surface_h)
Alexander Larsson's avatar
Alexander Larsson committed
272 273 274
    {
      new_surf_x = cache->surface_x;
      if (view_pos.x < cache->surface_x)
275
        new_surf_x = MAX (view_pos.x + view_pos.width - cache->surface_w, 0);
Alexander Larsson's avatar
Alexander Larsson committed
276
      else if (view_pos.x + view_pos.width >
277 278
               cache->surface_x + cache->surface_w)
        new_surf_x = MIN (view_pos.x, canvas_rect->width - cache->surface_w);
Alexander Larsson's avatar
Alexander Larsson committed
279 280 281

      new_surf_y = cache->surface_y;
      if (view_pos.y < cache->surface_y)
282
        new_surf_y = MAX (view_pos.y + view_pos.height - cache->surface_h, 0);
Alexander Larsson's avatar
Alexander Larsson committed
283
      else if (view_pos.y + view_pos.height >
284 285
               cache->surface_y + cache->surface_h)
        new_surf_y = MIN (view_pos.y, canvas_rect->height - cache->surface_h);
Alexander Larsson's avatar
Alexander Larsson committed
286 287 288 289 290 291 292 293

      r.x = 0;
      r.y = 0;
      r.width = cache->surface_w;
      r.height = cache->surface_h;
      copy_region = cairo_region_create_rectangle (&r);

      if (cache->surface_dirty)
294 295 296 297 298
        {
          cairo_region_subtract (copy_region, cache->surface_dirty);
          cairo_region_destroy (cache->surface_dirty);
          cache->surface_dirty = NULL;
        }
Alexander Larsson's avatar
Alexander Larsson committed
299 300

      cairo_region_translate (copy_region,
301 302
                              cache->surface_x - new_surf_x,
                              cache->surface_y - new_surf_y);
Alexander Larsson's avatar
Alexander Larsson committed
303 304 305 306 307 308 309 310
      cairo_region_intersect_rectangle (copy_region, &r);

      backing_cr = cairo_create (cache->surface);
      gdk_cairo_region (backing_cr, copy_region);
      cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
      cairo_clip (backing_cr);
      cairo_push_group (backing_cr);
      cairo_set_source_surface (backing_cr, cache->surface,
311 312
                                cache->surface_x - new_surf_x,
                                cache->surface_y - new_surf_y);
Alexander Larsson's avatar
Alexander Larsson committed
313 314 315 316 317 318 319 320 321 322 323 324 325
      cairo_paint (backing_cr);
      cairo_pop_group_to_source (backing_cr);
      cairo_paint (backing_cr);
      cairo_destroy (backing_cr);

      cache->surface_x = new_surf_x;
      cache->surface_y = new_surf_y;

      cairo_region_xor_rectangle (copy_region, &r);
      cache->surface_dirty = copy_region;
    }
}

326 327
static void
_gtk_pixel_cache_repaint (GtkPixelCache         *cache,
328
                          GdkWindow             *window,
329 330 331 332
                          GtkPixelCacheDrawFunc  draw,
                          cairo_rectangle_int_t *view_rect,
                          cairo_rectangle_int_t *canvas_rect,
                          gpointer               user_data)
Alexander Larsson's avatar
Alexander Larsson committed
333 334
{
  cairo_t *backing_cr;
335 336
  cairo_region_t *region_dirty = cache->surface_dirty;
  cache->surface_dirty = NULL;
Alexander Larsson's avatar
Alexander Larsson committed
337 338

  if (cache->surface &&
339 340
      region_dirty &&
      !cairo_region_is_empty (region_dirty))
Alexander Larsson's avatar
Alexander Larsson committed
341 342
    {
      backing_cr = cairo_create (cache->surface);
343
      gdk_cairo_region (backing_cr, region_dirty);
Alexander Larsson's avatar
Alexander Larsson committed
344 345
      cairo_clip (backing_cr);
      cairo_translate (backing_cr,
346 347
                       -cache->surface_x - canvas_rect->x - view_rect->x,
                       -cache->surface_y - canvas_rect->y - view_rect->y);
348 349

      cairo_save (backing_cr);
Alexander Larsson's avatar
Alexander Larsson committed
350
      cairo_set_source_rgba (backing_cr,
351
                             0.0, 0, 0, 0.0);
Alexander Larsson's avatar
Alexander Larsson committed
352 353
      cairo_set_operator (backing_cr, CAIRO_OPERATOR_SOURCE);
      cairo_paint (backing_cr);
354
      cairo_restore (backing_cr);
Alexander Larsson's avatar
Alexander Larsson committed
355

356
      cairo_save (backing_cr);
Alexander Larsson's avatar
Alexander Larsson committed
357
      draw (backing_cr, user_data);
358 359 360
      cairo_restore (backing_cr);

#ifdef G_ENABLE_DEBUG
361
      if (GTK_DISPLAY_DEBUG_CHECK (gdk_window_get_display (window), PIXEL_CACHE))
362 363 364 365 366 367 368 369 370 371 372 373 374 375
        {
          GdkRGBA colors[] = {
            { 1, 0, 0, 0.08},
            { 0, 1, 0, 0.08},
            { 0, 0, 1, 0.08},
            { 1, 0, 1, 0.08},
            { 1, 1, 0, 0.08},
            { 0, 1, 1, 0.08},
          };
          static int current_color = 0;

          gdk_cairo_set_source_rgba (backing_cr, &colors[(current_color++) % G_N_ELEMENTS (colors)]);
          cairo_paint (backing_cr);
        }
376
#endif
Alexander Larsson's avatar
Alexander Larsson committed
377 378 379 380

      cairo_destroy (backing_cr);
    }

381 382
  if (region_dirty)
    cairo_region_destroy (region_dirty);
Alexander Larsson's avatar
Alexander Larsson committed
383 384
}

385 386
static void
gtk_pixel_cache_blow_cache (GtkPixelCache *cache)
387
{
388 389 390
  g_clear_pointer (&cache->timeout_source, g_source_destroy);
  g_clear_pointer (&cache->surface, cairo_surface_destroy);
  g_clear_pointer (&cache->surface_dirty, cairo_region_destroy);
391 392 393 394 395 396 397
}

static gboolean
blow_cache_cb  (gpointer user_data)
{
  GtkPixelCache *cache = user_data;

398
  cache->timeout_source = NULL;
399 400

  gtk_pixel_cache_blow_cache (cache);
401 402 403 404

  return G_SOURCE_REMOVE;
}

405 406 407 408 409 410 411 412 413 414 415 416 417
static gboolean
context_is_unscaled (cairo_t *cr)
{
  cairo_matrix_t matrix;
  gdouble x, y;

  x = y = 1;
  cairo_get_matrix (cr, &matrix);
  cairo_matrix_transform_distance (&matrix, &x, &y);

  return x == 1 && y == 1;
}

418

Alexander Larsson's avatar
Alexander Larsson committed
419
void
420 421 422 423 424 425 426
_gtk_pixel_cache_draw (GtkPixelCache         *cache,
                       cairo_t               *cr,
                       GdkWindow             *window,
                       cairo_rectangle_int_t *view_rect,   /* View position in widget coords */
                       cairo_rectangle_int_t *canvas_rect, /* Size and position of canvas in view coords */
                       GtkPixelCacheDrawFunc  draw,
                       gpointer               user_data)
Alexander Larsson's avatar
Alexander Larsson committed
427
{
428 429 430
  if (cache->timeout_source)
    {
      gint64 deadline;
431

432 433 434 435 436 437 438 439 440 441 442
      deadline = g_get_monotonic_time () + (BLOW_CACHE_TIMEOUT_SEC * G_USEC_PER_SEC);
      g_source_set_ready_time (cache->timeout_source, deadline);
    }
  else
    {
      guint tag;

      tag = g_timeout_add_seconds (BLOW_CACHE_TIMEOUT_SEC, blow_cache_cb, cache);
      cache->timeout_source = g_main_context_find_source_by_id (NULL, tag);
      g_source_set_name (cache->timeout_source, "[gtk+] blow_cache_cb");
    }
443

Alexander Larsson's avatar
Alexander Larsson committed
444
  _gtk_pixel_cache_create_surface_if_needed (cache, window,
445
                                             view_rect, canvas_rect);
Alexander Larsson's avatar
Alexander Larsson committed
446
  _gtk_pixel_cache_set_position (cache, view_rect, canvas_rect);
447
  _gtk_pixel_cache_repaint (cache, window, draw, view_rect, canvas_rect, user_data);
Alexander Larsson's avatar
Alexander Larsson committed
448

449
  if (cache->surface && context_is_unscaled (cr) &&
Alexander Larsson's avatar
Alexander Larsson committed
450 451 452 453 454
      /* Don't use backing surface if rendering elsewhere */
      cairo_surface_get_type (cache->surface) == cairo_surface_get_type (cairo_get_target (cr)))
    {
      cairo_save (cr);
      cairo_set_source_surface (cr, cache->surface,
455 456
                                cache->surface_x + view_rect->x + canvas_rect->x,
                                cache->surface_y + view_rect->y + canvas_rect->y);
Alexander Larsson's avatar
Alexander Larsson committed
457
      cairo_rectangle (cr, view_rect->x, view_rect->y,
458
                       view_rect->width, view_rect->height);
Alexander Larsson's avatar
Alexander Larsson committed
459 460 461 462 463 464
      cairo_fill (cr);
      cairo_restore (cr);
    }
  else
    {
      cairo_rectangle (cr,
465 466
                       view_rect->x, view_rect->y,
                       view_rect->width, view_rect->height);
Alexander Larsson's avatar
Alexander Larsson committed
467 468 469 470
      cairo_clip (cr);
      draw (cr, user_data);
    }
}
471 472 473 474 475 476 477 478 479 480 481 482

void
_gtk_pixel_cache_map (GtkPixelCache *cache)
{
  _gtk_pixel_cache_invalidate (cache, NULL);
}

void
_gtk_pixel_cache_unmap (GtkPixelCache *cache)
{
  gtk_pixel_cache_blow_cache (cache);
}
483 484 485 486 487 488 489 490 491 492 493 494 495

gboolean
_gtk_pixel_cache_get_always_cache (GtkPixelCache *cache)
{
  return cache->always_cache;
}

void
_gtk_pixel_cache_set_always_cache (GtkPixelCache *cache,
                                   gboolean       always_cache)
{
  cache->always_cache = !!always_cache;
}
496 497

void
498 499
gtk_pixel_cache_set_is_opaque (GtkPixelCache *cache,
                               gboolean       is_opaque)
500
{
501 502 503 504 505
  if (cache->is_opaque == is_opaque)
    return;

  cache->is_opaque = is_opaque;
  _gtk_pixel_cache_invalidate (cache, NULL);
506
}