diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h new file mode 100644 index 00000000..953b1b87 --- /dev/null +++ b/include/wlr/types/wlr_scene.h @@ -0,0 +1,114 @@ +/* + * 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 scene on + * outputs. + * + * The scene-graph API only supports basic 2D composition operations (like the + * KMS API or the Wayland protocol does). For anything more complicated, + * compositors need to implement custom rendering logic. + */ + +#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 + + int x, y; // relative to parent +}; + +/** A node is an object in the scene. */ +struct wlr_scene_node { + enum wlr_scene_node_type type; + struct wlr_scene_node *parent; + struct wlr_scene_node_state state; + + 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 single surface. */ +struct wlr_scene_surface { + struct wlr_scene_node node; + struct wlr_surface *surface; + + // private state + + struct wl_listener surface_destroy; +}; + +/** + * Immediately destroy the scene-graph node. + */ +void wlr_scene_node_destroy(struct wlr_scene_node *node); +/** + * Set the position of the node relative to its parent. + */ +void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y); +/** + * Move the node right above the specified sibling. + */ +void wlr_scene_node_place_above(struct wlr_scene_node *node, + struct wlr_scene_node *sibling); +/** + * Move the node right below the specified sibling. + */ +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_output(struct wlr_scene *scene, struct wlr_output *output, + int lx, int ly, pixman_region32_t *damage); + +/** + * Add a node displaying a single surface to the scene-graph. + * + * The child sub-surfaces are ignored. + */ +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 6a650035..d297a753 100644 --- a/types/meson.build +++ b/types/meson.build @@ -49,6 +49,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_scene.c b/types/wlr_scene.c new file mode 100644 index 00000000..7b1690e9 --- /dev/null +++ b/types/wlr_scene.c @@ -0,0 +1,263 @@ +#include +#include +#include +#include +#include +#include +#include +#include "util/signal.h" + +static struct wlr_scene *scene_root_from_node(struct wlr_scene_node *node) { + assert(node->type == WLR_SCENE_NODE_ROOT); + return (struct wlr_scene *)node; +} + +static struct wlr_scene_surface *scene_surface_from_node( + 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); +} + +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->state); + wl_signal_init(&node->events.destroy); + + if (parent != NULL) { + wl_list_insert(parent->state.children.prev, &node->state.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->state.children, state.link) { + wlr_scene_node_destroy(child); + } + + scene_node_state_finish(&node->state); +} + +void wlr_scene_node_destroy(struct wlr_scene_node *node) { + if (node == NULL) { + return; + } + + scene_node_finish(node); + + switch (node->type) { + case WLR_SCENE_NODE_ROOT:; + struct wlr_scene *scene = scene_root_from_node(node); + free(scene); + break; + case WLR_SCENE_NODE_SURFACE:; + struct wlr_scene_surface *scene_surface = scene_surface_from_node(node); + 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; + + scene_surface->surface_destroy.notify = scene_surface_handle_surface_destroy; + wl_signal_add(&surface->events.destroy, &scene_surface->surface_destroy); + + return scene_surface; +} + +void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y) { + node->state.x = x; + node->state.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->state.link); + wl_list_insert(&sibling->state.link, &node->state.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->state.link); + wl_list_insert(sibling->state.link.prev, &node->state.link); +} + +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) { + lx += node->state.x; + ly += node->state.y; + + if (node->type == WLR_SCENE_NODE_SURFACE) { + struct wlr_scene_surface *scene_surface = scene_surface_from_node(node); + user_iterator(scene_surface->surface, lx, ly, user_data); + } + + struct wlr_scene_node *child; + wl_list_for_each(child, &node->state.children, state.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); +} + +struct render_data { + struct wlr_output *output; + pixman_region32_t *damage; +}; + +static void render_surface_iterator(struct wlr_surface *surface, + int x, int y, void *_data) { + struct render_data *data = _data; + struct wlr_output *output = data->output; + pixman_region32_t *output_damage = data->damage; + + struct wlr_texture *texture = wlr_surface_get_texture(surface); + if (texture == NULL) { + return; + } + + struct wlr_box box = { + .x = x, + .y = y, + .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); +} + +void wlr_scene_render_output(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)) { + struct render_data data = { + .output = output, + .damage = damage, + }; + scene_node_for_each_surface(&scene->node, lx, ly, + render_surface_iterator, &data); + wlr_renderer_scissor(renderer, NULL); + } + + pixman_region32_fini(&full_region); +}