diff options
Diffstat (limited to 'clutter/clutter/clutter-stage.c')
-rw-r--r-- | clutter/clutter/clutter-stage.c | 457 |
1 files changed, 267 insertions, 190 deletions
diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index eece2450b..93c1c82e8 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -105,6 +105,19 @@ struct _ClutterStageQueueRedrawEntry ClutterPaintVolume clip; }; +typedef struct _PickRecord +{ + ClutterPoint vertex[4]; + ClutterActor *actor; + int clip_stack_top; +} PickRecord; + +typedef struct _PickClipRecord +{ + int prev; + ClutterPoint vertex[4]; +} PickClipRecord; + struct _ClutterStagePrivate { /* the stage implementation */ @@ -138,7 +151,11 @@ struct _ClutterStagePrivate GTimer *fps_timer; gint32 timer_n_frames; - ClutterIDPool *pick_id_pool; + GArray *pick_stack; + GArray *pick_clip_stack; + int pick_clip_stack_top; + gboolean pick_stack_frozen; + ClutterPickMode cached_pick_mode; #ifdef CLUTTER_ENABLE_DEBUG gulong redraw_count; @@ -323,6 +340,214 @@ clutter_stage_get_preferred_height (ClutterActor *self, *natural_height_p = geom.height; } +/* In order to keep weak pointers valid between frames we need them to not + * move in memory, so the stack is marked as "frozen". + */ +static void +_clutter_stage_freeze_pick_stack (ClutterStage *stage) +{ + ClutterStagePrivate *priv = stage->priv; + int i; + + if (priv->pick_stack_frozen) + return; + + for (i = 0; i < priv->pick_stack->len; i++) + { + PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); + + if (rec->actor) + g_object_add_weak_pointer (G_OBJECT (rec->actor), + (gpointer) &rec->actor); + } + + priv->pick_stack_frozen = TRUE; +} + +static void +_clutter_stage_thaw_pick_stack (ClutterStage *stage) +{ + ClutterStagePrivate *priv = stage->priv; + int i; + + if (!priv->pick_stack_frozen) + return; + + for (i = 0; i < priv->pick_stack->len; i++) + { + PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); + + if (rec->actor) + g_object_remove_weak_pointer (G_OBJECT (rec->actor), + (gpointer) &rec->actor); + } + + priv->pick_stack_frozen = FALSE; +} + +static void +_clutter_stage_clear_pick_stack (ClutterStage *stage) +{ + ClutterStagePrivate *priv = stage->priv; + + _clutter_stage_thaw_pick_stack (stage); + g_array_set_size (priv->pick_stack, 0); + g_array_set_size (priv->pick_clip_stack, 0); + priv->pick_clip_stack_top = -1; + priv->cached_pick_mode = CLUTTER_PICK_NONE; +} + +void +_clutter_stage_log_pick (ClutterStage *stage, + const ClutterPoint *vertices, + ClutterActor *actor) +{ + ClutterStagePrivate *priv; + PickRecord rec; + + g_return_if_fail (CLUTTER_IS_STAGE (stage)); + g_return_if_fail (actor != NULL); + + priv = stage->priv; + + g_assert (!priv->pick_stack_frozen); + + memcpy (rec.vertex, vertices, 4 * sizeof (ClutterPoint)); + rec.actor = actor; + rec.clip_stack_top = priv->pick_clip_stack_top; + + g_array_append_val (priv->pick_stack, rec); +} + +void +_clutter_stage_push_pick_clip (ClutterStage *stage, + const ClutterPoint *vertices) +{ + ClutterStagePrivate *priv; + PickClipRecord clip; + + g_return_if_fail (CLUTTER_IS_STAGE (stage)); + + priv = stage->priv; + + g_assert (!priv->pick_stack_frozen); + + clip.prev = priv->pick_clip_stack_top; + memcpy (clip.vertex, vertices, 4 * sizeof (ClutterPoint)); + + g_array_append_val (priv->pick_clip_stack, clip); + priv->pick_clip_stack_top = priv->pick_clip_stack->len - 1; +} + +void +_clutter_stage_pop_pick_clip (ClutterStage *stage) +{ + ClutterStagePrivate *priv; + const PickClipRecord *top; + + g_return_if_fail (CLUTTER_IS_STAGE (stage)); + + priv = stage->priv; + + g_assert (!priv->pick_stack_frozen); + g_assert (priv->pick_clip_stack_top >= 0); + + /* Individual elements of pick_clip_stack are not freed. This is so they + * can be shared as part of a tree of different stacks used by different + * actors in the pick_stack. The whole pick_clip_stack does however get + * freed later in _clutter_stage_clear_pick_stack. + */ + + top = &g_array_index (priv->pick_clip_stack, + PickClipRecord, + priv->pick_clip_stack_top); + + priv->pick_clip_stack_top = top->prev; +} + +static gboolean +is_quadrilateral_axis_aligned_rectangle (const ClutterPoint *vertices) +{ + int i; + + for (i = 0; i < 4; i++) + { + if (!G_APPROX_VALUE (vertices[i].x, + vertices[(i + 1) % 4].x, + FLT_EPSILON) && + !G_APPROX_VALUE (vertices[i].y, + vertices[(i + 1) % 4].y, + FLT_EPSILON)) + return FALSE; + } + return TRUE; +} + +static gboolean +is_inside_axis_aligned_rectangle (const ClutterPoint *point, + const ClutterPoint *vertices) +{ + float min_x = FLT_MAX; + float max_x = FLT_MIN; + float min_y = FLT_MAX; + float max_y = FLT_MIN; + int i; + + for (i = 0; i < 3; i++) + { + min_x = MIN (min_x, vertices[i].x); + min_y = MIN (min_y, vertices[i].y); + max_x = MAX (max_x, vertices[i].x); + max_y = MAX (max_y, vertices[i].y); + } + + return (point->x >= min_x && + point->y >= min_y && + point->x < max_x && + point->y < max_y); +} + +static gboolean +is_inside_input_region (const ClutterPoint *point, + const ClutterPoint *vertices) +{ + + if (is_quadrilateral_axis_aligned_rectangle (vertices)) + return is_inside_axis_aligned_rectangle (point, vertices); + else + return clutter_point_inside_quadrilateral (point, vertices); +} + +static gboolean +pick_record_contains_pixel (ClutterStage *stage, + const PickRecord *rec, + int x, + int y) +{ + const ClutterPoint point = { x, y }; + ClutterStagePrivate *priv; + int clip_index; + + if (!is_inside_input_region (&point, rec->vertex)) + return FALSE; + + priv = stage->priv; + clip_index = rec->clip_stack_top; + while (clip_index >= 0) + { + const PickClipRecord *clip = &g_array_index (priv->pick_clip_stack, + PickClipRecord, + clip_index); + + if (!is_inside_input_region (&point, clip->vertex)) + return FALSE; + + clip_index = clip->prev; + } + + return TRUE; +} + static inline void queue_full_redraw (ClutterStage *stage) { @@ -630,6 +855,12 @@ clutter_stage_do_paint_view (ClutterStage *stage, float viewport[4]; cairo_rectangle_int_t geom; + /* Any mode of painting/picking invalidates the pick cache, unless we're + * in the middle of building it. So we reset the cached flag but don't + * completely clear the pick stack. + */ + priv->cached_pick_mode = CLUTTER_PICK_NONE; + _clutter_stage_window_get_geometry (priv->impl, &geom); viewport[0] = priv->viewport[0]; @@ -1398,40 +1629,6 @@ clutter_stage_get_redraw_clip_bounds (ClutterStage *stage, } } -static void -read_pixels_to_file (CoglFramebuffer *fb, - char *filename_stem, - int x, - int y, - int width, - int height) -{ - guint8 *data; - cairo_surface_t *surface; - static int read_count = 0; - char *filename = g_strdup_printf ("%s-%05d.png", - filename_stem, - read_count); - - data = g_malloc (4 * width * height); - cogl_framebuffer_read_pixels (fb, - x, y, width, height, - CLUTTER_CAIRO_FORMAT_ARGB32, - data); - - surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_RGB24, - width, height, - width * 4); - - cairo_surface_write_to_png (surface, filename); - cairo_surface_destroy (surface); - - g_free (data); - g_free (filename); - - read_count++; -} - static ClutterActor * _clutter_stage_do_pick_on_view (ClutterStage *stage, gint x, @@ -1439,140 +1636,42 @@ _clutter_stage_do_pick_on_view (ClutterStage *stage, ClutterPickMode mode, ClutterStageView *view) { - ClutterActor *actor = CLUTTER_ACTOR (stage); + ClutterMainContext *context = _clutter_context_get_default (); ClutterStagePrivate *priv = stage->priv; CoglFramebuffer *fb = clutter_stage_view_get_framebuffer (view); - cairo_rectangle_int_t view_layout; - ClutterMainContext *context; - guchar pixel[4] = { 0xff, 0xff, 0xff, 0xff }; - CoglColor stage_pick_id; - gboolean dither_enabled_save; - ClutterActor *retval; - gint dirty_x; - gint dirty_y; - gint read_x; - gint read_y; - float fb_width, fb_height; - float fb_scale; - float viewport_offset_x; - float viewport_offset_y; - - priv = stage->priv; - - context = _clutter_context_get_default (); - fb_scale = clutter_stage_view_get_scale (view); - clutter_stage_view_get_layout (view, &view_layout); - - fb_width = view_layout.width * fb_scale; - fb_height = view_layout.height * fb_scale; - cogl_push_framebuffer (fb); - - /* needed for when a context switch happens */ - _clutter_stage_maybe_setup_viewport (stage, view); - - /* FIXME: For some reason leaving the cogl clip stack empty causes the - * picking to not work at all, so setting it the whole framebuffer content - * for now. */ - cogl_framebuffer_push_scissor_clip (fb, 0, 0, - view_layout.width * fb_scale, - view_layout.height * fb_scale); - - _clutter_stage_window_get_dirty_pixel (priv->impl, view, &dirty_x, &dirty_y); + int i; - if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))) - { - CLUTTER_NOTE (PICK, "Pushing pick scissor clip x: %d, y: %d, 1x1", - (int) (dirty_x * fb_scale), - (int) (dirty_y * fb_scale)); - cogl_framebuffer_push_scissor_clip (fb, dirty_x * fb_scale, dirty_y * fb_scale, 1, 1); - } + g_assert (context->pick_mode == CLUTTER_PICK_NONE); - viewport_offset_x = x * fb_scale - dirty_x * fb_scale; - viewport_offset_y = y * fb_scale - dirty_y * fb_scale; - CLUTTER_NOTE (PICK, "Setting viewport to %f, %f, %f, %f", - priv->viewport[0] * fb_scale - viewport_offset_x, - priv->viewport[1] * fb_scale - viewport_offset_y, - priv->viewport[2] * fb_scale, - priv->viewport[3] * fb_scale); - cogl_framebuffer_set_viewport (fb, - priv->viewport[0] * fb_scale - viewport_offset_x, - priv->viewport[1] * fb_scale - viewport_offset_y, - priv->viewport[2] * fb_scale, - priv->viewport[3] * fb_scale); - - read_x = dirty_x * fb_scale; - read_y = dirty_y * fb_scale; - - CLUTTER_NOTE (PICK, "Performing pick at %i,%i on view %dx%d+%d+%d s: %f", - x, y, - view_layout.width, view_layout.height, - view_layout.x, view_layout.y, fb_scale); - - cogl_color_init_from_4ub (&stage_pick_id, 255, 255, 255, 255); - cogl_framebuffer_clear (fb, COGL_BUFFER_BIT_COLOR | COGL_BUFFER_BIT_DEPTH, &stage_pick_id); - - /* Disable dithering (if any) when doing the painting in pick mode */ - dither_enabled_save = cogl_framebuffer_get_dither_enabled (fb); - cogl_framebuffer_set_dither_enabled (fb, FALSE); - - /* Render the entire scence in pick mode - just single colored silhouette's - * are drawn offscreen (as we never swap buffers) - */ - context->pick_mode = mode; - - clutter_stage_do_paint_view (stage, view, NULL); - context->pick_mode = CLUTTER_PICK_NONE; - - /* Read the color of the screen co-ords pixel. RGBA_8888_PRE is used - even though we don't care about the alpha component because under - GLES this is the only format that is guaranteed to work so Cogl - will end up having to do a conversion if any other format is - used. The format is requested as pre-multiplied because Cogl - assumes that all pixels in the framebuffer are premultiplied so - it avoids a conversion. */ - cogl_framebuffer_read_pixels (fb, - read_x, read_y, 1, 1, - COGL_PIXEL_FORMAT_RGBA_8888_PRE, - pixel); - - if (G_UNLIKELY (clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS)) + if (mode != priv->cached_pick_mode) { - char *file_name = - g_strdup_printf ("pick-buffer-%s-view-x-%d", - _clutter_actor_get_debug_name (actor), - view_layout.x); + _clutter_stage_clear_pick_stack (stage); - read_pixels_to_file (fb, file_name, 0, 0, fb_width, fb_height); + cogl_push_framebuffer (fb); - g_free (file_name); - } - - /* Restore whether GL_DITHER was enabled */ - cogl_framebuffer_set_dither_enabled (fb, dither_enabled_save); - - if (G_LIKELY (!(clutter_pick_debug_flags & CLUTTER_DEBUG_DUMP_PICK_BUFFERS))) - cogl_framebuffer_pop_clip (fb); + context->pick_mode = mode; + clutter_stage_do_paint_view (stage, view, NULL); + context->pick_mode = CLUTTER_PICK_NONE; + priv->cached_pick_mode = mode; - cogl_framebuffer_pop_clip (fb); + cogl_pop_framebuffer (); - _clutter_stage_dirty_viewport (stage); + _clutter_stage_freeze_pick_stack (stage); + } - if (pixel[0] == 0xff && pixel[1] == 0xff && pixel[2] == 0xff) - retval = actor; - else + /* Search all "painted" pickable actors from front to back. A linear search + * is required, and also performs fine since there is typically only + * on the order of dozens of actors in the list (on screen) at a time. + */ + for (i = priv->pick_stack->len - 1; i >= 0; i--) { - guint32 id_ = _clutter_pixel_to_id (pixel); + const PickRecord *rec = &g_array_index (priv->pick_stack, PickRecord, i); - retval = _clutter_stage_get_actor_by_pick_id (stage, id_); - CLUTTER_NOTE (PICK, "Picking actor %s with id %u (pixel: 0x%x%x%x%x", - G_OBJECT_TYPE_NAME (retval), - id_, - pixel[0], pixel[1], pixel[2], pixel[3]); + if (rec->actor && pick_record_contains_pixel (stage, rec, x, y)) + return rec->actor; } - cogl_pop_framebuffer (); - - return retval; + return CLUTTER_ACTOR (stage); } /** @@ -1867,7 +1966,9 @@ clutter_stage_finalize (GObject *object) g_array_free (priv->paint_volume_stack, TRUE); - _clutter_id_pool_free (priv->pick_id_pool); + _clutter_stage_clear_pick_stack (stage); + g_array_free (priv->pick_clip_stack, TRUE); + g_array_free (priv->pick_stack, TRUE); if (priv->fps_timer != NULL) g_timer_destroy (priv->fps_timer); @@ -2302,7 +2403,10 @@ clutter_stage_init (ClutterStage *self) priv->paint_volume_stack = g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume)); - priv->pick_id_pool = _clutter_id_pool_new (256); + priv->pick_stack = g_array_new (FALSE, FALSE, sizeof (PickRecord)); + priv->pick_clip_stack = g_array_new (FALSE, FALSE, sizeof (PickClipRecord)); + priv->pick_clip_stack_top = -1; + priv->cached_pick_mode = CLUTTER_PICK_NONE; } /** @@ -3980,6 +4084,12 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage, CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ", _clutter_actor_get_debug_name (actor), clip); + /* Queuing a redraw or clip change invalidates the pick cache, unless we're + * in the middle of building it. So we reset the cached flag but don't + * completely clear the pick stack... + */ + priv->cached_pick_mode = CLUTTER_PICK_NONE; + if (!priv->redraw_pending) { ClutterMasterClock *master_clock; @@ -4240,39 +4350,6 @@ _clutter_stage_get_active_framebuffer (ClutterStage *stage) return stage->priv->active_framebuffer; } -gint32 -_clutter_stage_acquire_pick_id (ClutterStage *stage, - ClutterActor *actor) -{ - ClutterStagePrivate *priv = stage->priv; - - g_assert (priv->pick_id_pool != NULL); - - return _clutter_id_pool_add (priv->pick_id_pool, actor); -} - -void -_clutter_stage_release_pick_id (ClutterStage *stage, - gint32 pick_id) -{ - ClutterStagePrivate *priv = stage->priv; - - g_assert (priv->pick_id_pool != NULL); - - _clutter_id_pool_remove (priv->pick_id_pool, pick_id); -} - -ClutterActor * -_clutter_stage_get_actor_by_pick_id (ClutterStage *stage, - gint32 pick_id) -{ - ClutterStagePrivate *priv = stage->priv; - - g_assert (priv->pick_id_pool != NULL); - - return _clutter_id_pool_lookup (priv->pick_id_pool, pick_id); -} - void _clutter_stage_add_pointer_drag_actor (ClutterStage *stage, ClutterInputDevice *device, |