1 /*
2  * Copyright (C) 2016 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "clutter-build-config.h"
19 
20 #include "clutter/clutter-stage-view.h"
21 #include "clutter/clutter-stage-view-private.h"
22 
23 #include <cairo-gobject.h>
24 #include <math.h>
25 
26 #include "clutter/clutter-damage-history.h"
27 #include "clutter/clutter-frame-clock.h"
28 #include "clutter/clutter-frame-private.h"
29 #include "clutter/clutter-private.h"
30 #include "clutter/clutter-mutter.h"
31 #include "clutter/clutter-stage-private.h"
32 #include "cogl/cogl.h"
33 
34 enum
35 {
36   PROP_0,
37 
38   PROP_NAME,
39   PROP_STAGE,
40   PROP_LAYOUT,
41   PROP_FRAMEBUFFER,
42   PROP_OFFSCREEN,
43   PROP_USE_SHADOWFB,
44   PROP_SCALE,
45   PROP_REFRESH_RATE,
46   PROP_VBLANK_DURATION_US,
47 
48   PROP_LAST
49 };
50 
51 static GParamSpec *obj_props[PROP_LAST];
52 
53 typedef struct _ClutterStageViewPrivate
54 {
55   char *name;
56 
57   ClutterStage *stage;
58 
59   cairo_rectangle_int_t layout;
60   float scale;
61   CoglFramebuffer *framebuffer;
62 
63   CoglOffscreen *offscreen;
64   CoglPipeline *offscreen_pipeline;
65 
66   gboolean use_shadowfb;
67   struct {
68     struct {
69       CoglDmaBufHandle *handles[2];
70       int current_idx;
71       ClutterDamageHistory *damage_history;
72     } dma_buf;
73 
74     CoglOffscreen *framebuffer;
75   } shadow;
76 
77   CoglScanout *next_scanout;
78 
79   gboolean has_redraw_clip;
80   cairo_region_t *redraw_clip;
81 
82   float refresh_rate;
83   int64_t vblank_duration_us;
84   ClutterFrameClock *frame_clock;
85 
86   struct {
87     int frame_count;
88     int64_t last_print_time_us;
89     int64_t cumulative_draw_time_us;
90     int64_t began_draw_time_us;
91     int64_t worst_draw_time_us;
92   } frame_timings;
93 
94   guint dirty_viewport   : 1;
95   guint dirty_projection : 1;
96 } ClutterStageViewPrivate;
97 
G_DEFINE_TYPE_WITH_PRIVATE(ClutterStageView,clutter_stage_view,G_TYPE_OBJECT)98 G_DEFINE_TYPE_WITH_PRIVATE (ClutterStageView, clutter_stage_view, G_TYPE_OBJECT)
99 
100 void
101 clutter_stage_view_destroy (ClutterStageView *view)
102 {
103   g_object_run_dispose (G_OBJECT (view));
104   g_object_unref (view);
105 }
106 
107 void
clutter_stage_view_get_layout(ClutterStageView * view,cairo_rectangle_int_t * rect)108 clutter_stage_view_get_layout (ClutterStageView      *view,
109                                cairo_rectangle_int_t *rect)
110 {
111   ClutterStageViewPrivate *priv =
112     clutter_stage_view_get_instance_private (view);
113 
114   *rect = priv->layout;
115 }
116 
117 /**
118  * clutter_stage_view_get_framebuffer:
119  * @view: a #ClutterStageView
120  *
121  * Retrieves the framebuffer of @view to draw to.
122  *
123  * Returns: (transfer none): a #CoglFramebuffer
124  */
125 CoglFramebuffer *
clutter_stage_view_get_framebuffer(ClutterStageView * view)126 clutter_stage_view_get_framebuffer (ClutterStageView *view)
127 {
128   ClutterStageViewPrivate *priv =
129     clutter_stage_view_get_instance_private (view);
130 
131   if (priv->offscreen)
132     return COGL_FRAMEBUFFER (priv->offscreen);
133   else if (priv->shadow.framebuffer)
134     return COGL_FRAMEBUFFER (priv->shadow.framebuffer);
135   else
136     return priv->framebuffer;
137 }
138 
139 /**
140  * clutter_stage_view_get_onscreen:
141  * @view: a #ClutterStageView
142  *
143  * Retrieves the onscreen framebuffer of @view if available.
144  *
145  * Returns: (transfer none): a #CoglFramebuffer
146  */
147 CoglFramebuffer *
clutter_stage_view_get_onscreen(ClutterStageView * view)148 clutter_stage_view_get_onscreen (ClutterStageView *view)
149 {
150   ClutterStageViewPrivate *priv =
151     clutter_stage_view_get_instance_private (view);
152 
153   return priv->framebuffer;
154 }
155 
156 static CoglPipeline *
clutter_stage_view_create_offscreen_pipeline(CoglOffscreen * offscreen)157 clutter_stage_view_create_offscreen_pipeline (CoglOffscreen *offscreen)
158 {
159   CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (offscreen);
160   CoglPipeline *pipeline;
161 
162   pipeline = cogl_pipeline_new (cogl_framebuffer_get_context (framebuffer));
163 
164   cogl_pipeline_set_layer_filters (pipeline, 0,
165                                    COGL_PIPELINE_FILTER_NEAREST,
166                                    COGL_PIPELINE_FILTER_NEAREST);
167   cogl_pipeline_set_layer_texture (pipeline, 0,
168                                    cogl_offscreen_get_texture (offscreen));
169   cogl_pipeline_set_layer_wrap_mode (pipeline, 0,
170                                      COGL_PIPELINE_WRAP_MODE_CLAMP_TO_EDGE);
171 
172   return pipeline;
173 }
174 
175 static void
clutter_stage_view_ensure_offscreen_blit_pipeline(ClutterStageView * view)176 clutter_stage_view_ensure_offscreen_blit_pipeline (ClutterStageView *view)
177 {
178   ClutterStageViewPrivate *priv =
179     clutter_stage_view_get_instance_private (view);
180   ClutterStageViewClass *view_class =
181     CLUTTER_STAGE_VIEW_GET_CLASS (view);
182 
183   g_assert (priv->offscreen != NULL);
184 
185   if (priv->offscreen_pipeline)
186     return;
187 
188   priv->offscreen_pipeline =
189     clutter_stage_view_create_offscreen_pipeline (priv->offscreen);
190 
191   if (view_class->setup_offscreen_blit_pipeline)
192     view_class->setup_offscreen_blit_pipeline (view, priv->offscreen_pipeline);
193 }
194 
195 void
clutter_stage_view_invalidate_offscreen_blit_pipeline(ClutterStageView * view)196 clutter_stage_view_invalidate_offscreen_blit_pipeline (ClutterStageView *view)
197 {
198   ClutterStageViewPrivate *priv =
199     clutter_stage_view_get_instance_private (view);
200 
201   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
202 }
203 
204 void
clutter_stage_view_transform_rect_to_onscreen(ClutterStageView * view,const cairo_rectangle_int_t * src_rect,int dst_width,int dst_height,cairo_rectangle_int_t * dst_rect)205 clutter_stage_view_transform_rect_to_onscreen (ClutterStageView            *view,
206                                                const cairo_rectangle_int_t *src_rect,
207                                                int                          dst_width,
208                                                int                          dst_height,
209                                                cairo_rectangle_int_t       *dst_rect)
210 {
211   ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
212 
213   return view_class->transform_rect_to_onscreen (view,
214                                                  src_rect,
215                                                  dst_width,
216                                                  dst_height,
217                                                  dst_rect);
218 }
219 
220 static void
paint_transformed_framebuffer(ClutterStageView * view,CoglPipeline * pipeline,CoglOffscreen * src_framebuffer,CoglFramebuffer * dst_framebuffer,const cairo_region_t * redraw_clip)221 paint_transformed_framebuffer (ClutterStageView     *view,
222                                CoglPipeline         *pipeline,
223                                CoglOffscreen        *src_framebuffer,
224                                CoglFramebuffer      *dst_framebuffer,
225                                const cairo_region_t *redraw_clip)
226 {
227   graphene_matrix_t matrix;
228   unsigned int n_rectangles, i;
229   int dst_width, dst_height;
230   cairo_rectangle_int_t view_layout;
231   cairo_rectangle_int_t onscreen_layout;
232   float view_scale;
233   float *coordinates;
234 
235   dst_width = cogl_framebuffer_get_width (dst_framebuffer);
236   dst_height = cogl_framebuffer_get_height (dst_framebuffer);
237   clutter_stage_view_get_layout (view, &view_layout);
238   clutter_stage_view_transform_rect_to_onscreen (view,
239                                                  &(cairo_rectangle_int_t) {
240                                                    .width = view_layout.width,
241                                                    .height = view_layout.height,
242                                                  },
243                                                  view_layout.width,
244                                                  view_layout.height,
245                                                  &onscreen_layout);
246   view_scale = clutter_stage_view_get_scale (view);
247 
248   cogl_framebuffer_push_matrix (dst_framebuffer);
249 
250   graphene_matrix_init_translate (&matrix,
251                                   &GRAPHENE_POINT3D_INIT (-dst_width / 2.0,
252                                                           -dst_height / 2.0,
253                                                           0.f));
254   graphene_matrix_scale (&matrix,
255                          1.0 / (dst_width / 2.0),
256                          -1.0 / (dst_height / 2.0),
257                          0.f);
258   cogl_framebuffer_set_projection_matrix (dst_framebuffer, &matrix);
259   cogl_framebuffer_set_viewport (dst_framebuffer,
260                                  0, 0, dst_width, dst_height);
261 
262   n_rectangles = cairo_region_num_rectangles (redraw_clip);
263   coordinates = g_newa (float, 2 * 4 * n_rectangles);
264 
265   for (i = 0; i < n_rectangles; i++)
266     {
267       cairo_rectangle_int_t src_rect;
268       cairo_rectangle_int_t dst_rect;
269 
270       cairo_region_get_rectangle (redraw_clip, i, &src_rect);
271       _clutter_util_rectangle_offset (&src_rect,
272                                       -view_layout.x,
273                                       -view_layout.y,
274                                       &src_rect);
275 
276       clutter_stage_view_transform_rect_to_onscreen (view,
277                                                      &src_rect,
278                                                      onscreen_layout.width,
279                                                      onscreen_layout.height,
280                                                      &dst_rect);
281 
282       coordinates[i * 8 + 0] = (float) dst_rect.x * view_scale;
283       coordinates[i * 8 + 1] = (float) dst_rect.y * view_scale;
284       coordinates[i * 8 + 2] = ((float) (dst_rect.x + dst_rect.width) *
285                                 view_scale);
286       coordinates[i * 8 + 3] = ((float) (dst_rect.y + dst_rect.height) *
287                                 view_scale);
288 
289       coordinates[i * 8 + 4] = (((float) dst_rect.x / (float) dst_width) *
290                                 view_scale);
291       coordinates[i * 8 + 5] = (((float) dst_rect.y / (float) dst_height) *
292                                 view_scale);
293       coordinates[i * 8 + 6] = ((float) (dst_rect.x + dst_rect.width) /
294                                 (float) dst_width) * view_scale;
295       coordinates[i * 8 + 7] = ((float) (dst_rect.y + dst_rect.height) /
296                                 (float) dst_height) * view_scale;
297     }
298 
299   cogl_framebuffer_draw_textured_rectangles (dst_framebuffer,
300                                              pipeline,
301                                              coordinates,
302                                              n_rectangles);
303 
304   cogl_framebuffer_pop_matrix (dst_framebuffer);
305 }
306 
307 static gboolean
is_shadowfb_double_buffered(ClutterStageView * view)308 is_shadowfb_double_buffered (ClutterStageView *view)
309 {
310   ClutterStageViewPrivate *priv =
311     clutter_stage_view_get_instance_private (view);
312 
313   return priv->shadow.dma_buf.handles[0] && priv->shadow.dma_buf.handles[1];
314 }
315 
316 static gboolean
init_dma_buf_shadowfbs(ClutterStageView * view,CoglContext * cogl_context,int width,int height,GError ** error)317 init_dma_buf_shadowfbs (ClutterStageView  *view,
318                         CoglContext       *cogl_context,
319                         int                width,
320                         int                height,
321                         GError           **error)
322 {
323   ClutterStageViewPrivate *priv =
324     clutter_stage_view_get_instance_private (view);
325   CoglRenderer *cogl_renderer = cogl_context_get_renderer (cogl_context);
326   CoglFramebuffer *initial_shadowfb;
327 
328   if (!cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE))
329     {
330       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
331                    "Buffer age not supported");
332       return FALSE;
333     }
334 
335   if (!COGL_IS_ONSCREEN (priv->framebuffer))
336     {
337       g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
338                    "Tried to use shadow buffer without onscreen");
339       return FALSE;
340     }
341 
342   priv->shadow.dma_buf.handles[0] = cogl_renderer_create_dma_buf (cogl_renderer,
343                                                                   width, height,
344                                                                   error);
345   if (!priv->shadow.dma_buf.handles[0])
346     return FALSE;
347 
348   priv->shadow.dma_buf.handles[1] = cogl_renderer_create_dma_buf (cogl_renderer,
349                                                                   width, height,
350                                                                   error);
351   if (!priv->shadow.dma_buf.handles[1])
352     {
353       g_clear_pointer (&priv->shadow.dma_buf.handles[0],
354                        cogl_dma_buf_handle_free);
355       return FALSE;
356     }
357 
358   priv->shadow.dma_buf.damage_history = clutter_damage_history_new ();
359 
360   initial_shadowfb =
361     cogl_dma_buf_handle_get_framebuffer (priv->shadow.dma_buf.handles[0]);
362   priv->shadow.framebuffer = COGL_OFFSCREEN (g_object_ref (initial_shadowfb));
363 
364   return TRUE;
365 }
366 
367 static CoglOffscreen *
create_offscreen_framebuffer(CoglContext * context,int width,int height,GError ** error)368 create_offscreen_framebuffer (CoglContext  *context,
369                               int           width,
370                               int           height,
371                               GError      **error)
372 {
373   CoglOffscreen *framebuffer;
374   CoglTexture2D *texture;
375 
376   texture = cogl_texture_2d_new_with_size (context, width, height);
377   cogl_primitive_texture_set_auto_mipmap (COGL_PRIMITIVE_TEXTURE (texture),
378                                           FALSE);
379 
380   if (!cogl_texture_allocate (COGL_TEXTURE (texture), error))
381     {
382       cogl_object_unref (texture);
383       return FALSE;
384     }
385 
386   framebuffer = cogl_offscreen_new_with_texture (COGL_TEXTURE (texture));
387   cogl_object_unref (texture);
388   if (!cogl_framebuffer_allocate (COGL_FRAMEBUFFER (framebuffer), error))
389     {
390       g_object_unref (framebuffer);
391       return FALSE;
392     }
393 
394   return framebuffer;
395 }
396 
397 static gboolean
init_fallback_shadowfb(ClutterStageView * view,CoglContext * cogl_context,int width,int height,GError ** error)398 init_fallback_shadowfb (ClutterStageView  *view,
399                         CoglContext       *cogl_context,
400                         int                width,
401                         int                height,
402                         GError           **error)
403 {
404   ClutterStageViewPrivate *priv =
405     clutter_stage_view_get_instance_private (view);
406   CoglOffscreen *offscreen;
407 
408   offscreen = create_offscreen_framebuffer (cogl_context, width, height, error);
409   if (!offscreen)
410     return FALSE;
411 
412   priv->shadow.framebuffer = offscreen;
413   return TRUE;
414 }
415 
416 static void
init_shadowfb(ClutterStageView * view)417 init_shadowfb (ClutterStageView *view)
418 {
419   ClutterStageViewPrivate *priv =
420     clutter_stage_view_get_instance_private (view);
421   g_autoptr (GError) error = NULL;
422   int width;
423   int height;
424   CoglContext *cogl_context;
425 
426   width = cogl_framebuffer_get_width (priv->framebuffer);
427   height = cogl_framebuffer_get_height (priv->framebuffer);
428   cogl_context = cogl_framebuffer_get_context (priv->framebuffer);
429 
430   if (g_strcmp0 (g_getenv ("MUTTER_DEBUG_ENABLE_DOUBLE_SHADOWFB"), "1") == 0)
431     {
432       if (init_dma_buf_shadowfbs (view, cogl_context, width, height, &error))
433         {
434           g_message ("Initialized double buffered shadow fb for %s",
435                      priv->name);
436           return;
437         }
438 
439       g_warning ("Failed to initialize double buffered shadow fb for %s: %s",
440                  priv->name, error->message);
441       g_clear_error (&error);
442     }
443 
444   if (!init_fallback_shadowfb (view, cogl_context, width, height, &error))
445     {
446       g_warning ("Failed to initialize single buffered shadow fb for %s: %s",
447                  priv->name, error->message);
448     }
449   else
450     {
451       g_message ("Initialized single buffered shadow fb for %s", priv->name);
452     }
453 }
454 
455 void
clutter_stage_view_after_paint(ClutterStageView * view,cairo_region_t * redraw_clip)456 clutter_stage_view_after_paint (ClutterStageView *view,
457                                 cairo_region_t   *redraw_clip)
458 {
459   ClutterStageViewPrivate *priv =
460     clutter_stage_view_get_instance_private (view);
461 
462   if (priv->offscreen)
463     {
464       clutter_stage_view_ensure_offscreen_blit_pipeline (view);
465 
466       if (priv->shadow.framebuffer)
467         {
468           CoglFramebuffer *shadowfb =
469             COGL_FRAMEBUFFER (priv->shadow.framebuffer);
470 
471           paint_transformed_framebuffer (view,
472                                          priv->offscreen_pipeline,
473                                          priv->offscreen,
474                                          shadowfb,
475                                          redraw_clip);
476         }
477       else
478         {
479           paint_transformed_framebuffer (view,
480                                          priv->offscreen_pipeline,
481                                          priv->offscreen,
482                                          priv->framebuffer,
483                                          redraw_clip);
484         }
485     }
486 }
487 
488 static gboolean
is_tile_dirty(cairo_rectangle_int_t * tile,uint8_t * current_data,uint8_t * prev_data,int bpp,int stride)489 is_tile_dirty (cairo_rectangle_int_t *tile,
490                uint8_t               *current_data,
491                uint8_t               *prev_data,
492                int                    bpp,
493                int                    stride)
494 {
495   int y;
496 
497   for (y = tile->y; y < tile->y + tile->height; y++)
498     {
499       if (memcmp (prev_data + y * stride + tile->x * bpp,
500                   current_data + y * stride + tile->x * bpp,
501                   tile->width * bpp) != 0)
502         return TRUE;
503     }
504 
505   return FALSE;
506 }
507 
508 static int
flip_dma_buf_idx(int idx)509 flip_dma_buf_idx (int idx)
510 {
511   return (idx + 1) % 2;
512 }
513 
514 static cairo_region_t *
find_damaged_tiles(ClutterStageView * view,const cairo_region_t * damage_region,GError ** error)515 find_damaged_tiles (ClutterStageView      *view,
516                     const cairo_region_t  *damage_region,
517                     GError               **error)
518 {
519   ClutterStageViewPrivate *priv =
520     clutter_stage_view_get_instance_private (view);
521   cairo_region_t *tile_damage_region;
522   cairo_rectangle_int_t damage_extents;
523   cairo_rectangle_int_t fb_rect;
524   int prev_dma_buf_idx;
525   CoglDmaBufHandle *prev_dma_buf_handle;
526   uint8_t *prev_data;
527   int current_dma_buf_idx;
528   CoglDmaBufHandle *current_dma_buf_handle;
529   uint8_t *current_data;
530   int width, height, stride, bpp;
531   int tile_x_min, tile_x_max;
532   int tile_y_min, tile_y_max;
533   int tile_x, tile_y;
534   const int tile_size = 16;
535 
536   prev_dma_buf_idx = flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
537   prev_dma_buf_handle = priv->shadow.dma_buf.handles[prev_dma_buf_idx];
538 
539   current_dma_buf_idx = priv->shadow.dma_buf.current_idx;
540   current_dma_buf_handle = priv->shadow.dma_buf.handles[current_dma_buf_idx];
541 
542   width = cogl_dma_buf_handle_get_width (current_dma_buf_handle);
543   height = cogl_dma_buf_handle_get_height (current_dma_buf_handle);
544   stride = cogl_dma_buf_handle_get_stride (current_dma_buf_handle);
545   bpp = cogl_dma_buf_handle_get_bpp (current_dma_buf_handle);
546 
547   cogl_framebuffer_finish (COGL_FRAMEBUFFER (priv->shadow.framebuffer));
548 #ifdef __linux__
549   if (!cogl_dma_buf_handle_sync_read_start (prev_dma_buf_handle, error))
550     return NULL;
551 
552   if (!cogl_dma_buf_handle_sync_read_start (current_dma_buf_handle, error))
553     goto err_sync_read_current;
554 #endif
555   prev_data = cogl_dma_buf_handle_mmap (prev_dma_buf_handle, error);
556   if (!prev_data)
557     goto err_mmap_prev;
558   current_data = cogl_dma_buf_handle_mmap (current_dma_buf_handle, error);
559   if (!current_data)
560     goto err_mmap_current;
561 
562   fb_rect = (cairo_rectangle_int_t) {
563     .width = width,
564     .height = height,
565   };
566 
567   cairo_region_get_extents (damage_region, &damage_extents);
568 
569   tile_x_min = damage_extents.x / tile_size;
570   tile_x_max = ((damage_extents.x + damage_extents.width + tile_size - 1) /
571                 tile_size);
572   tile_y_min = damage_extents.y / tile_size;
573   tile_y_max = ((damage_extents.y + damage_extents.height + tile_size - 1) /
574                 tile_size);
575 
576   tile_damage_region = cairo_region_create ();
577 
578   for (tile_y = tile_y_min; tile_y <= tile_y_max; tile_y++)
579     {
580       for (tile_x = tile_x_min; tile_x <= tile_x_max; tile_x++)
581         {
582           cairo_rectangle_int_t tile = {
583             .x = tile_x * tile_size,
584             .y = tile_y * tile_size,
585             .width = tile_size,
586             .height = tile_size,
587           };
588 
589           if (cairo_region_contains_rectangle (damage_region, &tile) ==
590               CAIRO_REGION_OVERLAP_OUT)
591             continue;
592 
593           _clutter_util_rectangle_intersection (&tile, &fb_rect, &tile);
594 
595           if (is_tile_dirty (&tile, current_data, prev_data, bpp, stride))
596             cairo_region_union_rectangle (tile_damage_region, &tile);
597         }
598     }
599 #ifdef __linux__
600   if (!cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, error))
601     {
602       g_warning ("Failed to end DMA buffer read synchronization: %s",
603                  (*error)->message);
604       g_clear_error (error);
605     }
606 
607   if (!cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, error))
608     {
609       g_warning ("Failed to end DMA buffer read synchronization: %s",
610                  (*error)->message);
611       g_clear_error (error);
612     }
613 #endif
614   cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
615   cogl_dma_buf_handle_munmap (current_dma_buf_handle, current_data, NULL);
616 
617   cairo_region_intersect (tile_damage_region, damage_region);
618 
619   return tile_damage_region;
620 
621 err_mmap_current:
622   cogl_dma_buf_handle_munmap (prev_dma_buf_handle, prev_data, NULL);
623 #ifdef __linux__
624 err_mmap_prev:
625   cogl_dma_buf_handle_sync_read_end (current_dma_buf_handle, NULL);
626 
627 err_sync_read_current:
628   cogl_dma_buf_handle_sync_read_end (prev_dma_buf_handle, NULL);
629 #else
630 err_mmap_prev:
631 err_sync_read_current:
632 #endif
633   return NULL;
634 }
635 
636 static void
swap_dma_buf_framebuffer(ClutterStageView * view)637 swap_dma_buf_framebuffer (ClutterStageView *view)
638 {
639   ClutterStageViewPrivate *priv =
640     clutter_stage_view_get_instance_private (view);
641   int next_idx;
642   CoglDmaBufHandle *next_dma_buf_handle;
643   CoglFramebuffer *next_framebuffer;
644 
645   next_idx = ((priv->shadow.dma_buf.current_idx + 1) %
646               G_N_ELEMENTS (priv->shadow.dma_buf.handles));
647   priv->shadow.dma_buf.current_idx = next_idx;
648 
649   next_dma_buf_handle = priv->shadow.dma_buf.handles[next_idx];
650   next_framebuffer =
651     cogl_dma_buf_handle_get_framebuffer (next_dma_buf_handle);
652   g_clear_object (&priv->shadow.framebuffer);
653   priv->shadow.framebuffer = COGL_OFFSCREEN (g_object_ref (next_framebuffer));
654 }
655 
656 static void
copy_shadowfb_to_onscreen(ClutterStageView * view,const cairo_region_t * swap_region)657 copy_shadowfb_to_onscreen (ClutterStageView     *view,
658                            const cairo_region_t *swap_region)
659 {
660   ClutterStageViewPrivate *priv =
661     clutter_stage_view_get_instance_private (view);
662   ClutterDamageHistory *damage_history = priv->shadow.dma_buf.damage_history;
663   cairo_region_t *damage_region;
664   int age;
665   int i;
666 
667   if (cairo_region_is_empty (swap_region))
668     {
669       cairo_rectangle_int_t full_damage = {
670         .width = cogl_framebuffer_get_width (priv->framebuffer),
671         .height = cogl_framebuffer_get_height (priv->framebuffer),
672       };
673       damage_region = cairo_region_create_rectangle (&full_damage);
674     }
675   else
676     {
677       damage_region = cairo_region_copy (swap_region);
678     }
679 
680   if (is_shadowfb_double_buffered (view))
681     {
682       CoglOnscreen *onscreen = COGL_ONSCREEN (priv->framebuffer);
683       cairo_region_t *changed_region;
684 
685       if (cogl_onscreen_get_frame_counter (onscreen) >= 1)
686         {
687           g_autoptr (GError) error = NULL;
688 
689           changed_region = find_damaged_tiles (view, damage_region, &error);
690           if (!changed_region)
691             {
692               int other_dma_buf_idx;
693 
694               g_warning ("Disabling actual damage detection: %s",
695                          error->message);
696 
697               other_dma_buf_idx =
698                 flip_dma_buf_idx (priv->shadow.dma_buf.current_idx);
699               g_clear_pointer (&priv->shadow.dma_buf.handles[other_dma_buf_idx],
700                                cogl_dma_buf_handle_free);
701             }
702         }
703       else
704         {
705           changed_region = cairo_region_copy (damage_region);
706         }
707 
708       if (changed_region)
709         {
710           int buffer_age;
711 
712           clutter_damage_history_record (damage_history, changed_region);
713 
714           buffer_age = cogl_onscreen_get_buffer_age (onscreen);
715           if (clutter_damage_history_is_age_valid (damage_history, buffer_age))
716             {
717               for (age = 1; age <= buffer_age; age++)
718                 {
719                   const cairo_region_t *old_damage;
720 
721                   old_damage = clutter_damage_history_lookup (damage_history, age);
722                   cairo_region_union (changed_region, old_damage);
723                 }
724 
725               cairo_region_destroy (damage_region);
726               damage_region = g_steal_pointer (&changed_region);
727             }
728           else
729             {
730               cairo_region_destroy (changed_region);
731             }
732 
733           clutter_damage_history_step (damage_history);
734         }
735     }
736 
737   for (i = 0; i < cairo_region_num_rectangles (damage_region); i++)
738     {
739       CoglFramebuffer *shadowfb = COGL_FRAMEBUFFER (priv->shadow.framebuffer);
740       g_autoptr (GError) error = NULL;
741       cairo_rectangle_int_t rect;
742 
743       cairo_region_get_rectangle (damage_region, i, &rect);
744 
745       if (!cogl_blit_framebuffer (shadowfb,
746                                   priv->framebuffer,
747                                   rect.x, rect.y,
748                                   rect.x, rect.y,
749                                   rect.width, rect.height,
750                                   &error))
751         {
752           g_warning ("Failed to blit shadow buffer: %s", error->message);
753           cairo_region_destroy (damage_region);
754           return;
755         }
756     }
757 
758   cairo_region_destroy (damage_region);
759 
760   if (is_shadowfb_double_buffered (view))
761     swap_dma_buf_framebuffer (view);
762 }
763 
764 void
clutter_stage_view_before_swap_buffer(ClutterStageView * view,const cairo_region_t * swap_region)765 clutter_stage_view_before_swap_buffer (ClutterStageView     *view,
766                                        const cairo_region_t *swap_region)
767 {
768   ClutterStageViewPrivate *priv =
769     clutter_stage_view_get_instance_private (view);
770 
771   if (priv->shadow.framebuffer)
772     copy_shadowfb_to_onscreen (view, swap_region);
773 }
774 
775 float
clutter_stage_view_get_scale(ClutterStageView * view)776 clutter_stage_view_get_scale (ClutterStageView *view)
777 {
778   ClutterStageViewPrivate *priv =
779     clutter_stage_view_get_instance_private (view);
780 
781   return priv->scale;
782 }
783 
784 typedef void (*FrontBufferCallback) (CoglFramebuffer *framebuffer,
785                                      gconstpointer    user_data);
786 
787 static void
clutter_stage_view_foreach_front_buffer(ClutterStageView * view,FrontBufferCallback callback,gconstpointer user_data)788 clutter_stage_view_foreach_front_buffer (ClutterStageView    *view,
789                                          FrontBufferCallback  callback,
790                                          gconstpointer        user_data)
791 {
792   ClutterStageViewPrivate *priv =
793     clutter_stage_view_get_instance_private (view);
794 
795   if (priv->offscreen)
796     {
797       callback (COGL_FRAMEBUFFER (priv->offscreen), user_data);
798     }
799   else if (priv->shadow.framebuffer)
800     {
801       if (is_shadowfb_double_buffered (view))
802         {
803           int i;
804 
805           for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
806             {
807               CoglDmaBufHandle *handle = priv->shadow.dma_buf.handles[i];
808               CoglFramebuffer *framebuffer =
809                 cogl_dma_buf_handle_get_framebuffer (handle);
810 
811               callback (framebuffer, user_data);
812             }
813         }
814       else
815         {
816           callback (COGL_FRAMEBUFFER (priv->shadow.framebuffer), user_data);
817         }
818     }
819   else
820     {
821       callback (priv->framebuffer, user_data);
822     }
823 }
824 
825 gboolean
clutter_stage_view_is_dirty_viewport(ClutterStageView * view)826 clutter_stage_view_is_dirty_viewport (ClutterStageView *view)
827 {
828   ClutterStageViewPrivate *priv =
829     clutter_stage_view_get_instance_private (view);
830 
831   return priv->dirty_viewport;
832 }
833 
834 void
clutter_stage_view_invalidate_viewport(ClutterStageView * view)835 clutter_stage_view_invalidate_viewport (ClutterStageView *view)
836 {
837   ClutterStageViewPrivate *priv =
838     clutter_stage_view_get_instance_private (view);
839 
840   priv->dirty_viewport = TRUE;
841 }
842 
843 static void
set_framebuffer_viewport(CoglFramebuffer * framebuffer,gconstpointer user_data)844 set_framebuffer_viewport (CoglFramebuffer *framebuffer,
845                           gconstpointer    user_data)
846 {
847   const graphene_rect_t *rect = user_data;
848 
849   cogl_framebuffer_set_viewport (framebuffer,
850                                  rect->origin.x,
851                                  rect->origin.y,
852                                  rect->size.width,
853                                  rect->size.height);
854 }
855 
856 void
clutter_stage_view_set_viewport(ClutterStageView * view,float x,float y,float width,float height)857 clutter_stage_view_set_viewport (ClutterStageView *view,
858                                  float             x,
859                                  float             y,
860                                  float             width,
861                                  float             height)
862 {
863   ClutterStageViewPrivate *priv =
864     clutter_stage_view_get_instance_private (view);
865   graphene_rect_t rect;
866 
867   priv->dirty_viewport = FALSE;
868 
869   rect = (graphene_rect_t) {
870     .origin = { .x = x, .y = y },
871     .size = { .width = width, .height = height },
872   };
873   clutter_stage_view_foreach_front_buffer (view,
874                                            set_framebuffer_viewport,
875                                            &rect);
876 }
877 
878 gboolean
clutter_stage_view_is_dirty_projection(ClutterStageView * view)879 clutter_stage_view_is_dirty_projection (ClutterStageView *view)
880 {
881   ClutterStageViewPrivate *priv =
882     clutter_stage_view_get_instance_private (view);
883 
884   return priv->dirty_projection;
885 }
886 
887 static void
set_framebuffer_projection_matrix(CoglFramebuffer * framebuffer,gconstpointer user_data)888 set_framebuffer_projection_matrix (CoglFramebuffer *framebuffer,
889                                    gconstpointer    user_data)
890 {
891   cogl_framebuffer_set_projection_matrix (framebuffer, user_data);
892 }
893 
894 void
clutter_stage_view_invalidate_projection(ClutterStageView * view)895 clutter_stage_view_invalidate_projection (ClutterStageView *view)
896 {
897   ClutterStageViewPrivate *priv =
898     clutter_stage_view_get_instance_private (view);
899 
900   priv->dirty_projection = TRUE;
901 }
902 
903 void
clutter_stage_view_set_projection(ClutterStageView * view,const graphene_matrix_t * matrix)904 clutter_stage_view_set_projection (ClutterStageView        *view,
905                                    const graphene_matrix_t *matrix)
906 {
907   ClutterStageViewPrivate *priv =
908     clutter_stage_view_get_instance_private (view);
909 
910   priv->dirty_projection = FALSE;
911   clutter_stage_view_foreach_front_buffer (view,
912                                            set_framebuffer_projection_matrix,
913                                            matrix);
914 }
915 
916 void
clutter_stage_view_get_offscreen_transformation_matrix(ClutterStageView * view,graphene_matrix_t * matrix)917 clutter_stage_view_get_offscreen_transformation_matrix (ClutterStageView  *view,
918                                                         graphene_matrix_t *matrix)
919 {
920   ClutterStageViewClass *view_class = CLUTTER_STAGE_VIEW_GET_CLASS (view);
921 
922   view_class->get_offscreen_transformation_matrix (view, matrix);
923 }
924 
925 void
clutter_stage_view_add_redraw_clip(ClutterStageView * view,const cairo_rectangle_int_t * clip)926 clutter_stage_view_add_redraw_clip (ClutterStageView            *view,
927                                     const cairo_rectangle_int_t *clip)
928 {
929   ClutterStageViewPrivate *priv =
930     clutter_stage_view_get_instance_private (view);
931 
932   if (priv->has_redraw_clip && !priv->redraw_clip)
933     return;
934 
935   if (!clip)
936     {
937       g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
938       priv->has_redraw_clip = TRUE;
939       return;
940     }
941 
942   if (clip->width == 0 || clip->height == 0)
943     return;
944 
945   if (!priv->redraw_clip)
946     {
947       if (!clutter_util_rectangle_equal (&priv->layout, clip))
948         priv->redraw_clip = cairo_region_create_rectangle (clip);
949     }
950   else
951     {
952       cairo_region_union_rectangle (priv->redraw_clip, clip);
953 
954       if (cairo_region_num_rectangles (priv->redraw_clip) == 1)
955         {
956           cairo_rectangle_int_t redraw_clip_extents;
957 
958           cairo_region_get_extents (priv->redraw_clip, &redraw_clip_extents);
959           if (clutter_util_rectangle_equal (&priv->layout, &redraw_clip_extents))
960             g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
961         }
962     }
963 
964   priv->has_redraw_clip = TRUE;
965 }
966 
967 gboolean
clutter_stage_view_has_redraw_clip(ClutterStageView * view)968 clutter_stage_view_has_redraw_clip (ClutterStageView *view)
969 {
970   ClutterStageViewPrivate *priv =
971     clutter_stage_view_get_instance_private (view);
972 
973   return priv->has_redraw_clip;
974 }
975 
976 gboolean
clutter_stage_view_has_full_redraw_clip(ClutterStageView * view)977 clutter_stage_view_has_full_redraw_clip (ClutterStageView *view)
978 {
979   ClutterStageViewPrivate *priv =
980     clutter_stage_view_get_instance_private (view);
981 
982   return priv->has_redraw_clip && !priv->redraw_clip;
983 }
984 
985 const cairo_region_t *
clutter_stage_view_peek_redraw_clip(ClutterStageView * view)986 clutter_stage_view_peek_redraw_clip (ClutterStageView *view)
987 {
988   ClutterStageViewPrivate *priv =
989     clutter_stage_view_get_instance_private (view);
990 
991   return priv->redraw_clip;
992 }
993 
994 cairo_region_t *
clutter_stage_view_take_redraw_clip(ClutterStageView * view)995 clutter_stage_view_take_redraw_clip (ClutterStageView *view)
996 {
997   ClutterStageViewPrivate *priv =
998     clutter_stage_view_get_instance_private (view);
999 
1000   priv->has_redraw_clip = FALSE;
1001 
1002   return g_steal_pointer (&priv->redraw_clip);
1003 }
1004 
1005 static void
clutter_stage_default_get_offscreen_transformation_matrix(ClutterStageView * view,graphene_matrix_t * matrix)1006 clutter_stage_default_get_offscreen_transformation_matrix (ClutterStageView  *view,
1007                                                            graphene_matrix_t *matrix)
1008 {
1009   graphene_matrix_init_identity (matrix);
1010 }
1011 
1012 void
clutter_stage_view_assign_next_scanout(ClutterStageView * view,CoglScanout * scanout)1013 clutter_stage_view_assign_next_scanout (ClutterStageView *view,
1014                                         CoglScanout      *scanout)
1015 {
1016   ClutterStageViewPrivate *priv =
1017     clutter_stage_view_get_instance_private (view);
1018 
1019   g_set_object (&priv->next_scanout, scanout);
1020 }
1021 
1022 CoglScanout *
clutter_stage_view_take_scanout(ClutterStageView * view)1023 clutter_stage_view_take_scanout (ClutterStageView *view)
1024 {
1025   ClutterStageViewPrivate *priv =
1026     clutter_stage_view_get_instance_private (view);
1027 
1028   return g_steal_pointer (&priv->next_scanout);
1029 }
1030 
1031 /**
1032  * clutter_stage_view_peek_scanout: (skip)
1033  */
1034 CoglScanout *
clutter_stage_view_peek_scanout(ClutterStageView * view)1035 clutter_stage_view_peek_scanout (ClutterStageView *view)
1036 {
1037   ClutterStageViewPrivate *priv =
1038     clutter_stage_view_get_instance_private (view);
1039 
1040   return priv->next_scanout;
1041 }
1042 
1043 void
clutter_stage_view_schedule_update(ClutterStageView * view)1044 clutter_stage_view_schedule_update (ClutterStageView *view)
1045 {
1046   ClutterStageViewPrivate *priv =
1047     clutter_stage_view_get_instance_private (view);
1048 
1049   clutter_frame_clock_schedule_update (priv->frame_clock);
1050 }
1051 
1052 float
clutter_stage_view_get_refresh_rate(ClutterStageView * view)1053 clutter_stage_view_get_refresh_rate (ClutterStageView *view)
1054 {
1055   ClutterStageViewPrivate *priv =
1056     clutter_stage_view_get_instance_private (view);
1057 
1058   return priv->refresh_rate;
1059 }
1060 
1061 /**
1062  * clutter_stage_view_get_frame_clock: (skip)
1063  */
1064 ClutterFrameClock *
clutter_stage_view_get_frame_clock(ClutterStageView * view)1065 clutter_stage_view_get_frame_clock (ClutterStageView *view)
1066 {
1067   ClutterStageViewPrivate *priv =
1068     clutter_stage_view_get_instance_private (view);
1069 
1070   return priv->frame_clock;
1071 }
1072 
1073 static void
handle_frame_clock_before_frame(ClutterFrameClock * frame_clock,int64_t frame_count,gpointer user_data)1074 handle_frame_clock_before_frame (ClutterFrameClock *frame_clock,
1075                                  int64_t            frame_count,
1076                                  gpointer           user_data)
1077 {
1078   ClutterStageView *view = user_data;
1079   ClutterStageViewPrivate *priv =
1080     clutter_stage_view_get_instance_private (view);
1081 
1082   _clutter_stage_process_queued_events (priv->stage);
1083 }
1084 
1085 static void
begin_frame_timing_measurement(ClutterStageView * view)1086 begin_frame_timing_measurement (ClutterStageView *view)
1087 {
1088   ClutterStageViewPrivate *priv =
1089     clutter_stage_view_get_instance_private (view);
1090 
1091   priv->frame_timings.began_draw_time_us = g_get_monotonic_time ();
1092 }
1093 
1094 static void
end_frame_timing_measurement(ClutterStageView * view)1095 end_frame_timing_measurement (ClutterStageView *view)
1096 {
1097   ClutterStageViewPrivate *priv =
1098     clutter_stage_view_get_instance_private (view);
1099   int64_t now_us = g_get_monotonic_time ();
1100   int64_t draw_time_us;
1101 
1102   draw_time_us = now_us - priv->frame_timings.began_draw_time_us;
1103 
1104   priv->frame_timings.frame_count++;
1105   priv->frame_timings.cumulative_draw_time_us += draw_time_us;
1106   if (draw_time_us > priv->frame_timings.worst_draw_time_us)
1107     priv->frame_timings.worst_draw_time_us = draw_time_us;
1108 
1109   if (priv->frame_timings.frame_count && priv->frame_timings.last_print_time_us)
1110     {
1111       float time_since_last_print_s;
1112 
1113       time_since_last_print_s =
1114         (now_us - priv->frame_timings.last_print_time_us) /
1115         (float) G_USEC_PER_SEC;
1116 
1117       if (time_since_last_print_s >= 1.0)
1118         {
1119           float avg_fps, avg_draw_time_ms, worst_draw_time_ms;
1120 
1121           avg_fps = priv->frame_timings.frame_count / time_since_last_print_s;
1122 
1123           avg_draw_time_ms =
1124             (priv->frame_timings.cumulative_draw_time_us / 1000.0) /
1125             priv->frame_timings.frame_count;
1126 
1127           worst_draw_time_ms = priv->frame_timings.worst_draw_time_us / 1000.0;
1128 
1129           g_print ("*** %s frame timings over %.01fs: "
1130                    "%.02f FPS, average: %.01fms, peak: %.01fms\n",
1131                    priv->name,
1132                    time_since_last_print_s,
1133                    avg_fps,
1134                    avg_draw_time_ms,
1135                    worst_draw_time_ms);
1136 
1137           priv->frame_timings.frame_count = 0;
1138           priv->frame_timings.cumulative_draw_time_us = 0;
1139           priv->frame_timings.worst_draw_time_us = 0;
1140           priv->frame_timings.last_print_time_us = now_us;
1141         }
1142     }
1143   else if (!priv->frame_timings.last_print_time_us)
1144     {
1145       priv->frame_timings.last_print_time_us = now_us;
1146     }
1147 }
1148 
1149 static ClutterFrameResult
handle_frame_clock_frame(ClutterFrameClock * frame_clock,int64_t frame_count,int64_t time_us,gpointer user_data)1150 handle_frame_clock_frame (ClutterFrameClock *frame_clock,
1151                           int64_t            frame_count,
1152                           int64_t            time_us,
1153                           gpointer           user_data)
1154 {
1155   ClutterStageView *view = user_data;
1156   ClutterStageViewPrivate *priv =
1157     clutter_stage_view_get_instance_private (view);
1158   ClutterStage *stage = priv->stage;
1159   ClutterStageWindow *stage_window = _clutter_stage_get_window (stage);
1160   g_autoptr (GSList) devices = NULL;
1161   ClutterFrame frame;
1162 
1163   if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
1164     return CLUTTER_FRAME_RESULT_IDLE;
1165 
1166   if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage)))
1167     return CLUTTER_FRAME_RESULT_IDLE;
1168 
1169   if (!clutter_actor_is_mapped (CLUTTER_ACTOR (stage)))
1170     return CLUTTER_FRAME_RESULT_IDLE;
1171 
1172   if (_clutter_context_get_show_fps ())
1173     begin_frame_timing_measurement (view);
1174 
1175   _clutter_run_repaint_functions (CLUTTER_REPAINT_FLAGS_PRE_PAINT);
1176   clutter_stage_emit_before_update (stage, view);
1177 
1178   clutter_stage_maybe_relayout (CLUTTER_ACTOR (stage));
1179   clutter_stage_maybe_finish_queue_redraws (stage);
1180 
1181   clutter_stage_finish_layout (stage);
1182 
1183   devices = clutter_stage_find_updated_devices (stage);
1184 
1185   frame = CLUTTER_FRAME_INIT;
1186 
1187   _clutter_stage_window_prepare_frame (stage_window, view, &frame);
1188 
1189   if (clutter_stage_view_has_redraw_clip (view))
1190     {
1191       clutter_stage_emit_before_paint (stage, view);
1192 
1193       _clutter_stage_window_redraw_view (stage_window, view, &frame);
1194 
1195       clutter_frame_clock_record_flip_time (frame_clock,
1196                                             g_get_monotonic_time ());
1197 
1198       clutter_stage_emit_after_paint (stage, view);
1199 
1200       if (_clutter_context_get_show_fps ())
1201         end_frame_timing_measurement (view);
1202     }
1203 
1204   _clutter_stage_window_finish_frame (stage_window, view, &frame);
1205 
1206   clutter_stage_update_devices (stage, devices);
1207 
1208   _clutter_run_repaint_functions (CLUTTER_REPAINT_FLAGS_POST_PAINT);
1209   clutter_stage_emit_after_update (stage, view);
1210 
1211   return clutter_frame_get_result (&frame);
1212 }
1213 
1214 static const ClutterFrameListenerIface frame_clock_listener_iface = {
1215   .before_frame = handle_frame_clock_before_frame,
1216   .frame = handle_frame_clock_frame,
1217 };
1218 
1219 void
clutter_stage_view_notify_presented(ClutterStageView * view,ClutterFrameInfo * frame_info)1220 clutter_stage_view_notify_presented (ClutterStageView *view,
1221                                      ClutterFrameInfo *frame_info)
1222 {
1223   ClutterStageViewPrivate *priv =
1224     clutter_stage_view_get_instance_private (view);
1225 
1226   clutter_stage_presented (priv->stage, view, frame_info);
1227   clutter_frame_clock_notify_presented (priv->frame_clock, frame_info);
1228 }
1229 
1230 void
clutter_stage_view_notify_ready(ClutterStageView * view)1231 clutter_stage_view_notify_ready (ClutterStageView *view)
1232 {
1233   ClutterStageViewPrivate *priv =
1234     clutter_stage_view_get_instance_private (view);
1235 
1236   clutter_frame_clock_notify_ready (priv->frame_clock);
1237 }
1238 
1239 static void
sanity_check_framebuffer(ClutterStageView * view)1240 sanity_check_framebuffer (ClutterStageView *view)
1241 {
1242   ClutterStageViewPrivate *priv =
1243     clutter_stage_view_get_instance_private (view);
1244   G_GNUC_UNUSED int fb_width, fb_height;
1245 
1246   fb_width = cogl_framebuffer_get_width (priv->framebuffer);
1247   fb_height = cogl_framebuffer_get_height (priv->framebuffer);
1248 
1249   g_warn_if_fail (fabsf (roundf (fb_width / priv->scale) -
1250                          fb_width / priv->scale) < FLT_EPSILON);
1251   g_warn_if_fail (fabsf (roundf (fb_height / priv->scale) -
1252                          fb_height / priv->scale) < FLT_EPSILON);
1253 }
1254 
1255 static void
clutter_stage_view_set_framebuffer(ClutterStageView * view,CoglFramebuffer * framebuffer)1256 clutter_stage_view_set_framebuffer (ClutterStageView *view,
1257                                     CoglFramebuffer  *framebuffer)
1258 {
1259   ClutterStageViewPrivate *priv =
1260     clutter_stage_view_get_instance_private (view);
1261 
1262   g_warn_if_fail (!priv->framebuffer);
1263   if (framebuffer)
1264     {
1265       priv->framebuffer = g_object_ref (framebuffer);
1266       sanity_check_framebuffer (view);
1267     }
1268 }
1269 
1270 static void
clutter_stage_view_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)1271 clutter_stage_view_get_property (GObject    *object,
1272                                  guint       prop_id,
1273                                  GValue     *value,
1274                                  GParamSpec *pspec)
1275 {
1276   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
1277   ClutterStageViewPrivate *priv =
1278     clutter_stage_view_get_instance_private (view);
1279 
1280   switch (prop_id)
1281     {
1282     case PROP_NAME:
1283       g_value_set_string (value, priv->name);
1284       break;
1285     case PROP_STAGE:
1286       g_value_set_boxed (value, &priv->stage);
1287       break;
1288     case PROP_LAYOUT:
1289       g_value_set_boxed (value, &priv->layout);
1290       break;
1291     case PROP_FRAMEBUFFER:
1292       g_value_set_object (value, priv->framebuffer);
1293       break;
1294     case PROP_OFFSCREEN:
1295       g_value_set_object (value, priv->offscreen);
1296       break;
1297     case PROP_USE_SHADOWFB:
1298       g_value_set_boolean (value, priv->use_shadowfb);
1299       break;
1300     case PROP_SCALE:
1301       g_value_set_float (value, priv->scale);
1302       break;
1303     case PROP_REFRESH_RATE:
1304       g_value_set_float (value, priv->refresh_rate);
1305       break;
1306     case PROP_VBLANK_DURATION_US:
1307       g_value_set_int64 (value, priv->vblank_duration_us);
1308       break;
1309     default:
1310       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1311     }
1312 }
1313 
1314 static void
clutter_stage_view_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)1315 clutter_stage_view_set_property (GObject      *object,
1316                                  guint         prop_id,
1317                                  const GValue *value,
1318                                  GParamSpec   *pspec)
1319 {
1320   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
1321   ClutterStageViewPrivate *priv =
1322     clutter_stage_view_get_instance_private (view);
1323   cairo_rectangle_int_t *layout;
1324 
1325   switch (prop_id)
1326     {
1327     case PROP_NAME:
1328       priv->name = g_value_dup_string (value);
1329       break;
1330     case PROP_STAGE:
1331       priv->stage = g_value_get_object (value);
1332       break;
1333     case PROP_LAYOUT:
1334       layout = g_value_get_boxed (value);
1335       priv->layout = *layout;
1336       break;
1337     case PROP_FRAMEBUFFER:
1338       clutter_stage_view_set_framebuffer (view, g_value_get_object (value));
1339       break;
1340     case PROP_OFFSCREEN:
1341       priv->offscreen = g_value_dup_object (value);
1342       break;
1343     case PROP_USE_SHADOWFB:
1344       priv->use_shadowfb = g_value_get_boolean (value);
1345       break;
1346     case PROP_SCALE:
1347       priv->scale = g_value_get_float (value);
1348       break;
1349     case PROP_REFRESH_RATE:
1350       priv->refresh_rate = g_value_get_float (value);
1351       break;
1352     case PROP_VBLANK_DURATION_US:
1353       priv->vblank_duration_us = g_value_get_int64 (value);
1354       break;
1355     default:
1356       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1357     }
1358 }
1359 
1360 static void
clutter_stage_view_constructed(GObject * object)1361 clutter_stage_view_constructed (GObject *object)
1362 {
1363   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
1364   ClutterStageViewPrivate *priv =
1365     clutter_stage_view_get_instance_private (view);
1366 
1367   if (priv->use_shadowfb)
1368     init_shadowfb (view);
1369 
1370   priv->frame_clock = clutter_frame_clock_new (priv->refresh_rate,
1371                                                priv->vblank_duration_us,
1372                                                &frame_clock_listener_iface,
1373                                                view);
1374 
1375   clutter_stage_view_add_redraw_clip (view, NULL);
1376   clutter_stage_view_schedule_update (view);
1377 
1378   G_OBJECT_CLASS (clutter_stage_view_parent_class)->constructed (object);
1379 }
1380 
1381 static void
clutter_stage_view_dispose(GObject * object)1382 clutter_stage_view_dispose (GObject *object)
1383 {
1384   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
1385   ClutterStageViewPrivate *priv =
1386     clutter_stage_view_get_instance_private (view);
1387   int i;
1388 
1389   g_clear_pointer (&priv->name, g_free);
1390 
1391   g_clear_object (&priv->shadow.framebuffer);
1392   for (i = 0; i < G_N_ELEMENTS (priv->shadow.dma_buf.handles); i++)
1393     {
1394       g_clear_pointer (&priv->shadow.dma_buf.handles[i],
1395                        cogl_dma_buf_handle_free);
1396     }
1397   g_clear_pointer (&priv->shadow.dma_buf.damage_history,
1398                    clutter_damage_history_free);
1399 
1400   g_clear_object (&priv->offscreen);
1401   g_clear_pointer (&priv->offscreen_pipeline, cogl_object_unref);
1402   g_clear_pointer (&priv->redraw_clip, cairo_region_destroy);
1403   g_clear_pointer (&priv->frame_clock, clutter_frame_clock_destroy);
1404 
1405   G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
1406 }
1407 
1408 static void
clutter_stage_view_finalize(GObject * object)1409 clutter_stage_view_finalize (GObject *object)
1410 {
1411   ClutterStageView *view = CLUTTER_STAGE_VIEW (object);
1412   ClutterStageViewPrivate *priv =
1413     clutter_stage_view_get_instance_private (view);
1414 
1415   g_clear_object (&priv->framebuffer);
1416 
1417   G_OBJECT_CLASS (clutter_stage_view_parent_class)->dispose (object);
1418 }
1419 
1420 static void
clutter_stage_view_init(ClutterStageView * view)1421 clutter_stage_view_init (ClutterStageView *view)
1422 {
1423   ClutterStageViewPrivate *priv =
1424     clutter_stage_view_get_instance_private (view);
1425 
1426   priv->dirty_viewport = TRUE;
1427   priv->dirty_projection = TRUE;
1428   priv->scale = 1.0;
1429   priv->refresh_rate = 60.0;
1430 }
1431 
1432 static void
clutter_stage_view_class_init(ClutterStageViewClass * klass)1433 clutter_stage_view_class_init (ClutterStageViewClass *klass)
1434 {
1435   GObjectClass *object_class = G_OBJECT_CLASS (klass);
1436 
1437   klass->get_offscreen_transformation_matrix =
1438     clutter_stage_default_get_offscreen_transformation_matrix;
1439 
1440   object_class->get_property = clutter_stage_view_get_property;
1441   object_class->set_property = clutter_stage_view_set_property;
1442   object_class->constructed = clutter_stage_view_constructed;
1443   object_class->dispose = clutter_stage_view_dispose;
1444   object_class->finalize = clutter_stage_view_finalize;
1445 
1446   obj_props[PROP_NAME] =
1447     g_param_spec_string ("name",
1448                          "Name",
1449                          "Name of view",
1450                          NULL,
1451                          G_PARAM_READWRITE |
1452                          G_PARAM_CONSTRUCT_ONLY |
1453                          G_PARAM_STATIC_STRINGS);
1454 
1455   obj_props[PROP_STAGE] =
1456     g_param_spec_object ("stage",
1457                          "The stage",
1458                          "The ClutterStage",
1459                          CLUTTER_TYPE_STAGE,
1460                          G_PARAM_READWRITE |
1461                          G_PARAM_CONSTRUCT_ONLY |
1462                          G_PARAM_STATIC_STRINGS);
1463 
1464   obj_props[PROP_LAYOUT] =
1465     g_param_spec_boxed ("layout",
1466                         "View layout",
1467                         "The view layout on the screen",
1468                         CAIRO_GOBJECT_TYPE_RECTANGLE_INT,
1469                         G_PARAM_READWRITE |
1470                         G_PARAM_CONSTRUCT |
1471                         G_PARAM_STATIC_STRINGS);
1472 
1473   obj_props[PROP_FRAMEBUFFER] =
1474     g_param_spec_object ("framebuffer",
1475                          "View framebuffer",
1476                          "The front buffer of the view",
1477                          COGL_TYPE_FRAMEBUFFER,
1478                          G_PARAM_READWRITE |
1479                          G_PARAM_CONSTRUCT |
1480                          G_PARAM_STATIC_STRINGS);
1481 
1482   obj_props[PROP_OFFSCREEN] =
1483     g_param_spec_object ("offscreen",
1484                          "Offscreen buffer",
1485                          "Framebuffer used as intermediate buffer",
1486                          COGL_TYPE_OFFSCREEN,
1487                          G_PARAM_READWRITE |
1488                          G_PARAM_CONSTRUCT_ONLY |
1489                          G_PARAM_STATIC_STRINGS);
1490 
1491   obj_props[PROP_USE_SHADOWFB] =
1492     g_param_spec_boolean ("use-shadowfb",
1493                           "Use shadowfb",
1494                           "Whether to use one or more shadow framebuffers",
1495                           FALSE,
1496                           G_PARAM_READWRITE |
1497                           G_PARAM_CONSTRUCT_ONLY |
1498                           G_PARAM_STATIC_STRINGS);
1499 
1500   obj_props[PROP_SCALE] =
1501     g_param_spec_float ("scale",
1502                         "View scale",
1503                         "The view scale",
1504                         0.5, G_MAXFLOAT, 1.0,
1505                         G_PARAM_READWRITE |
1506                         G_PARAM_CONSTRUCT |
1507                         G_PARAM_STATIC_STRINGS);
1508 
1509   obj_props[PROP_REFRESH_RATE] =
1510     g_param_spec_float ("refresh-rate",
1511                         "Refresh rate",
1512                         "Update refresh rate",
1513                         1.0, G_MAXFLOAT, 60.0,
1514                         G_PARAM_READWRITE |
1515                         G_PARAM_CONSTRUCT |
1516                         G_PARAM_STATIC_STRINGS);
1517 
1518   obj_props[PROP_VBLANK_DURATION_US] =
1519     g_param_spec_int64 ("vblank-duration-us",
1520                         "Vblank duration (µs)",
1521                         "The vblank duration",
1522                         0, G_MAXINT64, 0,
1523                         G_PARAM_READWRITE |
1524                         G_PARAM_CONSTRUCT_ONLY |
1525                         G_PARAM_STATIC_STRINGS);
1526 
1527   g_object_class_install_properties (object_class, PROP_LAST, obj_props);
1528 }
1529