output/cursor: deferred cursor move

This feature is meant to be used with VRR / Adaptive Sync.
Currently cursor move drives the display at the poll rate of the mouse.
This adds a deferred delay for a variety situations:
To save power on battery
To maintain smooth video playback (i.e. render video 30fps at 60 or 120fps)
To maintain smooth gaming experience (i.e. only enforce redraw at VRR minimum Hz)

I've tried to make it as unopinionated as possible, while also reducing
complexity for downstream developers.

The design should be backwards compatible, as long as
wlr_set_cursor_max_latency() is never called wlr_cursor_move() should act as
normal

I considered using wl_signals, but I'm not familiar with the interface, and it
felt somewhat overkill.

There's a reference implementation in branch deferred-cursor-move:
github:YellowOnion/sway
This commit is contained in:
Daniel Hill 2023-08-28 17:08:10 +12:00
parent d98a3eb492
commit 47f8cefad8
6 changed files with 103 additions and 7 deletions

View file

@ -4,6 +4,8 @@
#include <stdint.h>
#include <time.h>
static const long NSEC_PER_SEC = 1000000000;
/**
* Get the current time, in milliseconds.
*/
@ -30,4 +32,6 @@ void timespec_from_nsec(struct timespec *r, int64_t nsec);
void timespec_sub(struct timespec *r, const struct timespec *a,
const struct timespec *b);
int32_t mhz_to_nsec(int32_t mhz);
#endif

View file

