From 1b63da12cd4e76b346111b39a75a131524a95225 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 control the rate of 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. Added code to measure the average FPS every 5 seconds and print it with DEBUG level. --- include/fps_limit.h | 15 ++++++++++ include/screencast_common.h | 5 ++++ include/time-util.h | 46 ++++++++++++++++++++++++++++++ meson.build | 2 ++ src/screencast/fps_limit.c | 50 +++++++++++++++++++++++++++++++++ src/screencast/wlr_screencast.c | 4 +++ 6 files changed, 122 insertions(+) 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..edf877ed --- /dev/null +++ b/include/fps_limit.h @@ -0,0 +1,15 @@ +#ifndef FPS_LIMIT_H +#define FPS_LIMIT_H + +#include + +struct fps_limit_state { + uint64_t last_time_us; + + uint64_t fps_measurement_last_time_us; + uint32_t fps_measurement_frame_count; +}; + +void fps_limit_apply(struct fps_limit_state* state); + +#endif diff --git a/include/screencast_common.h b/include/screencast_common.h index 0724bd11..e6fd164d 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; + + // fps limit + struct fps_limit_state fps_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..2b616454 --- /dev/null +++ b/src/screencast/fps_limit.c @@ -0,0 +1,50 @@ +#include "fps_limit.h" +#include "logger.h" +#include "time-util.h" +#include +#include + +#define FPS_MEASURE_PERIOD_SEC 5.0 +#define TARGET_FPS 10.0 + +void measure_fps(struct fps_limit_state* state, uint64_t now_us); + +void fps_limit_apply(struct fps_limit_state* state, double max_fps) { + if (max_fps <= 0.0) + return; + + uint64_t now_us = gettime_us(); + + if (state->last_time_us == 0) { + state->last_time_us = now_us; + state->fps_measurement_last_time_us = now_us; + } else { + uint64_t elapsed_us = now_us - state->last_time_us; + + measure_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: %u, sleeping for %u (microseconds)", elapsed_us, delay_us); + usleep(delay_us); + } + + state->last_time_us = gettime_us(); + } +} + +void measure_fps(struct fps_limit_state* state, uint64_t now_us) { + state->fps_measurement_frame_count++; + + double elapsed_since_last_measure_sec = (now_us - state->fps_measurement_last_time_us) / 1e6; + if (elapsed_since_last_measure_sec >= FPS_MEASURE_PERIOD_SEC) { + double average_delay_sec = elapsed_since_last_measure_sec / state->fps_measurement_frame_count; + double average_frames_per_sec = 1.0 / average_delay_sec; + + logprint(DEBUG, "fps_limit: average FPS in the last %0.0f seconds: %0.2f", FPS_MEASURE_PERIOD_SEC, average_frames_per_sec); + + state->fps_measurement_last_time_us = now_us; + state->fps_measurement_frame_count = 0; + } +} diff --git a/src/screencast/wlr_screencast.c b/src/screencast/wlr_screencast.c index a93e248a..81e5a104 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,6 +136,9 @@ static void wlr_frame_buffer_done(void *data, struct xdpw_screencast_instance *cast = data; logprint(TRACE, "wlroots: buffer_done event handler"); + + fps_limit_apply(&cast->fps_limit); + zwlr_screencopy_frame_v1_copy_with_damage(frame, cast->simple_frame.buffer); logprint(TRACE, "wlroots: frame copied"); }