Skip to content

Commit

Permalink
Control how many frames are captured per second
Browse files Browse the repository at this point in the history
The goal is to control the rate of the capturing, as it can represent a
performance issue and can cause a laggy mouse; see emersion#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.
  • Loading branch information
zsolt-donca committed Feb 12, 2021
1 parent 5401e7f commit e3c5ceb
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 0 deletions.
15 changes: 15 additions & 0 deletions include/fps_limit.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#ifndef FPS_LIMIT_H
#define FPS_LIMIT_H

#include <stdint.h>

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
5 changes: 5 additions & 0 deletions include/screencast_common.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
#include <spa/param/video/format-utils.h>
#include <wayland-client-protocol.h>

#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
Expand Down Expand Up @@ -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 {
Expand Down
52 changes: 52 additions & 0 deletions include/time-util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* 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 <time.h>
#include <stdint.h>

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 void us_to_timespec(uint64_t time_us, struct timespec* ts)
{
ts->tv_sec = time_us / UINT64_C(1000000);
ts->tv_nsec = time_us % UINT64_C(1000000) * 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);
}
1 change: 1 addition & 0 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,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,
Expand Down
53 changes: 53 additions & 0 deletions src/screencast/fps_limit.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#include "fps_limit.h"
#include "logger.h"
#include "time-util.h"
#include <stdint.h>
#include <unistd.h>

#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);

struct timespec delay;
us_to_timespec(delay_us, &delay);
nanosleep(&delay, NULL);
}

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;
}
}
4 changes: 4 additions & 0 deletions src/screencast/wlr_screencast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -133,6 +134,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");
}
Expand Down

0 comments on commit e3c5ceb

Please sign in to comment.