stage: Clear update_scheduled field when update discarded

clutter_stage_schedule_update() sets the field `update_scheduled` to
`TRUE` as an optimization to make redundant updates a no-op. This failed
if there was a pending event and if the stage was not yet mapped.

What happened is:

 * clutter_stage_schedule_update() is called
   - ClutterStage::update_scheduled is set to TRUE
   - frame clock scheduled
 * frame clock dispatches
   - frame is discarded early, no actual stage update happens
 * device is created (e.g. virtual device from remote desktop session)
   - `device-added` event reaches ClutterStage::event_queue
 * stage is shown
   - clutter_stage_schedule_update() is called
     - ClutterStage::update_scheduled is TRUE
     - ClutterStage::event_queue has events in it
     - These two conditions means clutter_schedule_update() becomes a
       no-op

At this point, no more updates will happen from
clutter_stage_schedule_update().

Fix this by resetting `ClutterStage::update_scheduled` to `FALSE` even
if the frame was discarded due to the stage not yet being mapped.

A test case is added that replicates the above descibed events.

Closes: https://gitlab.gnome.org/GNOME/mutter/-/issues/3804
Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4152>
This commit is contained in:
Jonas Ådahl 2024-11-29 12:50:13 +01:00 committed by Marge Bot
parent 904c39116e
commit 24083e1e58
5 changed files with 126 additions and 5 deletions

View File

@ -62,6 +62,9 @@ void clutter_stage_emit_after_paint (ClutterStage
void clutter_stage_after_update (ClutterStage *stage,
ClutterStageView *view,
ClutterFrame *frame);
void clutter_stage_frame_discarded (ClutterStage *stage,
ClutterStageView *view,
ClutterFrame *frame);
CLUTTER_EXPORT
void _clutter_stage_set_window (ClutterStage *stage,

View File

@ -1050,11 +1050,12 @@ handle_frame_clock_frame (ClutterFrameClock *frame_clock,
if (CLUTTER_ACTOR_IN_DESTRUCTION (stage))
return CLUTTER_FRAME_RESULT_IDLE;
if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage)))
return CLUTTER_FRAME_RESULT_IDLE;
if (!clutter_actor_is_mapped (CLUTTER_ACTOR (stage)))
return CLUTTER_FRAME_RESULT_IDLE;
if (!clutter_actor_is_realized (CLUTTER_ACTOR (stage)) ||
!clutter_actor_is_mapped (CLUTTER_ACTOR (stage)))
{
clutter_stage_frame_discarded (stage, view, frame);
return CLUTTER_FRAME_RESULT_IDLE;
}
if (clutter_context_get_show_fps (context))
begin_frame_timing_measurement (view);

View File

@ -535,6 +535,16 @@ clutter_stage_after_update (ClutterStage *stage,
priv->update_scheduled = FALSE;
}
void
clutter_stage_frame_discarded (ClutterStage *stage,
ClutterStageView *view,
ClutterFrame *frame)
{
ClutterStagePrivate *priv = clutter_stage_get_instance_private (stage);
priv->update_scheduled = FALSE;
}
static gboolean
clutter_stage_get_paint_volume (ClutterActor *self,
ClutterPaintVolume *volume)

View File

@ -313,6 +313,11 @@ test_cases += [
'--compile-schemas',
],
},
{
'name': 'stage',
'suite': 'backend',
'sources': [ 'stage-tests.c', ],
},
]
screen_cast_client = executable('mutter-screen-cast-client',

102
src/tests/stage-tests.c Normal file
View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2024 Red Hat Inc.
*
* 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 <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include "backends/meta-backend-private.h"
#include "core/meta-context-private.h"
#include "tests/meta-test-utils.h"
#include "tests/meta-test/meta-context-test.h"
static MetaContext *test_context;
static gboolean
event_filter_cb (const ClutterEvent *event,
ClutterActor *event_actor,
gpointer user_data)
{
gboolean *saw_event = user_data;
if (clutter_event_type (event) == CLUTTER_DEVICE_ADDED)
*saw_event = TRUE;
return CLUTTER_EVENT_PROPAGATE;
}
static void
meta_test_stage_scheduling_delayed_show (void)
{
MetaBackend *backend = meta_context_get_backend (test_context);
ClutterActor *stage = meta_backend_get_stage (backend);
ClutterSeat *seat = meta_backend_get_default_seat (backend);
g_autoptr (MetaVirtualMonitor) virtual_monitor = NULL;
g_autoptr (ClutterVirtualInputDevice) virtual_pointer = NULL;
guint filter_id;
gboolean saw_event = FALSE;
virtual_monitor = meta_create_test_monitor (test_context, 800, 600, 60.0f);
g_debug ("Wait for initial dummy dispatch");
while (TRUE)
{
if (!g_main_context_iteration (NULL, FALSE))
break;
}
filter_id = clutter_event_add_filter (NULL, event_filter_cb, NULL, &saw_event);
g_debug ("Creating virtual pointer");
virtual_pointer =
clutter_seat_create_virtual_device (seat, CLUTTER_KEYBOARD_DEVICE);
while (!saw_event)
g_main_context_iteration (NULL, TRUE);
g_debug ("Scheduling update with DEVICE_ADDED in stage queue");
clutter_stage_schedule_update (CLUTTER_STAGE (stage));
g_debug ("Showing stage");
clutter_actor_show (stage);
g_debug ("Waiting for paint");
clutter_actor_queue_redraw (stage);
meta_wait_for_paint (test_context);
clutter_event_remove_filter (filter_id);
}
int
main (int argc,
char **argv)
{
g_autoptr (MetaContext) context = NULL;
g_auto (GVariantBuilder) plugin_options_builder =
G_VARIANT_BUILDER_INIT (G_VARIANT_TYPE_VARDICT);
g_autoptr (GVariant) plugin_options = NULL;
context = meta_create_test_context (META_CONTEXT_TEST_TYPE_HEADLESS,
META_CONTEXT_TEST_FLAG_NO_X11);
g_assert (meta_context_configure (context, &argc, &argv, NULL));
g_variant_builder_add (&plugin_options_builder, "{sv}",
"show-stage", g_variant_new_boolean (FALSE));
plugin_options =
g_variant_ref_sink (g_variant_builder_end (&plugin_options_builder));
meta_context_set_plugin_options (context, plugin_options);
test_context = context;
g_test_add_func ("/stage/scheduling/delayed-show",
meta_test_stage_scheduling_delayed_show);
return meta_context_test_run_tests (META_CONTEXT_TEST (context),
META_TEST_RUN_FLAG_NONE);
}