mirror of
https://github.com/swaywm/sway.git
synced 2024-11-28 02:41:28 +00:00
Implement output max_render_time via wlr_frame_scheduler
This commit is contained in:
parent
25bc05a3d7
commit
31c891dddb
|
@ -4,6 +4,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <wayland-server-core.h>
|
#include <wayland-server-core.h>
|
||||||
#include <wlr/types/wlr_damage_ring.h>
|
#include <wlr/types/wlr_damage_ring.h>
|
||||||
|
#include <wlr/types/wlr_frame_scheduler.h>
|
||||||
#include <wlr/types/wlr_output.h>
|
#include <wlr/types/wlr_output.h>
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "sway/tree/node.h"
|
#include "sway/tree/node.h"
|
||||||
|
@ -52,10 +53,7 @@ struct sway_output {
|
||||||
struct wl_signal disable;
|
struct wl_signal disable;
|
||||||
} events;
|
} events;
|
||||||
|
|
||||||
struct timespec last_presentation;
|
|
||||||
uint32_t refresh_nsec;
|
|
||||||
int max_render_time; // In milliseconds
|
int max_render_time; // In milliseconds
|
||||||
struct wl_event_source *repaint_timer;
|
|
||||||
bool gamma_lut_changed;
|
bool gamma_lut_changed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -73,6 +71,21 @@ struct render_context {
|
||||||
struct wlr_render_pass *pass;
|
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);
|
struct sway_output *output_create(struct wlr_output *wlr_output);
|
||||||
|
|
||||||
void output_destroy(struct sway_output *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_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);
|
bool output_has_opaque_overlay_layer_surface(struct sway_output *output);
|
||||||
|
|
||||||
struct sway_workspace *output_get_active_workspace(struct sway_output *output);
|
struct sway_workspace *output_get_active_workspace(struct sway_output *output);
|
||||||
|
|
|
@ -584,7 +584,7 @@ bool apply_output_config(struct output_config *oc, struct sway_output *output) {
|
||||||
if (oc && oc->max_render_time >= 0) {
|
if (oc && oc->max_render_time >= 0) {
|
||||||
sway_log(SWAY_DEBUG, "Set %s max render time to %d",
|
sway_log(SWAY_DEBUG, "Set %s max render time to %d",
|
||||||
oc->name, oc->max_render_time);
|
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
|
// Reconfigure all devices, since input config may have been applied before
|
||||||
|
|
|
@ -6,10 +6,10 @@
|
||||||
#include <wayland-server-core.h>
|
#include <wayland-server-core.h>
|
||||||
#include <wlr/config.h>
|
#include <wlr/config.h>
|
||||||
#include <wlr/backend/headless.h>
|
#include <wlr/backend/headless.h>
|
||||||
|
#include <wlr/interfaces/wlr_frame_scheduler.h>
|
||||||
#include <wlr/render/swapchain.h>
|
#include <wlr/render/swapchain.h>
|
||||||
#include <wlr/render/wlr_renderer.h>
|
#include <wlr/render/wlr_renderer.h>
|
||||||
#include <wlr/types/wlr_buffer.h>
|
#include <wlr/types/wlr_buffer.h>
|
||||||
#include <wlr/types/wlr_frame_scheduler.h>
|
|
||||||
#include <wlr/types/wlr_gamma_control_v1.h>
|
#include <wlr/types/wlr_gamma_control_v1.h>
|
||||||
#include <wlr/types/wlr_matrix.h>
|
#include <wlr/types/wlr_matrix.h>
|
||||||
#include <wlr/types/wlr_output_layout.h>
|
#include <wlr/types/wlr_output_layout.h>
|
||||||
|
@ -558,16 +558,151 @@ static void get_frame_damage(struct sway_output *output,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int output_repaint_timer_handler(void *data) {
|
static int get_msec_until_refresh(const struct wlr_output_event_present *event) {
|
||||||
struct sway_output *output = data;
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
struct wlr_output *wlr_output = output->wlr_output;
|
||||||
if (wlr_output == NULL) {
|
if (wlr_output == NULL) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sway_workspace *workspace = output->current.active_workspace;
|
struct sway_workspace *workspace = output->current.active_workspace;
|
||||||
if (workspace == NULL) {
|
if (workspace == NULL) {
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct sway_container *fullscreen_con = root->fullscreen_global;
|
struct sway_container *fullscreen_con = root->fullscreen_global;
|
||||||
|
@ -675,7 +810,6 @@ static int output_repaint_timer_handler(void *data) {
|
||||||
|
|
||||||
out:
|
out:
|
||||||
wlr_output_state_finish(&pending);
|
wlr_output_state_finish(&pending);
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void handle_damage(struct wl_listener *listener, void *user_data) {
|
static void handle_damage(struct wl_listener *listener, void *user_data) {
|
||||||
|
@ -694,59 +828,33 @@ static void handle_frame(struct wl_listener *listener, void *user_data) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute predicted milliseconds until the next refresh. It's used for
|
output_redraw(output);
|
||||||
// delaying both output rendering and surface frame callbacks.
|
}
|
||||||
int msec_until_refresh = 0;
|
|
||||||
|
|
||||||
if (output->max_render_time != 0) {
|
static void output_set_frame_scheduler(struct sway_output *output,
|
||||||
struct timespec now;
|
struct wlr_frame_scheduler *scheduler) {
|
||||||
clock_gettime(CLOCK_MONOTONIC, &now);
|
wl_list_remove(&output->frame.link);
|
||||||
|
|
||||||
const long NSEC_IN_SECONDS = 1000000000;
|
wlr_frame_scheduler_destroy(output->frame_scheduler);
|
||||||
struct timespec predicted_refresh = output->last_presentation;
|
output->frame_scheduler = scheduler;
|
||||||
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
|
output->frame.notify = handle_frame;
|
||||||
// there's no point in delaying.
|
wl_signal_add(&output->frame_scheduler->events.frame, &output->frame);
|
||||||
//
|
}
|
||||||
// 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.
|
void output_set_max_render_time(struct sway_output *output, int max_render_time) {
|
||||||
// If we have 7.9 msec until refresh, we better compute the delay
|
struct wlr_frame_scheduler *scheduler;
|
||||||
// as if we had only 7 msec, so that we don't accidentally delay
|
if (max_render_time != 0) {
|
||||||
// more than necessary and miss a frame.
|
scheduler = timed_frame_scheduler_create(output->wlr_output, max_render_time);
|
||||||
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 {
|
} else {
|
||||||
wl_event_source_timer_update(output->repaint_timer, delay);
|
scheduler = wlr_frame_scheduler_autocreate(output->wlr_output);
|
||||||
|
}
|
||||||
|
if (scheduler == NULL) {
|
||||||
|
sway_log(SWAY_ERROR, "Failed to create output frame scheduler");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send frame done to all visible surfaces
|
output_set_frame_scheduler(output, scheduler);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void output_damage_whole(struct sway_output *output) {
|
void output_damage_whole(struct sway_output *output) {
|
||||||
|
@ -969,14 +1077,17 @@ static void handle_commit(struct wl_listener *listener, void *data) {
|
||||||
|
|
||||||
static void handle_present(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 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
output->last_presentation = *output_event->when;
|
// Send frame done to all visible surfaces
|
||||||
output->refresh_nsec = output_event->refresh;
|
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) {
|
static void handle_request_state(struct wl_listener *listener, void *data) {
|
||||||
|
@ -1041,13 +1152,11 @@ void handle_new_output(struct wl_listener *listener, void *data) {
|
||||||
output->present.notify = handle_present;
|
output->present.notify = handle_present;
|
||||||
wl_signal_add(&wlr_output->events.damage, &output->damage);
|
wl_signal_add(&wlr_output->events.damage, &output->damage);
|
||||||
output->damage.notify = handle_damage;
|
output->damage.notify = handle_damage;
|
||||||
wl_signal_add(&output->frame_scheduler->events.frame, &output->frame);
|
|
||||||
output->frame.notify = handle_frame;
|
|
||||||
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
|
wl_signal_add(&wlr_output->events.request_state, &output->request_state);
|
||||||
output->request_state.notify = handle_request_state;
|
output->request_state.notify = handle_request_state;
|
||||||
|
|
||||||
output->repaint_timer = wl_event_loop_add_timer(server->wl_event_loop,
|
wl_list_init(&output->frame.link);
|
||||||
output_repaint_timer_handler, output);
|
output_set_max_render_time(output, 0);
|
||||||
|
|
||||||
struct output_config *oc = find_output_config(output);
|
struct output_config *oc = find_output_config(output);
|
||||||
apply_output_config(oc, output);
|
apply_output_config(oc, output);
|
||||||
|
|
|
@ -95,7 +95,6 @@ struct sway_output *output_create(struct wlr_output *wlr_output) {
|
||||||
wlr_output->data = output;
|
wlr_output->data = output;
|
||||||
output->detected_subpixel = wlr_output->subpixel;
|
output->detected_subpixel = wlr_output->subpixel;
|
||||||
output->scale_filter = SCALE_FILTER_NEAREST;
|
output->scale_filter = SCALE_FILTER_NEAREST;
|
||||||
output->frame_scheduler = wlr_frame_scheduler_autocreate(wlr_output);
|
|
||||||
|
|
||||||
wl_signal_init(&output->events.disable);
|
wl_signal_init(&output->events.disable);
|
||||||
|
|
||||||
|
@ -242,7 +241,6 @@ void output_destroy(struct sway_output *output) {
|
||||||
}
|
}
|
||||||
list_free(output->workspaces);
|
list_free(output->workspaces);
|
||||||
list_free(output->current.workspaces);
|
list_free(output->current.workspaces);
|
||||||
wl_event_source_remove(output->repaint_timer);
|
|
||||||
free(output);
|
free(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue