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 GNOME/glib!3949 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:
parent
06e7a4fb68
commit
0810238d22
@ -19,6 +19,13 @@
|
||||
|
||||
#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-frame-private.h"
|
||||
#include "clutter/clutter-main.h"
|
||||
@ -50,6 +57,11 @@ typedef struct _ClutterClockSource
|
||||
GSource source;
|
||||
|
||||
ClutterFrameClock *frame_clock;
|
||||
|
||||
#ifdef HAVE_TIMERFD
|
||||
int tfd;
|
||||
struct itimerspec tfd_spec;
|
||||
#endif
|
||||
} ClutterClockSource;
|
||||
|
||||
typedef enum _ClutterFrameClockState
|
||||
@ -1051,11 +1063,81 @@ clutter_frame_clock_get_max_render_time_debug_info (ClutterFrameClock *frame_clo
|
||||
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 = {
|
||||
NULL,
|
||||
frame_clock_source_prepare,
|
||||
NULL,
|
||||
frame_clock_source_dispatch,
|
||||
NULL
|
||||
frame_clock_source_finalize,
|
||||
};
|
||||
|
||||
static void
|
||||
@ -1068,6 +1150,13 @@ init_frame_clock_source (ClutterFrameClock *frame_clock)
|
||||
source = g_source_new (&frame_clock_source_funcs, sizeof (ClutterClockSource));
|
||||
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);
|
||||
g_source_set_name (source, name);
|
||||
g_source_set_priority (source, CLUTTER_PRIORITY_REDRAW);
|
||||
|
@ -129,3 +129,6 @@
|
||||
|
||||
/* Supports PangoFt2 */
|
||||
#mesondefine HAVE_PANGO_FT2
|
||||
|
||||
/* Supports timerfd_create/timerfd_settime */
|
||||
#mesondefine HAVE_TIMERFD
|
||||
|
14
meson.build
14
meson.build
@ -331,6 +331,19 @@ if have_introspection
|
||||
}
|
||||
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_tests = get_option('tests')
|
||||
have_core_tests = false
|
||||
@ -551,6 +564,7 @@ cdata.set('HAVE_INTROSPECTION', have_introspection)
|
||||
cdata.set('HAVE_PROFILER', have_profiler)
|
||||
cdata.set('HAVE_LIBDISPLAY_INFO', have_libdisplay_info)
|
||||
cdata.set('HAVE_PANGO_FT2', have_pango_ft2)
|
||||
cdata.set('HAVE_TIMERFD', have_timerfd)
|
||||
|
||||
if have_x11_client
|
||||
xkb_base = xkeyboard_config_dep.get_variable('xkb_base')
|
||||
|
Loading…
Reference in New Issue
Block a user