Skip to content
This repository has been archived by the owner on Nov 1, 2021. It is now read-only.

Add explicit synchronization, with timelines #3282

Draft
wants to merge 10 commits into
base: master
Choose a base branch
from
54 changes: 54 additions & 0 deletions backend/drm/atomic.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include <stdlib.h>
#include <unistd.h>
#include <wlr/render/timeline.h>
#include <wlr/util/log.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
Expand Down Expand Up @@ -163,6 +165,41 @@ static void set_plane_props(struct atomic *atom, struct wlr_drm_backend *drm,
atom->failed = true;
}

static int set_plane_in_fence_fd(struct atomic *atom, struct wlr_drm_plane *plane,
struct wlr_render_timeline *timeline, uint64_t src_point) {
if (!plane->props.in_fence_fd) {
wlr_log(WLR_ERROR, "Missing IN_FENCE_FD property");
goto error;
}

int sync_file_fd = wlr_render_timeline_export_sync_file(timeline, src_point);
if (sync_file_fd < 0) {
goto error;
}

atomic_add(atom, plane->id, plane->props.in_fence_fd, sync_file_fd);

return sync_file_fd;

error:
wlr_log(WLR_ERROR, "Failed to set plane %"PRIu32" IN_FENCE_FD", plane->id);
atom->failed = true;
return -1;
}

static void set_crtc_out_fence_ptr(struct atomic *atom, struct wlr_drm_crtc *crtc,
int *fd_ptr) {
if (!crtc->props.out_fence_ptr) {
wlr_log(WLR_ERROR,
"CRTC %"PRIu32" is missing the OUT_FENCE_PTR property",
crtc->id);
atom->failed = true;
return;
}

atomic_add(atom, crtc->id, crtc->props.out_fence_ptr, (uintptr_t)fd_ptr);
}

static bool atomic_crtc_commit(struct wlr_drm_connector *conn,
const struct wlr_drm_connector_state *state, uint32_t flags,
bool test_only) {
Expand Down Expand Up @@ -228,6 +265,8 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn,
flags |= DRM_MODE_ATOMIC_NONBLOCK;
}

int in_fence_fd = -1, out_fence_fd = -1;

