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