This commit is contained in:
Simon Ser 2024-01-17 10:26:36 -08:00 committed by GitHub
commit 2669609582
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 203 additions and 96 deletions

View file

@ -4,6 +4,7 @@
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/types/wlr_damage_ring.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include <wlr/types/wlr_output.h>
#include "config.h"
#include "sway/tree/node.h"
@ -21,6 +22,7 @@ struct sway_output {
struct sway_node node;
struct wlr_output *wlr_output;
struct sway_server *server;
struct wlr_frame_scheduler *frame_scheduler;
struct wl_list link;
struct wl_list layers[4]; // sway_layer_surface::link
@ -45,17 +47,13 @@ struct sway_output {
struct wl_listener present;
struct wl_listener damage;
struct wl_listener frame;
struct wl_listener needs_frame;
struct wl_listener request_state;
struct {
struct wl_signal disable;
} events;
struct timespec last_presentation;
uint32_t refresh_nsec;
int max_render_time; // In milliseconds
struct wl_event_source *repaint_timer;
bool gamma_lut_changed;
};
@ -73,6 +71,21 @@ struct render_context {
struct wlr_render_pass *pass;
};
struct sway_timer_frame_scheduler {
struct wlr_frame_scheduler base;
int max_render_time;
struct wl_event_source *idle;
struct wl_event_source *timer;
bool frame_pending;
bool needs_frame;
struct wl_listener commit;
struct wl_listener present;
};
struct sway_output *output_create(struct wlr_output *wlr_output);
void output_destroy(struct sway_output *output);
@ -119,6 +132,8 @@ void output_enable(struct sway_output *output);
void output_disable(struct sway_output *output);
void output_set_max_render_time(struct sway_output *output, int max_render_time);
bool output_has_opaque_overlay_layer_surface(struct sway_output *output);
struct sway_workspace *output_get_active_workspace(struct sway_output *output);

View file

@ -584,7 +584,7 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
if (oc && oc->max_render_time >= 0) {
sway_log(SWAY_DEBUG, "Set %s max render time to %d",
oc->name, oc->max_render_time);
output->max_render_time = oc->max_render_time;
output_set_max_render_time(output, oc->max_render_time);
}
// Reconfigure all devices, since input config may have been applied before

View file

@ -6,6 +6,7 @@
#include <wayland-server-core.h>
#include <wlr/config.h>
#include <wlr/backend/headless.h>
#include <wlr/interfaces/wlr_frame_scheduler.h>
#include <wlr/render/swapchain.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_buffer.h>
@ -557,24 +558,151 @@ static void get_frame_damage(struct sway_output *output,
}
}
static int output_repaint_timer_handler(void *data) {
struct sway_output *output = data;
struct wlr_output *wlr_output = output->wlr_output;
if (wlr_output == NULL) {
return 0;
static int get_msec_until_refresh(const struct wlr_output_event_present *event) {
// Compute predicted milliseconds until the next refresh. It's used for
// delaying both output rendering and surface frame callbacks.
int msec_until_refresh = 0;
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
const long NSEC_IN_SECONDS = 1000000000;
struct timespec predicted_refresh = *event->when;
predicted_refresh.tv_nsec += event->refresh % NSEC_IN_SECONDS;
predicted_refresh.tv_sec += event->refresh / NSEC_IN_SECONDS;
if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) {
predicted_refresh.tv_sec += 1;
predicted_refresh.tv_nsec -= NSEC_IN_SECONDS;
}
wlr_output->frame_pending = false;
// If the predicted refresh time is before the current time then
// there's no point in delaying.
//
// We only check tv_sec because if the predicted refresh time is less
// than a second before the current time, then msec_until_refresh will
// end up slightly below zero, which will effectively disable the delay
// without potential disastrous negative overflows that could occur if
// tv_sec was not checked.
if (predicted_refresh.tv_sec >= now.tv_sec) {
long nsec_until_refresh
= (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS
+ (predicted_refresh.tv_nsec - now.tv_nsec);
if (!wlr_output->needs_frame &&
!output->gamma_lut_changed &&
!pixman_region32_not_empty(&output->damage_ring.current)) {
return 0;
// We want msec_until_refresh to be conservative, that is, floored.
// If we have 7.9 msec until refresh, we better compute the delay
// as if we had only 7 msec, so that we don't accidentally delay
// more than necessary and miss a frame.
msec_until_refresh = nsec_until_refresh / 1000000;
}
return msec_until_refresh;
}
static void timed_frame_scheduler_destroy(struct wlr_frame_scheduler *wlr_scheduler) {
struct sway_timer_frame_scheduler *scheduler =
wl_container_of(wlr_scheduler, scheduler, base);
if (scheduler->idle != NULL) {
wl_event_source_remove(scheduler->idle);
}
wl_event_source_remove(scheduler->timer);
free(scheduler);
}
static void timed_frame_scheduler_trigger_frame(
struct sway_timer_frame_scheduler *scheduler) {
if (!scheduler->needs_frame) {
return;
}
scheduler->needs_frame = false;
wl_signal_emit_mutable(&scheduler->base.events.frame, NULL);
}
static void timed_frame_scheduler_handle_idle(void *data) {
struct sway_timer_frame_scheduler *scheduler = data;
scheduler->idle = NULL;
timed_frame_scheduler_trigger_frame(scheduler);
}
static void timed_frame_scheduler_schedule_frame(struct wlr_frame_scheduler *wlr_scheduler) {
struct sway_timer_frame_scheduler *scheduler =
wl_container_of(wlr_scheduler, scheduler, base);
scheduler->needs_frame = true;
if (scheduler->idle != NULL || scheduler->frame_pending) {
return;
}
scheduler->idle = wl_event_loop_add_idle(scheduler->base.output->event_loop,
timed_frame_scheduler_handle_idle, scheduler);
}
static const struct wlr_frame_scheduler_impl timed_frame_scheduler_impl = {
.destroy = timed_frame_scheduler_destroy,
.schedule_frame = timed_frame_scheduler_schedule_frame,
};
static int timed_frame_scheduler_handle_timer(void *data) {
struct sway_timer_frame_scheduler *scheduler = data;
scheduler->frame_pending = false;
timed_frame_scheduler_trigger_frame(scheduler);
return 0;
}
static void timed_frame_scheduler_handle_present(struct wl_listener *listener, void *data) {
struct sway_timer_frame_scheduler *scheduler =
wl_container_of(listener, scheduler, present);
const struct wlr_output_event_present *event = data;
if (!event->presented) {
return;
}
assert(!scheduler->frame_pending);
if (scheduler->idle != NULL) {
wl_event_source_remove(scheduler->idle);
scheduler->idle = NULL;
}
int msec_until_refresh = get_msec_until_refresh(event);
int delay = msec_until_refresh - scheduler->max_render_time;
// If the delay is less than 1 millisecond (which is the least we can wait)
// then just render right away.
if (delay < 1) {
wl_signal_emit_mutable(&scheduler->base.events.frame, NULL);
} else {
scheduler->frame_pending = true;
wl_event_source_timer_update(scheduler->timer, delay);
}
}
static struct wlr_frame_scheduler *timed_frame_scheduler_create(struct wlr_output *output,
int max_render_time) {
struct sway_timer_frame_scheduler *scheduler = calloc(1, sizeof(*scheduler));
if (scheduler == NULL) {
return NULL;
}
wlr_frame_scheduler_init(&scheduler->base, &timed_frame_scheduler_impl, output);
scheduler->max_render_time = max_render_time;
scheduler->timer = wl_event_loop_add_timer(server.wl_event_loop,
timed_frame_scheduler_handle_timer, scheduler);
scheduler->present.notify = timed_frame_scheduler_handle_present;
wl_signal_add(&output->events.present, &scheduler->present);
return &scheduler->base;
}
static void output_redraw(struct sway_output *output) {
struct wlr_output *wlr_output = output->wlr_output;
if (wlr_output == NULL) {
return;
}
struct sway_workspace *workspace = output->current.active_workspace;
if (workspace == NULL) {
return 0;
return;
}
struct sway_container *fullscreen_con = root->fullscreen_global;
@ -682,7 +810,6 @@ static int output_repaint_timer_handler(void *data) {
out:
wlr_output_state_finish(&pending);
return 0;
}
static void handle_damage(struct wl_listener *listener, void *user_data) {
@ -690,7 +817,7 @@ static void handle_damage(struct wl_listener *listener, void *user_data) {
wl_container_of(listener, output, damage);
struct wlr_output_event_damage *event = user_data;
if (wlr_damage_ring_add(&output->damage_ring, event->damage)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
}
@ -701,66 +828,33 @@ static void handle_frame(struct wl_listener *listener, void *user_data) {
return;
}
// Compute predicted milliseconds until the next refresh. It's used for
// delaying both output rendering and surface frame callbacks.
int msec_until_refresh = 0;
if (output->max_render_time != 0) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
const long NSEC_IN_SECONDS = 1000000000;
struct timespec predicted_refresh = output->last_presentation;
predicted_refresh.tv_nsec += output->refresh_nsec % NSEC_IN_SECONDS;
predicted_refresh.tv_sec += output->refresh_nsec / NSEC_IN_SECONDS;
if (predicted_refresh.tv_nsec >= NSEC_IN_SECONDS) {
predicted_refresh.tv_sec += 1;
predicted_refresh.tv_nsec -= NSEC_IN_SECONDS;
}
// If the predicted refresh time is before the current time then
// there's no point in delaying.
//
// We only check tv_sec because if the predicted refresh time is less
// than a second before the current time, then msec_until_refresh will
// end up slightly below zero, which will effectively disable the delay
// without potential disastrous negative overflows that could occur if
// tv_sec was not checked.
if (predicted_refresh.tv_sec >= now.tv_sec) {
long nsec_until_refresh
= (predicted_refresh.tv_sec - now.tv_sec) * NSEC_IN_SECONDS
+ (predicted_refresh.tv_nsec - now.tv_nsec);
// We want msec_until_refresh to be conservative, that is, floored.
// If we have 7.9 msec until refresh, we better compute the delay
// as if we had only 7 msec, so that we don't accidentally delay
// more than necessary and miss a frame.
msec_until_refresh = nsec_until_refresh / 1000000;
}
}
int delay = msec_until_refresh - output->max_render_time;
// If the delay is less than 1 millisecond (which is the least we can wait)
// then just render right away.
if (delay < 1) {
output_repaint_timer_handler(output);
} else {
output->wlr_output->frame_pending = true;
wl_event_source_timer_update(output->repaint_timer, delay);
}
// Send frame done to all visible surfaces
struct send_frame_done_data data = {0};
clock_gettime(CLOCK_MONOTONIC, &data.when);
data.msec_until_refresh = msec_until_refresh;
send_frame_done(output, &data);
output_redraw(output);
}
static void handle_needs_frame(struct wl_listener *listener, void *user_data) {
struct sway_output *output =
wl_container_of(listener, output, needs_frame);
wlr_output_schedule_frame(output->wlr_output);
static void output_set_frame_scheduler(struct sway_output *output,
struct wlr_frame_scheduler *scheduler) {
wl_list_remove(&output->frame.link);
wlr_frame_scheduler_destroy(output->frame_scheduler);
output->frame_scheduler = scheduler;
output->frame.notify = handle_frame;
wl_signal_add(&output->frame_scheduler->events.frame, &output->frame);
}
void output_set_max_render_time(struct sway_output *output, int max_render_time) {
struct wlr_frame_scheduler *scheduler;
if (max_render_time != 0) {
scheduler = timed_frame_scheduler_create(output->wlr_output, max_render_time);
} else {
scheduler = wlr_frame_scheduler_autocreate(output->wlr_output);
}
if (scheduler == NULL) {
sway_log(SWAY_ERROR, "Failed to create output frame scheduler");
return;
}
output_set_frame_scheduler(output, scheduler);
}
void output_damage_whole(struct sway_output *output) {
@ -768,7 +862,7 @@ void output_damage_whole(struct sway_output *output) {
// and the transaction to evacuate it has't completed yet.
if (output != NULL && output->wlr_output != NULL) {
wlr_damage_ring_add_whole(&output->damage_ring);
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
}
@ -793,18 +887,18 @@ static void damage_surface_iterator(struct sway_output *output,
}
pixman_region32_translate(&damage, box.x, box.y);
if (wlr_damage_ring_add(&output->damage_ring, &damage)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
pixman_region32_fini(&damage);
if (whole) {
if (wlr_damage_ring_add_box(&output->damage_ring, &box)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
}
if (!wl_list_empty(&surface->current.frame_callback_list)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
}
@ -831,7 +925,7 @@ void output_damage_box(struct sway_output *output, struct wlr_box *_box) {
box.y -= output->ly;
scale_box(&box, output->wlr_output->scale);
if (wlr_damage_ring_add_box(&output->damage_ring, &box)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
}
@ -857,7 +951,7 @@ void output_damage_whole_container(struct sway_output *output,
};
scale_box(&box, output->wlr_output->scale);
if (wlr_damage_ring_add_box(&output->damage_ring, &box)) {
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
// Damage subsurfaces as well, which may extend outside the box
if (con->view) {
@ -909,7 +1003,6 @@ static void begin_destroy(struct sway_output *output) {
wl_list_remove(&output->present.link);
wl_list_remove(&output->damage.link);
wl_list_remove(&output->frame.link);
wl_list_remove(&output->needs_frame.link);
wl_list_remove(&output->request_state.link);
wlr_damage_ring_finish(&output->damage_ring);
@ -973,7 +1066,7 @@ static void handle_commit(struct wl_listener *listener, void *data) {
int width, height;
wlr_output_transformed_resolution(output->wlr_output, &width, &height);
wlr_damage_ring_set_bounds(&output->damage_ring, width, height);
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
// Next time the output is enabled, try to re-apply the gamma LUT
@ -984,14 +1077,17 @@ static void handle_commit(struct wl_listener *listener, void *data) {
static void handle_present(struct wl_listener *listener, void *data) {
struct sway_output *output = wl_container_of(listener, output, present);
struct wlr_output_event_present *output_event = data;
struct wlr_output_event_present *event = data;
if (!output->enabled || !output_event->presented) {
if (!output->enabled || !event->presented) {
return;
}
output->last_presentation = *output_event->when;
output->refresh_nsec = output_event->refresh;
// Send frame done to all visible surfaces
struct send_frame_done_data frame_done_data = {0};
clock_gettime(CLOCK_MONOTONIC, &frame_done_data.when);
frame_done_data.msec_until_refresh = get_msec_until_refresh(event);
send_frame_done(output, &frame_done_data);
}
static void handle_request_state(struct wl_listener *listener, void *data) {
@ -1056,15 +1152,11 @@ void handle_new_output(struct wl_listener *listener, void *data) {
output->present.notify = handle_present;
wl_signal_add(&wlr_output->events.damage, &output->damage);
output->damage.notify = handle_damage;
wl_signal_add(&wlr_output->events.frame, &output->frame);
output->frame.notify = handle_frame;
wl_signal_add(&wlr_output->events.needs_frame, &output->needs_frame);
output->needs_frame.notify = handle_needs_frame;
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
output->request_state.notify = handle_request_state;
output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
output_repaint_timer_handler, output);
wl_list_init(&output->frame.link);
output_set_max_render_time(output, 0);
struct output_config *oc = find_output_config(output);
apply_output_config(oc, output);
@ -1097,7 +1189,7 @@ void handle_gamma_control_set_gamma(struct wl_listener *listener, void *data) {
}
output->gamma_lut_changed = true;
wlr_output_schedule_frame(output->wlr_output);
wlr_frame_scheduler_schedule_frame(output->frame_scheduler);
}
static void output_manager_apply(struct sway_server *server,

View file

@ -3,6 +3,7 @@
#include <ctype.h>
#include <string.h>
#include <strings.h>
#include <wlr/types/wlr_frame_scheduler.h>
#include "sway/ipc-server.h"
#include "sway/layers.h"
#include "sway/output.h"
@ -240,7 +241,6 @@ void output_destroy(struct sway_output *output) {
}
list_free(output->workspaces);
list_free(output->current.workspaces);
wl_event_source_remove(output->repaint_timer);
free(output);
}