1 /*
2  * Clutter.
3  *
4  * An OpenGL based 'interactive canvas' library.
5  *
6  * Copyright (C) 2007,2008,2009,2010,2011  Intel Corporation.
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
20 
21  * Authors:
22  *  Matthew Allum
23  *  Robert Bragg
24  *  Neil Roberts
25  *  Emmanuele Bassi
26  */
27 
28 
29 #ifdef HAVE_CONFIG_H
30 #include "clutter-build-config.h"
31 #endif
32 
33 #define CLUTTER_ENABLE_EXPERIMENTAL_API
34 
35 #include "clutter-config.h"
36 
37 #include "clutter-stage-cogl.h"
38 
39 #include <stdlib.h>
40 #include <math.h>
41 
42 #include "clutter-actor-private.h"
43 #include "clutter-backend-private.h"
44 #include "clutter-debug.h"
45 #include "clutter-event.h"
46 #include "clutter-enum-types.h"
47 #include "clutter-feature.h"
48 #include "clutter-main.h"
49 #include "clutter-private.h"
50 #include "clutter-stage-private.h"
51 #include "clutter-muffin.h"
52 
53 typedef struct _ClutterStageViewCoglPrivate
54 {
55   /*
56    * List of previous damaged areas in stage view framebuffer coordinate space.
57    */
58 #define DAMAGE_HISTORY_MAX 16
59 #define DAMAGE_HISTORY(x) ((x) & (DAMAGE_HISTORY_MAX - 1))
60   cairo_rectangle_int_t damage_history[DAMAGE_HISTORY_MAX];
61   unsigned int damage_index;
62 } ClutterStageViewCoglPrivate;
63 
64 G_DEFINE_TYPE_WITH_PRIVATE (ClutterStageViewCogl, clutter_stage_view_cogl,
65                             CLUTTER_TYPE_STAGE_VIEW)
66 
67 static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
68 
69 G_DEFINE_TYPE_WITH_CODE (ClutterStageCogl,
70                          _clutter_stage_cogl,
71                          G_TYPE_OBJECT,
72                          G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW,
73                                                 clutter_stage_window_iface_init));
74 
75 enum {
76   PROP_0,
77   PROP_WRAPPER,
78   PROP_BACKEND,
79   PROP_LAST
80 };
81 
82 static void
clutter_stage_cogl_unrealize(ClutterStageWindow * stage_window)83 clutter_stage_cogl_unrealize (ClutterStageWindow *stage_window)
84 {
85   CLUTTER_NOTE (BACKEND, "Unrealizing Cogl stage [%p]", stage_window);
86 }
87 
88 void
_clutter_stage_cogl_presented(ClutterStageCogl * stage_cogl,CoglFrameEvent frame_event,ClutterFrameInfo * frame_info)89 _clutter_stage_cogl_presented (ClutterStageCogl *stage_cogl,
90                                CoglFrameEvent    frame_event,
91                                ClutterFrameInfo *frame_info)
92 {
93 
94   if (frame_event == COGL_FRAME_EVENT_SYNC)
95     {
96       /* Early versions of the swap_event implementation in Mesa
97        * deliver BufferSwapComplete event when not selected for,
98        * so if we get a swap event we aren't expecting, just ignore it.
99        *
100        * https://bugs.freedesktop.org/show_bug.cgi?id=27962
101        *
102        * FIXME: This issue can be hidden inside Cogl so we shouldn't
103        * need to care about this bug here.
104        */
105       if (stage_cogl->pending_swaps > 0)
106         stage_cogl->pending_swaps--;
107     }
108   else if (frame_event == COGL_FRAME_EVENT_COMPLETE)
109     {
110       gint64 presentation_time_cogl = frame_info->presentation_time;
111 
112       if (presentation_time_cogl != 0)
113         {
114           ClutterBackend *backend = stage_cogl->backend;
115           CoglContext *context = clutter_backend_get_cogl_context (backend);
116           gint64 current_time_cogl = cogl_get_clock_time (context);
117           gint64 now = g_get_monotonic_time ();
118 
119           stage_cogl->last_presentation_time =
120             now + (presentation_time_cogl - current_time_cogl) / 1000;
121         }
122 
123       stage_cogl->refresh_rate = frame_info->refresh_rate;
124     }
125 
126   _clutter_stage_presented (stage_cogl->wrapper, frame_event, frame_info);
127 }
128 
129 static gboolean
clutter_stage_cogl_realize(ClutterStageWindow * stage_window)130 clutter_stage_cogl_realize (ClutterStageWindow *stage_window)
131 {
132   ClutterBackend *backend;
133 
134   CLUTTER_NOTE (BACKEND, "Realizing stage '%s' [%p]",
135                 G_OBJECT_TYPE_NAME (stage_window),
136                 stage_window);
137 
138   backend = clutter_get_default_backend ();
139 
140   if (backend->cogl_context == NULL)
141     {
142       g_warning ("Failed to realize stage: missing Cogl context");
143       return FALSE;
144     }
145 
146   return TRUE;
147 }
148 
149 static void
clutter_stage_cogl_schedule_update(ClutterStageWindow * stage_window,gint sync_delay)150 clutter_stage_cogl_schedule_update (ClutterStageWindow *stage_window,
151                                     gint                sync_delay)
152 {
153   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
154   gint64 now;
155   gint64 target_presentation_time;
156   gint64 refresh_interval;
157   /*
158    * want_prerender_frames must be at least 1 (double buffered).
159    * Feel free to try triple buffering: want_prerender_frames = 2
160    * but it's not a good idea to default to that yet because filling all the
161    * buffers will cause the backend to block and throttle buffer releases even
162    * to unthrottled clients (swap interval 0).
163    */
164   gint want_prerender_frames = 1;
165   gint can_prerender_frames;
166   gint will_prerender_frames;
167   gint64 prerender_time;
168 
169   if (stage_cogl->update_time != -1)
170     return;
171 
172   now = g_get_monotonic_time ();
173 
174   if (sync_delay < 0 ||
175       stage_cogl->last_presentation_time <= 0 ||
176       stage_cogl->refresh_rate <= 0.0)
177     {
178       /* -1 now means that hardware presentation times are unsupported and
179        * that the caller needs to find a different way to achieve frame
180        * scheduling.
181        */
182       stage_cogl->update_time = -1;
183       return;
184     }
185 
186   /* FIXME (?) - On X11 this is performing worse than swap throttling. */
187 
188   if (cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION))
189     refresh_interval = (gint64) (0.5 + 1000000 / stage_cogl->refresh_rate);
190   else
191     refresh_interval = 0;
192 
193   target_presentation_time = stage_cogl->last_presentation_time
194                            + stage_cogl->pending_swaps * refresh_interval
195                            + refresh_interval;
196 
197   if (target_presentation_time < now)
198     {
199       /* If we missed some frames then find a more current target time that's
200        * in phase with the display hardware. This is logically equivalent to:
201        *
202        *   while (target_presentation_time < now)
203        *     target_presentation_time += refresh_interval;
204        *
205        * but with a better worst-case execution time...
206        */
207       target_presentation_time = now
208                                - now % refresh_interval
209                                + stage_cogl->last_presentation_time %
210                                  refresh_interval;
211 
212       if (target_presentation_time < now)
213         target_presentation_time += refresh_interval;
214     }
215 
216   can_prerender_frames = MAX (1, stage_cogl->max_buffer_age - 1);
217   will_prerender_frames = MIN (want_prerender_frames, can_prerender_frames);
218 
219   prerender_time = will_prerender_frames * refresh_interval
220                  - 1000 * sync_delay;
221 
222   stage_cogl->update_time = target_presentation_time - prerender_time;
223 
224   /* Are we repeating ourselves? If a clear and reschedule occur too close
225    * together while a swap is pending then we will often land on the same
226    * result as last time. That's not useful because it only suggests to the
227    * caller to try and render the same frame twice.
228    */
229   if (stage_cogl->update_time <= stage_cogl->last_update_time)
230     stage_cogl->update_time = stage_cogl->last_update_time + refresh_interval;
231 }
232 
233 static gint64
clutter_stage_cogl_get_update_time(ClutterStageWindow * stage_window)234 clutter_stage_cogl_get_update_time (ClutterStageWindow *stage_window)
235 {
236   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
237 
238   return stage_cogl->update_time;
239 }
240 
241 static void
clutter_stage_cogl_clear_update_time(ClutterStageWindow * stage_window)242 clutter_stage_cogl_clear_update_time (ClutterStageWindow *stage_window)
243 {
244   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
245 
246   stage_cogl->last_update_time = stage_cogl->update_time;
247   stage_cogl->update_time = -1;
248 }
249 
250 static ClutterActor *
clutter_stage_cogl_get_wrapper(ClutterStageWindow * stage_window)251 clutter_stage_cogl_get_wrapper (ClutterStageWindow *stage_window)
252 {
253   return CLUTTER_ACTOR (CLUTTER_STAGE_COGL (stage_window)->wrapper);
254 }
255 
256 static void
clutter_stage_cogl_show(ClutterStageWindow * stage_window,gboolean do_raise)257 clutter_stage_cogl_show (ClutterStageWindow *stage_window,
258 			 gboolean            do_raise)
259 {
260   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
261 
262   clutter_actor_map (CLUTTER_ACTOR (stage_cogl->wrapper));
263 }
264 
265 static void
clutter_stage_cogl_hide(ClutterStageWindow * stage_window)266 clutter_stage_cogl_hide (ClutterStageWindow *stage_window)
267 {
268   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
269 
270   clutter_actor_unmap (CLUTTER_ACTOR (stage_cogl->wrapper));
271 }
272 
273 static void
clutter_stage_cogl_resize(ClutterStageWindow * stage_window,gint width,gint height)274 clutter_stage_cogl_resize (ClutterStageWindow *stage_window,
275                            gint                width,
276                            gint                height)
277 {
278 }
279 
280 static gboolean
clutter_stage_cogl_has_redraw_clips(ClutterStageWindow * stage_window)281 clutter_stage_cogl_has_redraw_clips (ClutterStageWindow *stage_window)
282 {
283   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
284 
285   /* NB: at the start of each new frame there is an implied clip that
286    * clips everything (i.e. nothing would be drawn) so we need to make
287    * sure we return True in the un-initialized case here.
288    *
289    * NB: a clip width of 0 means a full stage redraw has been queued
290    * so we effectively don't have any redraw clips in that case.
291    */
292   if (!stage_cogl->initialized_redraw_clip ||
293       (stage_cogl->initialized_redraw_clip &&
294        stage_cogl->bounding_redraw_clip.width != 0))
295     return TRUE;
296   else
297     return FALSE;
298 }
299 
300 static gboolean
clutter_stage_cogl_ignoring_redraw_clips(ClutterStageWindow * stage_window)301 clutter_stage_cogl_ignoring_redraw_clips (ClutterStageWindow *stage_window)
302 {
303   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
304 
305   /* NB: a clip width of 0 means a full stage redraw is required */
306   if (stage_cogl->initialized_redraw_clip &&
307       stage_cogl->bounding_redraw_clip.width == 0)
308     return TRUE;
309   else
310     return FALSE;
311 }
312 
313 /* A redraw clip represents (in stage coordinates) the bounding box of
314  * something that needs to be redraw. Typically they are added to the
315  * StageWindow as a result of clutter_actor_queue_clipped_redraw() by
316  * actors such as ClutterGLXTexturePixmap. All redraw clips are
317  * discarded after the next paint.
318  *
319  * A NULL stage_clip means the whole stage needs to be redrawn.
320  *
321  * What we do with this information:
322  * - we keep track of the bounding box for all redraw clips
323  * - when we come to redraw; we scissor the redraw to that box and use
324  *   glBlitFramebuffer to present the redraw to the front
325  *   buffer.
326  */
327 static void
clutter_stage_cogl_add_redraw_clip(ClutterStageWindow * stage_window,cairo_rectangle_int_t * stage_clip)328 clutter_stage_cogl_add_redraw_clip (ClutterStageWindow    *stage_window,
329                                     cairo_rectangle_int_t *stage_clip)
330 {
331   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
332 
333   /* If we are already forced to do a full stage redraw then bail early */
334   if (clutter_stage_cogl_ignoring_redraw_clips (stage_window))
335     return;
336 
337   /* A NULL stage clip means a full stage redraw has been queued and
338    * we keep track of this by setting a zero width
339    * stage_cogl->bounding_redraw_clip */
340   if (stage_clip == NULL)
341     {
342       stage_cogl->bounding_redraw_clip.width = 0;
343       stage_cogl->initialized_redraw_clip = TRUE;
344       return;
345     }
346 
347   /* Ignore requests to add degenerate/empty clip rectangles */
348   if (stage_clip->width == 0 || stage_clip->height == 0)
349     return;
350 
351   if (!stage_cogl->initialized_redraw_clip)
352     {
353       stage_cogl->bounding_redraw_clip = *stage_clip;
354     }
355   else if (stage_cogl->bounding_redraw_clip.width > 0)
356     {
357       _clutter_util_rectangle_union (&stage_cogl->bounding_redraw_clip,
358                                      stage_clip,
359                                      &stage_cogl->bounding_redraw_clip);
360     }
361 
362   stage_cogl->initialized_redraw_clip = TRUE;
363 }
364 
365 static gboolean
clutter_stage_cogl_get_redraw_clip_bounds(ClutterStageWindow * stage_window,cairo_rectangle_int_t * stage_clip)366 clutter_stage_cogl_get_redraw_clip_bounds (ClutterStageWindow    *stage_window,
367                                            cairo_rectangle_int_t *stage_clip)
368 {
369   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
370 
371   if (stage_cogl->using_clipped_redraw)
372     {
373       *stage_clip = stage_cogl->bounding_redraw_clip;
374 
375       return TRUE;
376     }
377 
378   return FALSE;
379 }
380 
381 static inline gboolean
valid_buffer_age(ClutterStageViewCogl * view_cogl,int age)382 valid_buffer_age (ClutterStageViewCogl *view_cogl,
383                   int                   age)
384 {
385   ClutterStageViewCoglPrivate *view_priv =
386     clutter_stage_view_cogl_get_instance_private (view_cogl);
387 
388   if (age <= 0)
389     return FALSE;
390 
391   return age < MIN (view_priv->damage_index, DAMAGE_HISTORY_MAX);
392 }
393 
394 static gboolean
swap_framebuffer(ClutterStageWindow * stage_window,ClutterStageView * view,cairo_rectangle_int_t * swap_region,gboolean swap_with_damage)395 swap_framebuffer (ClutterStageWindow    *stage_window,
396                   ClutterStageView      *view,
397                   cairo_rectangle_int_t *swap_region,
398                   gboolean               swap_with_damage)
399 {
400   CoglFramebuffer *framebuffer = clutter_stage_view_get_onscreen (view);
401   int damage[4], ndamage;
402 
403   damage[0] = swap_region->x;
404   damage[1] = swap_region->y;
405   damage[2] = swap_region->width;
406   damage[3] = swap_region->height;
407 
408   if (swap_region->width != 0)
409     ndamage = 1;
410   else
411     ndamage = 0;
412 
413   if (cogl_is_onscreen (framebuffer))
414     {
415       CoglOnscreen *onscreen = COGL_ONSCREEN (framebuffer);
416 
417       /* push on the screen */
418       if (ndamage == 1 && !swap_with_damage)
419         {
420           CLUTTER_NOTE (BACKEND,
421                         "cogl_onscreen_swap_region (onscreen: %p, "
422                         "x: %d, y: %d, "
423                         "width: %d, height: %d)",
424                         onscreen,
425                         damage[0], damage[1], damage[2], damage[3]);
426 
427           cogl_onscreen_swap_region (onscreen,
428                                      damage, ndamage);
429 
430           return FALSE;
431         }
432       else
433         {
434           CLUTTER_NOTE (BACKEND, "cogl_onscreen_swap_buffers (onscreen: %p)",
435                         onscreen);
436 
437           cogl_onscreen_swap_buffers_with_damage (onscreen,
438                                                   damage, ndamage);
439 
440           return TRUE;
441         }
442     }
443   else
444     {
445       CLUTTER_NOTE (BACKEND, "cogl_framebuffer_finish (framebuffer: %p)",
446                     framebuffer);
447       cogl_framebuffer_finish (framebuffer);
448 
449       return FALSE;
450     }
451 }
452 
453 static void
paint_stage(ClutterStageCogl * stage_cogl,ClutterStageView * view,const cairo_rectangle_int_t * clip)454 paint_stage (ClutterStageCogl            *stage_cogl,
455              ClutterStageView            *view,
456              const cairo_rectangle_int_t *clip)
457 {
458   ClutterStage *stage = stage_cogl->wrapper;
459 
460   _clutter_stage_maybe_setup_viewport (stage, view);
461   _clutter_stage_paint_view (stage, view, clip);
462 
463   if (clutter_stage_view_get_onscreen (view) !=
464       clutter_stage_view_get_framebuffer (view))
465     {
466       clutter_stage_view_blit_offscreen (view, clip);
467     }
468 }
469 
470 static void
fill_current_damage_history_and_step(ClutterStageView * view)471 fill_current_damage_history_and_step (ClutterStageView *view)
472 {
473   ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
474   ClutterStageViewCoglPrivate *view_priv =
475     clutter_stage_view_cogl_get_instance_private (view_cogl);
476   cairo_rectangle_int_t view_rect;
477   float fb_scale;
478   cairo_rectangle_int_t *current_fb_damage;
479 
480   current_fb_damage =
481     &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index)];
482   clutter_stage_view_get_layout (view, &view_rect);
483   fb_scale = clutter_stage_view_get_scale (view);
484 
485   *current_fb_damage = (cairo_rectangle_int_t) {
486     .x = 0,
487     .y = 0,
488     .width = view_rect.width * fb_scale,
489     .height = view_rect.height * fb_scale
490   };
491   view_priv->damage_index++;
492 }
493 
494 static void
transform_swap_region_to_onscreen(ClutterStageView * view,cairo_rectangle_int_t * swap_region)495 transform_swap_region_to_onscreen (ClutterStageView      *view,
496                                    cairo_rectangle_int_t *swap_region)
497 {
498   CoglFramebuffer *framebuffer;
499   cairo_rectangle_int_t layout;
500   gfloat x1, y1, x2, y2;
501   gint width, height;
502 
503   framebuffer = clutter_stage_view_get_onscreen (view);
504   clutter_stage_view_get_layout (view, &layout);
505 
506   x1 = (float) swap_region->x / layout.width;
507   y1 = (float) swap_region->y / layout.height;
508   x2 = (float) (swap_region->x + swap_region->width) / layout.width;
509   y2 = (float) (swap_region->y + swap_region->height) / layout.height;
510 
511   clutter_stage_view_transform_to_onscreen (view, &x1, &y1);
512   clutter_stage_view_transform_to_onscreen (view, &x2, &y2);
513 
514   width = cogl_framebuffer_get_width (framebuffer);
515   height = cogl_framebuffer_get_height (framebuffer);
516 
517   x1 = floor (x1 * width);
518   y1 = floor (height - (y1 * height));
519   x2 = ceil (x2 * width);
520   y2 = ceil (height - (y2 * height));
521 
522   *swap_region = (cairo_rectangle_int_t) {
523     .x = x1,
524     .y = y1,
525     .width = x2 - x1,
526     .height = y2 - y1
527   };
528 }
529 
530 static void
calculate_scissor_region(cairo_rectangle_int_t * fb_clip_region,int subpixel_compensation,int fb_width,int fb_height,cairo_rectangle_int_t * out_scissor_rect)531 calculate_scissor_region (cairo_rectangle_int_t *fb_clip_region,
532                           int                    subpixel_compensation,
533                           int                    fb_width,
534                           int                    fb_height,
535                           cairo_rectangle_int_t *out_scissor_rect)
536 {
537   int scissor_x;
538   int scissor_y;
539   int scissor_width;
540   int scissor_height;
541 
542   scissor_x = fb_clip_region->x;
543   scissor_y = fb_clip_region->y;
544   scissor_width = fb_clip_region->width;
545   scissor_height = fb_clip_region->height;
546 
547   if (fb_clip_region->x > 0)
548     scissor_x += subpixel_compensation;
549   if (fb_clip_region->y > 0)
550     scissor_y += subpixel_compensation;
551   if (fb_clip_region->x + fb_clip_region->width < fb_width)
552     scissor_width -= 2 * subpixel_compensation;
553   if (fb_clip_region->y + fb_clip_region->height < fb_height)
554     scissor_height -= 2 * subpixel_compensation;
555 
556   *out_scissor_rect = (cairo_rectangle_int_t) {
557     .x = scissor_x,
558     .y = scissor_y,
559     .width = scissor_width,
560     .height = scissor_height
561   };
562 }
563 
564 static gboolean
clutter_stage_cogl_redraw_view(ClutterStageWindow * stage_window,ClutterStageView * view)565 clutter_stage_cogl_redraw_view (ClutterStageWindow *stage_window,
566                                 ClutterStageView   *view)
567 {
568   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
569   ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
570   ClutterStageViewCoglPrivate *view_priv =
571     clutter_stage_view_cogl_get_instance_private (view_cogl);
572   CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view);
573   cairo_rectangle_int_t view_rect;
574   gboolean have_clip;
575   gboolean may_use_clipped_redraw;
576   gboolean use_clipped_redraw;
577   gboolean can_blit_sub_buffer;
578   gboolean has_buffer_age;
579   gboolean do_swap_buffer;
580   gboolean swap_with_damage;
581   ClutterActor *wrapper;
582   cairo_rectangle_int_t redraw_clip;
583   cairo_rectangle_int_t swap_region;
584   cairo_rectangle_int_t fb_clip_region;
585   gboolean clip_region_empty;
586   float fb_scale;
587   int subpixel_compensation = 0;
588   int fb_width, fb_height;
589 
590   wrapper = CLUTTER_ACTOR (stage_cogl->wrapper);
591 
592   clutter_stage_view_get_layout (view, &view_rect);
593   fb_scale = clutter_stage_view_get_scale (view);
594   fb_width = cogl_framebuffer_get_width (fb);
595   fb_height = cogl_framebuffer_get_height (fb);
596 
597   can_blit_sub_buffer =
598     cogl_is_onscreen (fb) &&
599     cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION);
600 
601   has_buffer_age =
602     cogl_is_onscreen (fb) &&
603     cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE);
604 
605   /* NB: a zero width redraw clip == full stage redraw */
606   if (stage_cogl->bounding_redraw_clip.width == 0)
607     have_clip = FALSE;
608   else
609     {
610       redraw_clip = stage_cogl->bounding_redraw_clip;
611       _clutter_util_rectangle_intersection (&redraw_clip,
612                                             &view_rect,
613                                             &redraw_clip);
614 
615       have_clip = !(redraw_clip.x == view_rect.x &&
616                     redraw_clip.y == view_rect.y &&
617                     redraw_clip.width == view_rect.width &&
618                     redraw_clip.height == view_rect.height);
619     }
620 
621   may_use_clipped_redraw = FALSE;
622   if (_clutter_stage_window_can_clip_redraws (stage_window) &&
623       (can_blit_sub_buffer || has_buffer_age) &&
624       have_clip &&
625       /* some drivers struggle to get going and produce some junk
626        * frames when starting up... */
627       cogl_onscreen_get_frame_counter (COGL_ONSCREEN (fb)) > 3)
628     {
629       may_use_clipped_redraw = TRUE;
630 
631       if (fb_scale != floorf (fb_scale))
632         subpixel_compensation = ceilf (fb_scale);
633 
634       fb_clip_region = (cairo_rectangle_int_t) {
635         .x = (floorf ((redraw_clip.x - view_rect.x) * fb_scale) -
636               subpixel_compensation),
637         .y = (floorf ((redraw_clip.y - view_rect.y) * fb_scale) -
638               subpixel_compensation),
639         .width = (ceilf (redraw_clip.width * fb_scale) +
640                   (2 * subpixel_compensation)),
641         .height = (ceilf (redraw_clip.height * fb_scale) +
642                    (2 * subpixel_compensation))
643       };
644     }
645   else
646     {
647       fb_clip_region = (cairo_rectangle_int_t) { 0 };
648     }
649 
650   if (may_use_clipped_redraw &&
651       G_LIKELY (!(clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
652     use_clipped_redraw = TRUE;
653   else
654     use_clipped_redraw = FALSE;
655 
656   clip_region_empty = may_use_clipped_redraw && fb_clip_region.width == 0;
657 
658   swap_with_damage = FALSE;
659   if (has_buffer_age)
660     {
661       if (use_clipped_redraw && !clip_region_empty)
662         {
663           int age, i;
664           cairo_rectangle_int_t *current_fb_damage =
665             &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index++)];
666 
667           age = cogl_onscreen_get_buffer_age (COGL_ONSCREEN (fb));
668 
669           if (valid_buffer_age (view_cogl, age))
670             {
671               cairo_rectangle_int_t damage_region;
672 
673               if (age > stage_cogl->max_buffer_age)
674                 stage_cogl->max_buffer_age = age;
675 
676               *current_fb_damage = fb_clip_region;
677 
678               for (i = 1; i <= age; i++)
679                 {
680                   cairo_rectangle_int_t *fb_damage =
681                     &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - i - 1)];
682 
683                   _clutter_util_rectangle_union (&fb_clip_region,
684                                                  fb_damage,
685                                                  &fb_clip_region);
686                 }
687 
688               /* Update the bounding redraw clip state with the extra damage. */
689               damage_region = (cairo_rectangle_int_t) {
690                 .x = view_rect.x + floorf (fb_clip_region.x / fb_scale),
691                 .y = view_rect.y + floorf (fb_clip_region.y / fb_scale),
692                 .width = ceilf (fb_clip_region.width / fb_scale),
693                 .height = ceilf (fb_clip_region.height / fb_scale)
694               };
695               _clutter_util_rectangle_union (&stage_cogl->bounding_redraw_clip,
696                                              &damage_region,
697                                              &stage_cogl->bounding_redraw_clip);
698 
699               CLUTTER_NOTE (CLIPPING, "Reusing back buffer(age=%d) - repairing region: x=%d, y=%d, width=%d, height=%d\n",
700                             age,
701                             fb_clip_region.x,
702                             fb_clip_region.y,
703                             fb_clip_region.width,
704                             fb_clip_region.height);
705 
706               swap_with_damage = TRUE;
707             }
708           else
709             {
710               CLUTTER_NOTE (CLIPPING, "Invalid back buffer(age=%d): forcing full redraw\n", age);
711               use_clipped_redraw = FALSE;
712               *current_fb_damage = (cairo_rectangle_int_t) {
713                 .x = 0,
714                 .y = 0,
715                 .width = view_rect.width * fb_scale,
716                 .height = view_rect.height * fb_scale
717               };
718             }
719         }
720       else if (!use_clipped_redraw)
721         {
722           fill_current_damage_history_and_step (view);
723         }
724     }
725 
726   cogl_push_framebuffer (fb);
727   if (use_clipped_redraw && clip_region_empty)
728     {
729       CLUTTER_NOTE (CLIPPING, "Empty stage output paint\n");
730     }
731   else if (use_clipped_redraw)
732     {
733       cairo_rectangle_int_t scissor_rect;
734 
735       calculate_scissor_region (&fb_clip_region,
736                                 subpixel_compensation,
737                                 fb_width, fb_height,
738                                 &scissor_rect);
739 
740       CLUTTER_NOTE (CLIPPING,
741                     "Stage clip pushed: x=%d, y=%d, width=%d, height=%d\n",
742                     scissor_rect.x,
743                     scissor_rect.y,
744                     scissor_rect.width,
745                     scissor_rect.height);
746 
747       stage_cogl->using_clipped_redraw = TRUE;
748 
749       cogl_framebuffer_push_scissor_clip (fb,
750                                           scissor_rect.x,
751                                           scissor_rect.y,
752                                           scissor_rect.width,
753                                           scissor_rect.height);
754       paint_stage (stage_cogl, view,
755                    &(cairo_rectangle_int_t) {
756                      .x = view_rect.x + floorf ((fb_clip_region.x - 0) / fb_scale),
757                      .y = view_rect.y + floorf ((fb_clip_region.y - 0) / fb_scale),
758                      .width = ceilf ((fb_clip_region.width + 0) / fb_scale),
759                      .height = ceilf ((fb_clip_region.height + 0) / fb_scale)
760                    });
761       cogl_framebuffer_pop_clip (fb);
762 
763       stage_cogl->using_clipped_redraw = FALSE;
764     }
765   else
766     {
767       CLUTTER_NOTE (CLIPPING, "Unclipped stage paint\n");
768 
769       /* If we are trying to debug redraw issues then we want to pass
770        * the bounding_redraw_clip so it can be visualized */
771       if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS) &&
772           may_use_clipped_redraw &&
773           !clip_region_empty)
774         {
775           cairo_rectangle_int_t scissor_rect;
776 
777           calculate_scissor_region (&fb_clip_region,
778                                     subpixel_compensation,
779                                     fb_width, fb_height,
780                                     &scissor_rect);
781 
782           cogl_framebuffer_push_scissor_clip (fb,
783                                               scissor_rect.x,
784                                               scissor_rect.y,
785                                               scissor_rect.width,
786                                               scissor_rect.height);
787           paint_stage (stage_cogl, view,
788                        &(cairo_rectangle_int_t) {
789                          .x = view_rect.x + floorf (fb_clip_region.x / fb_scale),
790                          .y = view_rect.y + floorf (fb_clip_region.y / fb_scale),
791                          .width = ceilf (fb_clip_region.width / fb_scale),
792                          .height = ceilf (fb_clip_region.height / fb_scale)
793                        });
794           cogl_framebuffer_pop_clip (fb);
795         }
796       else
797         paint_stage (stage_cogl, view, &view_rect);
798     }
799   cogl_pop_framebuffer ();
800 
801   if (may_use_clipped_redraw &&
802       G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS)))
803     {
804       CoglContext *ctx = cogl_framebuffer_get_context (fb);
805       static CoglPipeline *outline = NULL;
806       ClutterActor *actor = CLUTTER_ACTOR (wrapper);
807       float x_1 = redraw_clip.x;
808       float x_2 = redraw_clip.x + redraw_clip.width;
809       float y_1 = redraw_clip.y;
810       float y_2 = redraw_clip.y + redraw_clip.height;
811       CoglVertexP2 quad[4] = {
812         { x_1, y_1 },
813         { x_2, y_1 },
814         { x_2, y_2 },
815         { x_1, y_2 }
816       };
817       CoglPrimitive *prim;
818       CoglMatrix modelview;
819 
820       if (outline == NULL)
821         {
822           outline = cogl_pipeline_new (ctx);
823           cogl_pipeline_set_color4ub (outline, 0xff, 0x00, 0x00, 0xff);
824         }
825 
826       prim = cogl_primitive_new_p2 (ctx,
827                                     COGL_VERTICES_MODE_LINE_LOOP,
828                                     4, /* n_vertices */
829                                     quad);
830 
831       cogl_framebuffer_push_matrix (fb);
832       cogl_matrix_init_identity (&modelview);
833       _clutter_actor_apply_modelview_transform (actor, &modelview);
834       cogl_framebuffer_set_modelview_matrix (fb, &modelview);
835       cogl_framebuffer_draw_primitive (fb, outline, prim);
836       cogl_framebuffer_pop_matrix (fb);
837       cogl_object_unref (prim);
838     }
839 
840   /* XXX: It seems there will be a race here in that the stage
841    * window may be resized before the cogl_onscreen_swap_region
842    * is handled and so we may copy the wrong region. I can't
843    * really see how we can handle this with the current state of X
844    * but at least in this case a full redraw should be queued by
845    * the resize anyway so it should only exhibit temporary
846    * artefacts.
847    */
848   if (use_clipped_redraw)
849     {
850       if (use_clipped_redraw && clip_region_empty)
851         {
852           do_swap_buffer = FALSE;
853         }
854       else if (use_clipped_redraw)
855         {
856           swap_region = fb_clip_region;
857           g_assert (swap_region.width > 0);
858           do_swap_buffer = TRUE;
859         }
860       else
861         {
862           swap_region = (cairo_rectangle_int_t) {
863             .x = 0,
864             .y = 0,
865             .width = view_rect.width * fb_scale,
866             .height = view_rect.height * fb_scale,
867           };
868           do_swap_buffer = TRUE;
869         }
870     }
871   else
872     {
873       swap_region = (cairo_rectangle_int_t) { 0 };
874       do_swap_buffer = TRUE;
875     }
876 
877   if (do_swap_buffer)
878     {
879       if (clutter_stage_view_get_onscreen (view) !=
880           clutter_stage_view_get_framebuffer (view))
881         {
882           transform_swap_region_to_onscreen (view, &swap_region);
883         }
884 
885       return swap_framebuffer (stage_window,
886                                view,
887                                &swap_region,
888                                swap_with_damage);
889     }
890   else
891     {
892       return FALSE;
893     }
894 }
895 
896 static void
clutter_stage_cogl_redraw(ClutterStageWindow * stage_window)897 clutter_stage_cogl_redraw (ClutterStageWindow *stage_window)
898 {
899   ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window);
900   gboolean swap_event = FALSE;
901   GList *l;
902 
903   for (l = _clutter_stage_window_get_views (stage_window); l; l = l->next)
904     {
905       ClutterStageView *view = l->data;
906 
907       swap_event =
908         clutter_stage_cogl_redraw_view (stage_window, view) || swap_event;
909     }
910 
911   _clutter_stage_window_finish_frame (stage_window);
912 
913   if (swap_event)
914     {
915       /* If we have swap buffer events then cogl_onscreen_swap_buffers
916        * will return immediately and we need to track that there is a
917        * swap in progress... */
918       if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
919         stage_cogl->pending_swaps++;
920     }
921 
922   /* reset the redraw clipping for the next paint... */
923   stage_cogl->initialized_redraw_clip = FALSE;
924 
925   stage_cogl->frame_count++;
926 }
927 
928 static void
clutter_stage_cogl_get_dirty_pixel(ClutterStageWindow * stage_window,ClutterStageView * view,int * x,int * y)929 clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window,
930                                     ClutterStageView   *view,
931                                     int                *x,
932                                     int                *y)
933 {
934   CoglFramebuffer *framebuffer = clutter_stage_view_get_framebuffer (view);
935   gboolean has_buffer_age =
936     cogl_is_onscreen (framebuffer) &&
937     cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE);
938   float fb_scale;
939   gboolean scale_is_fractional;
940 
941   fb_scale = clutter_stage_view_get_scale (view);
942   if (fb_scale != floorf (fb_scale))
943     scale_is_fractional = TRUE;
944   else
945     scale_is_fractional = FALSE;
946 
947   /*
948    * Buffer damage is tracked in the framebuffer coordinate space
949    * using the damage history. When fractional scaling is used, a
950    * coordinate on the stage might not correspond to the exact position of any
951    * physical pixel, which causes issues when painting using the pick mode.
952    *
953    * For now, always use the (0, 0) pixel for picking when using fractional
954    * framebuffer scaling.
955    */
956   if (!has_buffer_age || scale_is_fractional)
957     {
958       *x = 0;
959       *y = 0;
960     }
961   else
962     {
963       ClutterStageViewCogl *view_cogl = CLUTTER_STAGE_VIEW_COGL (view);
964       ClutterStageViewCoglPrivate *view_priv =
965         clutter_stage_view_cogl_get_instance_private (view_cogl);
966       cairo_rectangle_int_t *fb_damage;
967 
968       fb_damage = &view_priv->damage_history[DAMAGE_HISTORY (view_priv->damage_index - 1)];
969       *x = fb_damage->x / fb_scale;
970       *y = fb_damage->y / fb_scale;
971     }
972 }
973 
974 static void
clutter_stage_window_iface_init(ClutterStageWindowIface * iface)975 clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
976 {
977   iface->realize = clutter_stage_cogl_realize;
978   iface->unrealize = clutter_stage_cogl_unrealize;
979   iface->get_wrapper = clutter_stage_cogl_get_wrapper;
980   iface->resize = clutter_stage_cogl_resize;
981   iface->show = clutter_stage_cogl_show;
982   iface->hide = clutter_stage_cogl_hide;
983   iface->schedule_update = clutter_stage_cogl_schedule_update;
984   iface->get_update_time = clutter_stage_cogl_get_update_time;
985   iface->clear_update_time = clutter_stage_cogl_clear_update_time;
986   iface->add_redraw_clip = clutter_stage_cogl_add_redraw_clip;
987   iface->has_redraw_clips = clutter_stage_cogl_has_redraw_clips;
988   iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips;
989   iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds;
990   iface->redraw = clutter_stage_cogl_redraw;
991   iface->get_dirty_pixel = clutter_stage_cogl_get_dirty_pixel;
992 }
993 
994 static void
clutter_stage_cogl_set_property(GObject * gobject,guint prop_id,const GValue * value,GParamSpec * pspec)995 clutter_stage_cogl_set_property (GObject      *gobject,
996 				 guint         prop_id,
997 				 const GValue *value,
998 				 GParamSpec   *pspec)
999 {
1000   ClutterStageCogl *self = CLUTTER_STAGE_COGL (gobject);
1001 
1002   switch (prop_id)
1003     {
1004     case PROP_WRAPPER:
1005       self->wrapper = g_value_get_object (value);
1006       break;
1007 
1008     case PROP_BACKEND:
1009       self->backend = g_value_get_object (value);
1010       break;
1011 
1012     default:
1013       G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec);
1014       break;
1015     }
1016 }
1017 
1018 static void
_clutter_stage_cogl_class_init(ClutterStageCoglClass * klass)1019 _clutter_stage_cogl_class_init (ClutterStageCoglClass *klass)
1020 {
1021   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
1022 
1023   gobject_class->set_property = clutter_stage_cogl_set_property;
1024 
1025   g_object_class_override_property (gobject_class, PROP_WRAPPER, "wrapper");
1026   g_object_class_override_property (gobject_class, PROP_BACKEND, "backend");
1027 }
1028 
1029 static void
_clutter_stage_cogl_init(ClutterStageCogl * stage)1030 _clutter_stage_cogl_init (ClutterStageCogl *stage)
1031 {
1032   stage->last_presentation_time = 0;
1033   stage->refresh_rate = 0.0;
1034 
1035   stage->update_time = -1;
1036 }
1037 
1038 static void
clutter_stage_view_cogl_init(ClutterStageViewCogl * view_cogl)1039 clutter_stage_view_cogl_init (ClutterStageViewCogl *view_cogl)
1040 {
1041 }
1042 
1043 static void
clutter_stage_view_cogl_class_init(ClutterStageViewCoglClass * klass)1044 clutter_stage_view_cogl_class_init (ClutterStageViewCoglClass *klass)
1045 {
1046 }
1047