From 1eb16d136774c8fb3c9085df45156264f0db8814 Mon Sep 17 00:00:00 2001 From: Alexander Orzechowski Date: Thu, 18 Jan 2024 10:00:45 -0500 Subject: [PATCH] scene_graph: Maintain `wlr_scene_node`s for the sway tree. --- include/sway/output.h | 15 +++++++ include/sway/tree/container.h | 21 ++++++++++ include/sway/tree/node.h | 12 ++++++ include/sway/tree/root.h | 26 ++++++++++++ include/sway/tree/view.h | 4 ++ include/sway/tree/workspace.h | 7 ++++ sway/desktop/output.c | 22 ++++++++--- sway/tree/container.c | 74 +++++++++++++++++++++++++++++++++-- sway/tree/node.c | 29 ++++++++++++++ sway/tree/output.c | 34 ++++++++++++++++ sway/tree/root.c | 28 +++++++++++++ sway/tree/view.c | 10 ++++- sway/tree/workspace.c | 17 ++++++++ 13 files changed, 290 insertions(+), 9 deletions(-) diff --git a/include/sway/output.h b/include/sway/output.h index 96bd10db..691ac8dd 100644 --- a/include/sway/output.h +++ b/include/sway/output.h @@ -5,6 +5,7 @@ #include #include #include +#include #include "config.h" #include "sway/tree/node.h" #include "sway/tree/view.h" @@ -19,7 +20,21 @@ struct sway_output_state { struct sway_output { struct sway_node node; + + struct { + struct wlr_scene_tree *tiling; + struct wlr_scene_tree *fullscreen; + } layers; + + // when a container is fullscreen, in case the fullscreen surface is + // translucent (can see behind) we must make sure that the background is a + // solid color in order to conform to the wayland protocol. This rect + // ensures that when looking through a surface, all that will be seen + // is black. + struct wlr_scene_rect *fullscreen_background; + struct wlr_output *wlr_output; + struct wlr_scene_output *scene_output; struct sway_server *server; struct wl_list link; diff --git a/include/sway/tree/container.h b/include/sway/tree/container.h index fe3ee8a8..ee22a0d0 100644 --- a/include/sway/tree/container.h +++ b/include/sway/tree/container.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "list.h" #include "sway/tree/node.h" @@ -68,6 +69,26 @@ struct sway_container { struct sway_node node; struct sway_view *view; + struct wlr_scene_tree *scene_tree; + + struct { + struct wlr_scene_tree *tree; + + struct wlr_scene_tree *border; + struct wlr_scene_tree *background; + } title_bar; + + struct { + struct wlr_scene_tree *tree; + + struct wlr_scene_rect *top; + struct wlr_scene_rect *bottom; + struct wlr_scene_rect *left; + struct wlr_scene_rect *right; + } border; + + struct wlr_scene_tree *content_tree; + struct sway_container_state current; struct sway_container_state pending; diff --git a/include/sway/tree/node.h b/include/sway/tree/node.h index 03a389a4..e2dbcdf0 100644 --- a/include/sway/tree/node.h +++ b/include/sway/tree/node.h @@ -2,6 +2,7 @@ #define _SWAY_NODE_H #include #include +#include #include "list.h" #define MIN_SANE_W 100 @@ -75,4 +76,15 @@ list_t *node_get_children(struct sway_node *node); bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor); +// when destroying a sway tree, it's not known which order the tree will be +// destroyed. To prevent freeing of scene_nodes recursing up the tree, +// let's use this helper function to disown them to the staging node. +void scene_node_disown_children(struct wlr_scene_tree *tree); + +// a helper function used to allocate tree nodes. If an allocation failure +// occurs a flag is flipped that can be checked later to destroy a parent +// of this scene node preventing memory leaks. +struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent, + bool *failed); + #endif diff --git a/include/sway/tree/root.h b/include/sway/tree/root.h index b3dda12f..9cb3d7bf 100644 --- a/include/sway/tree/root.h +++ b/include/sway/tree/root.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "sway/tree/container.h" #include "sway/tree/node.h" @@ -16,6 +17,31 @@ struct sway_root { struct wlr_output_layout *output_layout; struct wl_listener output_layout_change; + + // scene node layout: + // - root + // - staging + // - layer shell stuff + // - tiling + // - floating + // - fullscreen stuff + // - seat stuff + // - ext_session_lock + struct wlr_scene *root_scene; + + // since wlr_scene nodes can't be orphaned and must always + // have a parent, use this staging scene_tree so that a + // node always have a valid parent. Nothing in this + // staging node will be visible. + struct wlr_scene_tree *staging; + + struct { + struct wlr_scene_tree *tiling; + struct wlr_scene_tree *floating; + struct wlr_scene_tree *fullscreen; + struct wlr_scene_tree *fullscreen_global; + } layers; + #if HAVE_XWAYLAND struct wl_list xwayland_unmanaged; // sway_xwayland_unmanaged::link #endif diff --git a/include/sway/tree/view.h b/include/sway/tree/view.h index 822e7bb3..4aaed9e3 100644 --- a/include/sway/tree/view.h +++ b/include/sway/tree/view.h @@ -2,6 +2,7 @@ #define _SWAY_VIEW_H #include #include +#include #include "sway/config.h" #if HAVE_XWAYLAND #include @@ -69,6 +70,9 @@ struct sway_view { enum sway_view_type type; const struct sway_view_impl *impl; + struct wlr_scene_tree *scene_tree; + struct wlr_scene_tree *content_tree; + struct sway_container *container; // NULL if unmapped and transactions finished struct wlr_surface *surface; // NULL for unmapped views struct sway_xdg_decoration *xdg_decoration; diff --git a/include/sway/tree/workspace.h b/include/sway/tree/workspace.h index b0fef4ca..58bde20c 100644 --- a/include/sway/tree/workspace.h +++ b/include/sway/tree/workspace.h @@ -2,6 +2,7 @@ #define _SWAY_WORKSPACE_H #include +#include #include "sway/config.h" #include "sway/tree/container.h" #include "sway/tree/node.h" @@ -23,6 +24,12 @@ struct sway_workspace_state { struct sway_workspace { struct sway_node node; + + struct { + struct wlr_scene_tree *tiling; + struct wlr_scene_tree *fullscreen; + } layers; + struct sway_container *fullscreen; char *name; diff --git a/sway/desktop/output.c b/sway/desktop/output.c index 928c77d6..288ccc7c 100644 --- a/sway/desktop/output.c +++ b/sway/desktop/output.c @@ -856,9 +856,6 @@ void output_damage_whole_container(struct sway_output *output, .height = con->current.height + 2, }; scale_box(&box, output->wlr_output->scale); - if (wlr_damage_ring_add_box(&output->damage_ring, &box)) { - wlr_output_schedule_frame(output->wlr_output); - } // Damage subsurfaces as well, which may extend outside the box if (con->view) { damage_child_views_iterator(con, output); @@ -914,6 +911,8 @@ static void begin_destroy(struct sway_output *output) { wlr_damage_ring_finish(&output->damage_ring); + wlr_scene_output_destroy(output->scene_output); + output->scene_output = NULL; output->wlr_output->data = NULL; output->wlr_output = NULL; @@ -1039,11 +1038,24 @@ void handle_new_output(struct wl_listener *listener, void *data) { return; } - struct sway_output *output = output_create(wlr_output); - if (!output) { + // Create the scene output here so we're not accidentally creating one for + // the fallback output + struct wlr_scene_output *scene_output = + wlr_scene_output_create(root->root_scene, wlr_output); + if (!scene_output) { + sway_log(SWAY_ERROR, "Failed to create a scene output"); return; } + + struct sway_output *output = output_create(wlr_output); + if (!output) { + sway_log(SWAY_ERROR, "Failed to create a sway output"); + wlr_scene_output_destroy(scene_output); + return; + } + output->server = server; + output->scene_output = scene_output; wlr_damage_ring_init(&output->damage_ring); wl_signal_add(&root->output_layout->events.destroy, &output->layout_destroy); diff --git a/sway/tree/container.c b/sway/tree/container.c index 8c344a6d..9ed08929 100644 --- a/sway/tree/container.c +++ b/sway/tree/container.c @@ -27,6 +27,24 @@ #include "log.h" #include "stringop.h" +static struct wlr_scene_rect *alloc_rect_node(struct wlr_scene_tree *parent, + bool *failed) { + if (*failed) { + return NULL; + } + + // just pass in random values. These will be overwritten when + // they need to be used. + struct wlr_scene_rect *rect = wlr_scene_rect_create( + parent, 0, 0, (float[4]){0.f, 0.f, 0.f, 1.f}); + if (!rect) { + sway_log(SWAY_ERROR, "Failed to allocate a wlr_scene_rect"); + *failed = true; + } + + return rect; +} + struct sway_container *container_create(struct sway_view *view) { struct sway_container *c = calloc(1, sizeof(struct sway_container)); if (!c) { @@ -34,14 +52,62 @@ struct sway_container *container_create(struct sway_view *view) { return NULL; } node_init(&c->node, N_CONTAINER, c); - c->pending.layout = L_NONE; - c->view = view; - c->alpha = 1.0f; + + // Container tree structure + // - scene tree + // - title bar + // - border + // - background + // - title text + // - marks text + // - border + // - border top/bottom/left/right + // - content_tree (we put the content node here so when we disable the + // border everything gets disabled. We only render the content iff there + // is a border as well) + bool failed = false; + c->scene_tree = alloc_scene_tree(root->staging, &failed); + + c->title_bar.tree = alloc_scene_tree(c->scene_tree, &failed); + c->title_bar.border = alloc_scene_tree(c->title_bar.tree, &failed); + c->title_bar.background = alloc_scene_tree(c->title_bar.tree, &failed); + + // for opacity purposes we need to carfully create the scene such that + // none of our rect nodes as well as text buffers don't overlap. To do + // this we have to create rects such that they go around text buffers + for (int i = 0; i < 4; i++) { + alloc_rect_node(c->title_bar.border, &failed); + } + + for (int i = 0; i < 5; i++) { + alloc_rect_node(c->title_bar.background, &failed); + } + + c->border.tree = alloc_scene_tree(c->scene_tree, &failed); + c->content_tree = alloc_scene_tree(c->border.tree, &failed); + + if (view) { + // only containers with views can have borders + c->border.top = alloc_rect_node(c->border.tree, &failed); + c->border.bottom = alloc_rect_node(c->border.tree, &failed); + c->border.left = alloc_rect_node(c->border.tree, &failed); + c->border.right = alloc_rect_node(c->border.tree, &failed); + } + + if (failed) { + wlr_scene_node_destroy(&c->scene_tree->node); + free(c); + return NULL; + } if (!view) { c->pending.children = create_list(); c->current.children = create_list(); } + + c->pending.layout = L_NONE; + c->view = view; + c->alpha = 1.0f; c->marks = create_list(); c->outputs = create_list(); @@ -85,6 +151,8 @@ void container_destroy(struct sway_container *con) { } } + scene_node_disown_children(con->content_tree); + wlr_scene_node_destroy(&con->scene_tree->node); free(con); } diff --git a/sway/tree/node.c b/sway/tree/node.c index 12361c75..213cf0a6 100644 --- a/sway/tree/node.c +++ b/sway/tree/node.c @@ -159,3 +159,32 @@ bool node_has_ancestor(struct sway_node *node, struct sway_node *ancestor) { } return false; } + +void scene_node_disown_children(struct wlr_scene_tree *tree) { + // this function can be called as part of destruction code that will be invoked + // upon an allocation failure. Let's not crash on NULL due to an allocation error. + if (!tree) { + return; + } + + struct wlr_scene_node *child, *tmp_child; + wl_list_for_each_safe(child, tmp_child, &tree->children, link) { + wlr_scene_node_reparent(child, root->staging); + } +} + +struct wlr_scene_tree *alloc_scene_tree(struct wlr_scene_tree *parent, + bool *failed) { + // fallthrough + if (*failed) { + return NULL; + } + + struct wlr_scene_tree *tree = wlr_scene_tree_create(parent); + if (!tree) { + sway_log(SWAY_ERROR, "Failed to allocate a scene node"); + *failed = true; + } + + return tree; +} diff --git a/sway/tree/output.c b/sway/tree/output.c index 2186ad0c..12a2f969 100644 --- a/sway/tree/output.c +++ b/sway/tree/output.c @@ -87,9 +87,41 @@ static void restore_workspaces(struct sway_output *output) { output_sort_workspaces(output); } +static void destroy_scene_layers(struct sway_output *output) { + wlr_scene_node_destroy(&output->fullscreen_background->node); + + scene_node_disown_children(output->layers.tiling); + scene_node_disown_children(output->layers.fullscreen); + + wlr_scene_node_destroy(&output->layers.tiling->node); + wlr_scene_node_destroy(&output->layers.fullscreen->node); +} + struct sway_output *output_create(struct wlr_output *wlr_output) { struct sway_output *output = calloc(1, sizeof(struct sway_output)); node_init(&output->node, N_OUTPUT, output); + + bool failed = false; + output->layers.tiling = alloc_scene_tree(root->staging, &failed); + output->layers.fullscreen = alloc_scene_tree(root->staging, &failed); + + if (!failed) { + output->fullscreen_background = wlr_scene_rect_create( + output->layers.fullscreen, 0, 0, (float[4]){0.f, 0.f, 0.f, 1.f}); + + if (!output->fullscreen_background) { + sway_log(SWAY_ERROR, "Unable to allocate a background rect"); + failed = true; + } + } + + if (failed) { + destroy_scene_layers(output); + wlr_scene_output_destroy(output->scene_output); + free(output); + return NULL; + } + output->wlr_output = wlr_output; wlr_output->data = output; output->detected_subpixel = wlr_output->subpixel; @@ -238,6 +270,8 @@ void output_destroy(struct sway_output *output) { "which is still referenced by transactions")) { return; } + + destroy_scene_layers(output); list_free(output->workspaces); list_free(output->current.workspaces); wl_event_source_remove(output->repaint_timer); diff --git a/sway/tree/root.c b/sway/tree/root.c index dc51c3be..75fb63f1 100644 --- a/sway/tree/root.c +++ b/sway/tree/root.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include "sway/desktop/transaction.h" #include "sway/input/seat.h" @@ -30,7 +31,33 @@ struct sway_root *root_create(struct wl_display *wl_display) { sway_log(SWAY_ERROR, "Unable to allocate sway_root"); return NULL; } + + struct wlr_scene *root_scene = wlr_scene_create(); + if (!root_scene) { + sway_log(SWAY_ERROR, "Unable to allocate root scene node"); + free(root); + return NULL; + } + node_init(&root->node, N_ROOT, root); + root->root_scene = root_scene; + + bool failed = false; + root->staging = alloc_scene_tree(&root_scene->tree, &failed); + + root->layers.tiling = alloc_scene_tree(&root_scene->tree, &failed); + root->layers.floating = alloc_scene_tree(&root_scene->tree, &failed); + root->layers.fullscreen = alloc_scene_tree(&root_scene->tree, &failed); + root->layers.fullscreen_global = alloc_scene_tree(&root_scene->tree, &failed); + + if (failed) { + wlr_scene_node_destroy(&root_scene->tree.node); + free(root); + return NULL; + } + + wlr_scene_node_set_enabled(&root->staging->node, false); + root->output_layout = wlr_output_layout_create(wl_display); wl_list_init(&root->all_outputs); #if HAVE_XWAYLAND @@ -53,6 +80,7 @@ void root_destroy(struct sway_root *root) { list_free(root->scratchpad); list_free(root->non_desktop_outputs); list_free(root->outputs); + wlr_scene_node_destroy(&root->root_scene->tree.node); free(root); } diff --git a/sway/tree/view.c b/sway/tree/view.c index d62a0667..bc968edc 100644 --- a/sway/tree/view.c +++ b/sway/tree/view.c @@ -37,6 +37,14 @@ bool view_init(struct sway_view *view, enum sway_view_type type, const struct sway_view_impl *impl) { + bool failed = false; + view->scene_tree = alloc_scene_tree(root->staging, &failed); + view->content_tree = alloc_scene_tree(view->scene_tree, &failed); + if (failed) { + wlr_scene_node_destroy(&view->scene_tree->node); + return false; + } + view->type = type; view->impl = impl; view->executed_criteria = create_list(); @@ -67,7 +75,7 @@ void view_destroy(struct sway_view *view) { list_free(view->executed_criteria); view_assign_ctx(view, NULL); - + wlr_scene_node_destroy(&view->scene_tree->node); free(view->title_format); if (view->impl->destroy) { diff --git a/sway/tree/workspace.c b/sway/tree/workspace.c index 18218768..f60b2366 100644 --- a/sway/tree/workspace.c +++ b/sway/tree/workspace.c @@ -71,6 +71,18 @@ struct sway_workspace *workspace_create(struct sway_output *output, return NULL; } node_init(&ws->node, N_WORKSPACE, ws); + + bool failed = false; + ws->layers.tiling = alloc_scene_tree(root->staging, &failed); + ws->layers.fullscreen = alloc_scene_tree(root->staging, &failed); + + if (failed) { + wlr_scene_node_destroy(&ws->layers.tiling->node); + wlr_scene_node_destroy(&ws->layers.fullscreen->node); + free(ws); + return NULL; + } + ws->name = strdup(name); ws->prev_split_layout = L_NONE; ws->layout = output_get_default_layout(output); @@ -131,6 +143,11 @@ void workspace_destroy(struct sway_workspace *workspace) { return; } + scene_node_disown_children(workspace->layers.tiling); + scene_node_disown_children(workspace->layers.fullscreen); + wlr_scene_node_destroy(&workspace->layers.tiling->node); + wlr_scene_node_destroy(&workspace->layers.fullscreen->node); + free(workspace->name); free(workspace->representation); list_free_items_and_destroy(workspace->output_priority);