1
0
mirror of https://github.com/brl/mutter.git synced 2025-04-29 13:19:39 +00:00

clutter/frame-clock: Use timerfd for clock timing

Currently, ClutterFrameClock uses g_source_set_ready_time() to determine
the usec timing of the next frame. That translates into a poll() with a
millisecond timeout if no trigger occurs to break the poll() out early.

To avoid spinning the CPU, GLib always rounds *up* to the next millisecond
value unless a timeout of 0 was provided by a GSource.

This means that timeouts for the ClutterFrameClock can easily skew beyond
their expected time as the precision is too coarse.

This applies the same concept as  but just for the
ClutterFrameClock. That may be more ideal than adding a timerfd for every
GMainContext, but we'll see if that lands upstream. I wanted to provide
this here because it could easily be cherry-picked in the mean time if
this is found to be useful.

From a timer stability perspective, this improves things from erratically
jumping between 100s and 1000s off of the expected awake time to single
or low double digits.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/3636>
This commit is contained in:
Christian Hergert 2024-03-03 13:50:00 -08:00 committed by Marge Bot
parent 06e7a4fb68
commit 0810238d22
3 changed files with 108 additions and 2 deletions

@ -19,6 +19,13 @@
#include "clutter/clutter-frame-clock.h" #include "clutter/clutter-frame-clock.h"
#include <glib/gstdio.h>
#ifdef HAVE_TIMERFD
#include <sys/timerfd.h>
#include <time.h>
#endif
#include "clutter/clutter-debug.h" #include "clutter/clutter-debug.h"
#include "clutter/clutter-frame-private.h" #include "clutter/clutter-frame-private.h"
#include "clutter/clutter-main.h" #include "clutter/clutter-main.h"
@ -50,6 +57,11 @@ typedef struct _ClutterClockSource
GSource source; GSource source;
ClutterFrameClock *frame_clock; ClutterFrameClock *frame_clock;
#ifdef HAVE_TIMERFD
int tfd;
struct itimerspec tfd_spec;
#endif
} ClutterClockSource; } ClutterClockSource;
typedef enum _ClutterFrameClockState typedef enum _ClutterFrameClockState
@ -1051,11 +1063,81 @@ clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clo
return string; return string;
} }
static gboolean
frame_clock_source_prepare (GSource *source,
int *timeout)
{
G_GNUC_UNUSED ClutterClockSource *clock_source = (ClutterClockSource *)source;
*timeout = -1;
#ifdef HAVE_TIMERFD
/* The cycle for GMainContext is:
*
* - prepare(): where we update our timerfd deadline
* - poll(): internal to GMainContext/GPollFunc
* - check(): where GLib will check POLLIN and make ready
* - dispatch(): where we actually process the pending work
*
* If we have a ready_time >= 0 then we need to set our deadline
* in nanoseconds for the timerfd. The timerfd will receive POLLIN
* after that point and poll() will return.
*
* If we have a ready_time of -1, then we need to disable our
* timerfd by setting tv_sec and tv_nsec to 0.
*
* In both cases, the POLLIN bit will be reset.
*/
if (clock_source->tfd > -1)
{
int64_t ready_time = g_source_get_ready_time (source);
struct itimerspec tfd_spec;
tfd_spec.it_interval.tv_sec = 0;
tfd_spec.it_interval.tv_nsec = 0;
if (ready_time > -1)
{
tfd_spec.it_value.tv_sec = ready_time / G_USEC_PER_SEC;
tfd_spec.it_value.tv_nsec = (ready_time % G_USEC_PER_SEC) * 1000L;
}
else
{
tfd_spec.it_value.tv_sec = 0;
tfd_spec.it_value.tv_nsec = 0;
}
/* Avoid extraneous calls timerfd_settime() */
if (memcmp (&tfd_spec, &clock_source->tfd_spec, sizeof tfd_spec) != 0)
{
clock_source->tfd_spec = tfd_spec;
timerfd_settime (clock_source->tfd,
TFD_TIMER_ABSTIME,
&clock_source->tfd_spec,
NULL);
}
}
#endif
return FALSE;
}
static void
frame_clock_source_finalize (GSource *source)
{
#ifdef HAVE_TIMERFD
ClutterClockSource *clock_source = (ClutterClockSource *)source;
g_clear_fd (&clock_source->tfd, NULL);
#endif
}
static GSourceFuncs frame_clock_source_funcs = { static GSourceFuncs frame_clock_source_funcs = {
NULL, frame_clock_source_prepare,
NULL, NULL,
frame_clock_source_dispatch, frame_clock_source_dispatch,
NULL frame_clock_source_finalize,
}; };
static void static void
@ -1068,6 +1150,13 @@ init_frame_clock_source (ClutterFrameClock *frame_clock)
source = g_source_new (&frame_clock_source_funcs, sizeof (ClutterClockSource)); source = g_source_new (&frame_clock_source_funcs, sizeof (ClutterClockSource));
clock_source = (ClutterClockSource *) source; clock_source = (ClutterClockSource *) source;
#ifdef HAVE_TIMERFD
clock_source->tfd = timerfd_create (CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
if (clock_source->tfd > -1)
g_source_add_unix_fd (source, clock_source->tfd, G_IO_IN);
#endif
name = g_strdup_printf ("[mutter] Clutter frame clock (%p)", frame_clock); name = g_strdup_printf ("[mutter] Clutter frame clock (%p)", frame_clock);
g_source_set_name (source, name); g_source_set_name (source, name);
g_source_set_priority (source, CLUTTER_PRIORITY_REDRAW); g_source_set_priority (source, CLUTTER_PRIORITY_REDRAW);

@ -129,3 +129,6 @@
/* Supports PangoFt2 */ /* Supports PangoFt2 */
#mesondefine HAVE_PANGO_FT2 #mesondefine HAVE_PANGO_FT2
/* Supports timerfd_create/timerfd_settime */
#mesondefine HAVE_TIMERFD

@ -331,6 +331,19 @@ if have_introspection
} }
endif endif
# Check for timerfd_create(2)
have_timerfd = cc.links('''
#include <sys/timerfd.h>
#include <time.h>
#include <unistd.h>
int main (int argc, char ** argv) {
struct itimerspec ts = {{0}};
int fd = timerfd_create (CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
timerfd_settime (fd, TFD_TIMER_ABSTIME, &ts, NULL);
return 0;
}
''', name : 'timerfd_create(2) system call')
have_documentation = get_option('docs') have_documentation = get_option('docs')
have_tests = get_option('tests') have_tests = get_option('tests')
have_core_tests = false have_core_tests = false
@ -551,6 +564,7 @@ cdata.set('HAVE_INTROSPECTION', have_introspection)
cdata.set('HAVE_PROFILER', have_profiler) cdata.set('HAVE_PROFILER', have_profiler)
cdata.set('HAVE_LIBDISPLAY_INFO', have_libdisplay_info) cdata.set('HAVE_LIBDISPLAY_INFO', have_libdisplay_info)
cdata.set('HAVE_PANGO_FT2', have_pango_ft2) cdata.set('HAVE_PANGO_FT2', have_pango_ft2)
cdata.set('HAVE_TIMERFD', have_timerfd)
if have_x11_client if have_x11_client
xkb_base = xkeyboard_config_dep.get_variable('xkb_base') xkb_base = xkeyboard_config_dep.get_variable('xkb_base')