struct atomic atom;
atomic_begin(&atom);
atomic_add(&atom, conn->id, conn->props.crtc_id, active ? crtc->id : 0);
Expand All @@ -249,6 +288,13 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn,
atomic_add(&atom, crtc->primary->id,
crtc->primary->props.fb_damage_clips, fb_damage_clips);
}
if (state->base->committed & WLR_OUTPUT_STATE_WAIT_TIMELINE) {
in_fence_fd = set_plane_in_fence_fd(&atom, crtc->primary,
state->base->wait_timeline, state->base->wait_point);
}
if (state->base->committed & WLR_OUTPUT_STATE_SIGNAL_TIMELINE) {
set_crtc_out_fence_ptr(&atom, crtc, &out_fence_fd);
}
if (crtc->cursor) {
if (drm_connector_is_cursor_visible(conn)) {
set_plane_props(&atom, drm, crtc->cursor, crtc->id,
Expand Down Expand Up @@ -288,6 +334,14 @@ static bool atomic_crtc_commit(struct wlr_drm_connector *conn,
wlr_log_errno(WLR_ERROR, "Failed to destroy FB_DAMAGE_CLIPS property blob");
}

close(in_fence_fd);

if (out_fence_fd > 0) {
ok = wlr_render_timeline_import_sync_file(state->base->signal_timeline,
state->base->signal_point, out_fence_fd);
close(out_fence_fd);
}

return ok;
}

Expand Down
11 changes: 10 additions & 1 deletion backend/drm/drm.c
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ static const uint32_t SUPPORTED_OUTPUT_STATE =
WLR_OUTPUT_STATE_BUFFER |
WLR_OUTPUT_STATE_MODE |
WLR_OUTPUT_STATE_ENABLED |
WLR_OUTPUT_STATE_GAMMA_LUT;
WLR_OUTPUT_STATE_GAMMA_LUT |
WLR_OUTPUT_STATE_WAIT_TIMELINE |
WLR_OUTPUT_STATE_SIGNAL_TIMELINE;

bool check_drm_features(struct wlr_drm_backend *drm) {
if (drmGetCap(drm->fd, DRM_CAP_CURSOR_WIDTH, &drm->cursor_width)) {
Expand Down Expand Up @@ -491,6 +493,13 @@ static bool drm_connector_test(struct wlr_output *output) {
}
}

if ((output->pending.committed & (WLR_OUTPUT_STATE_WAIT_TIMELINE |
WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) && conn->backend->parent) {
wlr_drm_conn_log(conn, WLR_DEBUG,
"Sync timelines are unsupported in multi-GPU mode");
return false;
}

struct wlr_drm_connector_state pending = {0};
drm_connector_state_init(&pending, conn, &output->pending);

Expand Down
7 changes: 7 additions & 0 deletions backend/drm/legacy.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,13 @@ static bool legacy_crtc_test(struct wlr_drm_connector *conn,
}
}

if (state->base->committed & (WLR_OUTPUT_STATE_WAIT_TIMELINE |
WLR_OUTPUT_STATE_SIGNAL_TIMELINE)) {
wlr_drm_conn_log(conn, WLR_DEBUG,
"Sync timelines are unsupported with legacy KMS interface");
return false;
}

return true;
}

Expand Down
2 changes: 2 additions & 0 deletions backend/drm/properties.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ static const struct prop_info crtc_info[] = {
{ "GAMMA_LUT", INDEX(gamma_lut) },
{ "GAMMA_LUT_SIZE", INDEX(gamma_lut_size) },
{ "MODE_ID", INDEX(mode_id) },
{ "OUT_FENCE_PTR", INDEX(out_fence_ptr) },
{ "VRR_ENABLED", INDEX(vrr_enabled) },
#undef INDEX
};
Expand All @@ -51,6 +52,7 @@ static const struct prop_info plane_info[] = {
{ "CRTC_Y", INDEX(crtc_y) },
{ "FB_DAMAGE_CLIPS", INDEX(fb_damage_clips) },
{ "FB_ID", INDEX(fb_id) },
{ "IN_FENCE_FD", INDEX(in_fence_fd) },
{ "IN_FORMATS", INDEX(in_formats) },
{ "SRC_H", INDEX(src_h) },
{ "SRC_W", INDEX(src_w) },
Expand Down
254 changes: 254 additions & 0 deletions examples/explicit-sync.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#define _POSIX_C_SOURCE 200112L
#include <assert.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/render/timeline.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_compositor.h>
#include <wlr/types/wlr_linux_explicit_synchronization_v1.h>
#include <wlr/types/wlr_output.h>
#include <wlr/types/wlr_surface.h>
#include <wlr/types/wlr_xdg_shell.h>
#include <wlr/util/log.h>

/* Simple compositor with explicit synchronization support. Input is
* unimplemented.
*
* New surfaces are stacked on top of the existing ones as they appear. */

struct server {
struct wl_display *display;
struct wlr_backend *backend;
struct wlr_renderer *renderer;
struct wlr_linux_explicit_synchronization_v1 *explicit_sync_v1;

struct wl_list outputs;
struct wl_list surfaces;

struct wl_listener new_output;
struct wl_listener new_surface;
};

struct surface {
struct wlr_surface *wlr;
struct wl_list link;

struct wlr_render_timeline *timeline;

struct wl_listener destroy;
};

struct output {
struct wl_list link;
struct server *server;
struct wlr_output *wlr;

struct wlr_render_timeline *in_timeline, *out_timeline;

struct wl_listener frame;
};

static void output_handle_frame(struct wl_listener *listener, void *data) {
struct output *output = wl_container_of(listener, output, frame);
struct wlr_renderer *renderer = output->server->renderer;

if (!wlr_output_attach_render(output->wlr, NULL)) {
return;
}

wlr_renderer_begin(renderer, output->wlr->width, output->wlr->height);
wlr_renderer_clear(renderer, (float[]){0.25f, 0.25f, 0.25f, 1});

struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);

int pos = 0;
struct surface *surface;
wl_list_for_each(surface, &output->server->surfaces, link) {
pos += 50;

struct wlr_texture *texture = wlr_surface_get_texture(surface->wlr);
if (texture == NULL) {
continue;
}

uint64_t surface_point = surface->wlr->current.seq;
if (!wlr_linux_explicit_synchronization_v1_signal_surface_timeline(
output->server->explicit_sync_v1, surface->wlr,
surface->timeline, surface_point)) {
wlr_log(WLR_ERROR, "Failed to signal surface timeline");
continue;
}
if (!wlr_renderer_wait_timeline(renderer,
surface->timeline, surface_point)) {
wlr_log(WLR_ERROR, "Failed to wait for surface timeline");
continue;
}

wlr_render_texture(renderer, texture, output->wlr->transform_matrix,
pos, pos, 1.0);

wlr_surface_send_frame_done(surface->wlr, &now);
}

uint64_t output_point = output->wlr->commit_seq;
if (!wlr_renderer_signal_timeline(renderer, output->in_timeline,
output_point)) {
wlr_log(WLR_ERROR, "Failed to signal renderer timeline");
return;
}

wlr_renderer_end(renderer);

wlr_output_set_wait_timeline(output->wlr,
output->in_timeline, output_point);
wlr_output_set_signal_timeline(output->wlr,
output->out_timeline, output_point);

if (!wlr_output_commit(output->wlr)) {
wlr_log(WLR_ERROR, "Failed to commit output");
return;
}

wl_list_for_each(surface, &output->server->surfaces, link) {
if (!wlr_linux_explicit_synchronization_v1_wait_surface_timeline(
output->server->explicit_sync_v1, surface->wlr,
output->out_timeline, output_point)) {
wlr_log(WLR_ERROR, "Failed to wait for surface timeline");
}
}
}

static void server_handle_new_output(struct wl_listener *listener, void *data) {
struct server *server = wl_container_of(listener, server, new_output);
struct wlr_output *wlr_output = data;

int drm_fd = wlr_renderer_get_drm_fd(server->renderer);
struct wlr_render_timeline *in_timeline = wlr_render_timeline_create(drm_fd);
struct wlr_render_timeline *out_timeline = wlr_render_timeline_create(drm_fd);
if (in_timeline == NULL || out_timeline == NULL) {
return;
}

struct output *output =
calloc(1, sizeof(struct output));
output->wlr = wlr_output;
output->server = server;
output->in_timeline = in_timeline;
output->out_timeline = out_timeline;
output->frame.notify = output_handle_frame;
wl_signal_add(&wlr_output->events.frame, &output->frame);
wl_list_insert(&server->outputs, &output->link);

if (!wl_list_empty(&wlr_output->modes)) {
struct wlr_output_mode *mode = wlr_output_preferred_mode(wlr_output);
wlr_output_set_mode(wlr_output, mode);
wlr_output_commit(wlr_output);
}

wlr_output_create_global(wlr_output);
}

static void surface_handle_destroy(struct wl_listener *listener, void *data) {
struct surface *surface = wl_container_of(listener, surface, destroy);
wlr_render_timeline_destroy(surface->timeline);
wl_list_remove(&surface->destroy.link);
wl_list_remove(&surface->link);
free(surface);
}

static void server_handle_new_surface(struct wl_listener *listener,
void *data) {
struct server *server = wl_container_of(listener, server, new_surface);
struct wlr_surface *wlr_surface = data;

int drm_fd = wlr_renderer_get_drm_fd(server->renderer);
struct wlr_render_timeline *timeline = wlr_render_timeline_create(drm_fd);
if (timeline == NULL) {
return;
}

struct surface *surface = calloc(1, sizeof(struct surface));
surface->wlr = wlr_surface;
surface->timeline = timeline;
surface->destroy.notify = surface_handle_destroy;
wl_signal_add(&wlr_surface->events.destroy, &surface->destroy);

wl_list_insert(&server->surfaces, &surface->link);
}

int main(int argc, char *argv[]) {
wlr_log_init(WLR_DEBUG, NULL);

const char *startup_cmd = NULL;
int c;
while ((c = getopt(argc, argv, "s:")) != -1) {
switch (c) {
case 's':
startup_cmd = optarg;
break;
default:
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}
}
if (optind < argc) {
printf("usage: %s [-s startup-command]\n", argv[0]);
return EXIT_FAILURE;
}

struct server server = {0};
server.display = wl_display_create();
server.backend = wlr_backend_autocreate(server.display);
server.renderer = wlr_backend_get_renderer(server.backend);
wlr_renderer_init_wl_display(server.renderer, server.display);

struct wlr_compositor *compositor =
wlr_compositor_create(server.display, server.renderer);

wlr_xdg_shell_create(server.display);

server.explicit_sync_v1 =
wlr_linux_explicit_synchronization_v1_create(server.display);

wl_list_init(&server.outputs);
wl_list_init(&server.surfaces);

server.new_output.notify = server_handle_new_output;
wl_signal_add(&server.backend->events.new_output, &server.new_output);

server.new_surface.notify = server_handle_new_surface;
wl_signal_add(&compositor->events.new_surface, &server.new_surface);

const char *socket = wl_display_add_socket_auto(server.display);
if (!socket) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}

if (!wlr_backend_start(server.backend)) {
wl_display_destroy(server.display);
return EXIT_FAILURE;
}

setenv("WAYLAND_DISPLAY", socket, true);
if (startup_cmd != NULL) {
if (fork() == 0) {
execl("/bin/sh", "/bin/sh", "-c", startup_cmd, (void *)NULL);
}
}

wlr_log(WLR_INFO, "Running Wayland compositor on WAYLAND_DISPLAY=%s",
socket);
wl_display_run(server.display);

wl_display_destroy_clients(server.display);
wl_display_destroy(server.display);
return EXIT_SUCCESS;
}
Loading