From c21fe8c71d2d56698002523dffcec3a9acc8f4e7 Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 13:58:38 +0400 Subject: [PATCH 1/6] Remove allocation flags and the "allocation-changed" signal from Clutter This commit removes the DELEGATE_LAYOUT and the ABSOLUTE_ORIGIN_CHANGED allocation flags from Clutter, replacing the latter one with an internal boolean that's more useful and won't get in the way of optimizations and bugfixes. Since that leaves no more allocation flags, those are removed entirely, which also leaves no reason to keep the "allocation-changed" signal, so remove that, too. --- .gitignore | 2 + clutter/clutter/clutter-actor.c | 320 +++----------------- clutter/clutter/clutter-actor.h | 27 +- clutter/clutter/clutter-align-constraint.c | 5 +- clutter/clutter/clutter-bin-layout.c | 6 +- clutter/clutter/clutter-box-layout.c | 14 +- clutter/clutter/clutter-clone.c | 9 +- clutter/clutter/clutter-deform-effect.c | 9 +- clutter/clutter/clutter-enums.h | 26 -- clutter/clutter/clutter-fixed-layout.c | 5 +- clutter/clutter/clutter-flow-layout.c | 5 +- clutter/clutter/clutter-grid-layout.c | 5 +- clutter/clutter/clutter-layout-manager.c | 9 +- clutter/clutter/clutter-layout-manager.h | 6 +- clutter/clutter/clutter-stage.c | 41 +-- clutter/clutter/clutter-text.c | 5 +- clutter/clutter/deprecated/clutter-group.c | 7 +- src/tests/clutter/conform/actor-anchors.c | 4 +- src/tests/clutter/conform/actor-pick.c | 2 +- src/tests/clutter/interactive/test-layout.c | 20 +- 20 files changed, 126 insertions(+), 401 deletions(-) diff --git a/.gitignore b/.gitignore index c8c08ea0f..730677e36 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ debian/muffin-common/ debian/muffin-dbg/ debian/muffin/ debian/tmp/ + +.cache diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 9d4d36ec3..94f5ce0d4 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -698,7 +698,6 @@ struct _ClutterActorPrivate * allocation */ ClutterActorBox allocation; - ClutterAllocationFlags allocation_flags; /* clip, in actor coordinates */ graphene_rect_t clip; @@ -854,6 +853,7 @@ struct _ClutterActorPrivate guint needs_paint_volume_update : 1; guint had_effects_on_last_paint_volume_update : 1; guint needs_compute_resource_scale : 1; + guint absolute_origin_changed : 1; }; enum @@ -1013,7 +1013,6 @@ enum MOTION_EVENT, ENTER_EVENT, LEAVE_EVENT, - ALLOCATION_CHANGED, TRANSITIONS_COMPLETED, TOUCH_EVENT, TRANSITION_STOPPED, @@ -2600,20 +2599,15 @@ clutter_actor_notify_if_geometry_changed (ClutterActor *self, * Return value: %TRUE if the allocation of the #ClutterActor has been * changed, and %FALSE otherwise */ -static inline gboolean +static inline void clutter_actor_set_allocation_internal (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { ClutterActorPrivate *priv = self->priv; GObject *obj; gboolean x1_changed, y1_changed, x2_changed, y2_changed; - gboolean retval; ClutterActorBox old_alloc = { 0, }; - g_return_val_if_fail (!isnan (box->x1) && !isnan (box->x2) && - !isnan (box->y1) && !isnan (box->y2), FALSE); - obj = G_OBJECT (self); g_object_freeze_notify (obj); @@ -2626,7 +2620,6 @@ clutter_actor_set_allocation_internal (ClutterActor *self, y2_changed = priv->allocation.y2 != box->y2; priv->allocation = *box; - priv->allocation_flags = flags; /* allocation is authoritative */ priv->needs_width_request = FALSE; @@ -2651,88 +2644,37 @@ clutter_actor_set_allocation_internal (ClutterActor *self, priv->content_box_valid = FALSE; g_object_notify_by_pspec (obj, obj_props[PROP_CONTENT_BOX]); } - - retval = TRUE; } - else - retval = FALSE; clutter_actor_notify_if_geometry_changed (self, &old_alloc); g_object_thaw_notify (obj); - - return retval; } -static void clutter_actor_real_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags); - -static inline void -clutter_actor_maybe_layout_children (ClutterActor *self, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) +static void +clutter_actor_real_allocate (ClutterActor *self, + const ClutterActorBox *box) { ClutterActorPrivate *priv = self->priv; - /* this is going to be a bit hard to follow, so let's put an explanation - * here. - * - * we want ClutterActor to have a default layout manager if the actor was - * created using "g_object_new (CLUTTER_TYPE_ACTOR, NULL)". - * - * we also want any subclass of ClutterActor that does not override the - * ::allocate() virtual function to delegate to a layout manager. - * - * finally, we want to allow people subclassing ClutterActor and overriding - * the ::allocate() vfunc to let Clutter delegate to the layout manager. - * - * on the other hand, we want existing actor subclasses overriding the - * ::allocate() virtual function and chaining up to the parent's - * implementation to continue working without allocating their children - * twice, or without entering an allocation loop. - * - * for the first two points, we check if the class of the actor is - * overridding the ::allocate() virtual function; if it isn't, then we - * follow through with checking whether we have children and a layout - * manager, and eventually calling clutter_layout_manager_allocate(). - * - * for the third point, we check the CLUTTER_DELEGATE_LAYOUT flag in the - * allocation flags that we got passed, and if it is present, we continue - * with the check above. - * - * if neither of these two checks yields a positive result, we just - * assume that the ::allocate() virtual function that resulted in this - * function being called will also allocate the children of the actor. - */ - - if (CLUTTER_ACTOR_GET_CLASS (self)->allocate == clutter_actor_real_allocate) - goto check_layout; - - if ((flags & CLUTTER_DELEGATE_LAYOUT) != 0) - goto check_layout; + g_object_freeze_notify (G_OBJECT (self)); - return; + clutter_actor_set_allocation_internal (self, box); -check_layout: +/* we allocate our children before we notify changes in our geometry, + * so that people connecting to properties will be able to get valid + * data out of the sub-tree of the scene graph that has this actor at + * the root. + */ if (priv->n_children != 0 && priv->layout_manager != NULL) { - ClutterContainer *container = CLUTTER_CONTAINER (self); - ClutterAllocationFlags children_flags; ClutterActorBox children_box; /* normalize the box passed to the layout manager */ children_box.x1 = children_box.y1 = 0.f; - children_box.x2 = (allocation->x2 - allocation->x1); - children_box.y2 = (allocation->y2 - allocation->y1); - - /* remove the DELEGATE_LAYOUT flag; this won't be passed to - * the actor's children, since it refers only to the current - * actor's allocation. - */ - children_flags = flags; - children_flags &= ~CLUTTER_DELEGATE_LAYOUT; + children_box.x2 = box->x2 - box->x1; + children_box.y2 = box->y2 - box->y1; CLUTTER_NOTE (LAYOUT, "Allocating %d children of %s " @@ -2740,46 +2682,15 @@ clutter_actor_maybe_layout_children (ClutterActor *self, "using %s", priv->n_children, _clutter_actor_get_debug_name (self), - allocation->x1, - allocation->y1, - (allocation->x2 - allocation->x1), - (allocation->y2 - allocation->y1), + box->x1, + box->y1, + (box->x2 - box->x1), + (box->y2 - box->y1), G_OBJECT_TYPE_NAME (priv->layout_manager)); clutter_layout_manager_allocate (priv->layout_manager, - container, - &children_box, - children_flags); - } -} - -static void -clutter_actor_real_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) -{ - ClutterActorPrivate *priv = self->priv; - gboolean changed; - - g_object_freeze_notify (G_OBJECT (self)); - - changed = clutter_actor_set_allocation_internal (self, box, flags); - - /* we allocate our children before we notify changes in our geometry, - * so that people connecting to properties will be able to get valid - * data out of the sub-tree of the scene graph that has this actor at - * the root. - */ - clutter_actor_maybe_layout_children (self, box, flags); - - if (changed) - { - ClutterActorBox signal_box = priv->allocation; - ClutterAllocationFlags signal_flags = priv->allocation_flags; - - g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0, - &signal_box, - signal_flags); + CLUTTER_CONTAINER (self), + &children_box); } g_object_thaw_notify (G_OBJECT (self)); @@ -8745,35 +8656,6 @@ clutter_actor_class_init (ClutterActorClass *klass) G_TYPE_NONE, 1, CLUTTER_TYPE_PICK_CONTEXT); - /** - * ClutterActor::allocation-changed: - * @actor: the #ClutterActor that emitted the signal - * @box: a #ClutterActorBox with the new allocation - * @flags: #ClutterAllocationFlags for the allocation - * - * The ::allocation-changed signal is emitted when the - * #ClutterActor:allocation property changes. Usually, application - * code should just use the notifications for the :allocation property - * but if you want to track the allocation flags as well, for instance - * to know whether the absolute origin of @actor changed, then you might - * want use this signal instead. - * - * Since: 1.0 - */ - actor_signals[ALLOCATION_CHANGED] = - g_signal_new (I_("allocation-changed"), - G_TYPE_FROM_CLASS (object_class), - G_SIGNAL_RUN_LAST, - 0, - NULL, NULL, - _clutter_marshal_VOID__BOXED_FLAGS, - G_TYPE_NONE, 2, - CLUTTER_TYPE_ACTOR_BOX | G_SIGNAL_TYPE_STATIC_SCOPE, - CLUTTER_TYPE_ALLOCATION_FLAGS); - g_signal_set_va_marshaller (actor_signals[ALLOCATION_CHANGED], - G_TYPE_FROM_CLASS (object_class), - _clutter_marshal_VOID__BOXED_FLAGSv); - /** * ClutterActor::transitions-completed: * @actor: a #ClutterActor @@ -10311,8 +10193,7 @@ clutter_actor_adjust_allocation (ClutterActor *self, static void clutter_actor_allocate_internal (ClutterActor *self, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterActorClass *klass; @@ -10322,7 +10203,7 @@ clutter_actor_allocate_internal (ClutterActor *self, _clutter_actor_get_debug_name (self)); klass = CLUTTER_ACTOR_GET_CLASS (self); - klass->allocate (self, allocation, flags); + klass->allocate (self, allocation); CLUTTER_UNSET_PRIVATE_FLAGS (self, CLUTTER_IN_RELAYOUT); @@ -10335,7 +10216,6 @@ clutter_actor_allocate_internal (ClutterActor *self, * clutter_actor_allocate: * @self: A #ClutterActor * @box: new allocation of the actor, in parent-relative coordinates - * @flags: flags that control the allocation * * Assigns the size of a #ClutterActor from the given @box. * @@ -10361,12 +10241,11 @@ clutter_actor_allocate_internal (ClutterActor *self, * Since: 0.8 */ void -clutter_actor_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) +clutter_actor_allocate (ClutterActor *self, + const ClutterActorBox *box) { ClutterActorBox old_allocation, real_allocation; - gboolean origin_changed, child_moved, size_changed; + gboolean origin_changed, size_changed; gboolean stage_allocation_changed; ClutterActorPrivate *priv; @@ -10414,18 +10293,19 @@ clutter_actor_allocate (ClutterActor *self, real_allocation.x2 = MAX (real_allocation.x2, real_allocation.x1); real_allocation.y2 = MAX (real_allocation.y2, real_allocation.y1); - origin_changed = (flags & CLUTTER_ABSOLUTE_ORIGIN_CHANGED); - - child_moved = (real_allocation.x1 != old_allocation.x1 || - real_allocation.y1 != old_allocation.y1); + origin_changed = (real_allocation.x1 != old_allocation.x1 || + real_allocation.y1 != old_allocation.y1); size_changed = (real_allocation.x2 != old_allocation.x2 || real_allocation.y2 != old_allocation.y2); - if (origin_changed || child_moved || size_changed) - stage_allocation_changed = TRUE; - else - stage_allocation_changed = FALSE; + priv->absolute_origin_changed = priv->parent + ? priv->parent->priv->absolute_origin_changed + : FALSE; + + priv->absolute_origin_changed |= origin_changed; + + stage_allocation_changed = priv->absolute_origin_changed || size_changed; /* If we get an allocation "out of the blue" * (we did not queue relayout), then we want to @@ -10455,24 +10335,10 @@ clutter_actor_allocate (ClutterActor *self, { /* If the actor didn't move but needs_allocation is set, we just * need to allocate the children */ - clutter_actor_allocate_internal (self, &real_allocation, flags); + clutter_actor_allocate_internal (self, &real_allocation); return; } - /* When ABSOLUTE_ORIGIN_CHANGED is passed in to - * clutter_actor_allocate(), it indicates whether the parent has its - * absolute origin moved; when passed in to ClutterActor::allocate() - * virtual method though, it indicates whether the child has its - * absolute origin moved. So we set it when child_moved is TRUE - */ - if (child_moved) - flags |= CLUTTER_ABSOLUTE_ORIGIN_CHANGED; - - /* store the flags here, so that they can be propagated by the - * transition code - */ - self->priv->allocation_flags = flags; - _clutter_actor_create_transition (self, obj_props[PROP_ALLOCATION], &priv->allocation, &real_allocation); @@ -10482,20 +10348,14 @@ clutter_actor_allocate (ClutterActor *self, * clutter_actor_set_allocation: * @self: a #ClutterActor * @box: a #ClutterActorBox - * @flags: allocation flags * * Stores the allocation of @self as defined by @box. * * This function can only be called from within the implementation of * the #ClutterActorClass.allocate() virtual function. * - * The allocation should have been adjusted to take into account constraints, - * alignment, and margin properties. If you are implementing a #ClutterActor - * subclass that provides its own layout management policy for its children - * instead of using a #ClutterLayoutManager delegate, you should not call - * this function on the children of @self; instead, you should call - * clutter_actor_allocate(), which will adjust the allocation box for - * you. + * The allocation @box should have been adjusted to take into account + * constraints, alignment, and margin properties. * * This function should only be used by subclasses of #ClutterActor * that wish to store their allocation but cannot chain up to the @@ -10503,69 +10363,12 @@ clutter_actor_allocate (ClutterActor *self, * #ClutterActorClass.allocate() virtual function will call this * function. * - * It is important to note that, while chaining up was the recommended - * behaviour for #ClutterActor subclasses prior to the introduction of - * this function, it is recommended to call clutter_actor_set_allocation() - * instead. - * - * If the #ClutterActor is using a #ClutterLayoutManager delegate object - * to handle the allocation of its children, this function will call - * the clutter_layout_manager_allocate() function only if the - * %CLUTTER_DELEGATE_LAYOUT flag is set on @flags, otherwise it is - * expected that the subclass will call clutter_layout_manager_allocate() - * by itself. For instance, the following code: - * - * |[ - * static void - * my_actor_allocate (ClutterActor *actor, - * const ClutterActorBox *allocation, - * ClutterAllocationFlags flags) - * { - * ClutterActorBox new_alloc; - * ClutterAllocationFlags new_flags; - * - * adjust_allocation (allocation, &new_alloc); - * - * new_flags = flags | CLUTTER_DELEGATE_LAYOUT; - * - * // this will use the layout manager set on the actor - * clutter_actor_set_allocation (actor, &new_alloc, new_flags); - * } - * ]| - * - * is equivalent to this: - * - * |[ - * static void - * my_actor_allocate (ClutterActor *actor, - * const ClutterActorBox *allocation, - * ClutterAllocationFlags flags) - * { - * ClutterLayoutManager *layout; - * ClutterActorBox new_alloc; - * - * adjust_allocation (allocation, &new_alloc); - * - * clutter_actor_set_allocation (actor, &new_alloc, flags); - * - * layout = clutter_actor_get_layout_manager (actor); - * clutter_layout_manager_allocate (layout, - * CLUTTER_CONTAINER (actor), - * &new_alloc, - * flags); - * } - * ]| - * * Since: 1.10 */ void clutter_actor_set_allocation (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { - ClutterActorPrivate *priv; - gboolean changed; - g_return_if_fail (CLUTTER_IS_ACTOR (self)); g_return_if_fail (box != NULL); @@ -10577,28 +10380,9 @@ clutter_actor_set_allocation (ClutterActor *self, return; } - priv = self->priv; - g_object_freeze_notify (G_OBJECT (self)); - changed = clutter_actor_set_allocation_internal (self, box, flags); - - /* we allocate our children before we notify changes in our geometry, - * so that people connecting to properties will be able to get valid - * data out of the sub-tree of the scene graph that has this actor at - * the root. - */ - clutter_actor_maybe_layout_children (self, box, flags); - - if (changed) - { - ClutterActorBox signal_box = priv->allocation; - ClutterAllocationFlags signal_flags = priv->allocation_flags; - - g_signal_emit (self, actor_signals[ALLOCATION_CHANGED], 0, - &signal_box, - signal_flags); - } + clutter_actor_set_allocation_internal (self, box); g_object_thaw_notify (G_OBJECT (self)); } @@ -15083,9 +14867,7 @@ clutter_actor_set_animatable_property (ClutterActor *actor, break; case PROP_ALLOCATION: - clutter_actor_allocate_internal (actor, - g_value_get_boxed (value), - actor->priv->allocation_flags); + clutter_actor_allocate_internal (actor, g_value_get_boxed (value)); clutter_actor_queue_redraw (actor); break; @@ -15479,7 +15261,6 @@ clutter_actor_get_stage (ClutterActor *actor) * actor's natural width * @available_height: the maximum available height, or -1 to use the * actor's natural height - * @flags: flags controlling the allocation * * Allocates @self taking into account the #ClutterActor's * preferred size, but limiting it to the maximum available width @@ -15526,7 +15307,7 @@ clutter_actor_get_stage (ClutterActor *actor) * box.x1 = x; box.y1 = y; * box.x2 = box.x1 + available_width; * box.y2 = box.y1 + available_height; - * clutter_actor_allocate (self, &box, flags); + * clutter_actor_allocate (self, &box); * ]| * * This function can be used by fluid layout managers to allocate @@ -15540,8 +15321,7 @@ clutter_actor_allocate_available_size (ClutterActor *self, gfloat x, gfloat y, gfloat available_width, - gfloat available_height, - ClutterAllocationFlags flags) + gfloat available_height) { ClutterActorPrivate *priv; gfloat width, height; @@ -15597,13 +15377,12 @@ clutter_actor_allocate_available_size (ClutterActor *self, box.y1 = y; box.x2 = box.x1 + width; box.y2 = box.y1 + height; - clutter_actor_allocate (self, &box, flags); + clutter_actor_allocate (self, &box); } /** * clutter_actor_allocate_preferred_size: * @self: a #ClutterActor - * @flags: flags controlling the allocation * * Allocates the natural size of @self. * @@ -15621,8 +15400,7 @@ clutter_actor_allocate_available_size (ClutterActor *self, * Since: 0.8 */ void -clutter_actor_allocate_preferred_size (ClutterActor *self, - ClutterAllocationFlags flags) +clutter_actor_allocate_preferred_size (ClutterActor *self) { gfloat actor_x, actor_y; gfloat natural_width, natural_height; @@ -15656,7 +15434,7 @@ clutter_actor_allocate_preferred_size (ClutterActor *self, actor_box.x2 = actor_box.x1 + natural_width; actor_box.y2 = actor_box.y1 + natural_height; - clutter_actor_allocate (self, &actor_box, flags); + clutter_actor_allocate (self, &actor_box); } /** @@ -15667,7 +15445,6 @@ clutter_actor_allocate_preferred_size (ClutterActor *self, * @y_align: the vertical alignment, between 0 and 1 * @x_fill: whether the actor should fill horizontally * @y_fill: whether the actor should fill vertically - * @flags: allocation flags to be passed to clutter_actor_allocate() * * Allocates @self by taking into consideration the available allocation * area; an alignment factor on either axis; and whether the actor should @@ -15694,8 +15471,7 @@ clutter_actor_allocate_align_fill (ClutterActor *self, gdouble x_align, gdouble y_align, gboolean x_fill, - gboolean y_fill, - ClutterAllocationFlags flags) + gboolean y_fill) { ClutterActorPrivate *priv; ClutterActorBox allocation = CLUTTER_ACTOR_BOX_INIT_ZERO; @@ -15811,7 +15587,7 @@ clutter_actor_allocate_align_fill (ClutterActor *self, allocation.x2 = ceilf (allocation.x1 + MAX (child_width, 0)); allocation.y2 = ceilf (allocation.y1 + MAX (child_height, 0)); - clutter_actor_allocate (self, &allocation, flags); + clutter_actor_allocate (self, &allocation); } /** diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index 0817c74bb..aebfa26f7 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -175,9 +175,12 @@ struct _ClutterActor * @get_preferred_height: virtual function, used when querying the minimum * and natural heights of an actor for a given width; it is used by * clutter_actor_get_preferred_height() - * @allocate: virtual function, used when settings the coordinates of an - * actor; it is used by clutter_actor_allocate(); it must chain up to - * the parent's implementation, or call clutter_actor_set_allocation() + * @allocate: virtual function, used when setting the coordinates of an + * actor; it is used by clutter_actor_allocate(); when overriding this + * function without chaining up, clutter_actor_set_allocation() must be + * called and children must be allocated by the implementation, when + * chaining up though, those things will be done by the parent's + * implementation. * @apply_transform: virtual function, used when applying the transformations * to an actor before painting it or when transforming coordinates or * the allocation; it must chain up to the parent's implementation @@ -253,8 +256,7 @@ struct _ClutterActorClass gfloat *min_height_p, gfloat *natural_height_p); void (* allocate) (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags); + const ClutterActorBox *box); /* transformations */ void (* apply_transform) (ClutterActor *actor, @@ -417,30 +419,25 @@ void clutter_actor_get_preferred_size gfloat *natural_height_p); CLUTTER_EXPORT void clutter_actor_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags); + const ClutterActorBox *box); CLUTTER_EXPORT -void clutter_actor_allocate_preferred_size (ClutterActor *self, - ClutterAllocationFlags flags); +void clutter_actor_allocate_preferred_size (ClutterActor *self); CLUTTER_EXPORT void clutter_actor_allocate_available_size (ClutterActor *self, gfloat x, gfloat y, gfloat available_width, - gfloat available_height, - ClutterAllocationFlags flags); + gfloat available_height); CLUTTER_EXPORT void clutter_actor_allocate_align_fill (ClutterActor *self, const ClutterActorBox *box, gdouble x_align, gdouble y_align, gboolean x_fill, - gboolean y_fill, - ClutterAllocationFlags flags); + gboolean y_fill); CLUTTER_EXPORT void clutter_actor_set_allocation (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags); + const ClutterActorBox *box); CLUTTER_EXPORT void clutter_actor_get_allocation_box (ClutterActor *self, ClutterActorBox *box); diff --git a/clutter/clutter/clutter-align-constraint.c b/clutter/clutter/clutter-align-constraint.c index a2694aa63..ed10ece2e 100644 --- a/clutter/clutter/clutter-align-constraint.c +++ b/clutter/clutter/clutter-align-constraint.c @@ -85,8 +85,7 @@ G_DEFINE_TYPE (ClutterAlignConstraint, static void source_position_changed (ClutterActor *actor, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags, + GParamSpec *pspec, ClutterAlignConstraint *align) { if (align->actor != NULL) @@ -410,7 +409,7 @@ clutter_align_constraint_set_source (ClutterAlignConstraint *align, align->source = source; if (align->source != NULL) { - g_signal_connect (align->source, "allocation-changed", + g_signal_connect (align->source, "notify::allocation", G_CALLBACK (source_position_changed), align); g_signal_connect (align->source, "destroy", diff --git a/clutter/clutter/clutter-bin-layout.c b/clutter/clutter/clutter-bin-layout.c index 8c63b802c..a17393c48 100644 --- a/clutter/clutter/clutter-bin-layout.c +++ b/clutter/clutter/clutter-bin-layout.c @@ -406,8 +406,7 @@ get_actor_align_factor (ClutterActorAlign alignment) static void clutter_bin_layout_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { gfloat allocation_x, allocation_y; gfloat available_w, available_h; @@ -515,8 +514,7 @@ clutter_bin_layout_allocate (ClutterLayoutManager *manager, clutter_actor_allocate_align_fill (child, &child_alloc, x_align, y_align, - x_fill, y_fill, - flags); + x_fill, y_fill); } } diff --git a/clutter/clutter/clutter-box-layout.c b/clutter/clutter/clutter-box-layout.c index 73eefe477..ed64d68db 100644 --- a/clutter/clutter/clutter-box-layout.c +++ b/clutter/clutter/clutter-box-layout.c @@ -739,8 +739,7 @@ static void allocate_box_child (ClutterBoxLayout *self, ClutterContainer *container, ClutterActor *child, - ClutterActorBox *child_box, - ClutterAllocationFlags flags) + ClutterActorBox *child_box) { ClutterBoxLayoutPrivate *priv = self->priv; ClutterBoxChild *box_child; @@ -769,14 +768,13 @@ allocate_box_child (ClutterBoxLayout *self, */ if (clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_HORIZONTAL) || clutter_actor_needs_expand (child, CLUTTER_ORIENTATION_VERTICAL)) - clutter_actor_allocate (child, child_box, flags); + clutter_actor_allocate (child, child_box); else clutter_actor_allocate_align_fill (child, child_box, get_box_alignment_factor (box_child->x_align), get_box_alignment_factor (box_child->y_align), box_child->x_fill, - box_child->y_fill, - flags); + box_child->y_fill); if (priv->use_animations) clutter_actor_restore_easing_state (child); @@ -975,8 +973,7 @@ distribute_natural_allocation (float extra_space, static void clutter_box_layout_allocate (ClutterLayoutManager *layout, ClutterContainer *container, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { ClutterBoxLayoutPrivate *priv = CLUTTER_BOX_LAYOUT (layout)->priv; ClutterActor *actor, *child; @@ -1251,8 +1248,7 @@ clutter_box_layout_allocate (ClutterLayoutManager *layout, allocate_box_child (CLUTTER_BOX_LAYOUT (layout), container, child, - &child_allocation, - flags); + &child_allocation); i += 1; } diff --git a/clutter/clutter/clutter-clone.c b/clutter/clutter/clutter-clone.c index 8c805725a..fb69f6c60 100644 --- a/clutter/clutter/clutter-clone.c +++ b/clutter/clutter/clutter-clone.c @@ -240,15 +240,14 @@ clutter_clone_has_overlaps (ClutterActor *actor) static void clutter_clone_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { ClutterClonePrivate *priv = CLUTTER_CLONE (self)->priv; ClutterActorClass *parent_class; /* chain up */ parent_class = CLUTTER_ACTOR_CLASS (clutter_clone_parent_class); - parent_class->allocate (self, box, flags); + parent_class->allocate (self, box); if (priv->clone_source == NULL) return; @@ -258,7 +257,7 @@ clutter_clone_allocate (ClutterActor *self, */ if (clutter_actor_get_parent (priv->clone_source) != NULL && !clutter_actor_has_allocation (priv->clone_source)) - clutter_actor_allocate_preferred_size (priv->clone_source, flags); + clutter_actor_allocate_preferred_size (priv->clone_source); #if 0 /* XXX - this is wrong: ClutterClone cannot clone unparented @@ -273,7 +272,7 @@ clutter_clone_allocate (ClutterActor *self, * paint cycle, we can safely give it as much size as it requires */ if (clutter_actor_get_parent (priv->clone_source) == NULL) - clutter_actor_allocate_preferred_size (priv->clone_source, flags); + clutter_actor_allocate_preferred_size (priv->clone_source); #endif } diff --git a/clutter/clutter/clutter-deform-effect.c b/clutter/clutter/clutter-deform-effect.c index 59ef1ff75..a1b7c89bb 100644 --- a/clutter/clutter/clutter-deform-effect.c +++ b/clutter/clutter/clutter-deform-effect.c @@ -128,10 +128,9 @@ clutter_deform_effect_deform_vertex (ClutterDeformEffect *effect, } static void -vbo_invalidate (ClutterActor *actor, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags, - ClutterDeformEffect *effect) +vbo_invalidate (ClutterActor *actor, + GParamSpec *pspec, + ClutterDeformEffect *effect) { effect->priv->is_dirty = TRUE; } @@ -156,7 +155,7 @@ clutter_deform_effect_set_actor (ClutterActorMeta *meta, * changes */ if (actor != NULL) - priv->allocation_id = g_signal_connect (actor, "allocation-changed", + priv->allocation_id = g_signal_connect (actor, "notify::allocation", G_CALLBACK (vbo_invalidate), meta); diff --git a/clutter/clutter/clutter-enums.h b/clutter/clutter/clutter-enums.h index 33a16f126..17105f8d7 100644 --- a/clutter/clutter/clutter-enums.h +++ b/clutter/clutter/clutter-enums.h @@ -554,32 +554,6 @@ typedef enum /*< prefix=CLUTTER_OFFSCREEN_REDIRECT >*/ CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE = 1 << 2 } ClutterOffscreenRedirect; -/** - * ClutterAllocationFlags: - * @CLUTTER_ALLOCATION_NONE: No flag set - * @CLUTTER_ABSOLUTE_ORIGIN_CHANGED: Whether the absolute origin of the - * actor has changed; this implies that any ancestor of the actor has - * been moved. - * @CLUTTER_DELEGATE_LAYOUT: Whether the allocation should be delegated - * to the #ClutterLayoutManager instance stored inside the - * #ClutterActor:layout-manager property of #ClutterActor. This flag - * should only be used if you are subclassing #ClutterActor and - * overriding the #ClutterActorClass.allocate() virtual function, but - * you wish to use the default implementation of the virtual function - * inside #ClutterActor. Added in Clutter 1.10. - * - * Flags passed to the #ClutterActorClass.allocate() virtual function - * and to the clutter_actor_allocate() function. - * - * Since: 1.0 - */ -typedef enum -{ - CLUTTER_ALLOCATION_NONE = 0, - CLUTTER_ABSOLUTE_ORIGIN_CHANGED = 1 << 1, - CLUTTER_DELEGATE_LAYOUT = 1 << 2 -} ClutterAllocationFlags; - /** * ClutterAlignAxis: * @CLUTTER_ALIGN_X_AXIS: Maintain the alignment on the X axis diff --git a/clutter/clutter/clutter-fixed-layout.c b/clutter/clutter/clutter-fixed-layout.c index 959f7652e..324084809 100644 --- a/clutter/clutter/clutter-fixed-layout.c +++ b/clutter/clutter/clutter-fixed-layout.c @@ -131,8 +131,7 @@ clutter_fixed_layout_get_preferred_height (ClutterLayoutManager *manager, static void clutter_fixed_layout_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterActor *child; @@ -140,7 +139,7 @@ clutter_fixed_layout_allocate (ClutterLayoutManager *manager, child != NULL; child = clutter_actor_get_next_sibling (child)) { - clutter_actor_allocate_preferred_size (child, flags); + clutter_actor_allocate_preferred_size (child); } } diff --git a/clutter/clutter/clutter-flow-layout.c b/clutter/clutter/clutter-flow-layout.c index 365c5d9d8..f7ced9837 100644 --- a/clutter/clutter/clutter-flow-layout.c +++ b/clutter/clutter/clutter-flow-layout.c @@ -566,8 +566,7 @@ clutter_flow_layout_get_preferred_height (ClutterLayoutManager *manager, static void clutter_flow_layout_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterFlowLayoutPrivate *priv = CLUTTER_FLOW_LAYOUT (manager)->priv; ClutterActor *actor, *child; @@ -729,7 +728,7 @@ clutter_flow_layout_allocate (ClutterLayoutManager *manager, child_alloc.y1 = ceil (item_y); child_alloc.x2 = ceil (child_alloc.x1 + item_width); child_alloc.y2 = ceil (child_alloc.y1 + item_height); - clutter_actor_allocate (child, &child_alloc, flags); + clutter_actor_allocate (child, &child_alloc); if (priv->orientation == CLUTTER_FLOW_HORIZONTAL) item_x = new_x; diff --git a/clutter/clutter/clutter-grid-layout.c b/clutter/clutter/clutter-grid-layout.c index 29ec54759..ebc022eb7 100644 --- a/clutter/clutter/clutter-grid-layout.c +++ b/clutter/clutter/clutter-grid-layout.c @@ -1391,8 +1391,7 @@ allocate_child (ClutterGridRequest *request, static void clutter_grid_layout_allocate (ClutterLayoutManager *layout, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterGridLayout *self = CLUTTER_GRID_LAYOUT (layout); ClutterOrientation orientation; @@ -1453,7 +1452,7 @@ clutter_grid_layout_allocate (ClutterLayoutManager *layout, child_allocation.x2 = child_allocation.x1 + width; child_allocation.y2 = child_allocation.y1 + height; - clutter_actor_allocate (child, &child_allocation, flags); + clutter_actor_allocate (child, &child_allocation); } } diff --git a/clutter/clutter/clutter-layout-manager.c b/clutter/clutter/clutter-layout-manager.c index 13dbd6c0b..5652a004e 100644 --- a/clutter/clutter/clutter-layout-manager.c +++ b/clutter/clutter/clutter-layout-manager.c @@ -255,8 +255,7 @@ layout_manager_real_get_preferred_height (ClutterLayoutManager *manager, static void layout_manager_real_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { LAYOUT_MANAGER_WARN_NOT_IMPLEMENTED (manager, "allocate"); } @@ -523,7 +522,6 @@ clutter_layout_manager_get_preferred_height (ClutterLayoutManager *manager, * @container: the #ClutterContainer using @manager * @allocation: the #ClutterActorBox containing the allocated area * of @container - * @flags: the allocation flags * * Allocates the children of @container given an area * @@ -534,8 +532,7 @@ clutter_layout_manager_get_preferred_height (ClutterLayoutManager *manager, void clutter_layout_manager_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterLayoutManagerClass *klass; @@ -544,7 +541,7 @@ clutter_layout_manager_allocate (ClutterLayoutManager *manager, g_return_if_fail (allocation != NULL); klass = CLUTTER_LAYOUT_MANAGER_GET_CLASS (manager); - klass->allocate (manager, container, allocation, flags); + klass->allocate (manager, container, allocation); } /** diff --git a/clutter/clutter/clutter-layout-manager.h b/clutter/clutter/clutter-layout-manager.h index 00f711803..4bc268706 100644 --- a/clutter/clutter/clutter-layout-manager.h +++ b/clutter/clutter/clutter-layout-manager.h @@ -115,8 +115,7 @@ struct _ClutterLayoutManagerClass gfloat *nat_height_p); void (* allocate) (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags); + const ClutterActorBox *allocation); void (* set_container) (ClutterLayoutManager *manager, ClutterContainer *container); @@ -167,8 +166,7 @@ void clutter_layout_manager_get_preferred_height (ClutterLayoutMa CLUTTER_EXPORT void clutter_layout_manager_allocate (ClutterLayoutManager *manager, ClutterContainer *container, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags); + const ClutterActorBox *allocation); CLUTTER_EXPORT void clutter_layout_manager_set_container (ClutterLayoutManager *manager, diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index f2894b56e..8b2556809 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -641,8 +641,7 @@ stage_is_default (ClutterStage *stage) static void clutter_stage_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv; ClutterActorBox alloc = CLUTTER_ACTOR_BOX_INIT_ZERO; @@ -650,6 +649,7 @@ clutter_stage_allocate (ClutterActor *self, float new_width, new_height; float width, height; cairo_rectangle_int_t window_size; + ClutterLayoutManager *layout_manager = clutter_actor_get_layout_manager (self); if (priv->impl == NULL) return; @@ -671,15 +671,21 @@ clutter_stage_allocate (ClutterActor *self, */ if (!clutter_feature_available (CLUTTER_FEATURE_STAGE_STATIC)) { + ClutterActorBox children_box; + + children_box.x1 = children_box.y1 = 0.f; + children_box.x2 = box->x2 - box->x1; + children_box.y2 = box->y2 - box->y1; + CLUTTER_NOTE (LAYOUT, - "Following allocation to %.2fx%.2f (absolute origin %s)", - width, height, - (flags & CLUTTER_ABSOLUTE_ORIGIN_CHANGED) - ? "changed" - : "not changed"); + "Following allocation to %.2fx%.2f", + width, height); - clutter_actor_set_allocation (self, box, - flags | CLUTTER_DELEGATE_LAYOUT); + clutter_actor_set_allocation (self, box); + + clutter_layout_manager_allocate (layout_manager, + CLUTTER_CONTAINER (self), + &children_box); /* Ensure the window is sized correctly */ if (priv->min_size_changed) @@ -727,16 +733,16 @@ clutter_stage_allocate (ClutterActor *self, CLUTTER_NOTE (LAYOUT, "Overriding original allocation of %.2fx%.2f " - "with %.2fx%.2f (absolute origin %s)", + "with %.2fx%.2f", width, height, - override.x2, override.y2, - (flags & CLUTTER_ABSOLUTE_ORIGIN_CHANGED) - ? "changed" - : "not changed"); + override.x2, override.y2); /* and store the overridden allocation */ - clutter_actor_set_allocation (self, &override, - flags | CLUTTER_DELEGATE_LAYOUT); + clutter_actor_set_allocation (self, &override); + + clutter_layout_manager_allocate (layout_manager, + CLUTTER_CONTAINER (self), + &override); } /* reset the viewport if the allocation effectively changed */ @@ -1407,8 +1413,7 @@ _clutter_stage_maybe_relayout (ClutterActor *actor) CLUTTER_SET_PRIVATE_FLAGS (queued_actor, CLUTTER_IN_RELAYOUT); old_version = priv->pending_relayouts_version; - clutter_actor_allocate_preferred_size (queued_actor, - CLUTTER_ALLOCATION_NONE); + clutter_actor_allocate_preferred_size (queued_actor); CLUTTER_UNSET_PRIVATE_FLAGS (queued_actor, CLUTTER_IN_RELAYOUT); diff --git a/clutter/clutter/clutter-text.c b/clutter/clutter/clutter-text.c index e18ed4c48..047fddd44 100644 --- a/clutter/clutter/clutter-text.c +++ b/clutter/clutter/clutter-text.c @@ -3033,8 +3033,7 @@ clutter_text_get_preferred_height (ClutterActor *self, static void clutter_text_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { ClutterText *text = CLUTTER_TEXT (self); ClutterActorClass *parent_class; @@ -3054,7 +3053,7 @@ clutter_text_allocate (ClutterActor *self, box->y2 - box->y1); parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class); - parent_class->allocate (self, box, flags); + parent_class->allocate (self, box); } static gboolean diff --git a/clutter/clutter/deprecated/clutter-group.c b/clutter/clutter/deprecated/clutter-group.c index af602fd51..dce35cff8 100644 --- a/clutter/clutter/deprecated/clutter-group.c +++ b/clutter/clutter/deprecated/clutter-group.c @@ -333,21 +333,20 @@ clutter_group_real_get_preferred_height (ClutterActor *actor, static void clutter_group_real_allocate (ClutterActor *actor, - const ClutterActorBox *allocation, - ClutterAllocationFlags flags) + const ClutterActorBox *allocation) { ClutterGroupPrivate *priv = CLUTTER_GROUP (actor)->priv; ClutterActorClass *klass; klass = CLUTTER_ACTOR_CLASS (clutter_group_parent_class); - klass->allocate (actor, allocation, flags); + klass->allocate (actor, allocation); if (priv->children == NULL) return; clutter_layout_manager_allocate (priv->layout, CLUTTER_CONTAINER (actor), - allocation, flags); + allocation); } static void diff --git a/src/tests/clutter/conform/actor-anchors.c b/src/tests/clutter/conform/actor-anchors.c index 3cf3191f3..bd934ff14 100644 --- a/src/tests/clutter/conform/actor-anchors.c +++ b/src/tests/clutter/conform/actor-anchors.c @@ -716,8 +716,8 @@ actor_pivot (void) clutter_actor_add_child (stage, actor_explicit); /* Fake allocation or pivot-point will not have any effect */ - clutter_actor_allocate (actor_implicit, &allocation, CLUTTER_ALLOCATION_NONE); - clutter_actor_allocate (actor_explicit, &allocation, CLUTTER_ALLOCATION_NONE); + clutter_actor_allocate (actor_implicit, &allocation); + clutter_actor_allocate (actor_explicit, &allocation); clutter_actor_set_pivot_point (actor_implicit, 0.5, 0.5); clutter_actor_set_pivot_point (actor_explicit, 0.5, 0.5); diff --git a/src/tests/clutter/conform/actor-pick.c b/src/tests/clutter/conform/actor-pick.c index dcee96424..3511be7d2 100644 --- a/src/tests/clutter/conform/actor-pick.c +++ b/src/tests/clutter/conform/actor-pick.c @@ -82,7 +82,7 @@ on_timeout (gpointer data) /* Only allocated actors can be picked, so force an allocation * of the overlay actor here. */ - clutter_actor_allocate (over_actor, &over_actor_box, 0); + clutter_actor_allocate (over_actor, &over_actor_box); if (g_test_verbose ()) g_print ("Clipped covering actor:\n"); diff --git a/src/tests/clutter/interactive/test-layout.c b/src/tests/clutter/interactive/test-layout.c index 9666f15df..5f697c907 100644 --- a/src/tests/clutter/interactive/test-layout.c +++ b/src/tests/clutter/interactive/test-layout.c @@ -276,15 +276,14 @@ my_thing_get_preferred_height (ClutterActor *self, static void my_thing_allocate (ClutterActor *self, - const ClutterActorBox *box, - ClutterAllocationFlags flags) + const ClutterActorBox *box) { MyThingPrivate *priv; gfloat current_x, current_y, max_row_height; ClutterActorIter iter; ClutterActor *child; - clutter_actor_set_allocation (self, box, flags); + clutter_actor_set_allocation (self, box); priv = MY_THING (self)->priv; @@ -322,7 +321,7 @@ my_thing_allocate (ClutterActor *self, child_box.x2 = child_box.x1 + natural_width; child_box.y2 = child_box.y1 + natural_height; - clutter_actor_allocate (child, &child_box, flags); + clutter_actor_allocate (child, &child_box); /* if we take into account the transformation of the children * then we first check if it's transformed; then we get the @@ -338,17 +337,8 @@ my_thing_allocate (ClutterActor *self, graphene_point3d_t v1 = { 0, }, v2 = { 0, }; ClutterActorBox transformed_box = { 0, }; - /* origin */ - if (!(flags & CLUTTER_ABSOLUTE_ORIGIN_CHANGED)) - { - v1.x = 0; - v1.y = 0; - } - else - { - v1.x = box->x1; - v1.y = box->y1; - } + v1.x = box->x1; + v1.y = box->y1; clutter_actor_apply_transform_to_point (child, &v1, &v2); transformed_box.x1 = v2.x; From e81e9956f2355130a8ea5527a65160d1ca9de571 Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 17:38:20 +0400 Subject: [PATCH 2/6] Fixes related to actor allocations and new optimization Fix some issues with allocations and add an optimization to bail out of allocation cycles if only the absolute origin changed (we can do this because now we no longer need to notify all actors about the absolute origin change using the allocation flag). --- clutter/clutter/clutter-actor.c | 90 ++++++++++++++++--------- clutter/clutter/clutter-stage-private.h | 6 +- clutter/clutter/clutter-stage.c | 50 +++++--------- 3 files changed, 76 insertions(+), 70 deletions(-) diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 94f5ce0d4..a2ce62d00 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -2583,6 +2583,22 @@ clutter_actor_notify_if_geometry_changed (ClutterActor *self, g_object_thaw_notify (obj); } +static void +absolute_allocation_changed (ClutterActor *actor) +{ + actor->priv->needs_compute_resource_scale = TRUE; +} + +static ClutterActorTraverseVisitFlags +absolute_allocation_changed_cb (ClutterActor *actor, + int depth, + gpointer user_data) +{ + absolute_allocation_changed (actor); + + return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE; +} + /*< private > * clutter_actor_set_allocation_internal: * @self: a #ClutterActor @@ -2626,6 +2642,11 @@ clutter_actor_set_allocation_internal (ClutterActor *self, priv->needs_height_request = FALSE; priv->needs_allocation = FALSE; + priv->absolute_origin_changed |= x1_changed || y1_changed; + + if (priv->absolute_origin_changed || x2_changed || y2_changed) + absolute_allocation_changed (self); + if (x1_changed || y1_changed || x2_changed || @@ -10246,7 +10267,6 @@ clutter_actor_allocate (ClutterActor *self, { ClutterActorBox old_allocation, real_allocation; gboolean origin_changed, size_changed; - gboolean stage_allocation_changed; ClutterActorPrivate *priv; g_return_if_fail (CLUTTER_IS_ACTOR (self)); @@ -10258,11 +10278,25 @@ clutter_actor_allocate (ClutterActor *self, return; } - if (!clutter_actor_is_visible (self)) - return; - priv = self->priv; + priv->absolute_origin_changed = priv->parent + ? priv->parent->priv->absolute_origin_changed + : FALSE; + + if (!CLUTTER_ACTOR_IS_VISIBLE (self)) + { + if (priv->absolute_origin_changed) + { + _clutter_actor_traverse (self, + CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST, + absolute_allocation_changed_cb, + NULL, + NULL); + } + goto out; + } + old_allocation = priv->allocation; real_allocation = *box; @@ -10299,49 +10333,45 @@ clutter_actor_allocate (ClutterActor *self, size_changed = (real_allocation.x2 != old_allocation.x2 || real_allocation.y2 != old_allocation.y2); - priv->absolute_origin_changed = priv->parent - ? priv->parent->priv->absolute_origin_changed - : FALSE; - - priv->absolute_origin_changed |= origin_changed; - - stage_allocation_changed = priv->absolute_origin_changed || size_changed; - - /* If we get an allocation "out of the blue" - * (we did not queue relayout), then we want to - * ignore it. But if we have needs_allocation set, - * we want to guarantee that allocate() virtual - * method is always called, i.e. that queue_relayout() - * always results in an allocate() invocation on - * an actor. + /* When needs_allocation is set but we didn't move nor resize, we still + * want to call the allocate() vfunc because a child probably called + * queue_relayout() and needs a new allocation. * - * The optimization here is to avoid re-allocating - * actors that did not queue relayout and were - * not moved. + * In case needs_allocation isn't set and we didn't move nor resize, we + * can safely stop allocating, but we need to notify the sub-tree in case + * our absolute origin changed. */ - if (!priv->needs_allocation && !stage_allocation_changed) + if (!priv->needs_allocation && !origin_changed && !size_changed) { + if (priv->absolute_origin_changed) + { + _clutter_actor_traverse (self, + CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST, + absolute_allocation_changed_cb, + NULL, + NULL); + } + CLUTTER_NOTE (LAYOUT, "No allocation needed"); - return; + goto out; } if (CLUTTER_ACTOR_IS_MAPPED (self)) self->priv->needs_paint_volume_update = TRUE; - if (stage_allocation_changed) - priv->needs_compute_resource_scale = TRUE; - - if (!stage_allocation_changed) + if (!origin_changed && !size_changed) { /* If the actor didn't move but needs_allocation is set, we just - * need to allocate the children */ + * need to allocate the children (see comment above) */ clutter_actor_allocate_internal (self, &real_allocation); - return; + goto out; } _clutter_actor_create_transition (self, obj_props[PROP_ALLOCATION], &priv->allocation, &real_allocation); +out: + priv->absolute_origin_changed = FALSE; } /** diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index 448fd3ef7..fa3699668 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -53,11 +53,7 @@ ClutterStageWindow *_clutter_stage_get_window (ClutterStage void _clutter_stage_get_projection_matrix (ClutterStage *stage, CoglMatrix *projection); void _clutter_stage_dirty_projection (ClutterStage *stage); -void _clutter_stage_set_viewport (ClutterStage *stage, - float x, - float y, - float width, - float height); + void _clutter_stage_get_viewport (ClutterStage *stage, float *x, float *y, diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 8b2556809..3b44b8923 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -215,6 +215,9 @@ static void capture_view_into (ClutterStage *stage, uint8_t *data, int stride); static void clutter_stage_update_view_perspective (ClutterStage *stage); +static void clutter_stage_set_viewport (ClutterStage *stage, + float width, + float height); static void clutter_container_iface_init (ClutterContainerIface *iface); @@ -645,7 +648,6 @@ clutter_stage_allocate (ClutterActor *self, { ClutterStagePrivate *priv = CLUTTER_STAGE (self)->priv; ClutterActorBox alloc = CLUTTER_ACTOR_BOX_INIT_ZERO; - float old_width, old_height; float new_width, new_height; float width, height; cairo_rectangle_int_t window_size; @@ -654,10 +656,6 @@ clutter_stage_allocate (ClutterActor *self, if (priv->impl == NULL) return; - /* our old allocation */ - clutter_actor_get_allocation_box (self, &alloc); - clutter_actor_box_get_size (&alloc, &old_width, &old_height); - /* the current allocation */ clutter_actor_box_get_size (box, &width, &height); @@ -745,27 +743,11 @@ clutter_stage_allocate (ClutterActor *self, &override); } - /* reset the viewport if the allocation effectively changed */ + /* set the viewport to the new allocation */ clutter_actor_get_allocation_box (self, &alloc); clutter_actor_box_get_size (&alloc, &new_width, &new_height); - if (CLUTTER_NEARBYINT (old_width) != CLUTTER_NEARBYINT (new_width) || - CLUTTER_NEARBYINT (old_height) != CLUTTER_NEARBYINT (new_height)) - { - int real_width = CLUTTER_NEARBYINT (new_width); - int real_height = CLUTTER_NEARBYINT (new_height); - - _clutter_stage_set_viewport (CLUTTER_STAGE (self), - 0, 0, - real_width, - real_height); - - /* Note: we don't assume that set_viewport will queue a full redraw - * since it may bail-out early if something preemptively set the - * viewport before the stage was really allocated its new size. - */ - queue_full_redraw (CLUTTER_STAGE (self)); - } + clutter_stage_set_viewport (CLUTTER_STAGE (self), new_width, new_height); } typedef struct _Vector4 @@ -2433,10 +2415,7 @@ clutter_stage_init (ClutterStage *self) g_signal_connect (self, "notify::min-height", G_CALLBACK (clutter_stage_notify_min_size), NULL); - _clutter_stage_set_viewport (self, - 0, 0, - geom.width, - geom.height); + clutter_stage_set_viewport (self, geom.width, geom.height); priv->paint_volume_stack = g_array_new (FALSE, FALSE, sizeof (ClutterPaintVolume)); @@ -2653,8 +2632,6 @@ _clutter_stage_dirty_projection (ClutterStage *stage) /* * clutter_stage_set_viewport: * @stage: A #ClutterStage - * @x: The X postition to render the stage at, in window coordinates - * @y: The Y position to render the stage at, in window coordinates * @width: The width to render the stage at, in window coordinates * @height: The height to render the stage at, in window coordinates * @@ -2687,19 +2664,22 @@ _clutter_stage_dirty_projection (ClutterStage *stage) * * Since: 1.6 */ -void -_clutter_stage_set_viewport (ClutterStage *stage, - float x, - float y, - float width, - float height) +static void +clutter_stage_set_viewport (ClutterStage *stage, + float width, + float height) { ClutterStagePrivate *priv; + float x, y; g_return_if_fail (CLUTTER_IS_STAGE (stage)); priv = stage->priv; + x = 0.f; + y = 0.f; + width = roundf (width); + height = roundf (height); if (x == priv->viewport[0] && y == priv->viewport[1] && From f1046bb97e7b633f97ed0de93ea4a28f430c41ea Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 17:44:50 +0400 Subject: [PATCH 3/6] clutter/clone: Build scale factor for transformation during allocation For ClutterClones we need to apply a scale to the texture of the clone to ensure the painted texture of the source actor actually fits the allocation of the clone. We're doing this using the transformation matrix instead of using the scale_x/scale_y properties of ClutterActor to allow users to scale ClutterClones using that API independently. Now it's quite a bad idea to get the allocation boxes for calculating that scale using clutter_actor_get_allocation_box(), since that method will internally do an immediate relayout of the stage in case the actor isn't allocated. Another side effect of that approach is that it makes it impossible to invalidate the transform (which is needed for the next commit when we start caching those matrices) properly. So since we eventually allocate both the source actor and the clone ourselves anyway, we can simply use the allocation box inside clutter_clone_allocate() (which is definitely updated and valid at that point) to calculate the scale factor. --- clutter/clutter/clutter-clone.c | 42 ++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 17 deletions(-) diff --git a/clutter/clutter/clutter-clone.c b/clutter/clutter/clutter-clone.c index fb69f6c60..fe34359d8 100644 --- a/clutter/clutter/clutter-clone.c +++ b/clutter/clutter/clutter-clone.c @@ -52,6 +52,8 @@ struct _ClutterClonePrivate { ClutterActor *clone_source; + float x_scale, y_scale; + gulong source_destroy_id; }; @@ -122,8 +124,6 @@ static void clutter_clone_apply_transform (ClutterActor *self, CoglMatrix *matrix) { ClutterClonePrivate *priv = CLUTTER_CLONE (self)->priv; - ClutterActorBox box, source_box; - gfloat x_scale, y_scale; /* First chain up and apply all the standard ClutterActor * transformations... */ @@ -134,21 +134,7 @@ clutter_clone_apply_transform (ClutterActor *self, CoglMatrix *matrix) if (priv->clone_source == NULL) return; - /* get our allocated size */ - clutter_actor_get_allocation_box (self, &box); - - /* and get the allocated size of the source */ - clutter_actor_get_allocation_box (priv->clone_source, &source_box); - - /* We need to scale what the clone-source actor paints to fill our own - * allocation... - */ - x_scale = clutter_actor_box_get_width (&box) - / clutter_actor_box_get_width (&source_box); - y_scale = clutter_actor_box_get_height (&box) - / clutter_actor_box_get_height (&source_box); - - cogl_matrix_scale (matrix, x_scale, y_scale, x_scale); + cogl_matrix_scale (matrix, priv->x_scale, priv->y_scale, 1.f); } static void @@ -244,6 +230,8 @@ clutter_clone_allocate (ClutterActor *self, { ClutterClonePrivate *priv = CLUTTER_CLONE (self)->priv; ClutterActorClass *parent_class; + ClutterActorBox source_box; + float x_scale, y_scale; /* chain up */ parent_class = CLUTTER_ACTOR_CLASS (clutter_clone_parent_class); @@ -259,6 +247,23 @@ clutter_clone_allocate (ClutterActor *self, !clutter_actor_has_allocation (priv->clone_source)) clutter_actor_allocate_preferred_size (priv->clone_source); + clutter_actor_get_allocation_box (priv->clone_source, &source_box); + + /* We need to scale what the clone-source actor paints to fill our own + * allocation... + */ + x_scale = clutter_actor_box_get_width (box) + / clutter_actor_box_get_width (&source_box); + y_scale = clutter_actor_box_get_height (box) + / clutter_actor_box_get_height (&source_box); + + if (!G_APPROX_VALUE (priv->x_scale, x_scale, FLT_EPSILON) || + !G_APPROX_VALUE (priv->y_scale, y_scale, FLT_EPSILON)) + { + priv->x_scale = x_scale; + priv->y_scale = y_scale; + } + #if 0 /* XXX - this is wrong: ClutterClone cannot clone unparented * actors, as it will break all invariants @@ -364,6 +369,9 @@ static void clutter_clone_init (ClutterClone *self) { self->priv = clutter_clone_get_instance_private (self); + + self->priv->x_scale = 1.f; + self->priv->y_scale = 1.f; } /** From 44f5b0e3ff1661f6e1888ba0ba4fad5e96df0856 Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 18:47:55 +0400 Subject: [PATCH 4/6] Add Clutter API to get the stage-views an actor is painted on Add a clutter_actor_peek_stage_views() method and a stage-views-changed signal to ClutterActor. This doesn't invalidate the stage-views list on changes to the transformation of the actors yet, for that we need another new ClutterActor method to invalidate the custom transformations applied via the apply_transform() vfunc. Also things will get quite a bit more expensive when doing that (the transformation matrix will have to be put together more often), so we probably also want the caching of transformation matrices in place when we start doing that. --- clutter/clutter/clutter-actor-private.h | 4 +- clutter/clutter/clutter-actor.c | 227 +++++++- clutter/clutter/clutter-actor.h | 3 + clutter/clutter/clutter-muffin.h | 2 +- clutter/clutter/clutter-stage-private.h | 6 +- clutter/clutter/clutter-stage.c | 50 +- src/backends/native/meta-stage-native.c | 2 +- .../x11/nested/meta-backend-x11-nested.c | 2 +- src/tests/meson.build | 28 + src/tests/stage-view-tests.c | 504 ++++++++++++++++++ 10 files changed, 794 insertions(+), 34 deletions(-) create mode 100644 src/tests/stage-view-tests.c diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index 9ff4b2d4b..ed398bf21 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -313,7 +313,7 @@ void _clutter_actor_detach_clone void _clutter_actor_queue_redraw_on_clones (ClutterActor *actor); void _clutter_actor_queue_relayout_on_clones (ClutterActor *actor); void _clutter_actor_queue_only_relayout (ClutterActor *actor); -void _clutter_actor_queue_update_resource_scale_recursive (ClutterActor *actor); +void clutter_actor_clear_stage_views_recursive (ClutterActor *actor); gboolean _clutter_actor_get_real_resource_scale (ClutterActor *actor, float *resource_scale); @@ -321,6 +321,8 @@ gboolean _clutter_actor_get_real_resource_scale ClutterPaintNode * clutter_actor_create_texture_paint_node (ClutterActor *self, CoglTexture *texture); +void clutter_actor_update_stage_views (ClutterActor *self); + G_END_DECLS #endif /* __CLUTTER_ACTOR_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index a2ce62d00..cf8f05175 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -812,6 +812,8 @@ struct _ClutterActorPrivate gulong resolution_changed_id; gulong font_changed_id; + GList *stage_views; + /* bitfields: KEEP AT THE END */ /* fixed position and sizes */ @@ -854,6 +856,7 @@ struct _ClutterActorPrivate guint had_effects_on_last_paint_volume_update : 1; guint needs_compute_resource_scale : 1; guint absolute_origin_changed : 1; + guint needs_update_stage_views : 1; }; enum @@ -1016,6 +1019,7 @@ enum TRANSITIONS_COMPLETED, TOUCH_EVENT, TRANSITION_STOPPED, + STAGE_VIEWS_CHANGED, LAST_SIGNAL }; @@ -1636,6 +1640,22 @@ clutter_actor_update_map_state (ClutterActor *self, #endif } +static void +queue_update_stage_views (ClutterActor *actor) +{ + while (actor && !actor->priv->needs_update_stage_views) + { + actor->priv->needs_update_stage_views = TRUE; + + /* We don't really need to update the stage-views of the actors up the + * hierarchy, we set the flag anyway though so we can avoid traversing + * the whole scenegraph when looking for actors which need an update + * in clutter_actor_update_stage_views(). + */ + actor = actor->priv->parent; + } +} + static void clutter_actor_real_map (ClutterActor *self) { @@ -1650,6 +1670,18 @@ clutter_actor_real_map (ClutterActor *self) self->priv->needs_paint_volume_update = TRUE; + /* We skip unmapped actors when updating the stage-views list, so if + * an actors list got invalidated while it was unmapped make sure to + * set priv->needs_update_stage_views to TRUE for all actors up the + * hierarchy now. + */ + if (self->priv->needs_update_stage_views) + { + /* Avoid the early return in queue_update_stage_views() */ + self->priv->needs_update_stage_views = FALSE; + queue_update_stage_views (self); + } + clutter_actor_ensure_resource_scale (self); /* notify on parent mapped before potentially mapping @@ -2587,6 +2619,7 @@ static void absolute_allocation_changed (ClutterActor *actor) { actor->priv->needs_compute_resource_scale = TRUE; + queue_update_stage_views (actor); } static ClutterActorTraverseVisitFlags @@ -4403,6 +4436,7 @@ typedef enum REMOVE_CHILD_FLUSH_QUEUE = 1 << 4, REMOVE_CHILD_NOTIFY_FIRST_LAST = 1 << 5, REMOVE_CHILD_STOP_TRANSITIONS = 1 << 6, + REMOVE_CHILD_CLEAR_STAGE_VIEWS = 1 << 7, /* default flags for public API */ REMOVE_CHILD_DEFAULT_FLAGS = REMOVE_CHILD_STOP_TRANSITIONS | @@ -4411,14 +4445,16 @@ typedef enum REMOVE_CHILD_EMIT_ACTOR_REMOVED | REMOVE_CHILD_CHECK_STATE | REMOVE_CHILD_FLUSH_QUEUE | - REMOVE_CHILD_NOTIFY_FIRST_LAST, + REMOVE_CHILD_NOTIFY_FIRST_LAST | + REMOVE_CHILD_CLEAR_STAGE_VIEWS, /* flags for legacy/deprecated API */ REMOVE_CHILD_LEGACY_FLAGS = REMOVE_CHILD_STOP_TRANSITIONS | REMOVE_CHILD_CHECK_STATE | REMOVE_CHILD_FLUSH_QUEUE | REMOVE_CHILD_EMIT_PARENT_SET | - REMOVE_CHILD_NOTIFY_FIRST_LAST + REMOVE_CHILD_NOTIFY_FIRST_LAST | + REMOVE_CHILD_CLEAR_STAGE_VIEWS } ClutterActorRemoveChildFlags; /*< private > @@ -4440,6 +4476,7 @@ clutter_actor_remove_child_internal (ClutterActor *self, gboolean notify_first_last; gboolean was_mapped; gboolean stop_transitions; + gboolean clear_stage_views; GObject *obj; if (self == child) @@ -4456,6 +4493,7 @@ clutter_actor_remove_child_internal (ClutterActor *self, flush_queue = (flags & REMOVE_CHILD_FLUSH_QUEUE) != 0; notify_first_last = (flags & REMOVE_CHILD_NOTIFY_FIRST_LAST) != 0; stop_transitions = (flags & REMOVE_CHILD_STOP_TRANSITIONS) != 0; + clear_stage_views = (flags & REMOVE_CHILD_CLEAR_STAGE_VIEWS) != 0; obj = G_OBJECT (self); g_object_freeze_notify (obj); @@ -4529,6 +4567,13 @@ clutter_actor_remove_child_internal (ClutterActor *self, clutter_actor_queue_compute_expand (self); } + /* Only actors which are attached to a stage get notified about changes + * to the stage views, so make sure all the stage-views lists are + * cleared as the child and its children leave the actor tree. + */ + if (clear_stage_views && !CLUTTER_ACTOR_IN_DESTRUCTION (child)) + clutter_actor_clear_stage_views_recursive (child); + if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child) && !CLUTTER_ACTOR_IN_DESTRUCTION (child)) { @@ -6201,6 +6246,8 @@ clutter_actor_dispose (GObject *object) priv->clones = NULL; } + g_clear_pointer (&priv->stage_views, g_list_free); + G_OBJECT_CLASS (clutter_actor_parent_class)->dispose (object); } @@ -8748,6 +8795,26 @@ clutter_actor_class_init (ClutterActorClass *klass) g_signal_set_va_marshaller (actor_signals[TOUCH_EVENT], G_TYPE_FROM_CLASS (object_class), _clutter_marshal_BOOLEAN__BOXEDv); + + /** + * ClutterActor::stage-views-changed: + * @actor: a #ClutterActor + * + * The ::stage-views-changed signal is emitted when the position or + * size an actor is being painted at have changed so that it's visible + * on different stage views. + * + * This signal is also emitted when the actor gets detached from the stage + * or when the views of the stage have been invalidated and will be + * replaced; it's not emitted when the actor gets hidden. + */ + actor_signals[STAGE_VIEWS_CHANGED] = + g_signal_new (I_("stage-views-changed"), + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, NULL, + G_TYPE_NONE, 0); } static void @@ -8766,6 +8833,7 @@ clutter_actor_init (ClutterActor *self) priv->needs_allocation = TRUE; priv->needs_paint_volume_update = TRUE; priv->needs_compute_resource_scale = TRUE; + priv->needs_update_stage_views = TRUE; priv->cached_width_age = 1; priv->cached_height_age = 1; @@ -17578,17 +17646,27 @@ _clutter_actor_get_resource_scale_for_rect (ClutterActor *self, float *resource_scale) { ClutterActor *stage; + g_autoptr (GList) views = NULL; + GList *l; float max_scale = 0; stage = _clutter_actor_get_stage_internal (self); if (!stage) return FALSE; - if (!_clutter_stage_get_max_view_scale_factor_for_rect (CLUTTER_STAGE (stage), - bounding_rect, - &max_scale)) + views = clutter_stage_get_views_for_rect (CLUTTER_STAGE (stage), + bounding_rect); + + if (!views) return FALSE; + for (l = views; l; l = l->next) + { + ClutterStageView *view = l->data; + + max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); + } + *resource_scale = max_scale; return TRUE; @@ -17664,20 +17742,30 @@ _clutter_actor_compute_resource_scale (ClutterActor *self, } static ClutterActorTraverseVisitFlags -queue_update_resource_scale_cb (ClutterActor *actor, - int depth, - void *user_data) +clear_stage_views_cb (ClutterActor *actor, + int depth, + gpointer user_data) { + g_autoptr (GList) old_stage_views = NULL; + + actor->priv->needs_update_stage_views = TRUE; + actor->priv->needs_compute_resource_scale = TRUE; + + old_stage_views = g_steal_pointer (&actor->priv->stage_views); + + if (old_stage_views) + g_signal_emit (actor, actor_signals[STAGE_VIEWS_CHANGED], 0); + return CLUTTER_ACTOR_TRAVERSE_VISIT_CONTINUE; } void -_clutter_actor_queue_update_resource_scale_recursive (ClutterActor *self) +clutter_actor_clear_stage_views_recursive (ClutterActor *self) { _clutter_actor_traverse (self, CLUTTER_ACTOR_TRAVERSE_DEPTH_FIRST, - queue_update_resource_scale_cb, + clear_stage_views_cb, NULL, NULL); } @@ -17769,6 +17857,125 @@ clutter_actor_get_resource_scale (ClutterActor *self, return FALSE; } +static gboolean +sorted_lists_equal (GList *list_a, + GList *list_b) +{ + GList *a, *b; + + if (!list_a && !list_b) + return TRUE; + + for (a = list_a, b = list_b; + a && b; + a = a->next, b = b->next) + { + if (a->data != b->data) + break; + + if (!a->next && !b->next) + return TRUE; + } + + return FALSE; +} + +static void +update_stage_views (ClutterActor *self) +{ + ClutterActorPrivate *priv = self->priv; + g_autoptr (GList) old_stage_views = NULL; + ClutterStage *stage; + graphene_rect_t bounding_rect; + + old_stage_views = g_steal_pointer (&priv->stage_views); + + if (priv->needs_allocation) + { + g_warning ("Can't update stage views actor %s is on because it needs an " + "allocation.", _clutter_actor_get_debug_name (self)); + goto out; + } + + stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); + g_return_if_fail (stage); + + clutter_actor_get_transformed_position (self, + &bounding_rect.origin.x, + &bounding_rect.origin.y); + clutter_actor_get_transformed_size (self, + &bounding_rect.size.width, + &bounding_rect.size.height); + + if (bounding_rect.size.width == 0.0 || + bounding_rect.size.height == 0.0) + goto out; + + priv->stage_views = clutter_stage_get_views_for_rect (stage, + &bounding_rect); + +out: + if (g_signal_has_handler_pending (self, actor_signals[STAGE_VIEWS_CHANGED], + 0, TRUE)) + { + if (!sorted_lists_equal (old_stage_views, priv->stage_views)) + g_signal_emit (self, actor_signals[STAGE_VIEWS_CHANGED], 0); + } +} + +void +clutter_actor_update_stage_views (ClutterActor *self) +{ + ClutterActorPrivate *priv = self->priv; + ClutterActor *child; + + if (!CLUTTER_ACTOR_IS_MAPPED (self) || + CLUTTER_ACTOR_IN_DESTRUCTION (self)) + return; + + if (!priv->needs_update_stage_views) + return; + + update_stage_views (self); + + priv->needs_update_stage_views = FALSE; + + for (child = priv->first_child; child; child = child->priv->next_sibling) + clutter_actor_update_stage_views (child); +} + +/** + * clutter_actor_peek_stage_views: + * @self: A #ClutterActor + * + * Retrieves the list of #ClutterStageViews the actor is being + * painted on. + * + * If this function is called during the paint cycle, the list is guaranteed + * to be up-to-date, if called outside the paint cycle, the list will + * contain the views the actor was painted on last. + * + * The list returned by this function is not updated when the actors + * visibility changes: If an actor gets hidden and is not being painted + * anymore, this function will return the list of views the actor was + * painted on last. + * + * If an actor is not attached to a stage (realized), this function will + * always return an empty list. + * + * Returns: (transfer none) (element-type Clutter.StageView): The list of + * #ClutterStageViews the actor is being painted on. The list and + * its contents are owned by the #ClutterActor and the list may not be + * freed or modified. + */ +GList * +clutter_actor_peek_stage_views (ClutterActor *self) +{ + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); + + return self->priv->stage_views; +} + /** * clutter_actor_has_overlaps: * @self: A #ClutterActor diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index aebfa26f7..0af99b351 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -932,6 +932,9 @@ void clutter_actor_class_set_layout_manager_type (ClutterActorClass *actor_class CLUTTER_EXPORT GType clutter_actor_class_get_layout_manager_type (ClutterActorClass *actor_class); +CLUTTER_EXPORT +GList * clutter_actor_peek_stage_views (ClutterActor *self); + G_END_DECLS #endif /* __CLUTTER_ACTOR_H__ */ diff --git a/clutter/clutter/clutter-muffin.h b/clutter/clutter/clutter-muffin.h index c0d9015d8..606961a5f 100644 --- a/clutter/clutter/clutter-muffin.h +++ b/clutter/clutter/clutter-muffin.h @@ -73,7 +73,7 @@ CLUTTER_EXPORT void clutter_stage_thaw_updates (ClutterStage *stage); CLUTTER_EXPORT -void clutter_stage_update_resource_scales (ClutterStage *stage); +void clutter_stage_clear_stage_views (ClutterStage *stage); CLUTTER_EXPORT gboolean clutter_actor_has_damage (ClutterActor *actor); diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index fa3699668..c9c52e6e8 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -130,9 +130,6 @@ gboolean _clutter_stage_update_state (ClutterStage *stag void _clutter_stage_set_scale_factor (ClutterStage *stage, int factor); -gboolean _clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, - graphene_rect_t *rect, - float *view_scale); void _clutter_stage_presented (ClutterStage *stage, CoglFrameEvent frame_event, @@ -143,6 +140,9 @@ GList * _clutter_stage_peek_stage_views (ClutterStage *stage); void clutter_stage_queue_actor_relayout (ClutterStage *stage, ClutterActor *actor); +GList * clutter_stage_get_views_for_rect (ClutterStage *stage, + const graphene_rect_t *rect); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 3b44b8923..7061367e7 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -1512,6 +1512,14 @@ _clutter_stage_check_updated_pointers (ClutterStage *stage) return updating; } +static void +update_actor_stage_views (ClutterStage *stage) +{ + ClutterActor *actor = CLUTTER_ACTOR (stage); + + clutter_actor_update_stage_views (actor); +} + /** * _clutter_stage_do_update: * @stage: A #ClutterStage @@ -1564,6 +1572,10 @@ _clutter_stage_do_update (ClutterStage *stage) if (stage_was_relayout) pointers = _clutter_stage_check_updated_pointers (stage); + COGL_TRACE_BEGIN (ClutterStageUpdateActorStageViews, "Actor stage-views"); + update_actor_stage_views (stage); + COGL_TRACE_END (ClutterStageUpdateActorStageViews); + COGL_TRACE_BEGIN (ClutterStagePaint, "Paint"); clutter_stage_maybe_finish_queue_redraws (stage); @@ -4503,20 +4515,29 @@ clutter_stage_get_capture_final_size (ClutterStage *stage, int *out_height, float *out_scale) { - float max_scale; + float max_scale = 1.0; g_return_val_if_fail (CLUTTER_IS_STAGE (stage), FALSE); if (rect) { graphene_rect_t capture_rect; + g_autoptr (GList) views = NULL; + GList *l; _clutter_util_rect_from_rectangle (rect, &capture_rect); - if (!_clutter_stage_get_max_view_scale_factor_for_rect (stage, - &capture_rect, - &max_scale)) + views = clutter_stage_get_views_for_rect (stage, &capture_rect); + + if (!views) return FALSE; + for (l = views; l; l = l->next) + { + ClutterStageView *view = l->data; + + max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); + } + if (out_width) *out_width = (gint) roundf (rect->width * max_scale); @@ -4764,18 +4785,17 @@ _clutter_stage_peek_stage_views (ClutterStage *stage) } void -clutter_stage_update_resource_scales (ClutterStage *stage) +clutter_stage_clear_stage_views (ClutterStage *stage) { - _clutter_actor_queue_update_resource_scale_recursive (CLUTTER_ACTOR (stage)); + clutter_actor_clear_stage_views_recursive (CLUTTER_ACTOR (stage)); } -gboolean -_clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, - graphene_rect_t *rect, - float *view_scale) +GList * +clutter_stage_get_views_for_rect (ClutterStage *stage, + const graphene_rect_t *rect) { ClutterStagePrivate *priv = stage->priv; - float scale = 0.0f; + GList *views_for_rect = NULL; GList *l; for (l = _clutter_stage_window_get_views (priv->impl); l; l = l->next) @@ -4788,14 +4808,10 @@ _clutter_stage_get_max_view_scale_factor_for_rect (ClutterStage *stage, _clutter_util_rect_from_rectangle (&view_layout, &view_rect); if (graphene_rect_intersection (&view_rect, rect, NULL)) - scale = MAX (clutter_stage_view_get_scale (view), scale); + views_for_rect = g_list_prepend (views_for_rect, view); } - if (scale == 0.0) - return FALSE; - - *view_scale = scale; - return TRUE; + return views_for_rect; } static void diff --git a/src/backends/native/meta-stage-native.c b/src/backends/native/meta-stage-native.c index 9b9c45ef3..9a3d11cb9 100644 --- a/src/backends/native/meta-stage-native.c +++ b/src/backends/native/meta-stage-native.c @@ -140,7 +140,7 @@ meta_stage_native_rebuild_views (MetaStageNative *stage_native) ClutterActor *stage = meta_backend_get_stage (backend); meta_renderer_rebuild_views (renderer); - clutter_stage_update_resource_scales (CLUTTER_STAGE (stage)); + clutter_stage_clear_stage_views (CLUTTER_STAGE (stage)); ensure_frame_callbacks (stage_native); } diff --git a/src/backends/x11/nested/meta-backend-x11-nested.c b/src/backends/x11/nested/meta-backend-x11-nested.c index def74bb74..8b45eada7 100644 --- a/src/backends/x11/nested/meta-backend-x11-nested.c +++ b/src/backends/x11/nested/meta-backend-x11-nested.c @@ -85,7 +85,7 @@ meta_backend_x11_nested_update_screen_size (MetaBackend *backend, if (meta_is_stage_views_enabled ()) { meta_renderer_rebuild_views (renderer); - clutter_stage_update_resource_scales (CLUTTER_STAGE (stage)); + clutter_stage_clear_stage_views (CLUTTER_STAGE (stage)); } clutter_actor_set_size (stage, width, height); } diff --git a/src/tests/meson.build b/src/tests/meson.build index 72cc5aa05..fdbf43bcd 100644 --- a/src/tests/meson.build +++ b/src/tests/meson.build @@ -123,6 +123,27 @@ headless_start_test = executable('muffin-headless-start-test', install_dir: muffin_installed_tests_libexecdir, ) +stage_view_tests = executable('muffin-stage-view-tests', + sources: [ + 'meta-backend-test.c', + 'meta-backend-test.h', + 'meta-gpu-test.c', + 'meta-gpu-test.h', + 'meta-monitor-manager-test.c', + 'meta-monitor-manager-test.h', + 'monitor-test-utils.c', + 'monitor-test-utils.h', + 'stage-view-tests.c', + 'test-utils.c', + 'test-utils.h', + ], + include_directories: tests_includepath, + c_args: tests_c_args, + dependencies: [tests_deps], + install: have_installed_tests, + install_dir: muffin_installed_tests_libexecdir, +) + stacking_tests = [ 'basic-x11', 'basic-wayland', @@ -172,3 +193,10 @@ test('headless-start', headless_start_test, is_parallel: false, timeout: 60, ) + +test('stage-view', stage_view_tests, + suite: ['core', 'muffin/unit'], + env: test_env, + is_parallel: false, + timeout: 60, +) diff --git a/src/tests/stage-view-tests.c b/src/tests/stage-view-tests.c new file mode 100644 index 000000000..47eb4bc99 --- /dev/null +++ b/src/tests/stage-view-tests.c @@ -0,0 +1,504 @@ +/* + * Copyright (C) 2020 Jonas Dreßler + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include "config.h" + +#include "compositor/meta-plugin-manager.h" +#include "core/main-private.h" +#include "meta/main.h" +#include "tests/meta-backend-test.h" +#include "tests/monitor-test-utils.h" +#include "tests/test-utils.h" + +#define FRAME_WARNING "Frame has assigned frame counter but no frame drawn time" + +static gboolean +run_tests (gpointer data) +{ + MetaBackend *backend = meta_get_backend (); + MetaSettings *settings = meta_backend_get_settings (backend); + gboolean ret; + + g_test_log_set_fatal_handler (NULL, NULL); + + meta_settings_override_experimental_features (settings); + + meta_settings_enable_experimental_feature ( + settings, + META_EXPERIMENTAL_FEATURE_SCALE_MONITOR_FRAMEBUFFER); + + ret = g_test_run (); + + meta_quit (ret != 0); + + return G_SOURCE_REMOVE; +} + +static gboolean +ignore_frame_counter_warning (const gchar *log_domain, + GLogLevelFlags log_level, + const gchar *message, + gpointer user_data) +{ + if ((log_level & G_LOG_LEVEL_WARNING) && + g_strcmp0 (log_domain, "mutter") == 0 && + g_str_has_suffix (message, FRAME_WARNING)) + return FALSE; + + return TRUE; +} + +static MonitorTestCaseSetup initial_test_case_setup = { + .modes = { + { + .width = 1024, + .height = 768, + .refresh_rate = 60.0 + } + }, + .n_modes = 1, + .outputs = { + { + .crtc = 0, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 0 }, + .n_possible_crtcs = 1, + .width_mm = 222, + .height_mm = 125 + }, + { + .crtc = 1, + .modes = { 0 }, + .n_modes = 1, + .preferred_mode = 0, + .possible_crtcs = { 1 }, + .n_possible_crtcs = 1, + .width_mm = 220, + .height_mm = 124 + } + }, + .n_outputs = 2, + .crtcs = { + { + .current_mode = 0 + }, + { + .current_mode = 0 + } + }, + .n_crtcs = 2 +}; + +static void +meta_test_stage_views_exist (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage; + GList *stage_views; + + stage = meta_backend_get_stage (backend); + g_assert_cmpint (clutter_actor_get_width (stage), ==, 1024 * 2); + g_assert_cmpint (clutter_actor_get_height (stage), ==, 768); + + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + g_assert_cmpint (g_list_length (stage_views), ==, 2); +} + +static void +on_after_paint (ClutterStage *stage, + gboolean *was_painted) +{ + *was_painted = TRUE; +} + +static void +wait_for_paint (ClutterActor *stage) +{ + gboolean was_painted = FALSE; + gulong was_painted_id; + + was_painted_id = g_signal_connect (CLUTTER_STAGE (stage), + "after-paint", + G_CALLBACK (on_after_paint), + &was_painted); + + while (!was_painted) + g_main_context_iteration (NULL, FALSE); + + g_signal_handler_disconnect (stage, was_painted_id); +} + +static void +on_stage_views_changed (ClutterActor *actor, + gboolean *stage_views_changed) +{ + *stage_views_changed = TRUE; +} + +static void +is_on_stage_views (ClutterActor *actor, + unsigned int n_views, + ...) +{ + va_list valist; + int i = 0; + GList *stage_views = clutter_actor_peek_stage_views (actor); + + va_start (valist, n_views); + for (i = 0; i < n_views; i++) + { + ClutterStageView *view = va_arg (valist, ClutterStageView*); + g_assert_nonnull (g_list_find (stage_views, view)); + } + + va_end (valist); + g_assert (g_list_length (stage_views) == n_views); +} + +static void +meta_test_actor_stage_views (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_container_ptr = + &stage_views_changed_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + container = clutter_actor_new (); + clutter_actor_set_size (container, 100, 100); + clutter_actor_add_child (stage, container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 50, 50); + clutter_actor_add_child (container, test_actor); + + g_signal_connect (container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + is_on_stage_views (container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the initial change */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Move the container to the second stage view */ + clutter_actor_set_x (container, 1040); + + wait_for_paint (stage); + + is_on_stage_views (container, 1, stage_views->next->data); + is_on_stage_views (test_actor, 1, stage_views->next->data); + + /* The signal was emitted again */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Move the container so it's on both stage views while the test_actor + * is only on the first one. + */ + clutter_actor_set_x (container, 940); + + wait_for_paint (stage); + + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted again */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (container, on_stage_views_changed, + stage_views_changed_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (container); +} + +static void +meta_test_actor_stage_views_reparent (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_container_ptr = + &stage_views_changed_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + container = clutter_actor_new (); + clutter_actor_set_size (container, 100, 100); + clutter_actor_set_x (container, 1020); + clutter_actor_add_child (stage, container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 20, 20); + clutter_actor_add_child (container, test_actor); + + g_signal_connect (container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 2, stage_views->data, stage_views->next->data); + + /* The signal was emitted for both actors */ + g_assert (stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Remove the test_actor from the scene-graph */ + g_object_ref (test_actor); + clutter_actor_remove_child (container, test_actor); + + /* While the test_actor is not on stage, it must be on no stage views */ + is_on_stage_views (test_actor, 0); + + /* When the test_actor left the stage, the signal was emitted */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* Add the test_actor again as a child of the stage */ + clutter_actor_add_child (stage, test_actor); + g_object_unref (test_actor); + + wait_for_paint (stage); + + /* The container is still on both stage views... */ + is_on_stage_views (container, 2, stage_views->data, stage_views->next->data); + + /* ...while the test_actor is only on the first one now */ + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the test_actor again */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* Move the container out of the stage... */ + clutter_actor_set_y (container, 2000); + g_object_ref (test_actor); + clutter_actor_remove_child (stage, test_actor); + + /* When the test_actor left the stage, the signal was emitted */ + g_assert (!stage_views_changed_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_test_actor = FALSE; + + /* ...and reparent the test_actor to the container again */ + clutter_actor_add_child (container, test_actor); + g_object_unref (test_actor); + + wait_for_paint (stage); + + /* Now both actors are on no stage views */ + is_on_stage_views (container, 0); + is_on_stage_views (test_actor, 0); + + /* The signal was emitted only for the container, the test_actor already + * has no stage-views. + */ + g_assert (stage_views_changed_container); + g_assert (!stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (container, on_stage_views_changed, + stage_views_changed_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (container); +} + +static void +meta_test_actor_stage_views_hide_parent (void) +{ + MetaBackend *backend = meta_get_backend (); + ClutterActor *stage, *outer_container, *inner_container, *test_actor; + GList *stage_views; + gboolean stage_views_changed_outer_container = FALSE; + gboolean stage_views_changed_inner_container = FALSE; + gboolean stage_views_changed_test_actor = FALSE; + gboolean *stage_views_changed_outer_container_ptr = + &stage_views_changed_outer_container; + gboolean *stage_views_changed_inner_container_ptr = + &stage_views_changed_inner_container; + gboolean *stage_views_changed_test_actor_ptr = + &stage_views_changed_test_actor; + + stage = meta_backend_get_stage (backend); + stage_views = clutter_stage_peek_stage_views (CLUTTER_STAGE (stage)); + + outer_container = clutter_actor_new (); + clutter_actor_add_child (stage, outer_container); + + inner_container = clutter_actor_new (); + clutter_actor_add_child (outer_container, inner_container); + + test_actor = clutter_actor_new (); + clutter_actor_set_size (test_actor, 20, 20); + clutter_actor_add_child (inner_container, test_actor); + + g_signal_connect (outer_container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_outer_container_ptr); + g_signal_connect (inner_container, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_inner_container_ptr); + g_signal_connect (test_actor, "stage-views-changed", + G_CALLBACK (on_stage_views_changed), + stage_views_changed_test_actor_ptr); + + clutter_actor_show (stage); + + wait_for_paint (stage); + + /* The containers and the test_actor are on all on the first view */ + is_on_stage_views (outer_container, 1, stage_views->data); + is_on_stage_views (inner_container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for all three */ + g_assert (stage_views_changed_outer_container); + g_assert (stage_views_changed_inner_container); + g_assert (stage_views_changed_test_actor); + stage_views_changed_outer_container = FALSE; + stage_views_changed_inner_container = FALSE; + stage_views_changed_test_actor = FALSE; + + /* Hide the inner_container */ + clutter_actor_hide (inner_container); + + /* Move the outer_container so it's still on the first view */ + clutter_actor_set_x (outer_container, 1023); + + wait_for_paint (stage); + + /* The outer_container is still expanded so it should be on both views */ + is_on_stage_views (outer_container, 2, + stage_views->data, stage_views->next->data); + + /* The inner_container and test_actor aren't updated because they're hidden */ + is_on_stage_views (inner_container, 1, stage_views->data); + is_on_stage_views (test_actor, 1, stage_views->data); + + /* The signal was emitted for the outer_container */ + g_assert (stage_views_changed_outer_container); + g_assert (!stage_views_changed_inner_container); + g_assert (!stage_views_changed_test_actor); + stage_views_changed_outer_container = FALSE; + + /* Show the inner_container again */ + clutter_actor_show (inner_container); + + wait_for_paint (stage); + + /* All actors are on both views now */ + is_on_stage_views (outer_container, 2, + stage_views->data, stage_views->next->data); + is_on_stage_views (inner_container, 2, + stage_views->data, stage_views->next->data); + is_on_stage_views (test_actor, 2, + stage_views->data, stage_views->next->data); + + /* The signal was emitted for the inner_container and test_actor */ + g_assert (!stage_views_changed_outer_container); + g_assert (stage_views_changed_inner_container); + g_assert (stage_views_changed_test_actor); + + g_signal_handlers_disconnect_by_func (outer_container, on_stage_views_changed, + stage_views_changed_outer_container_ptr); + g_signal_handlers_disconnect_by_func (inner_container, on_stage_views_changed, + stage_views_changed_inner_container_ptr); + g_signal_handlers_disconnect_by_func (test_actor, on_stage_views_changed, + stage_views_changed_test_actor_ptr); + clutter_actor_destroy (outer_container); +} + +static void +init_tests (int argc, char **argv) +{ + MetaMonitorTestSetup *test_setup; + + test_setup = create_monitor_test_setup (&initial_test_case_setup, + MONITOR_TEST_FLAG_NO_STORED); + + meta_monitor_manager_test_init_test_setup (test_setup); + + g_test_add_func ("/stage-view/stage-views-exist", + meta_test_stage_views_exist); + g_test_add_func ("/stage-views/actor-stage-views", + meta_test_actor_stage_views); + g_test_add_func ("/stage-views/actor-stage-views-reparent", + meta_test_actor_stage_views_reparent); + g_test_add_func ("/stage-views/actor-stage-views-hide-parent", + meta_test_actor_stage_views_hide_parent); +} + +int +main (int argc, char *argv[]) +{ + test_init (&argc, &argv); + init_tests (argc, argv); + + meta_plugin_manager_load (test_get_plugin_name ()); + + meta_override_compositor_configuration (META_COMPOSITOR_TYPE_WAYLAND, + META_TYPE_BACKEND_TEST); + + meta_init (); + meta_register_with_session (); + + g_test_log_set_fatal_handler (ignore_frame_counter_warning, NULL); + + g_idle_add (run_tests, NULL); + + return meta_run (); +} From 4f96dc1adfd0ea2efa18c13366c9d7ff18622e07 Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 19:44:55 +0400 Subject: [PATCH 5/6] Refactor resource scale API to be based on stage-views lists This commit changes the resource scale API to be based on ClutterActors new stage-views list. It also introduces proper support for actors like ClutterText where the allocation depends on the resource-scale, which allows getting the size and position perfect on the first frame. --- clutter/clutter/clutter-actor-private.h | 8 +- clutter/clutter/clutter-actor.c | 357 +++++++++------------ clutter/clutter/clutter-actor.h | 7 +- clutter/clutter/clutter-backend-private.h | 8 + clutter/clutter/clutter-backend.c | 13 + clutter/clutter/clutter-offscreen-effect.c | 25 +- clutter/clutter/clutter-stage-private.h | 4 +- clutter/clutter/clutter-stage.c | 49 ++- clutter/clutter/clutter-text.c | 75 +++-- src/backends/meta-renderer.c | 13 + src/compositor/meta-window-actor.c | 6 +- src/tests/clutter/conform/text.c | 3 +- 12 files changed, 290 insertions(+), 278 deletions(-) diff --git a/clutter/clutter/clutter-actor-private.h b/clutter/clutter/clutter-actor-private.h index ed398bf21..e85a99431 100644 --- a/clutter/clutter/clutter-actor-private.h +++ b/clutter/clutter/clutter-actor-private.h @@ -315,13 +315,15 @@ void _clutter_actor_queue_relayout_on_clones void _clutter_actor_queue_only_relayout (ClutterActor *actor); void clutter_actor_clear_stage_views_recursive (ClutterActor *actor); -gboolean _clutter_actor_get_real_resource_scale (ClutterActor *actor, - float *resource_scale); +float clutter_actor_get_real_resource_scale (ClutterActor *actor); ClutterPaintNode * clutter_actor_create_texture_paint_node (ClutterActor *self, CoglTexture *texture); -void clutter_actor_update_stage_views (ClutterActor *self); +void clutter_actor_update_stage_views (ClutterActor *self, + int phase); + +void clutter_actor_queue_immediate_relayout (ClutterActor *self); G_END_DECLS diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index cf8f05175..93dd13ead 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -854,7 +854,6 @@ struct _ClutterActorPrivate guint needs_y_expand : 1; guint needs_paint_volume_update : 1; guint had_effects_on_last_paint_volume_update : 1; - guint needs_compute_resource_scale : 1; guint absolute_origin_changed : 1; guint needs_update_stage_views : 1; }; @@ -928,7 +927,6 @@ enum PROP_SCALE_CENTER_X, /* XXX:2.0 remove */ PROP_SCALE_CENTER_Y, /* XXX:2.0 remove */ PROP_SCALE_GRAVITY, /* XXX:2.0 remove */ - PROP_RESOURCE_SCALE, PROP_ROTATION_ANGLE_X, /* XXX:2.0 rename to rotation-x */ PROP_ROTATION_ANGLE_Y, /* XXX:2.0 rename to rotation-y */ @@ -1020,6 +1018,7 @@ enum TOUCH_EVENT, TRANSITION_STOPPED, STAGE_VIEWS_CHANGED, + RESOURCE_SCALE_CHANGED, LAST_SIGNAL }; @@ -1107,8 +1106,6 @@ static void clutter_actor_set_child_transform_internal (ClutterActor *sel static void clutter_actor_realize_internal (ClutterActor *self); static void clutter_actor_unrealize_internal (ClutterActor *self); -static gboolean clutter_actor_update_resource_scale (ClutterActor *self); -static void clutter_actor_ensure_resource_scale (ClutterActor *self); static void clutter_actor_push_in_cloned_branch (ClutterActor *self, gulong count); @@ -1682,8 +1679,6 @@ clutter_actor_real_map (ClutterActor *self) queue_update_stage_views (self); } - clutter_actor_ensure_resource_scale (self); - /* notify on parent mapped before potentially mapping * children, so apps see a top-down notification. */ @@ -2618,7 +2613,6 @@ clutter_actor_notify_if_geometry_changed (ClutterActor *self, static void absolute_allocation_changed (ClutterActor *actor) { - actor->priv->needs_compute_resource_scale = TRUE; queue_update_stage_views (actor); } @@ -3954,8 +3948,6 @@ clutter_actor_paint (ClutterActor *self, if (!CLUTTER_ACTOR_IS_MAPPED (self)) return; - clutter_actor_ensure_resource_scale (self); - actor_node = clutter_actor_node_new (self); root_node = clutter_paint_node_ref (actor_node); @@ -4239,8 +4231,6 @@ clutter_actor_pick (ClutterActor *actor, if (!CLUTTER_ACTOR_IS_MAPPED (actor)) return; - clutter_actor_ensure_resource_scale (actor); - /* mark that we are in the paint process */ CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_IN_PICK); @@ -4576,10 +4566,7 @@ clutter_actor_remove_child_internal (ClutterActor *self, if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child) && !CLUTTER_ACTOR_IN_DESTRUCTION (child)) - { - child->priv->needs_compute_resource_scale = TRUE; - g_signal_emit (child, actor_signals[PARENT_SET], 0, self); - } + g_signal_emit (child, actor_signals[PARENT_SET], 0, self); /* if the child was mapped then we need to relayout ourselves to account * for the removed child @@ -5880,16 +5867,6 @@ clutter_actor_get_property (GObject *object, g_value_set_enum (value, clutter_actor_get_scale_gravity (actor)); break; - case PROP_RESOURCE_SCALE: - if (priv->needs_compute_resource_scale) - { - if (!clutter_actor_update_resource_scale (actor)) - g_warning ("Getting invalid resource scale property"); - } - - g_value_set_float (value, priv->resource_scale); - break; - case PROP_REACTIVE: g_value_set_boolean (value, clutter_actor_get_reactive (actor)); break; @@ -6505,6 +6482,25 @@ clutter_actor_real_has_overlaps (ClutterActor *self) return TRUE; } +static float +clutter_actor_real_calculate_resource_scale (ClutterActor *self, + int phase) +{ + ClutterActorPrivate *priv = self->priv; + GList *l; + float new_resource_scale = -1.f; + + for (l = priv->stage_views; l; l = l->next) + { + ClutterStageView *view = l->data; + + new_resource_scale = MAX (clutter_stage_view_get_scale (view), + new_resource_scale); + } + + return new_resource_scale; +} + static void clutter_actor_real_destroy (ClutterActor *actor) { @@ -6600,6 +6596,7 @@ clutter_actor_class_init (ClutterActorClass *klass) klass->get_accessible = clutter_actor_real_get_accessible; klass->get_paint_volume = clutter_actor_real_get_paint_volume; klass->has_overlaps = clutter_actor_real_has_overlaps; + klass->calculate_resource_scale = clutter_actor_real_calculate_resource_scale; klass->paint = clutter_actor_real_paint; klass->destroy = clutter_actor_real_destroy; @@ -7354,19 +7351,6 @@ clutter_actor_class_init (ClutterActorClass *klass) G_PARAM_STATIC_STRINGS | G_PARAM_DEPRECATED); - /** - * ClutterActor:resource-scale: - * - * The resource-scale of the #ClutterActor if any or -1 if not available - */ - obj_props[PROP_RESOURCE_SCALE] = - g_param_spec_float ("resource-scale", - P_("Resource Scale"), - P_("The Scaling factor for resources painting"), - -1.0f, G_MAXFLOAT, - 1.0f, - CLUTTER_PARAM_READABLE); - /** * ClutterActor:rotation-angle-x: * @@ -8815,6 +8799,24 @@ clutter_actor_class_init (ClutterActorClass *klass) 0, NULL, NULL, NULL, G_TYPE_NONE, 0); + + /** + * ClutterActor::resource-scale-changed: + * @actor: a #ClutterActor + * + * The ::resource-scale-changed signal is emitted when the resource scale + * value returned by clutter_actor_get_resource_scale() changes. + * + * This signal can be used to get notified about the correct resource scale + * when the scale had to be queried outside of the paint cycle. + */ + actor_signals[RESOURCE_SCALE_CHANGED] = + g_signal_new (I_("resource-scale-changed"), + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (ClutterActorClass, resource_scale_changed), + NULL, NULL, NULL, + G_TYPE_NONE, 0); } static void @@ -8832,7 +8834,6 @@ clutter_actor_init (ClutterActor *self) priv->needs_height_request = TRUE; priv->needs_allocation = TRUE; priv->needs_paint_volume_update = TRUE; - priv->needs_compute_resource_scale = TRUE; priv->needs_update_stage_views = TRUE; priv->cached_width_age = 1; @@ -13111,10 +13112,7 @@ clutter_actor_add_child_internal (ClutterActor *self, } if (emit_parent_set && !CLUTTER_ACTOR_IN_REPARENT (child)) - { - child->priv->needs_compute_resource_scale = TRUE; - g_signal_emit (child, actor_signals[PARENT_SET], 0, NULL); - } + g_signal_emit (child, actor_signals[PARENT_SET], 0, NULL); if (check_state) { @@ -17640,107 +17638,6 @@ clutter_actor_get_paint_box (ClutterActor *self, return TRUE; } -static gboolean -_clutter_actor_get_resource_scale_for_rect (ClutterActor *self, - graphene_rect_t *bounding_rect, - float *resource_scale) -{ - ClutterActor *stage; - g_autoptr (GList) views = NULL; - GList *l; - float max_scale = 0; - - stage = _clutter_actor_get_stage_internal (self); - if (!stage) - return FALSE; - - views = clutter_stage_get_views_for_rect (CLUTTER_STAGE (stage), - bounding_rect); - - if (!views) - return FALSE; - - for (l = views; l; l = l->next) - { - ClutterStageView *view = l->data; - - max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); - } - - *resource_scale = max_scale; - - return TRUE; -} - -static gboolean -_clutter_actor_compute_resource_scale (ClutterActor *self, - float *resource_scale) -{ - graphene_rect_t bounding_rect; - ClutterActorPrivate *priv = self->priv; - - if (CLUTTER_ACTOR_IN_DESTRUCTION (self) || - CLUTTER_ACTOR_IN_PREF_SIZE (self) || - !clutter_actor_is_mapped (self)) - { - return FALSE; - } - - clutter_actor_get_transformed_position (self, - &bounding_rect.origin.x, - &bounding_rect.origin.y); - clutter_actor_get_transformed_size (self, - &bounding_rect.size.width, - &bounding_rect.size.height); - - if (bounding_rect.size.width == 0.0 || - bounding_rect.size.height == 0.0 || - !_clutter_actor_get_resource_scale_for_rect (self, - &bounding_rect, - resource_scale)) - { - if (priv->parent) - { - gboolean in_clone_paint; - gboolean was_parent_in_clone_paint; - gboolean was_parent_unmapped; - gboolean was_parent_paint_unmapped; - gboolean ret; - - in_clone_paint = clutter_actor_is_in_clone_paint (self); - was_parent_unmapped = !clutter_actor_is_mapped (priv->parent); - was_parent_in_clone_paint = - clutter_actor_is_in_clone_paint (priv->parent); - was_parent_paint_unmapped = priv->parent->priv->enable_paint_unmapped; - - if (in_clone_paint && was_parent_unmapped) - { - _clutter_actor_set_in_clone_paint (priv->parent, TRUE); - _clutter_actor_set_enable_paint_unmapped (priv->parent, TRUE); - } - - ret = _clutter_actor_compute_resource_scale (priv->parent, - resource_scale); - - if (in_clone_paint && was_parent_unmapped) - { - _clutter_actor_set_in_clone_paint (priv->parent, - was_parent_in_clone_paint); - _clutter_actor_set_enable_paint_unmapped (priv->parent, - was_parent_paint_unmapped); - } - - return ret; - } - else - { - return FALSE; - } - } - - return TRUE; -} - static ClutterActorTraverseVisitFlags clear_stage_views_cb (ClutterActor *actor, int depth, @@ -17750,8 +17647,6 @@ clear_stage_views_cb (ClutterActor *actor, actor->priv->needs_update_stage_views = TRUE; - actor->priv->needs_compute_resource_scale = TRUE; - old_stage_views = g_steal_pointer (&actor->priv->stage_views); if (old_stage_views) @@ -17770,66 +17665,63 @@ clutter_actor_clear_stage_views_recursive (ClutterActor *self) NULL); } -static gboolean -clutter_actor_update_resource_scale (ClutterActor *self) +float +clutter_actor_get_real_resource_scale (ClutterActor *self) { - ClutterActorPrivate *priv; - float resource_scale; - float old_resource_scale; - priv = self->priv; + ClutterActorPrivate *priv = self->priv; + float guessed_scale; - g_return_val_if_fail (priv->needs_compute_resource_scale, FALSE); + if (priv->resource_scale != -1.f) + return priv->resource_scale; - old_resource_scale = priv->resource_scale; - priv->resource_scale = -1.0f; + /* If the scale hasn't been computed yet, we return a best guess */ - if (_clutter_actor_compute_resource_scale (self, &resource_scale)) + if (priv->parent != NULL) { - priv->resource_scale = resource_scale; - priv->needs_compute_resource_scale = FALSE; - - return fabsf (old_resource_scale - resource_scale) > FLT_EPSILON; + /* If the scale hasn't been calculated yet, assume this actor is located + * inside its parents box and go up the hierarchy. + */ + guessed_scale = clutter_actor_get_real_resource_scale (priv->parent); } + else if (CLUTTER_ACTOR_IS_TOPLEVEL (self)) + { + /* This must be the first allocation cycle and the resource scale of + * the stage has not been updated yet, so return it manually. + */ + GList *l; + ClutterStage *stage = CLUTTER_STAGE (self); + float max_scale = -1.f; - return FALSE; -} - -static void -clutter_actor_ensure_resource_scale (ClutterActor *self) -{ - ClutterActorPrivate *priv = self->priv; - - if (!priv->needs_compute_resource_scale) - return; - - if (clutter_actor_update_resource_scale (self)) - g_object_notify_by_pspec (G_OBJECT (self), obj_props[PROP_RESOURCE_SCALE]); -} -gboolean -_clutter_actor_get_real_resource_scale (ClutterActor *self, - gfloat *resource_scale) -{ - ClutterActorPrivate *priv = self->priv; + for (l = clutter_stage_peek_stage_views (stage); l; l = l->next) + { + ClutterStageView *view = l->data; + max_scale = MAX (clutter_stage_view_get_scale (view), max_scale); + } + guessed_scale = max_scale; + } + else + { + ClutterBackend *backend = clutter_get_default_backend (); + guessed_scale = clutter_backend_get_fallback_resource_scale (backend); + } - clutter_actor_ensure_resource_scale (self); + g_assert (guessed_scale >= 1.f); - if (!priv->needs_compute_resource_scale) - { - *resource_scale = priv->resource_scale; - return TRUE; - } + /* Always return this value until we compute the correct one later. + * If our guess turns out to be wrong, we'll emit "resource-scale-changed" + * and correct it before painting. + */ + priv->resource_scale = guessed_scale; - *resource_scale = -1.0f; - return FALSE; + return priv->resource_scale; } /** * clutter_actor_get_resource_scale: * @self: A #ClutterActor - * @resource_scale: (out): return location for the resource scale * - * Retrieves the resource scale for this actor, if available. + * Retrieves the resource scale for this actor. * * The resource scale refers to the scale the actor should use for its resources. * For example if an actor draws a a picture of size 100 x 100 in the stage @@ -17839,22 +17731,32 @@ _clutter_actor_get_real_resource_scale (ClutterActor *self, * The resource scale is determined by calculating the highest #ClutterStageView * scale the actor will get painted on. * - * Returns: TRUE if resource scale is set for the actor, otherwise FALSE + * Note that the scale returned by this function is only guaranteed to be + * correct when queried during the paint cycle, in all other cases this + * function will only return a best guess. If your implementation really + * needs to get a resource scale outside of the paint cycle, make sure to + * subscribe to the "resource-scale-changed" signal to get notified about + * the new, correct resource scale before painting. + * + * Also avoid getting the resource scale for actors that are not attached + * to a stage. There's no sane way for Clutter to guess which #ClutterStageView + * the actor is going to be painted on, so you'll probably end up receiving + * the "resource-scale-changed" signal and having to rebuild your resources. + * + * The best guess this function may return is usually just the last resource + * scale the actor got painted with. If this resource scale couldn't be found + * because the actor was never painted so far or Clutter was unable to + * determine its position and size, this function will return the resource + * scale of a parent. + * + * Returns: The resource scale the actor should use for its textures */ -gboolean -clutter_actor_get_resource_scale (ClutterActor *self, - gfloat *resource_scale) +float +clutter_actor_get_resource_scale (ClutterActor *self) { - g_return_val_if_fail (CLUTTER_IS_ACTOR (self), FALSE); - g_return_val_if_fail (resource_scale != NULL, FALSE); + g_return_val_if_fail (CLUTTER_IS_ACTOR (self), 1.f); - if (_clutter_actor_get_real_resource_scale (self, resource_scale)) - { - *resource_scale = ceilf (*resource_scale); - return TRUE; - } - - return FALSE; + return ceilf (clutter_actor_get_real_resource_scale (self)); } static gboolean @@ -17923,8 +17825,40 @@ update_stage_views (ClutterActor *self) } } +static void +update_resource_scale (ClutterActor *self, + int phase) +{ + ClutterActorPrivate *priv = self->priv; + float new_resource_scale, old_resource_scale; + + new_resource_scale = + CLUTTER_ACTOR_GET_CLASS (self)->calculate_resource_scale (self, phase); + + if (priv->resource_scale == new_resource_scale) + return; + + /* If the actor moved out of the stage, simply keep the last scale */ + if (new_resource_scale == -1.f) + return; + + old_resource_scale = priv->resource_scale; + priv->resource_scale = new_resource_scale; + + /* Never notify the initial change, otherwise, to be consistent, + * we'd also have to notify if we guessed correctly in + * clutter_actor_get_real_resource_scale(). + */ + if (old_resource_scale == -1.f) + return; + + if (ceilf (old_resource_scale) != ceilf (priv->resource_scale)) + g_signal_emit (self, actor_signals[RESOURCE_SCALE_CHANGED], 0); +} + void -clutter_actor_update_stage_views (ClutterActor *self) +clutter_actor_update_stage_views (ClutterActor *self, + gboolean use_max_scale) { ClutterActorPrivate *priv = self->priv; ClutterActor *child; @@ -17937,11 +17871,12 @@ clutter_actor_update_stage_views (ClutterActor *self) return; update_stage_views (self); + update_resource_scale (self, use_max_scale); priv->needs_update_stage_views = FALSE; for (child = priv->first_child; child; child = child->priv->next_sibling) - clutter_actor_update_stage_views (child); + clutter_actor_update_stage_views (child, use_max_scale); } /** @@ -21453,6 +21388,20 @@ clutter_actor_has_accessible (ClutterActor *actor) return TRUE; } +void +clutter_actor_queue_immediate_relayout (ClutterActor *self) +{ + ClutterStage *stage; + + g_return_if_fail (CLUTTER_IS_ACTOR (self)); + + clutter_actor_queue_relayout (self); + + stage = CLUTTER_STAGE (_clutter_actor_get_stage_internal (self)); + if (stage) + clutter_stage_set_actor_needs_immediate_relayout (stage); +} + /** * clutter_actor_class_set_layout_manager_type * @actor_class: A #ClutterActor class diff --git a/clutter/clutter/clutter-actor.h b/clutter/clutter/clutter-actor.h index 0af99b351..a06ac07cf 100644 --- a/clutter/clutter/clutter-actor.h +++ b/clutter/clutter/clutter-actor.h @@ -303,6 +303,10 @@ struct _ClutterActorClass ClutterTouchEvent *event); gboolean (* has_accessible) (ClutterActor *self); + void (* resource_scale_changed) (ClutterActor *self); + float (* calculate_resource_scale) (ClutterActor *self, + int phase); + /*< private >*/ /* padding for future expansion */ GType layout_manager_type; @@ -599,8 +603,7 @@ gboolean clutter_actor_get_paint_box ClutterActorBox *box); CLUTTER_EXPORT -gboolean clutter_actor_get_resource_scale (ClutterActor *self, - gfloat *resource_scale); +float clutter_actor_get_resource_scale (ClutterActor *self); CLUTTER_EXPORT gboolean clutter_actor_has_overlaps (ClutterActor *self); diff --git a/clutter/clutter/clutter-backend-private.h b/clutter/clutter/clutter-backend-private.h index c7c54f85b..d5dca0d25 100644 --- a/clutter/clutter/clutter-backend-private.h +++ b/clutter/clutter/clutter-backend-private.h @@ -53,6 +53,8 @@ struct _ClutterBackend gfloat units_per_em; gint32 units_serial; + float fallback_resource_scale; + ClutterStageWindow *stage_window; ClutterInputMethod *input_method; @@ -134,6 +136,12 @@ void clutter_set_allowed_drivers (const c CLUTTER_EXPORT ClutterStageWindow * clutter_backend_get_stage_window (ClutterBackend *backend); +CLUTTER_EXPORT +void clutter_backend_set_fallback_resource_scale (ClutterBackend *backend, + float fallback_resource_scale); + +float clutter_backend_get_fallback_resource_scale (ClutterBackend *backend); + G_END_DECLS #endif /* __CLUTTER_BACKEND_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-backend.c b/clutter/clutter/clutter-backend.c index ace8cd93a..37543e510 100644 --- a/clutter/clutter/clutter-backend.c +++ b/clutter/clutter/clutter-backend.c @@ -1031,3 +1031,16 @@ clutter_backend_get_default_seat (ClutterBackend *backend) return CLUTTER_BACKEND_GET_CLASS (backend)->get_default_seat (backend); } + +void +clutter_backend_set_fallback_resource_scale (ClutterBackend *backend, + float fallback_resource_scale) +{ + backend->fallback_resource_scale = fallback_resource_scale; +} + +float +clutter_backend_get_fallback_resource_scale (ClutterBackend *backend) +{ + return backend->fallback_resource_scale; +} diff --git a/clutter/clutter/clutter-offscreen-effect.c b/clutter/clutter/clutter-offscreen-effect.c index 10a9cc24a..85ca187ae 100644 --- a/clutter/clutter/clutter-offscreen-effect.c +++ b/clutter/clutter/clutter-offscreen-effect.c @@ -238,8 +238,8 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect, gfloat stage_width, stage_height; gfloat target_width = -1, target_height = -1; CoglFramebuffer *framebuffer; - gfloat resource_scale; - gfloat ceiled_resource_scale; + float resource_scale; + float ceiled_resource_scale; graphene_point3d_t local_offset; gfloat old_viewport[4]; @@ -254,17 +254,11 @@ clutter_offscreen_effect_pre_paint (ClutterEffect *effect, stage = _clutter_actor_get_stage_internal (priv->actor); clutter_actor_get_size (stage, &stage_width, &stage_height); - if (_clutter_actor_get_real_resource_scale (priv->actor, &resource_scale)) - { - ceiled_resource_scale = ceilf (resource_scale); - stage_width *= ceiled_resource_scale; - stage_height *= ceiled_resource_scale; - } - else - { - /* We are sure we have a resource scale set to a good value at paint */ - g_assert_not_reached (); - } + resource_scale = clutter_actor_get_real_resource_scale (priv->actor); + + ceiled_resource_scale = ceilf (resource_scale); + stage_width *= ceiled_resource_scale; + stage_height *= ceiled_resource_scale; /* Get the minimal bounding box for what we want to paint, relative to the * parent of priv->actor. Note that we may actually be painting a clone of @@ -417,8 +411,9 @@ clutter_offscreen_effect_paint_texture (ClutterOffscreenEffect *effect, */ cogl_framebuffer_get_modelview_matrix (framebuffer, &modelview); - if (clutter_actor_get_resource_scale (priv->actor, &resource_scale) && - resource_scale != 1.0f) + resource_scale = clutter_actor_get_resource_scale (priv->actor); + + if (resource_scale != 1.0f) { float paint_scale = 1.0f / resource_scale; cogl_matrix_scale (&modelview, paint_scale, paint_scale, 1); diff --git a/clutter/clutter/clutter-stage-private.h b/clutter/clutter/clutter-stage-private.h index c9c52e6e8..2c3b4e62c 100644 --- a/clutter/clutter/clutter-stage-private.h +++ b/clutter/clutter/clutter-stage-private.h @@ -135,7 +135,7 @@ void _clutter_stage_presented (ClutterStage *stag CoglFrameEvent frame_event, ClutterFrameInfo *frame_info); -GList * _clutter_stage_peek_stage_views (ClutterStage *stage); +GList * clutter_stage_peek_stage_views (ClutterStage *stage); void clutter_stage_queue_actor_relayout (ClutterStage *stage, ClutterActor *actor); @@ -143,6 +143,8 @@ void clutter_stage_queue_actor_relayout (ClutterStage *stage, GList * clutter_stage_get_views_for_rect (ClutterStage *stage, const graphene_rect_t *rect); +void clutter_stage_set_actor_needs_immediate_relayout (ClutterStage *stage); + G_END_DECLS #endif /* __CLUTTER_STAGE_PRIVATE_H__ */ diff --git a/clutter/clutter/clutter-stage.c b/clutter/clutter/clutter-stage.c index 7061367e7..649920a0c 100644 --- a/clutter/clutter/clutter-stage.c +++ b/clutter/clutter/clutter-stage.c @@ -168,6 +168,7 @@ struct _ClutterStagePrivate guint motion_events_enabled : 1; guint has_custom_perspective : 1; guint stage_was_relayout : 1; + guint actor_needs_immediate_relayout : 1; }; enum @@ -583,7 +584,7 @@ clutter_stage_add_redraw_clip (ClutterStage *stage, { GList *l; - for (l = _clutter_stage_peek_stage_views (stage); l; l = l->next) + for (l = clutter_stage_peek_stage_views (stage); l; l = l->next) { ClutterStageView *view = l->data; @@ -1516,8 +1517,34 @@ static void update_actor_stage_views (ClutterStage *stage) { ClutterActor *actor = CLUTTER_ACTOR (stage); + ClutterStagePrivate *priv = stage->priv; + int phase; + + COGL_TRACE_BEGIN_SCOPED (ClutterStageUpdateActorStageViews, + "Actor stage-views"); + + /* If an actor needs an immediate relayout because its resource scale + * changed, we give it another chance to allocate correctly before + * the paint. + * + * We're doing the whole thing twice and pass the phase to + * clutter_actor_update_stage_views() to allow actors to detect loops: + * If the resource scale changes again after the relayout, the new + * allocation of an actor probably moved the actor onto another stage + * view, so if an actor sees phase == 1, it can choose a "final" scale. + */ + for (phase = 0; phase < 2; phase++) + { + clutter_actor_update_stage_views (actor, phase); + + if (!priv->actor_needs_immediate_relayout) + break; + + priv->actor_needs_immediate_relayout = FALSE; + _clutter_stage_maybe_relayout (actor); + } - clutter_actor_update_stage_views (actor); + g_warn_if_fail (!priv->actor_needs_immediate_relayout); } /** @@ -1572,9 +1599,7 @@ _clutter_stage_do_update (ClutterStage *stage) if (stage_was_relayout) pointers = _clutter_stage_check_updated_pointers (stage); - COGL_TRACE_BEGIN (ClutterStageUpdateActorStageViews, "Actor stage-views"); update_actor_stage_views (stage); - COGL_TRACE_END (ClutterStageUpdateActorStageViews); COGL_TRACE_BEGIN (ClutterStagePaint, "Paint"); @@ -1629,7 +1654,7 @@ is_full_stage_redraw_queued (ClutterStage *stage) { GList *l; - for (l = _clutter_stage_peek_stage_views (stage); l; l = l->next) + for (l = clutter_stage_peek_stage_views (stage); l; l = l->next) { ClutterStageView *view = l->data; @@ -4551,9 +4576,7 @@ clutter_stage_get_capture_final_size (ClutterStage *stage, clutter_actor_get_allocation_box (CLUTTER_ACTOR (stage), &alloc); clutter_actor_box_get_size (&alloc, &stage_width, &stage_height); - if (!_clutter_actor_get_real_resource_scale (CLUTTER_ACTOR (stage), - &max_scale)) - return FALSE; + max_scale = clutter_actor_get_real_resource_scale (CLUTTER_ACTOR (stage)); if (out_width) *out_width = (gint) roundf (stage_width * max_scale); @@ -4777,7 +4800,7 @@ clutter_stage_thaw_updates (ClutterStage *stage) } GList * -_clutter_stage_peek_stage_views (ClutterStage *stage) +clutter_stage_peek_stage_views (ClutterStage *stage) { ClutterStagePrivate *priv = stage->priv; @@ -4879,3 +4902,11 @@ clutter_stage_get_device_coords (ClutterStage *stage, if (entry && coords) *coords = entry->coords; } + +void +clutter_stage_set_actor_needs_immediate_relayout (ClutterStage *stage) +{ + ClutterStagePrivate *priv = stage->priv; + + priv->actor_needs_immediate_relayout = TRUE; +} diff --git a/clutter/clutter/clutter-text.c b/clutter/clutter/clutter-text.c index 047fddd44..b7f97762f 100644 --- a/clutter/clutter/clutter-text.c +++ b/clutter/clutter/clutter-text.c @@ -187,9 +187,6 @@ struct _ClutterTextPrivate ClutterInputContentHintFlags input_hints; ClutterInputContentPurpose input_purpose; - /* Signal handler for when the :resource-scale changes */ - gulong resource_scale_changed_id; - /* bitfields */ guint alignment : 2; guint wrap : 1; @@ -598,9 +595,7 @@ ensure_effective_pango_scale_attribute (ClutterText *self) float resource_scale; ClutterTextPrivate *priv = self->priv; - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (self), &resource_scale) || - resource_scale == 1.0) - return; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); if (priv->effective_attrs != NULL) { @@ -922,18 +917,6 @@ clutter_text_direction_changed_cb (GObject *gobject, /* no need to queue a relayout: set_text_direction() will do that for us */ } -static void -clutter_text_resource_scale_changed_cb (GObject *gobject, - GParamSpec *pspec) -{ - ClutterText *self = CLUTTER_TEXT (gobject); - ClutterTextPrivate *priv = self->priv; - - g_clear_pointer (&priv->effective_attrs, pango_attr_list_unref); - clutter_text_dirty_cache (self); - clutter_actor_queue_relayout (CLUTTER_ACTOR (gobject)); -} - /* * clutter_text_create_layout: * @text: a #ClutterText @@ -1137,8 +1120,7 @@ maybe_create_text_layout_with_resource_scale (ClutterText *text, { float resource_scale; - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (text), &resource_scale)) - return NULL; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (text)); return create_text_layout_with_scale (text, allocation_width, @@ -1170,8 +1152,7 @@ clutter_text_coords_to_position (ClutterText *self, g_return_val_if_fail (CLUTTER_IS_TEXT (self), 0); - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (self), &resource_scale)) - return 0; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); /* Take any offset due to scrolling into account, and normalize * the coordinates to PangoScale units @@ -1299,8 +1280,7 @@ clutter_text_position_to_coords (ClutterText *self, g_return_val_if_fail (CLUTTER_IS_TEXT (self), FALSE); - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (self), &resource_scale)) - return FALSE; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); ret = clutter_text_position_to_coords_internal (self, position, x, y, line_height); @@ -1776,7 +1756,6 @@ clutter_text_dispose (GObject *gobject) clutter_text_dirty_cache (self); g_clear_signal_handler (&priv->direction_changed_id, self); - g_clear_signal_handler (&priv->resource_scale_changed_id, self); g_clear_signal_handler (&priv->settings_changed_id, clutter_get_default_backend ()); @@ -2637,8 +2616,7 @@ clutter_text_paint (ClutterActor *self, !clutter_text_should_draw_cursor (text)) return; - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (self), &resource_scale)) - return; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (self)); clutter_actor_box_scale (&alloc, resource_scale); clutter_actor_box_get_size (&alloc, &alloc_width, &alloc_height); @@ -2870,8 +2848,7 @@ clutter_text_get_paint_volume (ClutterActor *self, if (!clutter_actor_has_allocation (self)) return FALSE; - if (!clutter_actor_get_resource_scale (self, &resource_scale)) - return FALSE; + resource_scale = clutter_actor_get_resource_scale (self); _clutter_paint_volume_init_static (&priv->paint_volume, self); @@ -2928,8 +2905,7 @@ clutter_text_get_preferred_width (ClutterActor *self, gfloat layout_width; gfloat resource_scale; - if (!clutter_actor_get_resource_scale (self, &resource_scale)) - resource_scale = 1; + resource_scale = clutter_actor_get_resource_scale (self); layout = clutter_text_create_layout (text, -1, -1); pango_layout_get_extents (layout, NULL, &logical_rect); @@ -2985,8 +2961,7 @@ clutter_text_get_preferred_height (ClutterActor *self, gfloat layout_height; gfloat resource_scale; - if (!clutter_actor_get_resource_scale (self, &resource_scale)) - resource_scale = 1; + resource_scale = clutter_actor_get_resource_scale (self); if (priv->single_line_mode) for_width = -1; @@ -3062,6 +3037,33 @@ clutter_text_has_overlaps (ClutterActor *self) return clutter_text_should_draw_cursor ((ClutterText *) self); } +static float +clutter_text_calculate_resource_scale (ClutterActor *actor, + int phase) +{ + ClutterActorClass *parent_class = CLUTTER_ACTOR_CLASS (clutter_text_parent_class); + float new_resource_scale; + + new_resource_scale = parent_class->calculate_resource_scale (actor, phase); + + if (phase == 1) + return MAX (new_resource_scale, clutter_actor_get_real_resource_scale (actor)); + + return new_resource_scale; +} + +static void +clutter_text_resource_scale_changed (ClutterActor *actor) +{ + ClutterText *text = CLUTTER_TEXT (actor); + ClutterTextPrivate *priv = text->priv; + + g_clear_pointer (&priv->effective_attrs, pango_attr_list_unref); + clutter_text_dirty_cache (text); + + clutter_actor_queue_immediate_relayout (actor); +} + static gboolean clutter_text_event (ClutterActor *self, ClutterEvent *event) @@ -3828,6 +3830,8 @@ clutter_text_class_init (ClutterTextClass *klass) actor_class->key_focus_in = clutter_text_key_focus_in; actor_class->key_focus_out = clutter_text_key_focus_out; actor_class->has_overlaps = clutter_text_has_overlaps; + actor_class->calculate_resource_scale = clutter_text_calculate_resource_scale; + actor_class->resource_scale_changed = clutter_text_resource_scale_changed; actor_class->event = clutter_text_event; /** @@ -4636,11 +4640,6 @@ clutter_text_init (ClutterText *self) NULL); priv->input_focus = clutter_text_input_focus_new (self); - - priv->resource_scale_changed_id = - g_signal_connect (self, "notify::resource-scale", - G_CALLBACK (clutter_text_resource_scale_changed_cb), - NULL); } /** diff --git a/src/backends/meta-renderer.c b/src/backends/meta-renderer.c index 983a570e1..046c7b2c7 100644 --- a/src/backends/meta-renderer.c +++ b/src/backends/meta-renderer.c @@ -153,6 +153,19 @@ meta_renderer_real_rebuild_views (MetaRenderer *renderer) { MetaLogicalMonitor *logical_monitor = l->data; + if (meta_logical_monitor_is_primary (logical_monitor)) + { + ClutterBackend *clutter_backend; + float scale; + + clutter_backend = meta_backend_get_clutter_backend (backend); + scale = meta_is_stage_views_scaled () + ? meta_logical_monitor_get_scale (logical_monitor) + : 1.f; + + clutter_backend_set_fallback_resource_scale (clutter_backend, scale); + } + meta_logical_monitor_foreach_crtc (logical_monitor, create_crtc_view, renderer); diff --git a/src/compositor/meta-window-actor.c b/src/compositor/meta-window-actor.c index 919da582a..c4c2b66ea 100644 --- a/src/compositor/meta-window-actor.c +++ b/src/compositor/meta-window-actor.c @@ -1288,8 +1288,7 @@ meta_window_actor_blit_to_framebuffer (MetaScreenCastWindow *screen_cast_window, if (width == 0 || height == 0) return FALSE; - if (!clutter_actor_get_resource_scale (actor, &resource_scale)) - return FALSE; + resource_scale = clutter_actor_get_resource_scale (actor); clutter_actor_inhibit_culling (actor); @@ -1445,8 +1444,7 @@ meta_window_actor_get_image (MetaWindowActor *self, if (width == 0 || height == 0) goto out; - if (!clutter_actor_get_resource_scale (actor, &resource_scale)) - goto out; + resource_scale = clutter_actor_get_resource_scale (actor); width = ceilf (width * resource_scale); height = ceilf (height * resource_scale); diff --git a/src/tests/clutter/conform/text.c b/src/tests/clutter/conform/text.c index ebe4b7bb1..d0525b6a4 100644 --- a/src/tests/clutter/conform/text.c +++ b/src/tests/clutter/conform/text.c @@ -475,8 +475,7 @@ validate_markup_attributes (ClutterText *text, PangoAttrFloat *scale = (PangoAttrFloat*) a; float resource_scale; - if (!clutter_actor_get_resource_scale (CLUTTER_ACTOR (text), &resource_scale)) - resource_scale = 1.0; + resource_scale = clutter_actor_get_resource_scale (CLUTTER_ACTOR (text)); g_assert_cmpfloat (scale->value, ==, resource_scale); g_slist_free_full (attributes, (GDestroyNotify) pango_attribute_destroy); From 780c91c9a24e04eb110571152c16c3ef8297c5f2 Mon Sep 17 00:00:00 2001 From: Aleksey Samoilov Date: Sun, 8 Feb 2026 20:51:53 +0400 Subject: [PATCH 6/6] Update libmuffin0.symbols --- debian/libmuffin0.symbols | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/debian/libmuffin0.symbols b/debian/libmuffin0.symbols index 928320459..70e2edfd9 100644 --- a/debian/libmuffin0.symbols +++ b/debian/libmuffin0.symbols @@ -249,6 +249,7 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_actor_node_get_type@Base 5.3.0 clutter_actor_node_new@Base 5.3.0 clutter_actor_paint@Base 5.3.0 + clutter_actor_peek_stage_views@Base 6.6.3 clutter_actor_pick@Base 5.3.0 clutter_actor_pick_box@Base 5.3.0 clutter_actor_queue_redraw@Base 5.3.0 @@ -343,7 +344,6 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_align_constraint_set_align_axis@Base 5.3.0 clutter_align_constraint_set_factor@Base 5.3.0 clutter_align_constraint_set_source@Base 5.3.0 - clutter_allocation_flags_get_type@Base 5.3.0 clutter_alpha_get_alpha@Base 5.3.0 clutter_alpha_get_mode@Base 5.3.0 clutter_alpha_get_timeline@Base 5.3.0 @@ -381,6 +381,7 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_backend_get_resolution@Base 5.3.0 clutter_backend_get_stage_window@Base 5.3.0 clutter_backend_get_type@Base 5.3.0 + clutter_backend_set_fallback_resource_scale@Base 6.6.3 clutter_backend_set_font_options@Base 5.3.0 clutter_backend_set_input_method@Base 5.3.0 clutter_backend_x11_get_type@Base 5.3.0 @@ -587,7 +588,7 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_event_get_event_sequence@Base 5.3.0 clutter_event_get_flags@Base 5.3.0 clutter_event_get_gesture_motion_delta@Base 5.3.0 - clutter_event_get_gesture_motion_delta_unaccelerated@Base 6.7.0 + clutter_event_get_gesture_motion_delta_unaccelerated@Base 6.6.3 clutter_event_get_gesture_phase@Base 5.3.0 clutter_event_get_gesture_pinch_angle_delta@Base 5.3.0 clutter_event_get_gesture_pinch_scale@Base 5.3.0 @@ -1109,6 +1110,7 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_snap_edge_get_type@Base 5.3.0 clutter_stage_capture@Base 5.3.0 clutter_stage_capture_into@Base 5.3.0 + clutter_stage_clear_stage_views@Base 6.6.3 clutter_stage_ensure_current@Base 5.3.0 clutter_stage_ensure_redraw@Base 5.3.0 clutter_stage_ensure_viewport@Base 5.3.0 @@ -1158,7 +1160,6 @@ libmuffin-clutter-0.so.0 libmuffin0 #MINVER# clutter_stage_skip_sync_delay@Base 5.3.0 clutter_stage_state_get_type@Base 5.3.0 clutter_stage_thaw_updates@Base 5.3.0 - clutter_stage_update_resource_scales@Base 5.3.0 clutter_stage_view_cogl_get_type@Base 5.3.0 clutter_stage_view_get_framebuffer@Base 5.3.0 clutter_stage_view_get_layout@Base 5.3.0