diff --git a/CHANGELOG.md b/CHANGELOG.md index c993ddef..e0e33d03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,10 @@ * Fix window shader not having an effect when frame opacity is enabled (#1174) * Fix binding root pixmap in case of depth mismatch (#984) +## Dependency changes + +* picom now optionally depends on `rtkit` at runtime to give itself realtime scheduling priority. + # v11.1 (2024-Jan-28) ## Bug fixes diff --git a/src/meson.build b/src/meson.build index d7108fba..badb6a7e 100644 --- a/src/meson.build +++ b/src/meson.build @@ -66,7 +66,7 @@ endif if get_option('dbus') cflags += ['-DCONFIG_DBUS'] deps += [dependency('dbus-1', required: true)] - srcs += [ 'dbus.c' ] + srcs += [ 'dbus.c', 'rtkit.c' ] endif if get_option('xrescheck') diff --git a/src/picom.c b/src/picom.c index 57893514..6bdfd1d9 100644 --- a/src/picom.c +++ b/src/picom.c @@ -19,7 +19,6 @@ #include #include #include -#include #include #include #include @@ -2603,39 +2602,6 @@ err: return NULL; } -/// Switch to real-time scheduling policy (SCHED_RR) if possible -/// -/// Make picom realtime to reduce latency, and make rendering times more predictable to -/// help pacing. -/// -/// This requires the user to set up permissions for the real-time scheduling. e.g. by -/// setting `ulimit -r`, or giving us the CAP_SYS_NICE capability. -void set_rr_scheduling(void) { - int priority = sched_get_priority_min(SCHED_RR); - - int ret; - struct sched_param param; - int old_policy; - ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); - if (ret != 0) { - log_debug("Failed to get old scheduling priority"); - return; - } - - param.sched_priority = priority; - - ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); - if (ret != 0) { - log_info("Failed to set real-time scheduling priority to %d. Consider " - "giving picom the CAP_SYS_NICE capability or equivalent " - "support.", - priority); - return; - } - - log_info("Set real-time scheduling priority to %d", priority); -} - /** * Destroy a session. * diff --git a/src/rtkit.c b/src/rtkit.c new file mode 100644 index 00000000..ecb89a25 --- /dev/null +++ b/src/rtkit.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright 2009 Lennart Poettering +// Copyright 2010 David Henningsson +// Copyright (c) Yuxuan Shui + +// Imported from https://github.com/heftig/rtkit/blob/master/rtkit.c + +#include +#include +#include +#include +#include + +#if defined(__linux__) +#include +#elif defined(__NetBSD__) +#include +#elif defined(__FreeBSD__) +#include +#elif defined(__DragonFly__) +#include +#endif + +#include "log.h" +#include "rtkit.h" +#include "utils.h" + +#define RTKIT_SERVICE_NAME "org.freedesktop.RealtimeKit1" +#define RTKIT_OBJECT_PATH "/org/freedesktop/RealtimeKit1" +#define RTKIT_INTERFACE "org.freedesktop.RealtimeKit1" + +static inline long compat_gettid(void) { + long ret = -1; +#if defined(__linux__) + ret = (pid_t)syscall(SYS_gettid); +#elif defined(__NetBSD__) + ret = _lwp_self(); +#elif defined(__FreeBSD__) + long lwpid; + thr_self(&lwpid); + ret = lwpid; +#elif defined(__DragonFly__) + ret = lwp_gettid(); +#endif + return ret; +} + +static bool +rtkit_get_int_property(DBusConnection *connection, const char *propname, long long *propval) { + DBusMessage *m = NULL, *r = NULL; + DBusMessageIter iter, subiter; + dbus_int64_t i64; + dbus_int32_t i32; + DBusError error; + int current_type; + int ret = 0; + const char *interfacestr = RTKIT_INTERFACE; + + dbus_error_init(&error); + + m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + "org.freedesktop.DBus.Properties", "Get"); + if (!m) { + ret = -ENOMEM; + goto finish; + } + + if (!dbus_message_append_args(m, DBUS_TYPE_STRING, &interfacestr, + DBUS_TYPE_STRING, &propname, DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error); + if (!r) { + goto finish; + } + + if (dbus_set_error_from_message(&error, r)) { + goto finish; + } + + ret = -EBADMSG; + dbus_message_iter_init(r, &iter); + while ((current_type = dbus_message_iter_get_arg_type(&iter)) != DBUS_TYPE_INVALID) { + if (current_type == DBUS_TYPE_VARIANT) { + dbus_message_iter_recurse(&iter, &subiter); + + while ((current_type = dbus_message_iter_get_arg_type(&subiter)) != + DBUS_TYPE_INVALID) { + + if (current_type == DBUS_TYPE_INT32) { + dbus_message_iter_get_basic(&subiter, &i32); + *propval = i32; + ret = 0; + } + + if (current_type == DBUS_TYPE_INT64) { + dbus_message_iter_get_basic(&subiter, &i64); + *propval = i64; + ret = 0; + } + + dbus_message_iter_next(&subiter); + } + } + dbus_message_iter_next(&iter); + } + +finish: + if (m) { + dbus_message_unref(m); + } + if (r) { + dbus_message_unref(r); + } + if (dbus_error_is_set(&error)) { + log_debug("Couldn't get property %s from rtkit: (dbus) %s", propname, + error.message); + dbus_error_free(&error); + return false; + } + if (ret != 0) { + log_debug("Couldn't get property %s from rtkit: %s", propname, strerror(-ret)); + return false; + } + + return true; +} + +static bool rtkit_get_rttime_usec_max(DBusConnection *connection, long long *retval) { + return rtkit_get_int_property(connection, "RTTimeUSecMax", retval); +} + +static inline void free_dbus_connection(DBusConnection **connection) { + if (*connection) { + dbus_connection_close(*connection); + dbus_connection_unref(*connection); + *connection = NULL; + } +} + +static inline void free_dbus_message(DBusMessage **message) { + if (*message) { + dbus_message_unref(*message); + *message = NULL; + } +} + +bool rtkit_make_realtime(long thread, int priority) { + cleanup(free_dbus_message) DBusMessage *m = NULL; + cleanup(free_dbus_message) DBusMessage *r = NULL; + dbus_uint64_t u64; + dbus_uint32_t u32; + DBusError error; + int ret = 0; + long long rttime_usec_max = 0; + bool succeeded = true; + + dbus_error_init(&error); + + cleanup(free_dbus_connection) DBusConnection *connection = + dbus_bus_get_private(DBUS_BUS_SYSTEM, &error); + if (dbus_error_is_set(&error)) { + log_info("Couldn't get system bus: %s", error.message); + dbus_error_free(&error); + return false; + } + dbus_connection_set_exit_on_disconnect(connection, false); + + if (thread == 0) { + thread = compat_gettid(); + } + + if (!rtkit_get_rttime_usec_max(connection, &rttime_usec_max)) { + log_debug("Couldn't get RTTimeUSecMax from rtkit."); + return false; + } + if (rttime_usec_max <= 0) { + log_debug("Unreasonable RTTimeUSecMax from rtkit: %lld", rttime_usec_max); + return false; + } + +#if defined(RLIMIT_RTTIME) + struct rlimit old_rlim, new_rlim; + // For security reasons, rtkit requires us to set RLIMIT_RTTIME before it will + // give us realtime priority. + if (getrlimit(RLIMIT_RTTIME, &old_rlim) != 0) { + log_debug("Couldn't get RLIMIT_RTTIME."); + return false; + } + new_rlim = old_rlim; + new_rlim.rlim_cur = min3(new_rlim.rlim_max, (rlim_t)rttime_usec_max, 100000); // 100ms + new_rlim.rlim_max = new_rlim.rlim_cur; + if (setrlimit(RLIMIT_RTTIME, &new_rlim) != 0) { + log_debug("Couldn't set RLIMIT_RTTIME."); + return false; + } +#endif + + m = dbus_message_new_method_call(RTKIT_SERVICE_NAME, RTKIT_OBJECT_PATH, + RTKIT_INTERFACE, "MakeThreadRealtime"); + if (!m) { + ret = -ENOMEM; + goto finish; + } + + u64 = (dbus_uint64_t)thread; + u32 = (dbus_uint32_t)priority; + + if (!dbus_message_append_args(m, DBUS_TYPE_UINT64, &u64, DBUS_TYPE_UINT32, &u32, + DBUS_TYPE_INVALID)) { + ret = -ENOMEM; + goto finish; + } + + r = dbus_connection_send_with_reply_and_block(connection, m, -1, &error); + if (!r) { + goto finish; + } + + if (dbus_set_error_from_message(&error, r)) { + goto finish; + } + + ret = 0; + +finish: + if (dbus_error_is_set(&error)) { + log_info("Couldn't make thread realtime with rtkit: (dbus) %s", error.message); + dbus_error_free(&error); + succeeded = false; + } else if (ret != 0) { + log_info("Couldn't make thread realtime with rtkit: %s", strerror(-ret)); + succeeded = false; + } +#if defined(RLIMIT_RTTIME) + if (!succeeded) { + // Restore RLIMIT_RTTIME + setrlimit(RLIMIT_RTTIME, &old_rlim); + } +#endif + + return succeeded; +} diff --git a/src/rtkit.h b/src/rtkit.h new file mode 100644 index 00000000..a5b04e88 --- /dev/null +++ b/src/rtkit.h @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MPL-2.0 +// Copyright (c) Yuxuan Shui + +#pragma once +#include +#include + +#ifdef CONFIG_DBUS + +#include + +bool rtkit_make_realtime(long thread, int priority); + +#else + +static inline bool rtkit_make_realtime(pid_t thread attr_unused, int priority attr_unused) { + return false; +} + +#endif diff --git a/src/utils.c b/src/utils.c index 53766d6d..68ec697e 100644 --- a/src/utils.c +++ b/src/utils.c @@ -1,8 +1,10 @@ +#include #include #include #include #include "compiler.h" +#include "rtkit.h" #include "string_utils.h" #include "test.h" #include "utils.h" @@ -272,4 +274,39 @@ void rolling_quantile_pop_front(struct rolling_quantile *rq, int x) { } } +/// Switch to real-time scheduling policy (SCHED_RR) if possible +/// +/// Make picom realtime to reduce latency, and make rendering times more predictable to +/// help pacing. +/// +/// This requires the user to set up permissions for the real-time scheduling. e.g. by +/// setting `ulimit -r`, or giving us the CAP_SYS_NICE capability. +void set_rr_scheduling(void) { + int priority = sched_get_priority_min(SCHED_RR); + + if (rtkit_make_realtime(0, priority)) { + log_info("Set realtime priority to %d with rtkit.", priority); + return; + } + + // Fallback to use pthread_setschedparam + struct sched_param param; + int old_policy; + int ret = pthread_getschedparam(pthread_self(), &old_policy, ¶m); + if (ret != 0) { + log_info("Couldn't get old scheduling priority."); + return; + } + + param.sched_priority = priority; + + ret = pthread_setschedparam(pthread_self(), SCHED_RR, ¶m); + if (ret != 0) { + log_info("Couldn't set real-time scheduling priority to %d.", priority); + return; + } + + log_info("Set real-time scheduling priority to %d.", priority); +} + // vim: set noet sw=8 ts=8 : diff --git a/src/utils.h b/src/utils.h index 22da6c29..962f160f 100644 --- a/src/utils.h +++ b/src/utils.h @@ -406,6 +406,7 @@ void rolling_quantile_destroy(struct rolling_quantile *rq); int rolling_quantile_estimate(struct rolling_quantile *rq, struct rolling_window *elements); void rolling_quantile_push_back(struct rolling_quantile *rq, int x); void rolling_quantile_pop_front(struct rolling_quantile *rq, int x); +void set_rr_scheduling(void); // Some versions of the Android libc do not have timespec_get(), use // clock_gettime() instead. diff --git a/src/vblank.c b/src/vblank.c index 81a6cb9e..776d258b 100644 --- a/src/vblank.c +++ b/src/vblank.c @@ -183,6 +183,8 @@ static void *sgi_video_sync_thread(void *data) { goto start_failed; } + log_init_tls(); + set_rr_scheduling(); pthread_mutex_lock(&args->start_mtx); args->start_status = 0; pthread_cond_signal(&args->start_cnd);