summaryrefslogtreecommitdiff
path: root/clutter/clutter/clutter-stage.c
diff options
context:
space:
mode:
Diffstat (limited to 'clutter/clutter/clutter-stage.c')
-rw-r--r--clutter/clutter/clutter-stage.c457
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,