compositor: add .present event to struct wlr_surface_output

For the implementation of fifo-v1, we need to be able to track
the presentation of individual surface commits.
This is done already in `wlr_presentation_time`, but the way
that is implemented is suited only for use by clients through the
protocol interface (.feedback requests).

Here we use the `surface_output` structure, which is a low-level
structure that links wlr_surfaces with wlr_outputs.
Within this structure we listen to the surface's and the output's
.commit events. Through these 2 we link a surface commit to an output
commit. Then, a new struct wlr_surface_output_commit is created which
listens to the output's .present event.

For the interface to this functionality, 2 elements are introduced:
the new .present event in struct wlr_surface_output, which signals
listeners whether a particular pair of (surface_seq, output_seq) has
been presented or discarded, and the wlr_surface_textured_on_output()
function, used by the scene logic to inform the implementation that
the surface's buffer has been textured on a particular output.

Signed-off-by: Sergio Gómez <sergio.g.delreal@gmail.com>
This commit is contained in:
Sergio Gómez 2025-12-21 19:55:37 -05:00
parent 71e9b16235
commit bd91aed2c9
3 changed files with 138 additions and 0 deletions

View file

@ -118,14 +118,47 @@ struct wlr_surface_role {
void (*destroy)(struct wlr_surface *surface);
};
struct wlr_surface_output_event_present {
struct wlr_surface_output_commit *commit;
struct timespec when;
};
struct wlr_surface_output_commit {
struct wlr_surface_output *surface_output;
uint32_t surface_seq;
uint32_t output_seq;
bool committed_on_surface;
bool textured_on_output;
bool committed_on_output;
bool presented_on_output;
struct wl_list link; // wlr_surface_output.commits
struct {
struct wl_listener output_present;
} WLR_PRIVATE;
};
struct wlr_surface_output {
struct wlr_surface *surface;
struct wlr_output *output;
struct wl_list link; // wlr_surface.current_outputs
struct wlr_surface_output_commit current_commit;
struct wl_list commits; // wlr_surface_output_commit.link
struct {
/**
* Signals that a surface commit has been presented or discarded on the output.
*/
struct wl_signal present;
} events;
struct {
struct wl_listener bind;
struct wl_listener output_commit;
struct wl_listener surface_commit;
struct wl_listener destroy;
} WLR_PRIVATE;
};
@ -399,6 +432,12 @@ void wlr_surface_send_enter(struct wlr_surface *surface,
void wlr_surface_send_leave(struct wlr_surface *surface,
struct wlr_output *output);
/**
* Mark the current surface's buffer as textured on the given output.
*/
void wlr_surface_textured_on_output(struct wlr_surface *surface,
struct wlr_output *output);
/**
* Complete the queued frame callbacks for this surface.
*

View file

@ -141,6 +141,8 @@ static void handle_scene_buffer_output_sample(
} else {
wlr_presentation_surface_textured_on_output(surface->surface, output);
}
wlr_surface_textured_on_output(surface->surface, output);
}
static void handle_scene_buffer_frame_done(

View file

@ -1051,9 +1051,18 @@ struct wlr_surface *wlr_surface_surface_at(struct wlr_surface *surface,
static void surface_output_destroy(struct wlr_surface_output *surface_output) {
wl_list_remove(&surface_output->bind.link);
wl_list_remove(&surface_output->output_commit.link);
wl_list_remove(&surface_output->surface_commit.link);
wl_list_remove(&surface_output->destroy.link);
wl_list_remove(&surface_output->link);
struct wlr_surface_output_commit *commit, *tmp;
wl_list_for_each_safe(commit, tmp, &surface_output->commits, link) {
wl_list_remove(&commit->link);
wl_list_remove(&commit->output_present.link);
free(commit);
}
free(surface_output);
}
@ -1069,6 +1078,87 @@ static void surface_handle_output_bind(struct wl_listener *listener,
}
}
static void commit_handle_output_present(struct wl_listener *listener,
void *data) {
struct wlr_surface_output_commit *commit =
wl_container_of(listener, commit, output_present);
struct wlr_surface_output *surface_output = commit->surface_output;
struct wlr_output_event_present *output_event = data;
assert(commit->output_seq >= output_event->commit_seq);
if (commit->output_seq > output_event->commit_seq) {
return;
}
struct wlr_surface_output_event_present surface_output_event_present = {0};
surface_output_event_present.commit = commit;
surface_output_event_present.when = output_event->when;
commit->presented_on_output = output_event->presented;
wl_signal_emit_mutable(&surface_output->events.present, &surface_output_event_present);
wl_list_remove(&commit->link);
wl_list_remove(&commit->output_present.link);
free(commit);
}
static void surface_handle_output_commit(struct wl_listener *listener,
void *data) {
struct wlr_surface_output *surface_output =
wl_container_of(listener, surface_output, output_commit);
if (!surface_output->current_commit.textured_on_output) {
return;
}
struct wlr_surface_output_commit *commit = calloc(1, sizeof(*commit));
*commit = surface_output->current_commit;
commit->committed_on_output = true;
commit->output_present.notify = commit_handle_output_present;
wl_signal_add(&surface_output->output->events.present, &commit->output_present);
wl_list_insert(&surface_output->commits, &commit->link);
// reset current commit
surface_output->current_commit = (struct wlr_surface_output_commit){0};
}
void wlr_surface_textured_on_output(struct wlr_surface *surface,
struct wlr_output *output) {
struct wlr_surface_output *surface_output;
wl_list_for_each(surface_output, &surface->current_outputs, link) {
if (surface_output->output == output) {
break;
}
}
assert(surface_output->output == output);
if (!surface_output->current_commit.committed_on_surface) {
return;
}
surface_output->current_commit.textured_on_output = true;
surface_output->current_commit.output_seq = surface_output->output->commit_seq+1;
}
static void surface_handle_surface_commit(struct wl_listener *listener,
void *data) {
struct wlr_surface_output *surface_output =
wl_container_of(listener, surface_output, surface_commit);
if (wl_list_empty(&surface_output->events.present.listener_list)) {
return;
}
struct wlr_surface_output_commit *current_commit = &surface_output->current_commit;
// if a surface commit is replaced by another one before it is latched onto the output,
// it won't be presented.
if (current_commit->committed_on_surface) {
struct wlr_surface_output_event_present event_present = { .commit = current_commit };
wl_signal_emit_mutable(&surface_output->events.present, &event_present);
}
current_commit->surface_output = surface_output;
current_commit->committed_on_surface = true;
current_commit->surface_seq = surface_output->surface->current.seq;
}
static void surface_handle_output_destroy(struct wl_listener *listener,
void *data) {
struct wlr_surface_output *surface_output =
@ -1093,10 +1183,17 @@ void wlr_surface_send_enter(struct wlr_surface *surface,
return;
}
surface_output->bind.notify = surface_handle_output_bind;
surface_output->output_commit.notify = surface_handle_output_commit;
surface_output->surface_commit.notify = surface_handle_surface_commit;
surface_output->destroy.notify = surface_handle_output_destroy;
wl_signal_add(&output->events.bind, &surface_output->bind);
wl_signal_add(&output->events.commit, &surface_output->output_commit);
wl_signal_add(&output->events.destroy, &surface_output->destroy);
wl_signal_add(&surface->events.commit, &surface_output->surface_commit);
wl_signal_init(&surface_output->events.present);
wl_list_init(&surface_output->commits);
surface_output->surface = surface;
surface_output->output = output;