/* * Clutter. * * An OpenGL based 'interactive canvas' library. * * Copyright (C) 2007,2008,2009,2010,2011 Intel Corporation. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see . * Authors: * Matthew Allum * Robert Bragg * Neil Roberts * Emmanuele Bassi */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define CLUTTER_ENABLE_EXPERIMENTAL_API #include "clutter-config.h" #include "clutter-stage-cogl.h" #include "clutter-actor-private.h" #include "clutter-backend-private.h" #include "clutter-debug.h" #include "clutter-event.h" #include "clutter-enum-types.h" #include "clutter-feature.h" #include "clutter-main.h" #include "clutter-private.h" #include "clutter-stage-private.h" static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface); G_DEFINE_TYPE_WITH_CODE (ClutterStageCogl, _clutter_stage_cogl, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (CLUTTER_TYPE_STAGE_WINDOW, clutter_stage_window_iface_init)); enum { PROP_0, PROP_WRAPPER, PROP_BACKEND, PROP_LAST }; static void clutter_stage_cogl_unrealize (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); CLUTTER_NOTE (BACKEND, "Unrealizing Cogl stage [%p]", stage_cogl); if (stage_cogl->onscreen != NULL) { cogl_onscreen_remove_frame_callback (stage_cogl->onscreen, stage_cogl->frame_closure); stage_cogl->frame_closure = NULL; cogl_object_unref (stage_cogl->onscreen); stage_cogl->onscreen = NULL; } stage_cogl->pending_swaps = 0; } static void frame_cb (CoglOnscreen *onscreen, CoglFrameEvent event, CoglFrameInfo *info, void *user_data) { ClutterStageCogl *stage_cogl = user_data; if (event == COGL_FRAME_EVENT_SYNC) { /* Early versions of the swap_event implementation in Mesa * deliver BufferSwapComplete event when not selected for, * so if we get a swap event we aren't expecting, just ignore it. * * https://bugs.freedesktop.org/show_bug.cgi?id=27962 * * FIXME: This issue can be hidden inside Cogl so we shouldn't * need to care about this bug here. */ if (stage_cogl->pending_swaps > 0) stage_cogl->pending_swaps--; } else if (event == COGL_FRAME_EVENT_COMPLETE) { gint64 presentation_time_cogl = cogl_frame_info_get_presentation_time (info); if (presentation_time_cogl != 0) { CoglContext *context = cogl_framebuffer_get_context (COGL_FRAMEBUFFER (onscreen)); gint64 current_time_cogl = cogl_get_clock_time (context); gint64 now = g_get_monotonic_time (); stage_cogl->last_presentation_time = now + (presentation_time_cogl - current_time_cogl) / 1000; } stage_cogl->refresh_rate = cogl_frame_info_get_refresh_rate (info); } } static gboolean clutter_stage_cogl_realize (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); ClutterBackend *backend; CoglFramebuffer *framebuffer; GError *error = NULL; gfloat width = 800; gfloat height = 600; CLUTTER_NOTE (BACKEND, "Realizing stage '%s' [%p]", G_OBJECT_TYPE_NAME (stage_cogl), stage_cogl); backend = clutter_get_default_backend (); if (backend->cogl_context == NULL) { g_warning ("Failed to realize stage: missing Cogl context"); return FALSE; } if (stage_cogl->onscreen == NULL) { stage_cogl->onscreen = cogl_onscreen_new (backend->cogl_context, width, height); } cogl_onscreen_set_swap_throttled (stage_cogl->onscreen, _clutter_get_sync_to_vblank ()); framebuffer = COGL_FRAMEBUFFER (stage_cogl->onscreen); if (!cogl_framebuffer_allocate (framebuffer, &error)) { g_warning ("Failed to allocate stage: %s", error->message); g_error_free (error); cogl_object_unref (stage_cogl->onscreen); stage_cogl->onscreen = NULL; return FALSE; } /* FIXME: for fullscreen Cogl platforms then the size we gave * will be ignored, so we need to make sure the stage size is * updated to this size. */ stage_cogl->frame_closure = cogl_onscreen_add_frame_callback (stage_cogl->onscreen, frame_cb, stage_cogl, NULL); return TRUE; } static void clutter_stage_cogl_schedule_update (ClutterStageWindow *stage_window, gint sync_delay) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); gint64 now; float refresh_rate; gint64 refresh_interval; if (stage_cogl->update_time != -1) return; now = g_get_monotonic_time (); if (sync_delay < 0) { stage_cogl->update_time = now; return; } /* We only extrapolate presentation times for 150ms - this is somewhat * arbitrary. The reasons it might not be accurate for larger times are * that the refresh interval might be wrong or the vertical refresh * might be downclocked if nothing is going on onscreen. */ if (stage_cogl->last_presentation_time == 0|| stage_cogl->last_presentation_time < now - 150000) { stage_cogl->update_time = now; return; } refresh_rate = stage_cogl->refresh_rate; if (refresh_rate == 0.0) refresh_rate = 60.0; refresh_interval = (gint64) (0.5 + 1000000 / refresh_rate); if (refresh_interval == 0) refresh_interval = 16667; /* 1/60th second */ stage_cogl->update_time = stage_cogl->last_presentation_time + 1000 * sync_delay; while (stage_cogl->update_time < now) stage_cogl->update_time += refresh_interval; } static gint64 clutter_stage_cogl_get_update_time (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); if (stage_cogl->pending_swaps) return -1; /* in the future, indefinite */ return stage_cogl->update_time; } static void clutter_stage_cogl_clear_update_time (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); stage_cogl->update_time = -1; } static ClutterActor * clutter_stage_cogl_get_wrapper (ClutterStageWindow *stage_window) { return CLUTTER_ACTOR (CLUTTER_STAGE_COGL (stage_window)->wrapper); } static void clutter_stage_cogl_show (ClutterStageWindow *stage_window, gboolean do_raise) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); clutter_actor_map (CLUTTER_ACTOR (stage_cogl->wrapper)); } static void clutter_stage_cogl_hide (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); clutter_actor_unmap (CLUTTER_ACTOR (stage_cogl->wrapper)); } static void clutter_stage_cogl_get_geometry (ClutterStageWindow *stage_window, cairo_rectangle_int_t *geometry) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); int window_scale; window_scale = _clutter_stage_window_get_scale_factor (stage_window); if (geometry != NULL) { if (stage_cogl->onscreen) { CoglFramebuffer *framebuffer = COGL_FRAMEBUFFER (stage_cogl->onscreen); geometry->x = geometry->y = 0; geometry->width = cogl_framebuffer_get_width (framebuffer) / window_scale; geometry->height = cogl_framebuffer_get_height (framebuffer) / window_scale; } else { geometry->x = geometry->y = 0; geometry->width = 800; geometry->height = 600; } } } static void clutter_stage_cogl_resize (ClutterStageWindow *stage_window, gint width, gint height) { } static gboolean clutter_stage_cogl_has_redraw_clips (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); /* NB: at the start of each new frame there is an implied clip that * clips everything (i.e. nothing would be drawn) so we need to make * sure we return True in the un-initialized case here. * * NB: a clip width of 0 means a full stage redraw has been queued * so we effectively don't have any redraw clips in that case. */ if (!stage_cogl->initialized_redraw_clip || (stage_cogl->initialized_redraw_clip && stage_cogl->bounding_redraw_clip.width != 0)) return TRUE; else return FALSE; } static gboolean clutter_stage_cogl_ignoring_redraw_clips (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); /* NB: a clip width of 0 means a full stage redraw is required */ if (stage_cogl->initialized_redraw_clip && stage_cogl->bounding_redraw_clip.width == 0) return TRUE; else return FALSE; } /* A redraw clip represents (in stage coordinates) the bounding box of * something that needs to be redraw. Typically they are added to the * StageWindow as a result of clutter_actor_queue_clipped_redraw() by * actors such as ClutterGLXTexturePixmap. All redraw clips are * discarded after the next paint. * * A NULL stage_clip means the whole stage needs to be redrawn. * * What we do with this information: * - we keep track of the bounding box for all redraw clips * - when we come to redraw; we scissor the redraw to that box and use * glBlitFramebuffer to present the redraw to the front * buffer. */ static void clutter_stage_cogl_add_redraw_clip (ClutterStageWindow *stage_window, cairo_rectangle_int_t *stage_clip) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); /* If we are already forced to do a full stage redraw then bail early */ if (clutter_stage_cogl_ignoring_redraw_clips (stage_window)) return; /* A NULL stage clip means a full stage redraw has been queued and * we keep track of this by setting a zero width * stage_cogl->bounding_redraw_clip */ if (stage_clip == NULL) { stage_cogl->bounding_redraw_clip.width = 0; stage_cogl->initialized_redraw_clip = TRUE; return; } /* Ignore requests to add degenerate/empty clip rectangles */ if (stage_clip->width == 0 || stage_clip->height == 0) return; if (!stage_cogl->initialized_redraw_clip) { stage_cogl->bounding_redraw_clip = *stage_clip; } else if (stage_cogl->bounding_redraw_clip.width > 0) { _clutter_util_rectangle_union (&stage_cogl->bounding_redraw_clip, stage_clip, &stage_cogl->bounding_redraw_clip); } stage_cogl->initialized_redraw_clip = TRUE; } static gboolean clutter_stage_cogl_get_redraw_clip_bounds (ClutterStageWindow *stage_window, cairo_rectangle_int_t *stage_clip) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); if (stage_cogl->using_clipped_redraw) { *stage_clip = stage_cogl->bounding_redraw_clip; return TRUE; } return FALSE; } static inline gboolean valid_buffer_age (ClutterStageCogl *stage_cogl, int age) { if (age <= 0 || stage_cogl->dirty_backbuffer) return FALSE; return age < MIN (stage_cogl->damage_index, DAMAGE_HISTORY_MAX); } /* XXX: This is basically identical to clutter_stage_glx_redraw */ static void clutter_stage_cogl_redraw (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); cairo_rectangle_int_t geom; gboolean have_clip; gboolean may_use_clipped_redraw; gboolean use_clipped_redraw; gboolean can_blit_sub_buffer; gboolean has_buffer_age; ClutterActor *wrapper; cairo_rectangle_int_t *clip_region; int damage[4], ndamage; gboolean force_swap; int window_scale; wrapper = CLUTTER_ACTOR (stage_cogl->wrapper); if (!stage_cogl->onscreen) return; can_blit_sub_buffer = cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_SWAP_REGION); has_buffer_age = cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE); _clutter_stage_window_get_geometry (stage_window, &geom); /* NB: a zero width redraw clip == full stage redraw */ have_clip = (stage_cogl->bounding_redraw_clip.width != 0 && !(stage_cogl->bounding_redraw_clip.x == 0 && stage_cogl->bounding_redraw_clip.y == 0 && stage_cogl->bounding_redraw_clip.width == geom.width && stage_cogl->bounding_redraw_clip.height == geom.height)); may_use_clipped_redraw = FALSE; if (_clutter_stage_window_can_clip_redraws (stage_window) && (can_blit_sub_buffer || has_buffer_age) && have_clip && /* some drivers struggle to get going and produce some junk * frames when starting up... */ stage_cogl->frame_count > 3) { may_use_clipped_redraw = TRUE; clip_region = &stage_cogl->bounding_redraw_clip; } else clip_region = NULL; if (may_use_clipped_redraw && G_LIKELY (!(clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS))) use_clipped_redraw = TRUE; else use_clipped_redraw = FALSE; force_swap = FALSE; window_scale = _clutter_stage_window_get_scale_factor (stage_window); if (has_buffer_age) { cairo_rectangle_int_t *current_damage = &stage_cogl->damage_history[DAMAGE_HISTORY (stage_cogl->damage_index++)]; if (use_clipped_redraw) { int age = cogl_onscreen_get_buffer_age (stage_cogl->onscreen), i; *current_damage = *clip_region; if (valid_buffer_age (stage_cogl, age)) { for (i = 1; i <= age; i++) _clutter_util_rectangle_union (clip_region, &stage_cogl->damage_history[DAMAGE_HISTORY (stage_cogl->damage_index - i - 1)], clip_region); CLUTTER_NOTE (CLIPPING, "Reusing back buffer(age=%d) - repairing region: x=%d, y=%d, width=%d, height=%d\n", age, clip_region->x, clip_region->y, clip_region->width, clip_region->height); force_swap = TRUE; } else { CLUTTER_NOTE (CLIPPING, "Invalid back buffer(age=%d): forcing full redraw\n", age); use_clipped_redraw = FALSE; } } else { current_damage->x = 0; current_damage->y = 0; current_damage->width = geom.width; current_damage->height = geom.height; } } if (use_clipped_redraw) { CoglFramebuffer *fb = COGL_FRAMEBUFFER (stage_cogl->onscreen); CLUTTER_NOTE (CLIPPING, "Stage clip pushed: x=%d, y=%d, width=%d, height=%d\n", clip_region->x, clip_region->y, clip_region->width, clip_region->height); stage_cogl->using_clipped_redraw = TRUE; cogl_framebuffer_push_scissor_clip (fb, clip_region->x * window_scale, clip_region->y * window_scale, clip_region->width * window_scale, clip_region->height * window_scale); _clutter_stage_do_paint (CLUTTER_STAGE (wrapper), clip_region); cogl_framebuffer_pop_clip (fb); stage_cogl->using_clipped_redraw = FALSE; } else { CLUTTER_NOTE (CLIPPING, "Unclipped stage paint\n"); /* If we are trying to debug redraw issues then we want to pass * the bounding_redraw_clip so it can be visualized */ if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS) && may_use_clipped_redraw) { _clutter_stage_do_paint (CLUTTER_STAGE (wrapper), clip_region); } else _clutter_stage_do_paint (CLUTTER_STAGE (wrapper), NULL); } if (may_use_clipped_redraw && G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS))) { CoglFramebuffer *fb = COGL_FRAMEBUFFER (stage_cogl->onscreen); CoglContext *ctx = cogl_framebuffer_get_context (fb); static CoglPipeline *outline = NULL; cairo_rectangle_int_t *clip = &stage_cogl->bounding_redraw_clip; ClutterActor *actor = CLUTTER_ACTOR (wrapper); float x_1 = clip->x * window_scale; float x_2 = clip->x + clip->width * window_scale; float y_1 = clip->y * window_scale; float y_2 = clip->y + clip->height * window_scale; CoglVertexP2 quad[4] = { { x_1, y_1 }, { x_2, y_1 }, { x_2, y_2 }, { x_1, y_2 } }; CoglPrimitive *prim; CoglMatrix modelview; if (outline == NULL) { outline = cogl_pipeline_new (ctx); cogl_pipeline_set_color4ub (outline, 0xff, 0x00, 0x00, 0xff); } prim = cogl_primitive_new_p2 (ctx, COGL_VERTICES_MODE_LINE_LOOP, 4, /* n_vertices */ quad); cogl_framebuffer_push_matrix (fb); cogl_matrix_init_identity (&modelview); _clutter_actor_apply_modelview_transform (actor, &modelview); cogl_framebuffer_set_modelview_matrix (fb, &modelview); cogl_framebuffer_draw_primitive (COGL_FRAMEBUFFER (stage_cogl->onscreen), outline, prim); cogl_framebuffer_pop_matrix (fb); cogl_object_unref (prim); } /* XXX: It seems there will be a race here in that the stage * window may be resized before the cogl_onscreen_swap_region * is handled and so we may copy the wrong region. I can't * really see how we can handle this with the current state of X * but at least in this case a full redraw should be queued by * the resize anyway so it should only exhibit temporary * artefacts. */ if (use_clipped_redraw || force_swap) { damage[0] = clip_region->x * window_scale; damage[1] = clip_region->y * window_scale; damage[2] = clip_region->width * window_scale; damage[3] = clip_region->height * window_scale; ndamage = 1; } else { ndamage = 0; } /* push on the screen */ if (use_clipped_redraw && !force_swap) { CLUTTER_NOTE (BACKEND, "cogl_onscreen_swap_region (onscreen: %p, " "x: %d, y: %d, " "width: %d, height: %d)", stage_cogl->onscreen, damage[0], damage[1], damage[2], damage[3]); cogl_onscreen_swap_region (stage_cogl->onscreen, damage, ndamage); } else { CLUTTER_NOTE (BACKEND, "cogl_onscreen_swap_buffers (onscreen: %p)", stage_cogl->onscreen); /* If we have swap buffer events then cogl_onscreen_swap_buffers * will return immediately and we need to track that there is a * swap in progress... */ if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS)) stage_cogl->pending_swaps++; cogl_onscreen_swap_buffers_with_damage (stage_cogl->onscreen, damage, ndamage); } /* reset the redraw clipping for the next paint... */ stage_cogl->initialized_redraw_clip = FALSE; /* We have repaired the backbuffer */ stage_cogl->dirty_backbuffer = FALSE; stage_cogl->frame_count++; } static CoglFramebuffer * clutter_stage_cogl_get_active_framebuffer (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); return COGL_FRAMEBUFFER (stage_cogl->onscreen); } static void clutter_stage_cogl_dirty_back_buffer (ClutterStageWindow *stage_window) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); stage_cogl->dirty_backbuffer = TRUE; } static void clutter_stage_cogl_get_dirty_pixel (ClutterStageWindow *stage_window, int *x, int *y) { ClutterStageCogl *stage_cogl = CLUTTER_STAGE_COGL (stage_window); gboolean has_buffer_age = cogl_clutter_winsys_has_feature (COGL_WINSYS_FEATURE_BUFFER_AGE); if (!has_buffer_age) { *x = 0; *y = 0; } else { cairo_rectangle_int_t *rect; rect = &stage_cogl->damage_history[DAMAGE_HISTORY (stage_cogl->damage_index-1)]; *x = rect->x; *y = rect->y; } } static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface) { iface->realize = clutter_stage_cogl_realize; iface->unrealize = clutter_stage_cogl_unrealize; iface->get_wrapper = clutter_stage_cogl_get_wrapper; iface->get_geometry = clutter_stage_cogl_get_geometry; iface->resize = clutter_stage_cogl_resize; iface->show = clutter_stage_cogl_show; iface->hide = clutter_stage_cogl_hide; iface->schedule_update = clutter_stage_cogl_schedule_update; iface->get_update_time = clutter_stage_cogl_get_update_time; iface->clear_update_time = clutter_stage_cogl_clear_update_time; iface->add_redraw_clip = clutter_stage_cogl_add_redraw_clip; iface->has_redraw_clips = clutter_stage_cogl_has_redraw_clips; iface->ignoring_redraw_clips = clutter_stage_cogl_ignoring_redraw_clips; iface->get_redraw_clip_bounds = clutter_stage_cogl_get_redraw_clip_bounds; iface->redraw = clutter_stage_cogl_redraw; iface->get_active_framebuffer = clutter_stage_cogl_get_active_framebuffer; iface->dirty_back_buffer = clutter_stage_cogl_dirty_back_buffer; iface->get_dirty_pixel = clutter_stage_cogl_get_dirty_pixel; } static void clutter_stage_cogl_set_property (GObject *gobject, guint prop_id, const GValue *value, GParamSpec *pspec) { ClutterStageCogl *self = CLUTTER_STAGE_COGL (gobject); switch (prop_id) { case PROP_WRAPPER: self->wrapper = g_value_get_object (value); break; case PROP_BACKEND: self->backend = g_value_get_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); break; } } static void _clutter_stage_cogl_class_init (ClutterStageCoglClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->set_property = clutter_stage_cogl_set_property; g_object_class_override_property (gobject_class, PROP_WRAPPER, "wrapper"); g_object_class_override_property (gobject_class, PROP_BACKEND, "backend"); } static void _clutter_stage_cogl_init (ClutterStageCogl *stage) { stage->last_presentation_time = 0; stage->refresh_rate = 0.0; stage->update_time = -1; }