From 6e638493b11032fce35efe2f2745db2b56ac11c0 Mon Sep 17 00:00:00 2001 From: Zsolt Donca Date: Wed, 23 Dec 2020 20:47:10 +0200 Subject: [PATCH] Control how many frames are captured per second The goal is to rate-control the capturing, as it can represent a performance issue and can cause a laggy mouse; see #66. The delay for the current frame is based on the time it took to capture the previous frame. The targeted frame rate is a constant. --- include/fps_limit.h | 15 +++++++++++ include/screencast_common.h | 5 ++++ include/time-util.h | 46 ++++++++++++++++++++++++++++++++ meson.build | 2 ++ src/screencast/fps_limit.c | 47 +++++++++++++++++++++++++++++++++ src/screencast/wlr_screencast.c | 7 ++++- 6 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 include/fps_limit.h create mode 100644 include/time-util.h create mode 100644 src/screencast/fps_limit.c diff --git a/include/fps_limit.h b/include/fps_limit.h new file mode 100644 index 00000000..95ddf29b --- /dev/null +++ b/include/fps_limit.h @@ -0,0 +1,15 @@ +#ifndef RATE_LIMIT_H +#define RATE_LIMIT_H + +#include + +struct rate_limit_state { + uint64_t last_time_us; + + uint64_t last_measured_time_us; + uint32_t measured_delays_count; +}; + +void fps_limit_apply(struct rate_limit_state* state); + +#endif diff --git a/include/screencast_common.h b/include/screencast_common.h index 0724bd11..7cf3c5a9 100644 --- a/include/screencast_common.h +++ b/include/screencast_common.h @@ -5,6 +5,8 @@ #include #include +#include "fps_limit.h" + // this seems to be right based on // https://github.com/flatpak/xdg-desktop-portal/blob/309a1fc0cf2fb32cceb91dbc666d20cf0a3202c2/src/screen-cast.c#L955 #define XDP_CAST_PROTO_VER 2 @@ -91,6 +93,9 @@ struct xdpw_screencast_instance { bool with_cursor; int err; bool quit; + + // rate limit + struct rate_limit_state rate_limit; }; struct xdpw_wlr_output { diff --git a/include/time-util.h b/include/time-util.h new file mode 100644 index 00000000..9f9fa2b8 --- /dev/null +++ b/include/time-util.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 - 2020 Andri Yngvason + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE + * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +#pragma once + +#include +#include + +static inline uint64_t timespec_to_us(const struct timespec* ts) +{ + return (uint64_t)ts->tv_sec * UINT64_C(1000000) + + (uint64_t)ts->tv_nsec / UINT64_C(1000); +} + +static inline uint64_t timespec_to_ms(const struct timespec* ts) +{ + return (uint64_t)ts->tv_sec * UINT64_C(1000) + + (uint64_t)ts->tv_nsec / UINT64_C(1000000); +} + +static inline uint64_t gettime_us(void) +{ + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + return timespec_to_us(&ts); +} + +static inline uint64_t gettime_ms(void) +{ + struct timespec ts = { 0 }; + clock_gettime(CLOCK_MONOTONIC, &ts); + return timespec_to_ms(&ts); +} diff --git a/meson.build b/meson.build index 76a9d7f4..8ff9940e 100644 --- a/meson.build +++ b/meson.build @@ -18,6 +18,7 @@ add_project_arguments(cc.get_supported_arguments([ '-Wno-missing-field-initializers', '-Wno-unused-parameter', '-D_POSIX_C_SOURCE=200809L', + '-D_GNU_SOURCE', ]), language: 'c') inc = include_directories('include') @@ -49,6 +50,7 @@ executable( 'src/screencast/screencast_common.c', 'src/screencast/wlr_screencast.c', 'src/screencast/pipewire_screencast.c', + 'src/screencast/fps_limit.c' ]), dependencies: [ wayland_client, diff --git a/src/screencast/fps_limit.c b/src/screencast/fps_limit.c new file mode 100644 index 00000000..04b87323 --- /dev/null +++ b/src/screencast/fps_limit.c @@ -0,0 +1,47 @@ +#include "fps_limit.h" +#include "logger.h" +#include "time-util.h" +#include +#include + +#define FPS_MEASURE_PERIOD_SEC 2.0 +#define TARGET_FPS 10.0 + +void measure_and_log_fps(struct rate_limit_state* state, uint64_t now_us); + +void fps_limit_apply(struct rate_limit_state* state) { + uint64_t now_us = gettime_us(); + + if (state->last_time_us == 0) { + state->last_time_us = now_us; + state->last_measured_time_us = now_us; + } else { + uint64_t elapsed_us = now_us - state->last_time_us; + + measure_and_log_fps(state, now_us); + + uint64_t target_us = (1.0 / TARGET_FPS) * 1e6; + int64_t delay_us = target_us - elapsed_us; + if (delay_us > 0) { + logprint(TRACE, "fps_limit: elapsed time since the end of last sleep: %ul, sleeping for %ul (microseconds)", elapsed_us, delay_us); + usleep(delay_us); + } + + state->last_time_us = gettime_us(); + } +} + +void measure_and_log_fps(struct rate_limit_state* state, uint64_t now_us) { + state->measured_delays_count++; + + double elapsed_since_last_measure_sec = (now_us - state->last_measured_time_us) / 1e6; + if (elapsed_since_last_measure_sec >= FPS_MEASURE_PERIOD_SEC) { + double average_delay_sec = elapsed_since_last_measure_sec / state->measured_delays_count; + double average_frames_per_sec = 1.0 / average_delay_sec; + + logprint(DEBUG, "fps_limit: measured FPS: %f", average_frames_per_sec); + + state->last_measured_time_us = now_us; + state->measured_delays_count = 0; + } +} diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c index a93e248a..a447a848 100644 --- a/src/screencast/wlr_screencast.c +++ b/src/screencast/wlr_screencast.c @@ -18,6 +18,7 @@ #include "pipewire_screencast.h" #include "xdpw.h" #include "logger.h" +#include "fps_limit.h" static void wlr_frame_buffer_destroy(struct xdpw_screencast_instance *cast) { // Even though this check may be deemed unnecessary, @@ -135,7 +136,11 @@ static void wlr_frame_buffer_done(void *data, struct xdpw_screencast_instance *cast = data; logprint(TRACE, "wlroots: buffer_done event handler"); - zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->simple_frame.buffer); + + fps_limit_apply(&cast->rate_limit); + + // zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->simple_frame.buffer); + zwlr_screencopy_frame_v1_copy(frame, cast->simple_frame.buffer); logprint(TRACE, "wlroots: frame copied"); }