diff --git a/backend/wayland/backend.c b/backend/wayland/backend.c index 8de63acf7e..2c30689ca5 100644 --- a/backend/wayland/backend.c +++ b/backend/wayland/backend.c @@ -102,6 +102,9 @@ static void registry_global(void *data, struct wl_registry *registry, if (strcmp(iface, wl_compositor_interface.name) == 0) { wl->compositor = wl_registry_bind(registry, name, &wl_compositor_interface, 4); + } else if (strcmp(iface, wl_subcompositor_interface.name) == 0) { + wl->subcompositor = wl_registry_bind(registry, name, + &wl_subcompositor_interface, 1); } else if (strcmp(iface, wl_seat_interface.name) == 0) { wl->seat = wl_registry_bind(registry, name, &wl_seat_interface, 5); diff --git a/backend/wayland/output.c b/backend/wayland/output.c index 077aa16d3f..bed2a0f6c0 100644 --- a/backend/wayland/output.c +++ b/backend/wayland/output.c @@ -145,11 +145,15 @@ static bool test_buffer(struct wlr_wl_backend *wl, static struct wlr_wl_buffer *create_wl_buffer(struct wlr_wl_backend *wl, struct wlr_buffer *wlr_buffer) { if (!test_buffer(wl, wlr_buffer)) { + wlr_log(WLR_ERROR, "Failed to create remote wl_buffer: " + "buffer test failed"); return NULL; } struct wlr_dmabuf_attributes attribs; if (!wlr_buffer_get_dmabuf(wlr_buffer, &attribs)) { + wlr_log(WLR_ERROR, "Failed to create remote wl_buffer: " + "cannot get DMA-BUF attributes"); return NULL; } @@ -189,6 +193,62 @@ static struct wlr_wl_buffer *create_wl_buffer(struct wlr_wl_backend *wl, return buffer; } +static void output_layer_pending_box(struct wlr_wl_output_layer *layer, + struct wlr_box *out) { + out->x = layer->base.current.x; + out->y = layer->base.current.y; + if (layer->base.pending.committed & WLR_OUTPUT_LAYER_STATE_POSITION) { + out->x = layer->base.pending.x; + out->y = layer->base.pending.y; + } + + struct wlr_buffer *wlr_buffer = layer->base.current.buffer; + if (layer->base.pending.committed & WLR_OUTPUT_LAYER_STATE_BUFFER) { + wlr_buffer = layer->base.pending.buffer; + } + + out->width = out->height = 0; + if (wlr_buffer != NULL) { + out->width = wlr_buffer->width; + out->height = wlr_buffer->height; + } +} + +/* Update the output layers' "accepted" flag */ +static void output_test_layers(struct wlr_wl_output *output) { + struct wlr_output *wlr_output = &output->wlr_output; + + // Iterate over layers from top to bottom. Reject layers that are under a + // rejected layer. + pixman_region32_t rejected_region; + pixman_region32_init(&rejected_region); + pixman_region32_t intersect; + pixman_region32_init(&intersect); + struct wlr_wl_output_layer *layer; + wl_list_for_each_reverse(layer, &wlr_output->pending.layers, + base.pending.link) { + struct wlr_box box = {0}; + output_layer_pending_box(layer, &box); + + pixman_region32_intersect_rect(&intersect, &rejected_region, + box.x, box.y, box.width, box.height); + bool accepted = !pixman_region32_not_empty(&intersect); + + if ((layer->base.pending.committed & WLR_OUTPUT_LAYER_STATE_BUFFER) && + accepted) { + accepted = test_buffer(output->backend, layer->base.pending.buffer); + } + + if (!accepted) { + pixman_region32_union_rect(&rejected_region, &rejected_region, + box.x, box.y, box.width, box.height); + } + layer->base.accepted = accepted; + } + pixman_region32_fini(&intersect); + pixman_region32_fini(&rejected_region); +} + static bool output_test(struct wlr_output *wlr_output) { struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); @@ -208,6 +268,28 @@ static bool output_test(struct wlr_output *wlr_output) { return false; } + output_test_layers(output); + + return true; +} + +static bool output_layer_attach(struct wlr_wl_output_layer *layer, + struct wlr_buffer *wlr_buffer) { + struct wlr_wl_output *output = + get_wl_output_from_output(layer->base.output); + + if (wlr_buffer == NULL) { + wl_surface_attach(layer->surface, NULL, 0, 0); + return true; + } + + struct wlr_wl_buffer *buffer = + create_wl_buffer(output->backend, wlr_buffer); + if (buffer == NULL) { + return false; + } + + wl_surface_attach(layer->surface, buffer->wl_buffer, 0, 0); return true; } @@ -228,6 +310,47 @@ static bool output_commit(struct wlr_output *wlr_output) { } } + // Update layer ordering + if (wlr_output->pending.committed & WLR_OUTPUT_STATE_LAYERS) { + struct wlr_wl_output_layer *layer, *prev = NULL; + wl_list_for_each(layer, &wlr_output->pending.layers, base.pending.link) { + if (prev != NULL) { + wl_subsurface_place_above(layer->subsurface, prev->surface); + } + + prev = layer; + } + } + + output_test_layers(output); + + struct wlr_wl_output_layer *layer; + wl_list_for_each(layer, &wlr_output->pending.layers, base.pending.link) { + if (!layer->base.accepted && layer->prev_accepted) { + wl_surface_attach(layer->surface, NULL, 0, 0); + } + + if ((layer->base.pending.committed & WLR_OUTPUT_LAYER_STATE_BUFFER) && + layer->base.accepted) { + if (!output_layer_attach(layer, layer->base.pending.buffer)) { + return false; + } + } + + if (layer->base.pending.committed & WLR_OUTPUT_LAYER_STATE_POSITION) { + wl_subsurface_set_position(layer->subsurface, + layer->base.pending.x, layer->base.pending.y); + } + + if (layer->base.pending.committed != 0 || + layer->base.accepted != layer->prev_accepted) { + // TODO: make sure to commit the parent surface too + wl_surface_commit(layer->surface); + } + + layer->prev_accepted = layer->base.accepted; + } + if (wlr_output->pending.committed & WLR_OUTPUT_STATE_BUFFER) { struct wp_presentation_feedback *wp_feedback = NULL; if (output->backend->presentation != NULL) { @@ -435,6 +558,36 @@ static bool output_move_cursor(struct wlr_output *_output, int x, int y) { return true; } +static struct wlr_output_layer *output_create_layer( + struct wlr_output *wlr_output) { + struct wlr_wl_output *output = get_wl_output_from_output(wlr_output); + struct wlr_wl_backend *backend = output->backend; + + struct wlr_wl_output_layer *layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + wlr_output_layer_init(&layer->base, wlr_output); + layer->surface = wl_compositor_create_surface(backend->compositor); + layer->subsurface = wl_subcompositor_get_subsurface(backend->subcompositor, + layer->surface, output->surface); + + struct wl_region *empty_region = + wl_compositor_create_region(backend->compositor); + wl_surface_set_input_region(layer->surface, empty_region); + wl_region_destroy(empty_region); + + return &layer->base; +} + +static void output_destroy_layer(struct wlr_output_layer *wlr_layer) { + struct wlr_wl_output_layer *layer = + (struct wlr_wl_output_layer *)wlr_layer; + wl_subsurface_destroy(layer->subsurface); + wl_surface_destroy(layer->surface); + free(layer); +} + static const struct wlr_output_impl output_impl = { .destroy = output_destroy, .attach_render = output_attach_render, @@ -443,6 +596,8 @@ static const struct wlr_output_impl output_impl = { .rollback = output_rollback, .set_cursor = output_set_cursor, .move_cursor = output_move_cursor, + .create_layer = output_create_layer, + .destroy_layer = output_destroy_layer, }; bool wlr_output_is_wl(struct wlr_output *wlr_output) { diff --git a/examples/meson.build b/examples/meson.build index 7a64da464f..186608b291 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -44,6 +44,14 @@ compositors = { 'src': 'fullscreen-shell.c', 'proto': ['fullscreen-shell-unstable-v1'], }, + 'output-layers': { + 'src': 'output-layers.c', + 'proto': ['xdg-shell'], + }, + 'scene-graph': { + 'src': 'scene-graph.c', + 'proto': ['xdg-shell'], + }, } clients = { diff --git a/examples/output-layers.c b/examples/output-layers.c new file mode 100644 index 0000000000..5daff7f677 --- /dev/null +++ b/examples/output-layers.c @@ -0,0 +1,261 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple compositor making use of the output layers API. The compositor will + * attempt to display client surfaces with output layers. Input is + * unimplemented. + * + * New surfaces are stacked on top of the existing ones as they appear. + * Surfaces that don't make it into an output layer are rendered as usual. */ + +struct server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_renderer *renderer; + + struct wl_list outputs; + + struct wl_listener new_output; + struct wl_listener new_surface; +}; + +struct output_surface { + struct wlr_surface *wlr_surface; + struct wlr_output_layer *layer; + struct wl_list link; + + int x, y; + + bool first_commit, prev_layer_accepted; + + struct wl_listener destroy; + struct wl_listener commit; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr_output; + struct wl_list surfaces; + + 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_test(output->wlr_output)) { + return; + } + + if (!wlr_output_attach_render(output->wlr_output, NULL)) { + return; + } + + int width, height; + wlr_output_effective_resolution(output->wlr_output, &width, &height); + wlr_renderer_begin(renderer, width, height); + wlr_renderer_clear(renderer, (float[4]){ 0.3, 0.3, 0.3, 1.0 }); + + struct output_surface *output_surface; + wl_list_for_each(output_surface, &output->surfaces, link) { + struct wlr_surface *wlr_surface = output_surface->wlr_surface; + + if (wlr_surface->buffer == NULL || output_surface->layer->accepted) { + continue; + } + + struct wlr_texture *texture = wlr_surface_get_texture(wlr_surface); + if (texture == NULL) { + continue; + } + + wlr_render_texture(renderer, texture, output->wlr_output->transform_matrix, + output_surface->x, output_surface->y, 1.0); + } + + wlr_renderer_end(renderer); + + wlr_output_commit(output->wlr_output); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + wl_list_for_each(output_surface, &output->surfaces, link) { + wlr_surface_send_frame_done(output_surface->wlr_surface, &now); + + if (output_surface->wlr_surface->buffer == NULL) { + continue; + } + + if ((output_surface->first_commit || + !output_surface->prev_layer_accepted) && + output_surface->layer->accepted) { + wlr_log(WLR_DEBUG, "Scanning out wlr_surface %p on output '%s'", + output_surface->wlr_surface, output->wlr_output->name); + } + if ((output_surface->first_commit || + output_surface->prev_layer_accepted) && + !output_surface->layer->accepted) { + wlr_log(WLR_DEBUG, "Cannot scan out wlr_surface %p on output '%s'", + output_surface->wlr_surface, output->wlr_output->name); + } + output_surface->prev_layer_accepted = output_surface->layer->accepted; + output_surface->first_commit = false; + } +} + +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; + + struct output *output = + calloc(1, sizeof(struct output)); + output->wlr_output = wlr_output; + output->server = server; + wl_list_init(&output->surfaces); + 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 = + wl_container_of(wlr_output->modes.prev, mode, link); + wlr_output_set_mode(wlr_output, mode); + wlr_output_commit(wlr_output); + } + + wlr_output_create_global(wlr_output); +} + +static void output_surface_handle_destroy(struct wl_listener *listener, + void *data) { + struct output_surface *output_surface = + wl_container_of(listener, output_surface, destroy); + wlr_output_layer_remove(output_surface->layer); + wl_list_remove(&output_surface->destroy.link); + wl_list_remove(&output_surface->commit.link); + wl_list_remove(&output_surface->link); + free(output_surface); +} + +static void output_surface_handle_commit(struct wl_listener *listener, + void *data) { + struct output_surface *output_surface = + wl_container_of(listener, output_surface, commit); + struct wlr_buffer *buffer = NULL; + if (output_surface->wlr_surface->buffer != NULL) { + buffer = &output_surface->wlr_surface->buffer->base; + } + wlr_output_layer_attach_buffer(output_surface->layer, buffer); +} + +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; + + struct output *output; + wl_list_for_each(output, &server->outputs, link) { + struct output_surface *output_surface = + calloc(1, sizeof(struct output_surface)); + output_surface->wlr_surface = wlr_surface; + output_surface->destroy.notify = output_surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &output_surface->destroy); + output_surface->commit.notify = output_surface_handle_commit; + wl_signal_add(&wlr_surface->events.commit, &output_surface->commit); + + output_surface->layer = wlr_output_layer_create(output->wlr_output); + int pos = 50 * wl_list_length(&output->surfaces); + wlr_output_layer_move(output_surface->layer, pos, pos); + + output_surface->x = output_surface->y = pos; + output_surface->first_commit = true; + + wl_list_insert(output->surfaces.prev, &output_surface->link); + } +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + 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.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(server.wl_display, NULL); + server.renderer = wlr_backend_get_renderer(server.backend); + wlr_renderer_init_wl_display(server.renderer, server.wl_display); + + struct wlr_compositor *compositor = + wlr_compositor_create(server.wl_display, server.renderer); + + wlr_xdg_shell_create(server.wl_display); + + wl_list_init(&server.outputs); + + 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.wl_display); + if (!socket) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + setenv("WAYLAND_DISPLAY", socket, true); + setenv("WAYLAND_DEBUG", "", 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.wl_display); + + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return EXIT_SUCCESS; +} diff --git a/examples/scene-graph.c b/examples/scene-graph.c new file mode 100644 index 0000000000..b2e1e8a7c1 --- /dev/null +++ b/examples/scene-graph.c @@ -0,0 +1,181 @@ +#define _POSIX_C_SOURCE 200112L +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Simple compositor making use of the scene-graph API. Input is unimplemented. + * + * New surfaces are stacked on top of the existing ones as they appear. */ + +struct server { + struct wl_display *wl_display; + struct wlr_backend *backend; + struct wlr_scene *scene; + + struct wl_list outputs; + struct wl_list surfaces; + + struct wl_listener new_output; + struct wl_listener new_surface; +}; + +struct surface { + struct wlr_surface *wlr_surface; + struct wlr_scene_surface *scene_surface; + struct wl_list link; + + struct wl_listener destroy; +}; + +struct output { + struct wl_list link; + struct server *server; + struct wlr_output *wlr_output; + + struct wl_listener frame; +}; + +static void output_handle_frame(struct wl_listener *listener, void *data) { + struct output *output = wl_container_of(listener, output, frame); + + wlr_scene_commit_output(output->server->scene, output->wlr_output, 0, 0); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + struct surface *surface; + wl_list_for_each(surface, &output->server->surfaces, link) { + wlr_surface_send_frame_done(surface->wlr_surface, &now); + } +} + +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; + + struct output *output = + calloc(1, sizeof(struct output)); + output->wlr_output = wlr_output; + output->server = server; + 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 = + wl_container_of(wlr_output->modes.prev, mode, link); + 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_scene_node_destroy(&surface->scene_surface->node); + 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 pos = 50 * wl_list_length(&server->surfaces); + + struct surface *surface = calloc(1, sizeof(struct surface)); + surface->wlr_surface = wlr_surface; + surface->destroy.notify = surface_handle_destroy; + wl_signal_add(&wlr_surface->events.destroy, &surface->destroy); + surface->scene_surface = + wlr_scene_surface_create(&server->scene->node, wlr_surface); + wl_list_insert(server->surfaces.prev, &surface->link); + + wlr_scene_node_move(&surface->scene_surface->node, pos, pos); + wlr_scene_node_commit(&server->scene->node); +} + +int main(int argc, char *argv[]) { + wlr_log_init(WLR_DEBUG, NULL); + + 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.wl_display = wl_display_create(); + server.backend = wlr_backend_autocreate(server.wl_display, NULL); + server.scene = wlr_scene_create(); + + struct wlr_renderer *renderer = wlr_backend_get_renderer(server.backend); + wlr_renderer_init_wl_display(renderer, server.wl_display); + + struct wlr_compositor *compositor = + wlr_compositor_create(server.wl_display, renderer); + + wlr_xdg_shell_create(server.wl_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.wl_display); + if (!socket) { + wl_display_destroy(server.wl_display); + return EXIT_FAILURE; + } + + if (!wlr_backend_start(server.backend)) { + wl_display_destroy(server.wl_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.wl_display); + + wl_display_destroy_clients(server.wl_display); + wl_display_destroy(server.wl_display); + return EXIT_SUCCESS; +} diff --git a/include/backend/wayland.h b/include/backend/wayland.h index 9a8a404bbc..a0c83348aa 100644 --- a/include/backend/wayland.h +++ b/include/backend/wayland.h @@ -9,10 +9,11 @@ #include #include +#include #include #include #include -#include +#include struct wlr_wl_backend { struct wlr_backend backend; @@ -32,6 +33,7 @@ struct wlr_wl_backend { struct wl_event_source *remote_display_src; struct wl_registry *registry; struct wl_compositor *compositor; + struct wl_subcompositor *subcompositor; struct xdg_wm_base *xdg_wm_base; struct zxdg_decoration_manager_v1 *zxdg_decoration_manager_v1; struct zwp_pointer_gestures_v1 *zwp_pointer_gestures_v1; @@ -84,6 +86,13 @@ struct wlr_wl_output { } cursor; }; +struct wlr_wl_output_layer { + struct wlr_output_layer base; + struct wl_surface *surface; + struct wl_subsurface *subsurface; + bool prev_accepted; +}; + struct wlr_wl_input_device { struct wlr_input_device wlr_input_device; uint32_t fingers; diff --git a/include/types/wlr_output_layer.h b/include/types/wlr_output_layer.h new file mode 100644 index 0000000000..64412494a8 --- /dev/null +++ b/include/types/wlr_output_layer.h @@ -0,0 +1,11 @@ +#ifndef TYPES_WLR_OUTPUT_LAYER_H +#define TYPES_WLR_OUTPUT_LAYER_H + +#include + +void output_layer_destroy(struct wlr_output_layer *layer); +void output_layer_state_move(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src); +void output_layer_state_clear(struct wlr_output_layer_state *state); + +#endif diff --git a/include/wlr/interfaces/wlr_output.h b/include/wlr/interfaces/wlr_output.h index 34b3e675e3..6324592a95 100644 --- a/include/wlr/interfaces/wlr_output.h +++ b/include/wlr/interfaces/wlr_output.h @@ -29,6 +29,8 @@ struct wlr_output_impl { size_t (*get_gamma_size)(struct wlr_output *output); bool (*export_dmabuf)(struct wlr_output *output, struct wlr_dmabuf_attributes *attribs); + struct wlr_output_layer *(*create_layer)(struct wlr_output *output); + void (*destroy_layer)(struct wlr_output_layer *layer); }; void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, @@ -44,4 +46,7 @@ void wlr_output_send_frame(struct wlr_output *output); void wlr_output_send_present(struct wlr_output *output, struct wlr_output_event_present *event); +void wlr_output_layer_init(struct wlr_output_layer *layer, + struct wlr_output *output); + #endif diff --git a/include/wlr/types/wlr_output.h b/include/wlr/types/wlr_output.h index 89ad509c96..ed8a90985e 100644 --- a/include/wlr/types/wlr_output.h +++ b/include/wlr/types/wlr_output.h @@ -60,6 +60,7 @@ enum wlr_output_state_field { WLR_OUTPUT_STATE_SCALE = 1 << 4, WLR_OUTPUT_STATE_TRANSFORM = 1 << 5, WLR_OUTPUT_STATE_ADAPTIVE_SYNC_ENABLED = 1 << 6, + WLR_OUTPUT_STATE_LAYERS = 1 << 7, }; enum wlr_output_state_buffer_type { @@ -94,6 +95,8 @@ struct wlr_output_state { int32_t width, height; int32_t refresh; // mHz, may be zero } custom_mode; + + struct wl_list layers; // wlr_output_layer.pending.link }; struct wlr_output_impl; @@ -141,6 +144,8 @@ struct wlr_output { bool frame_pending; float transform_matrix[9]; + struct wl_list layers; // wlr_output_layer.current.link + struct wlr_output_state pending; // Commit sequence number. Incremented on each commit, may overflow. diff --git a/include/wlr/types/wlr_output_layer.h b/include/wlr/types/wlr_output_layer.h new file mode 100644 index 0000000000..abb13aad1d --- /dev/null +++ b/include/wlr/types/wlr_output_layer.h @@ -0,0 +1,83 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_OUTPUT_LAYER_H +#define WLR_TYPES_WLR_OUTPUT_LAYER_H + +/** + * Output layers provide an API to display buffers without rendering them. It + * relies on backend features which are not always available: the backend can + * refuse to display a layer. However when the backend accepts to display a + * layer, performance and battery usage will be improved. + * + * Operations on output layers are double-buffered. An output commit is needed + * to apply the pending state. + */ + +#include + +enum wlr_output_layer_state_field { + WLR_OUTPUT_LAYER_STATE_LINK = 1 << 0, + WLR_OUTPUT_LAYER_STATE_BUFFER = 1 << 1, + WLR_OUTPUT_LAYER_STATE_POSITION = 1 << 2, +}; + +struct wlr_output_layer_state { + uint32_t committed; // enum wlr_output_layer_state_field + struct wl_list link; + struct wlr_buffer *buffer; // only valid if WLR_OUTPUT_LAYER_STATE_BUFFER + int x, y; // only valid if WLR_OUTPUT_LAYER_STATE_POSITION +}; + +struct wlr_output_layer { + struct wlr_output *output; + /** If true, the backend has accepted to display the layer. If false, the + * compositor needs to manually render the layer. After each output commit, + * the backend will update this field. */ + bool accepted; + + struct wlr_output_layer_state current, pending; +}; + +/** + * Create a new output layer. + */ +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output); +/** + * Remove the output layer. This operation is double-buffered, see + * wlr_output_commit. + * + * Callers must not access the wlr_output_layer after calling this function. + */ +void wlr_output_layer_remove(struct wlr_output_layer *layer); +/** + * Attach a buffer to the layer. This operation is double-buffered, see + * wlr_output_commit. + */ +void wlr_output_layer_attach_buffer(struct wlr_output_layer *layer, + struct wlr_buffer *buffer); +/** + * Set the position of the layer relative to the output. The coordinates are + * given in output-buffer-local coordinates. This operation is double-buffered, + * see wlr_output_commit. + */ +void wlr_output_layer_move(struct wlr_output_layer *layer, int x, int y); +/** + * Move the layer right above the specified sibling. This operation is + * double-buffered, see wlr_output_commit. + */ +void wlr_output_layer_place_above(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling); +/** + * Move the layer right below the specified sibling. This operation is + * double-buffered, see wlr_output_commit. + */ +void wlr_output_layer_place_below(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling); + +#endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h new file mode 100644 index 0000000000..cad2f73e41 --- /dev/null +++ b/include/wlr/types/wlr_scene.h @@ -0,0 +1,127 @@ +/* + * This an unstable interface of wlroots. No guarantees are made regarding the + * future consistency of this API. + */ +#ifndef WLR_USE_UNSTABLE +#error "Add -DWLR_USE_UNSTABLE to enable unstable wlroots features" +#endif + +#ifndef WLR_TYPES_WLR_SCENE_H +#define WLR_TYPES_WLR_SCENE_H + +/** + * The scene-graph API provides a declarative way to display surfaces. The + * compositor creates a scene, adds surfaces, then renders the whole scene. + * + * The API is atomic: changes are not applied until a commit. + */ + +#include +#include +#include + +struct wlr_output; + +enum wlr_scene_node_type { + WLR_SCENE_NODE_ROOT, + WLR_SCENE_NODE_SURFACE, +}; + +struct wlr_scene_node_state { + struct wl_list link; // wlr_scene_node_state.children + + struct wl_list children; // wlr_scene_node_state.link + + bool enabled; + int x, y; +}; + +struct wlr_scene_node { + enum wlr_scene_node_type type; + struct wlr_scene_node *parent; + struct wlr_scene_node_state current, pending; + + struct { + struct wl_signal destroy; + } events; +}; + +/** The root scene-graph node. */ +struct wlr_scene { + struct wlr_scene_node node; +}; + +/** A scene-graph node displaying a surface and its subsurfaces. */ +struct wlr_scene_surface { + struct wlr_scene_node node; + struct wlr_surface *surface; + + struct wl_list surface_outputs; // wlr_scene_surface_output.link + + struct wl_listener surface_destroy; +}; + +/** + * Immediately destroy the scene-graph node. + */ +void wlr_scene_node_destroy(struct wlr_scene_node *node); +/** + * Atomically apply pending changes for this node and all of its children. + */ +void wlr_scene_node_commit(struct wlr_scene_node *node); +/** + * Enable or disable this node. If a node is disabled, all of its children are + * implicitly disabled as well. This state is double-buffered, see + * wlr_scene_node_commit. + */ +void wlr_scene_node_toggle(struct wlr_scene_node *node, bool enabled); +/** + * Set the position of the node relative to its parent. This state is + * double-buffered, see wlr_scene_node_commit. + */ +void wlr_scene_node_move(struct wlr_scene_node *node, int x, int y); +/** + * Move the node right above the specified sibling. This state is + * double-buffered, see wlr_scene_node_commit. + */ +void wlr_scene_node_place_above(struct wlr_scene_node *node, + struct wlr_scene_node *sibling); +/** + * Move the node right below the specified sibling. This state is + * double-buffered, see wlr_scene_node_commit. + */ +void wlr_scene_node_place_below(struct wlr_scene_node *node, + struct wlr_scene_node *sibling); +/** + * Call `iterator` on each surface in the scene-graph, with the surface's + * position in layout coordinates. The function is called from root to leaves + * (in rendering order). + */ +void wlr_scene_node_for_each_surface(struct wlr_scene_node *node, + wlr_surface_iterator_func_t iterator, void *user_data); + +/** + * Create a new scene-graph. + */ +struct wlr_scene *wlr_scene_create(void); +/** + * Manually render the scene-graph on an output. The compositor needs to call + * wlr_renderer_begin before and wlr_renderer_end after calling this function. + * Damage is given in output-buffer-local coordinates and can be set to NULL to + * disable damage tracking. + */ +void wlr_scene_render(struct wlr_scene *scene, struct wlr_output *output, + int lx, int ly, pixman_region32_t *damage); +/** + * Perform an output commit. + */ +bool wlr_scene_commit_output(struct wlr_scene *scene, struct wlr_output *output, + int lx, int ly); + +/** + * Add a node displaying a surface (and its sub-surfaces) to the scene-graph. + */ +struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_node *parent, + struct wlr_surface *surface); + +#endif diff --git a/types/meson.build b/types/meson.build index face4bdf27..36e9dc2d99 100644 --- a/types/meson.build +++ b/types/meson.build @@ -45,6 +45,7 @@ wlr_files += files( 'wlr_matrix.c', 'wlr_output_damage.c', 'wlr_output_layout.c', + 'wlr_output_layer.c', 'wlr_output_management_v1.c', 'wlr_output_power_management_v1.c', 'wlr_output.c', @@ -56,6 +57,7 @@ wlr_files += files( 'wlr_primary_selection.c', 'wlr_region.c', 'wlr_relative_pointer_v1.c', + 'wlr_scene.c', 'wlr_screencopy_v1.c', 'wlr_server_decoration.c', 'wlr_surface.c', diff --git a/types/wlr_output.c b/types/wlr_output.c index 3316653a2c..adebf16cf6 100644 --- a/types/wlr_output.c +++ b/types/wlr_output.c @@ -17,6 +17,7 @@ #include #include "util/global.h" #include "util/signal.h" +#include "types/wlr_output_layer.h" #define OUTPUT_VERSION 3 @@ -340,6 +341,8 @@ void wlr_output_init(struct wlr_output *output, struct wlr_backend *backend, wl_signal_init(&output->events.description); wl_signal_init(&output->events.destroy); pixman_region32_init(&output->pending.damage); + wl_list_init(&output->layers); + wl_list_init(&output->pending.layers); const char *no_hardware_cursors = getenv("WLR_NO_HARDWARE_CURSORS"); if (no_hardware_cursors != NULL && strcmp(no_hardware_cursors, "1") == 0) { @@ -493,6 +496,42 @@ static void output_pending_resolution(struct wlr_output *output, int *width, } } +static void output_commit_layers(struct wlr_output *output) { + if (output->pending.committed & WLR_OUTPUT_STATE_LAYERS) { + // Update the layer ordering in the current output state + struct wlr_output_layer *layer; + wl_list_for_each(layer, &output->pending.layers, pending.link) { + wl_list_remove(&layer->current.link); + wl_list_insert(output->layers.prev, &layer->current.link); + } + } + + struct wlr_output_layer *layer, *tmp; + wl_list_for_each_safe(layer, tmp, &output->layers, current.link) { + if (wl_list_empty(&layer->pending.link)) { + output_layer_destroy(layer); + } else { + output_layer_state_move(&layer->current, &layer->pending); + } + } +} + +static void output_rollback_layers(struct wlr_output *output) { + if (output->pending.committed & WLR_OUTPUT_STATE_LAYERS) { + // Rollback the layer ordering in the pending output state + struct wlr_output_layer *layer; + wl_list_for_each(layer, &output->layers, current.link) { + wl_list_remove(&layer->pending.link); + wl_list_insert(output->pending.layers.prev, &layer->pending.link); + } + } + + struct wlr_output_layer *layer, *tmp; + wl_list_for_each_safe(layer, tmp, &output->pending.layers, pending.link) { + output_layer_state_clear(&layer->pending); + } +} + static bool output_basic_test(struct wlr_output *output) { if (output->pending.committed & WLR_OUTPUT_STATE_BUFFER) { if (output->frame_pending) { @@ -576,7 +615,7 @@ bool wlr_output_commit(struct wlr_output *output) { wlr_signal_emit_safe(&output->events.precommit, &event); if (!output->impl->commit(output)) { - output_state_clear(&output->pending); + wlr_output_rollback(output); return false; } @@ -626,11 +665,13 @@ bool wlr_output_commit(struct wlr_output *output) { output->needs_frame = false; } + output_commit_layers(output); output_state_clear(&output->pending); return true; } void wlr_output_rollback(struct wlr_output *output) { + output_rollback_layers(output); output_state_clear(&output->pending); if (output->impl->rollback) { diff --git a/types/wlr_output_layer.c b/types/wlr_output_layer.c new file mode 100644 index 0000000000..0aa19d9b14 --- /dev/null +++ b/types/wlr_output_layer.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include +#include "types/wlr_output_layer.h" + +struct wlr_output_layer *wlr_output_layer_create(struct wlr_output *output) { + if (output->impl->create_layer) { + return output->impl->create_layer(output); + } + + struct wlr_output_layer *layer = calloc(1, sizeof(*layer)); + if (layer == NULL) { + return NULL; + } + wlr_output_layer_init(layer, output); + return layer; +} + +void wlr_output_layer_init(struct wlr_output_layer *layer, + struct wlr_output *output) { + layer->output = output; + wl_list_insert(output->layers.prev, &layer->current.link); + wl_list_insert(output->pending.layers.prev, &layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void output_layer_destroy(struct wlr_output_layer *layer) { + output_layer_state_clear(&layer->current); + output_layer_state_clear(&layer->pending); + + wl_list_remove(&layer->current.link); + wl_list_remove(&layer->pending.link); + + if (layer->output->impl->destroy_layer) { + layer->output->impl->destroy_layer(layer); + } else { + free(layer); + } +} + +void wlr_output_layer_remove(struct wlr_output_layer *layer) { + wlr_output_layer_attach_buffer(layer, NULL); + + wl_list_remove(&layer->pending.link); + wl_list_init(&layer->pending.link); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void wlr_output_layer_attach_buffer(struct wlr_output_layer *layer, + struct wlr_buffer *buffer) { + if (buffer == layer->current.buffer) { + layer->pending.committed &= ~WLR_OUTPUT_LAYER_STATE_BUFFER; + return; + } + + wlr_buffer_unlock(layer->pending.buffer); + layer->pending.buffer = NULL; + + if (buffer == NULL) { + return; + } + + layer->pending.buffer = wlr_buffer_lock(buffer); + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_BUFFER; +} + +void wlr_output_layer_move(struct wlr_output_layer *layer, int x, int y) { + if (x == layer->current.x && y == layer->current.y) { + layer->pending.committed &= ~WLR_OUTPUT_LAYER_STATE_POSITION; + return; + } + + layer->pending.x = x; + layer->pending.y = y; + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_POSITION; +} + +void wlr_output_layer_place_above(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling) { + assert(layer->output == sibling->output); + + wl_list_remove(&layer->pending.link); + wl_list_insert(&sibling->pending.link, &layer->pending.link); + + if (layer->current.link.prev != &layer->output->layers) { + struct wlr_output_layer *prev = + wl_container_of(layer->current.link.prev, prev, current.link); + if (prev == sibling) { + return; + } + } + + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +void wlr_output_layer_place_below(struct wlr_output_layer *layer, + struct wlr_output_layer *sibling) { + assert(layer->output == sibling->output); + + wl_list_remove(&layer->pending.link); + wl_list_insert(sibling->pending.link.prev, &layer->pending.link); + + if (layer->current.link.next != &layer->output->layers) { + struct wlr_output_layer *next = + wl_container_of(layer->current.link.next, next, current.link); + if (next == sibling) { + return; + } + } + + layer->pending.committed |= WLR_OUTPUT_LAYER_STATE_LINK; + layer->output->pending.committed |= WLR_OUTPUT_STATE_LAYERS; +} + +static void output_layer_state_copy(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src) { + // link has already been taken care of + if (src->committed & WLR_OUTPUT_LAYER_STATE_BUFFER) { + wlr_buffer_unlock(dst->buffer); + if (src->buffer != NULL) { + dst->buffer = wlr_buffer_lock(src->buffer); + } else { + dst->buffer = NULL; + } + } + if (src->committed & WLR_OUTPUT_LAYER_STATE_POSITION) { + dst->x = src->x; + dst->y = src->y; + } + dst->committed |= src->committed; +} + +void output_layer_state_clear(struct wlr_output_layer_state *state) { + wlr_buffer_unlock(state->buffer); + state->buffer = NULL; + state->committed = 0; +} + +void output_layer_state_move(struct wlr_output_layer_state *dst, + struct wlr_output_layer_state *src) { + output_layer_state_copy(dst, src); + output_layer_state_clear(src); +} diff --git a/types/wlr_scene.c b/types/wlr_scene.c new file mode 100644 index 0000000000..4d7fc45a96 --- /dev/null +++ b/types/wlr_scene.c @@ -0,0 +1,454 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "util/signal.h" + +struct wlr_scene_surface_output { + struct wlr_output *output; + struct wl_list link; // wlr_scene_surface.surface_outputs + struct wlr_output_layer *layer; + + struct wl_listener output_destroy; +}; + +static struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_ROOT); + return (struct wlr_scene *)node; +} + +static struct wlr_scene_surface *scene_node_get_surface( + struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_SURFACE); + return (struct wlr_scene_surface *)node; +} + +static void scene_node_state_init(struct wlr_scene_node_state *state) { + wl_list_init(&state->children); + wl_list_init(&state->link); + state->enabled = true; +} + +static void scene_node_state_finish(struct wlr_scene_node_state *state) { + wl_list_remove(&state->link); +} + +static void scene_node_init(struct wlr_scene_node *node, + enum wlr_scene_node_type type, struct wlr_scene_node *parent) { + assert(type == WLR_SCENE_NODE_ROOT || parent != NULL); + assert(parent == NULL || parent->type == WLR_SCENE_NODE_ROOT); + + node->type = type; + node->parent = parent; + scene_node_state_init(&node->current); + scene_node_state_init(&node->pending); + wl_signal_init(&node->events.destroy); + + if (parent != NULL) { + wl_list_insert(parent->pending.children.prev, &node->pending.link); + } +} + +static void scene_node_finish(struct wlr_scene_node *node) { + wlr_signal_emit_safe(&node->events.destroy, NULL); + + struct wlr_scene_node *child, *child_tmp; + wl_list_for_each_safe(child, child_tmp, + &node->current.children, current.link) { + wlr_scene_node_destroy(child); + } + wl_list_for_each_safe(child, child_tmp, + &node->pending.children, pending.link) { + wlr_scene_node_destroy(child); + } + + scene_node_state_finish(&node->current); + scene_node_state_finish(&node->pending); +} + +static void surface_output_destroy(struct wlr_scene_surface_output *so); + +void wlr_scene_node_destroy(struct wlr_scene_node *node) { + if (node == NULL) { + return; + } + + // TODO: make this atomic: don't remove the node immediately, destroy it + // on commit (rename to wlr_scene_node_remove?) + scene_node_finish(node); + + switch (node->type) { + case WLR_SCENE_NODE_ROOT:; + struct wlr_scene *scene = scene_node_get_root(node); + free(scene); + break; + case WLR_SCENE_NODE_SURFACE:; + struct wlr_scene_surface *scene_surface = scene_node_get_surface(node); + struct wlr_scene_surface_output *so, *so_tmp; + wl_list_for_each_safe(so, so_tmp, &scene_surface->surface_outputs, link) { + surface_output_destroy(so); + } + wl_list_remove(&scene_surface->surface_destroy.link); + free(scene_surface); + break; + } +} + +struct wlr_scene *wlr_scene_create(void) { + struct wlr_scene *scene = calloc(1, sizeof(struct wlr_scene)); + if (scene == NULL) { + return NULL; + } + scene_node_init(&scene->node, WLR_SCENE_NODE_ROOT, NULL); + + return scene; +} + +static void scene_surface_handle_surface_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_surface *scene_surface = + wl_container_of(listener, scene_surface, surface_destroy); + wlr_scene_node_destroy(&scene_surface->node); +} + +struct wlr_scene_surface *wlr_scene_surface_create(struct wlr_scene_node *parent, + struct wlr_surface *surface) { + struct wlr_scene_surface *scene_surface = + calloc(1, sizeof(struct wlr_scene_surface)); + if (scene_surface == NULL) { + return NULL; + } + scene_node_init(&scene_surface->node, WLR_SCENE_NODE_SURFACE, parent); + + scene_surface->surface = surface; + wl_list_init(&scene_surface->surface_outputs); + + scene_surface->surface_destroy.notify = scene_surface_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &scene_surface->surface_destroy); + + return scene_surface; +} + +static void scene_node_state_move(struct wlr_scene_node_state *dst, + const struct wlr_scene_node_state *src) { + dst->enabled = src->enabled; + dst->x = src->x; + dst->y = src->y; +} + +void wlr_scene_node_commit(struct wlr_scene_node *node) { + scene_node_state_move(&node->current, &node->pending); + + struct wlr_scene_node *child; + wl_list_for_each(child, &node->pending.children, pending.link) { + wl_list_remove(&child->current.link); + wl_list_insert(node->current.children.prev, &child->current.link); + } + + wl_list_for_each(child, &node->current.children, current.link) { + wlr_scene_node_commit(child); + } +} + +void wlr_scene_node_toggle(struct wlr_scene_node *node, bool enabled) { + node->pending.enabled = enabled; +} + +void wlr_scene_node_move(struct wlr_scene_node *node, int x, int y) { + node->pending.x = x; + node->pending.y = y; +} + +void wlr_scene_node_place_above(struct wlr_scene_node *node, + struct wlr_scene_node *sibling) { + assert(node->parent == sibling->parent); + + wl_list_remove(&node->pending.link); + wl_list_insert(&sibling->pending.link, &node->pending.link); +} + +void wlr_scene_node_place_below(struct wlr_scene_node *node, + struct wlr_scene_node *sibling) { + assert(node->parent == sibling->parent); + + wl_list_remove(&node->pending.link); + wl_list_insert(sibling->pending.link.prev, &node->pending.link); +} + +struct iterator_data { + wlr_surface_iterator_func_t user_iterator; + void *user_data; + int lx, ly; +}; + +static void surface_iterator(struct wlr_surface *surface, + int sx, int sy, void *_data) { + struct iterator_data *data = _data; + data->user_iterator(surface, data->lx + sx, data->ly + sy, data->user_data); +} + +static void scene_node_for_each_surface(struct wlr_scene_node *node, + int lx, int ly, wlr_surface_iterator_func_t user_iterator, + void *user_data) { + if (!node->current.enabled) { + return; + } + + lx += node->current.x; + ly += node->current.y; + + if (node->type == WLR_SCENE_NODE_SURFACE) { + struct wlr_scene_surface *scene_surface = scene_node_get_surface(node); + struct iterator_data data = { + .user_iterator = user_iterator, + .user_data = user_data, + .lx = lx, + .ly = ly, + }; + wlr_surface_for_each_surface(scene_surface->surface, + surface_iterator, &data); + } + + struct wlr_scene_node *child; + wl_list_for_each(child, &node->current.children, current.link) { + scene_node_for_each_surface(child, lx, ly, user_iterator, user_data); + } +} + +void wlr_scene_node_for_each_surface(struct wlr_scene_node *node, + wlr_surface_iterator_func_t user_iterator, void *user_data) { + scene_node_for_each_surface(node, 0, 0, user_iterator, user_data); +} + +static int scale_length(int length, int offset, float scale) { + return round((offset + length) * scale) - round(offset * scale); +} + +static void scale_box(struct wlr_box *box, float scale) { + box->width = scale_length(box->width, box->x, scale); + box->height = scale_length(box->height, box->y, scale); + box->x = round(box->x * scale); + box->y = round(box->y * scale); +} + +static void scissor_output(struct wlr_output *output, pixman_box32_t *rect) { + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer); + + struct wlr_box box = { + .x = rect->x1, + .y = rect->y1, + .width = rect->x2 - rect->x1, + .height = rect->y2 - rect->y1, + }; + + int ow, oh; + wlr_output_transformed_resolution(output, &ow, &oh); + + enum wl_output_transform transform = + wlr_output_transform_invert(output->transform); + wlr_box_transform(&box, &box, transform, ow, oh); + + wlr_renderer_scissor(renderer, &box); +} + +static void render_texture(struct wlr_output *output, + pixman_region32_t *output_damage, struct wlr_texture *texture, + const struct wlr_box *box, const float matrix[static 9]) { + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer); + + pixman_region32_t damage; + pixman_region32_init(&damage); + pixman_region32_init_rect(&damage, box->x, box->y, box->width, box->height); + pixman_region32_intersect(&damage, &damage, output_damage); + if (pixman_region32_not_empty(&damage)) { + int nrects; + pixman_box32_t *rects = pixman_region32_rectangles(&damage, &nrects); + for (int i = 0; i < nrects; ++i) { + scissor_output(output, &rects[i]); + wlr_render_texture_with_matrix(renderer, texture, matrix, 1.0); + } + } + pixman_region32_fini(&damage); +} + +static void render_surface(struct wlr_surface *surface, + struct wlr_output *output, int ox, int oy, pixman_region32_t *output_damage) { + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + + struct wlr_box box = { + .x = ox, + .y = oy, + .width = surface->current.width, + .height = surface->current.height, + }; + scale_box(&box, output->scale); + + float matrix[9]; + enum wl_output_transform transform = + wlr_output_transform_invert(surface->current.transform); + wlr_matrix_project_box(matrix, &box, transform, 0.0, + output->transform_matrix); + + render_texture(output, output_damage, texture, &box, matrix); +} + +static struct wlr_scene_surface_output *get_or_create_surface_output( + struct wlr_scene_surface *scene_surface, struct wlr_output *output); + +static void node_render(struct wlr_scene_node *node, + struct wlr_output *output, int ox, int oy, + pixman_region32_t *output_damage) { + if (!node->current.enabled) { + return; + } + + ox += node->current.x; + oy += node->current.y; + + if (node->type == WLR_SCENE_NODE_SURFACE) { + struct wlr_scene_surface *scene_surface = scene_node_get_surface(node); + struct wlr_scene_surface_output *so = + get_or_create_surface_output(scene_surface, output); + if (so == NULL) { + return; + } + + if (!so->layer->accepted) { + render_surface(scene_surface->surface, output, ox, oy, output_damage); + } + } + + struct wlr_scene_node *child; + wl_list_for_each(child, &node->current.children, current.link) { + node_render(child, output, ox, oy, output_damage); + } +} + + +void wlr_scene_render(struct wlr_scene *scene, struct wlr_output *output, + int lx, int ly, pixman_region32_t *damage) { + pixman_region32_t full_region; + pixman_region32_init_rect(&full_region, 0, 0, output->width, output->height); + if (damage == NULL) { + damage = &full_region; + } + + struct wlr_renderer *renderer = + wlr_backend_get_renderer(output->backend); + assert(renderer); + + if (output->enabled && pixman_region32_not_empty(damage)) { + node_render(&scene->node, output, -lx, -ly, damage); + wlr_renderer_scissor(renderer, NULL); + } + + pixman_region32_fini(&full_region); +} + +static void surface_output_destroy(struct wlr_scene_surface_output *so) { + wl_list_remove(&so->link); + wl_list_remove(&so->output_destroy.link); + free(so); +} + +static void surface_output_handle_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_surface_output *so = + wl_container_of(listener, so, output_destroy); + surface_output_destroy(so); +} + +static struct wlr_scene_surface_output *get_or_create_surface_output( + struct wlr_scene_surface *scene_surface, struct wlr_output *output) { + struct wlr_scene_surface_output *so; + wl_list_for_each(so, &scene_surface->surface_outputs, link) { + if (so->output == output) { + return so; + } + } + + so = calloc(1, sizeof(struct wlr_scene_surface_output)); + if (so == NULL) { + return NULL; + } + so->output = output; + so->layer = wlr_output_layer_create(output); + if (so->layer == NULL) { + free(so); + return NULL; + } + so->output_destroy.notify = surface_output_handle_output_destroy; + wl_signal_add(&output->events.destroy, &so->output_destroy); + wl_list_insert(&scene_surface->surface_outputs, &so->link); + return so; +} + +static void node_setup_output_layers(struct wlr_scene_node *node, + struct wlr_output *output, int ox, int oy, + struct wlr_output_layer **prev_layer) { + ox += node->current.x; + oy += node->current.y; + + if (node->type == WLR_SCENE_NODE_SURFACE) { + struct wlr_scene_surface *scene_surface = scene_node_get_surface(node); + struct wlr_scene_surface_output *so = + get_or_create_surface_output(scene_surface, output); + if (so == NULL) { + return; + } + + struct wlr_buffer *buffer = NULL; + if (node->current.enabled && scene_surface->surface->buffer != NULL) { + buffer = &scene_surface->surface->buffer->base; + } + + if (*prev_layer != NULL) { + wlr_output_layer_place_above(so->layer, *prev_layer); + } + *prev_layer = so->layer; + wlr_output_layer_move(so->layer, ox, oy); + wlr_output_layer_attach_buffer(so->layer, buffer); + } + + struct wlr_scene_node *child; + wl_list_for_each(child, &node->current.children, current.link) { + node_setup_output_layers(child, output, ox, oy, prev_layer); + } +} + +bool wlr_scene_commit_output(struct wlr_scene *scene, struct wlr_output *output, + int lx, int ly) { + struct wlr_output_layer *prev_layer = NULL; + node_setup_output_layers(&scene->node, output, -lx, -ly, &prev_layer); + + if (!wlr_output_test(output)) { + return false; + } + + if (!wlr_output_attach_render(output, NULL)) { + return false; + } + + struct wlr_renderer *renderer = wlr_backend_get_renderer(output->backend); + assert(renderer != NULL); + + int width, height; + wlr_output_effective_resolution(output, &width, &height); + wlr_renderer_begin(renderer, width, height); + wlr_renderer_clear(renderer, (float[4]){ 0.3, 0.3, 0.3, 1.0 }); + + wlr_scene_render(scene, output, lx, ly, NULL); + + wlr_renderer_end(renderer); + + return wlr_output_commit(output); +}