@ -34,6 +34,8 @@ struct wlr_cursor {
struct wlr_cursor_state *state;
double x, y;
int max_latency;
/**
* The interpretation of these signals is the responsibility of the
* compositor, but some helpers are provided for your benefit. If you
@ -166,6 +168,8 @@ void wlr_cursor_set_xcursor(struct wlr_cursor *cur,
void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface,
int32_t hotspot_x, int32_t hotspot_y);
void wlr_cursor_set_max_latency(struct wlr_cursor *cur, int max_latency);
/**
* Attaches this input device to this cursor. The input device must be one of:
*

View file

@ -43,9 +43,15 @@ struct wlr_output_cursor {
struct wlr_fbox src_box;
enum wl_output_transform transform;
int32_t hotspot_x, hotspot_y;
// only when using a software cursor without a surface
struct wlr_texture *texture;
bool own_texture;
struct wl_list link;
struct timespec last_presentation;
bool deferred;
double deferred_x, deferred_y;
int max_latency;
};
enum wlr_output_adaptive_sync_status {
@ -561,6 +567,13 @@ bool wlr_output_cursor_set_buffer(struct wlr_output_cursor *cursor,
struct wlr_buffer *buffer, int32_t hotspot_x, int32_t hotspot_y);
bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
double x, double y);
/**
* Call any_expired() before you check needs_frame.
* call all_deferred() whenever a new frame is needed.
* */
void wlr_output_cursor_move_expired(struct wlr_output_cursor *cursor, struct timespec *now);
void wlr_output_cursor_move_any_expired(struct wlr_output *output, struct timespec *now);
void wlr_output_cursor_move_all_deferred(struct wlr_output *output, struct timespec *now);
void wlr_output_cursor_destroy(struct wlr_output_cursor *cursor);
/**

View file

@ -1,3 +1,4 @@
#define _POSIX_C_SOURCE 200809L
#include <assert.h>
#include <drm_fourcc.h>
#include <stdlib.h>
@ -12,6 +13,9 @@
#include "types/wlr_buffer.h"
#include "types/wlr_output.h"
#include <time.h>
#include <util/time.h>
static bool output_set_hardware_cursor(struct wlr_output *output,
struct wlr_buffer *buffer, int hotspot_x, int hotspot_y) {
if (!output->impl->set_cursor) {
@ -461,11 +465,10 @@ bool output_cursor_set_texture(struct wlr_output_cursor *cursor,
return true;
}
bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
double x, double y) {
// Scale coordinates for the output
x *= cursor->output->scale;
y *= cursor->output->scale;
static bool output_cursor_move(struct wlr_output_cursor *cursor,
double x, double y, struct timespec *now) {
cursor->last_presentation = *now;
cursor->deferred = false;
if (cursor->x == x && cursor->y == y) {
return true;
@ -494,6 +497,71 @@ bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
return cursor->output->impl->move_cursor(cursor->output, (int)x, (int)y);
}
static bool output_cursor_move_should_defer(struct wlr_output_cursor *cursor,
struct timespec *now) {
if (!cursor->max_latency
|| !cursor->output->refresh // avoid divide by zero
|| cursor->output->adaptive_sync_status != WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)
return false;
struct timespec delta;
int32_t vrr_min = NSEC_PER_SEC / 30; // edid? enforce 30fps minimum for now.
timespec_sub(&delta, now, &cursor->last_presentation);
if (delta.tv_sec
|| delta.tv_nsec >= cursor->max_latency
|| delta.tv_nsec >= vrr_min)
return false;
return true;
}
bool wlr_output_cursor_move(struct wlr_output_cursor *cursor,
double x, double y) {
// Scale coordinates for the output
x *= cursor->output->scale;
y *= cursor->output->scale;
if (cursor->x == x && cursor->y == y) {
return true;
}
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
if (output_cursor_move_should_defer(cursor, &now)) {
cursor->deferred_x = x;
cursor->deferred_y = y;
cursor->deferred = true;
return true;
}
return output_cursor_move(cursor, x, y, &now);
}
void wlr_output_cursor_move_expired(struct wlr_output_cursor *cursor, struct timespec *now) {
if (cursor->deferred && !output_cursor_move_should_defer(cursor, now))
output_cursor_move(cursor, cursor->deferred_x, cursor->deferred_y, now);
}
void wlr_output_cursor_move_any_expired(struct wlr_output *output, struct timespec *now) {
struct wlr_output_cursor *cursor;
wl_list_for_each(cursor, &output->cursors, link) {
wlr_output_cursor_move_expired(cursor, now);
}
}
void wlr_output_cursor_move_all_deferred(struct wlr_output *output, struct timespec *now) {
struct wlr_output_cursor *cursor;
wl_list_for_each(cursor, &output->cursors, link) {
if (cursor->deferred)
output_cursor_move(cursor, cursor->deferred_x, cursor->deferred_y, now);
else {
// Should be on wlr_output?
cursor->last_presentation = *now;
};
}
}
struct wlr_output_cursor *wlr_output_cursor_create(struct wlr_output *output) {
struct wlr_output_cursor *cursor = calloc(1, sizeof(*cursor));
if (cursor == NULL) {

View file

@ -670,6 +670,11 @@ void wlr_cursor_set_surface(struct wlr_cursor *cur, struct wlr_surface *surface,
cursor_update_outputs(cur);
}
void wlr_cursor_set_max_latency(struct wlr_cursor *cur, int max_latency) {
cur->max_latency = max_latency;
wlr_log(WLR_DEBUG, "setting max_latency %i", max_latency);
}
static void handle_pointer_motion(struct wl_listener *listener, void *data) {
struct wlr_pointer_motion_event *event = data;
struct wlr_cursor_device *device =

View file

@ -4,8 +4,6 @@
#include "util/time.h"
static const long NSEC_PER_SEC = 1000000000;
int64_t timespec_to_msec(const struct timespec *a) {
return (int64_t)a->tv_sec * 1000 + a->tv_nsec / 1000000;
}
@ -34,3 +32,7 @@ void timespec_sub(struct timespec *r, const struct timespec *a,
r->tv_nsec += NSEC_PER_SEC;
}
}
int32_t mhz_to_nsec(int32_t mhz) {
return 1000000000000LL / mhz;
}