diff --git a/include/util/array.h b/include/util/array.h index a51bdb640..862921680 100644 --- a/include/util/array.h +++ b/include/util/array.h @@ -15,4 +15,15 @@ void array_remove_at(struct wl_array *arr, size_t offset, size_t size); */ bool array_realloc(struct wl_array *arr, size_t size); +/** + * Returns a pointer to the first valid element in a reversed array. + */ +void *array_reversed_start(struct wl_array *arr); + +/** + * Adds a new element to the array inserting them starting from a higher + * memory address effectively inserting them in reverse order. + */ +void *array_reversed_add(struct wl_array *arr, size_t size); + #endif diff --git a/include/wlr/types/wlr_scene.h b/include/wlr/types/wlr_scene.h index b6f0d2175..c7396fb59 100644 --- a/include/wlr/types/wlr_scene.h +++ b/include/wlr/types/wlr_scene.h @@ -106,6 +106,8 @@ struct wlr_scene { enum wlr_scene_debug_damage_option debug_damage_option; bool direct_scanout; bool calculate_visibility; + + struct wl_array render_list; }; /** A scene-graph node displaying a single surface. */ @@ -201,7 +203,7 @@ struct wlr_scene_output { struct wl_list damage_highlight_regions; - struct wl_array render_list; + struct wl_array output_layers; }; /** A layer shell scene helper */ diff --git a/types/scene/wlr_scene.c b/types/scene/wlr_scene.c index 953fb2c28..31d282e87 100644 --- a/types/scene/wlr_scene.c +++ b/types/scene/wlr_scene.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -122,6 +123,8 @@ void wlr_scene_node_destroy(struct wlr_scene_node *node) { wl_list_remove(&scene->presentation_destroy.link); wl_list_remove(&scene->linux_dmabuf_v1_destroy.link); + + wl_array_release(&scene->render_list); } else { assert(node->parent); } @@ -1335,7 +1338,8 @@ void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) { wl_list_remove(&scene_output->output_damage.link); wl_list_remove(&scene_output->output_needs_frame.link); - wl_array_release(&scene_output->render_list); + wl_array_release(&scene_output->output_layers); + free(scene_output); } @@ -1363,66 +1367,83 @@ void wlr_scene_output_set_position(struct wlr_scene_output *scene_output, scene_output_update_geometry(scene_output); } -static bool scene_node_invisible(struct wlr_scene_node *node) { +struct render_list_entry { + struct wlr_scene_node *node; + struct wlr_output_layer_state *state; + bool sent_feedback; + bool visible; +}; + +static bool scene_construct_render_list(struct wlr_scene_node *node, + struct wl_array *render_list, struct wlr_box *box, bool calculate_visibility, + bool enabled) { + enabled = enabled && node->enabled; + if (node->type == WLR_SCENE_NODE_TREE) { + struct wlr_scene_tree *scene_tree = scene_tree_from_node(node); + + struct wlr_scene_node *child; + wl_list_for_each_reverse(child, &scene_tree->children, link) { + if (!scene_construct_render_list(child, render_list, box, + calculate_visibility, enabled)) { + return false; + } + } + return true; - } else if (node->type == WLR_SCENE_NODE_RECT) { + } + + struct render_list_entry *entry = wl_array_add(render_list, sizeof(*entry)); + if (!entry) { + return false; + } + + memset(entry, 0, sizeof(*entry)); + entry->node = node; + + if (!enabled) { + return true; + } + + if (node->type == WLR_SCENE_NODE_RECT) { struct wlr_scene_rect *rect = scene_rect_from_node(node); - return rect->color[3] == 0.f; + if (rect->color[3] == 0.f) { + return true; + } } else if (node->type == WLR_SCENE_NODE_BUFFER) { struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - return buffer->buffer == NULL; - } - - return false; -} - -struct render_list_constructor_data { - struct wlr_box box; - struct wl_array *render_list; - bool calculate_visibility; -}; - -static bool construct_render_list_iterator(struct wlr_scene_node *node, - int lx, int ly, void *_data) { - struct render_list_constructor_data *data = _data; - - if (scene_node_invisible(node)) { - return false; + if (!buffer->buffer) { + return true; + } } // while rendering, the background should always be black. // If we see a black rect, we can ignore rendering everything under the rect // and even the rect itself. - if (node->type == WLR_SCENE_NODE_RECT && data->calculate_visibility) { + if (node->type == WLR_SCENE_NODE_RECT && calculate_visibility) { struct wlr_scene_rect *rect = scene_rect_from_node(node); float *black = (float[4]){ 0.f, 0.f, 0.f, 1.f }; if (memcmp(rect->color, black, sizeof(float) * 4) == 0) { - return false; + return true; } } pixman_region32_t intersection; pixman_region32_init(&intersection); pixman_region32_intersect_rect(&intersection, &node->visible, - data->box.x, data->box.y, - data->box.width, data->box.height); + box->x, box->y, box->width, box->height); if (!pixman_region32_not_empty(&intersection)) { pixman_region32_fini(&intersection); - return false; + return true; } pixman_region32_fini(&intersection); - struct wlr_scene_node **entry = wl_array_add(data->render_list, - sizeof(struct wlr_scene_node *)); - if (entry) { - *entry = node; - } - return false; + entry->visible = true; + return true; } static void get_frame_damage(struct wlr_scene_output *scene_output, pixman_region32_t *frame_damage) { @@ -1471,10 +1492,8 @@ static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene, wlr_linux_dmabuf_feedback_v1_finish(&feedback); } -static bool scene_buffer_can_consider_direct_scanout(struct wlr_scene_buffer *buffer, - const struct wlr_scene_output *scene_output) { - struct wlr_scene_node *node = &buffer->node; - +static bool scene_buffer_try_direct_scanout(struct render_list_entry *entry, + struct wlr_scene_output *scene_output) { if (!scene_output->scene->direct_scanout) { return false; } @@ -1486,10 +1505,13 @@ static bool scene_buffer_can_consider_direct_scanout(struct wlr_scene_buffer *bu return false; } + struct wlr_scene_node *node = entry->node; if (node->type != WLR_SCENE_NODE_BUFFER) { return false; } + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + struct wlr_fbox default_box = {0}; if (buffer->transform & WL_OUTPUT_TRANSFORM_90) { default_box.width = buffer->buffer->height; @@ -1523,11 +1545,16 @@ static bool scene_buffer_can_consider_direct_scanout(struct wlr_scene_buffer *bu return false; } - return true; -} + if (buffer->primary_output == scene_output) { + struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = scene_output->output->renderer, + .scanout_primary_output = scene_output->output, + }; + + scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); + entry->sent_feedback = true; + } -static bool scene_buffer_try_direct_scanout(struct wlr_scene_buffer *buffer, - struct wlr_scene_output *scene_output) { struct wlr_output_state state = { .committed = WLR_OUTPUT_STATE_BUFFER, .buffer = buffer->buffer, @@ -1550,6 +1577,260 @@ static bool scene_buffer_try_direct_scanout(struct wlr_scene_buffer *buffer, return true; } +static bool scene_buffer_can_be_output_layer(struct wlr_scene_buffer *buffer, + struct wlr_scene_output *scene_output) { + struct wlr_fbox default_box = {0}; + if (buffer->transform & WL_OUTPUT_TRANSFORM_90) { + default_box.width = buffer->buffer->height; + default_box.height = buffer->buffer->width; + } else { + default_box.width = buffer->buffer->width; + default_box.height = buffer->buffer->height; + } + + if (!wlr_fbox_empty(&buffer->src_box) && + !wlr_fbox_equal(&buffer->src_box, &default_box)) { + return false; + } + + if (buffer->transform != scene_output->output->transform) { + return false; + } + + return true; +} + +struct wlr_scene_node_output_layer { + struct wlr_addon addon; + struct wlr_output_layer *layer; + struct wlr_scene_buffer *buffer; + struct wlr_scene_output *output; + struct render_list_entry *current_render_entry; + + struct wl_listener layer_feedback; + struct wl_listener output_destroy; +}; + +static void node_output_layer_destroy(struct wlr_scene_node_output_layer *layer) { + wlr_output_layer_destroy(layer->layer); + wlr_addon_finish(&layer->addon); + wl_list_remove(&layer->output_destroy.link); + free(layer); +} + +static void handle_output_layer_addon_finish(struct wlr_addon *addon) { + struct wlr_scene_node_output_layer *layer = wl_container_of(addon, layer, addon); + node_output_layer_destroy(layer); +} + +static void handle_output_layer_output_destroy(struct wl_listener *listener, + void *data) { + struct wlr_scene_node_output_layer *layer = wl_container_of(listener, layer, output_destroy); + wl_list_remove(&layer->layer_feedback.link); + node_output_layer_destroy(layer); +} + +static const struct wlr_addon_interface node_addon_output_layer = { + .name = "wlr_scene_output_layer", + .destroy = handle_output_layer_addon_finish, +}; + +static struct wlr_scene_node_output_layer *scene_node_get_output_layer( + struct wlr_scene_node *node, struct wlr_scene_output *output) { + struct wlr_addon *addon = wlr_addon_find( + &node->addons, output, &node_addon_output_layer); + if (!addon) { + return NULL; + } + + struct wlr_scene_node_output_layer *layer = wl_container_of(addon, layer, addon); + return layer; +} + +static void output_handle_layer_feedback(struct wl_listener *listener, void *data) { + struct wlr_scene_node_output_layer *layer = + wl_container_of(listener, layer, layer_feedback); + struct wlr_output_layer_feedback_event *event = data; + struct wlr_linux_dmabuf_feedback_v1_init_options options = { + .main_renderer = layer->buffer->primary_output->output->renderer, + .output_layer_feedback_event = event, + }; + + if (layer->buffer->primary_output == layer->output) { + scene_buffer_send_dmabuf_feedback(scene_node_get_root(&layer->buffer->node), + layer->buffer, &options); + layer->current_render_entry->sent_feedback = true; + } +} + +static struct wlr_scene_node_output_layer *scene_buffer_create_output_layer( + struct wlr_scene_buffer *buffer, struct wlr_scene_output *output) { + struct wlr_scene_node_output_layer *layer = calloc(1, sizeof(*layer)); + if (!layer) { + return NULL; + } + + layer->layer = wlr_output_layer_create(output->output); + if (!layer->layer) { + free(layer); + return NULL; + } + + layer->layer_feedback.notify = output_handle_layer_feedback; + wl_signal_add(&layer->layer->events.feedback, &layer->layer_feedback); + + layer->output_destroy.notify = handle_output_layer_output_destroy; + wl_signal_add(&output->output->events.destroy, &layer->output_destroy); + + layer->buffer = buffer; + layer->output = output; + + wlr_addon_init(&layer->addon, &buffer->node.addons, output, &node_addon_output_layer); + return layer; +} + +static bool scene_submit_output_layers(struct wlr_scene_output *scene_output, + struct wl_array *render_list) { + // if we render software cursors, that means they will be a part of the + // primary plane. If we render output layers, it means we could render + // over the cursor. A lock will also be registered if there is screen share. + // Screen share cannot use output layers. + if (scene_output->output->software_cursor_locks) { + return false; + } + + if (scene_output->scene->debug_damage_option == + WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { + return false; + } + + struct render_list_entry *list_data = render_list->data; + int list_len = render_list->size / sizeof(*list_data); + + scene_output->output_layers.size = 0; + if (!array_realloc(&scene_output->output_layers, + list_len * sizeof(struct wlr_output_layer_state))) { + return false; + } + + // area of that the base layer must render (such as rects) + pixman_region32_t base_layer_area; + pixman_region32_init(&base_layer_area); + + for (int i = 0; i < list_len; i++) { + struct render_list_entry *entry = &list_data[i]; + struct wlr_scene_node *node = entry->node; + + if (node->type != WLR_SCENE_NODE_BUFFER) { + continue; + } + + struct wlr_box box; + wlr_scene_node_coords(node, &box.x, &box.y); + scene_node_get_size(node, &box.width, &box.height); + + box.x = floor(box.x * scene_output->output->scale) - scene_output->x; + box.y = floor(box.y * scene_output->output->scale) - scene_output->y; + box.width = ceil(box.width * scene_output->output->scale); + box.height = ceil(box.height * scene_output->output->scale); + + struct wlr_scene_node_output_layer *layer = + scene_node_get_output_layer(node, scene_output); + + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + + if (entry->visible && !scene_buffer_can_be_output_layer(buffer, scene_output)) { + pixman_region32_union_rect(&base_layer_area, &base_layer_area, + box.x, box.y, box.width, box.height); + + if (layer) { + node_output_layer_destroy(layer); + } + + continue; + } + + if (entry->visible && !layer) { + layer = scene_buffer_create_output_layer(buffer, scene_output); + if (!layer) { + pixman_region32_union_rect(&base_layer_area, &base_layer_area, + box.x, box.y, box.width, box.height); + continue; + } + } + + if (layer) { + // we preallocted the array length, this should never return NULL. + entry->state = array_reversed_add(&scene_output->output_layers, sizeof(*entry->state)); + + layer->current_render_entry = entry; + *entry->state = (struct wlr_output_layer_state){ + .layer = layer->layer, + .buffer = NULL, + .dst_box = box, + }; + } + + if (!entry->visible) { + continue; + } + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, &base_layer_area, + box.x, box.y, box.width, box.height); + if (pixman_region32_not_empty(&intersection)) { + pixman_region32_union_rect(&base_layer_area, &base_layer_area, + box.x, box.y, box.width, box.height); + pixman_region32_fini(&intersection); + continue; + } + pixman_region32_fini(&intersection); + + // only consider nodes that are in the damage + pixman_region32_init(&intersection); + pixman_region32_intersect_rect(&intersection, &scene_output->damage_ring.current, + box.x, box.y, box.width, box.height); + if (!pixman_region32_not_empty(&intersection)) { + pixman_region32_union_rect(&base_layer_area, &base_layer_area, + box.x, box.y, box.width, box.height); + pixman_region32_fini(&intersection); + continue; + } + + if (!buffer_is_opaque(buffer->buffer)) { + // With transparent nodes, we can't get away with just having the + // node partially in the damage or else the user can see through + // the node to find an older version of the node software composited + // in the base layer. + pixman_region32_t node_box; + pixman_region32_init_rect(&node_box, box.x, box.y, box.width, box.height); + pixman_region32_subtract(&intersection, &node_box, &intersection); + pixman_region32_fini(&node_box); + if (pixman_region32_not_empty(&intersection)) { + pixman_region32_union_rect(&base_layer_area, &base_layer_area, + box.x, box.y, box.width, box.height); + pixman_region32_fini(&intersection); + continue; + } + } + pixman_region32_fini(&intersection); + + entry->state->buffer = buffer->buffer; + } + + pixman_region32_fini(&base_layer_area); + + wlr_output_set_layers(scene_output->output, array_reversed_start(&scene_output->output_layers), + scene_output->output_layers.size / sizeof(struct wlr_output_layer_state)); + + if (!wlr_output_test(scene_output->output)) { + return false; + } + + return true; +} + bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { struct wlr_output *output = scene_output->output; enum wlr_scene_debug_damage_option debug_damage = @@ -1563,50 +1844,58 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { return true; } - struct render_list_constructor_data list_con = { - .box = { .x = scene_output->x, .y = scene_output->y }, - .render_list = &scene_output->render_list, - .calculate_visibility = scene_output->scene->calculate_visibility, - }; - wlr_output_effective_resolution(output, - &list_con.box.width, &list_con.box.height); + struct wl_array *render_list = &scene_output->scene->render_list; + render_list->size = 0; - list_con.render_list->size = 0; - scene_nodes_in_box(&scene_output->scene->tree.node, &list_con.box, - construct_render_list_iterator, &list_con); - array_realloc(list_con.render_list, list_con.render_list->size); + struct wlr_box box = { .x = scene_output->x, .y = scene_output->y }; + wlr_output_effective_resolution(output, &box.width, &box.height); + if (!scene_construct_render_list(&scene_output->scene->tree.node, render_list, + &box, scene_output->scene->calculate_visibility, true)) { + return false; + } + array_realloc(render_list, render_list->size); - int list_len = list_con.render_list->size / sizeof(struct wlr_scene_node *); - struct wlr_scene_node **list_data = list_con.render_list->data; + scene_submit_output_layers(scene_output, render_list); - bool sent_direct_scanout_feedback = false; + struct render_list_entry *list_data = render_list->data; + int list_len = render_list->size / sizeof(*list_data); - // if there is only one thing to render let's see if that thing can be - // directly scanned out - bool scanout = false; - if (list_len == 1) { - struct wlr_scene_node *node = list_data[0]; + struct render_list_entry *scanout = NULL; + int render_count = 0; + pixman_region32_t forward_damage; + pixman_region32_init(&forward_damage); - if (node->type == WLR_SCENE_NODE_BUFFER) { - struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); - - if (scene_buffer_can_consider_direct_scanout(buffer, scene_output)) { - if (buffer->primary_output == scene_output) { - struct wlr_linux_dmabuf_feedback_v1_init_options options = { - .main_renderer = output->renderer, - .scanout_primary_output = output, - }; - - scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options); - sent_direct_scanout_feedback = true; - } - - scanout = scene_buffer_try_direct_scanout(buffer, scene_output); - } + for (int i = list_len - 1; i >= 0; i--) { + struct render_list_entry *entry = &list_data[i]; + if (!entry->visible) { + continue; } + + if (entry->state && entry->state->accepted && entry->state->buffer) { + // we need to damage the output here for the next frame because since + // the we're using output layers here. The contents won't be part + // of the primary plane and will therefore have stale contents. + pixman_region32_union(&forward_damage, &forward_damage, &entry->node->visible); + + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); + wl_signal_emit_mutable(&buffer->events.output_present, scene_output); + + continue; + } + + scanout = entry; + render_count++; } - if (scene_output->prev_scanout != scanout) { + pixman_region32_translate(&forward_damage, -scene_output->x, -scene_output->y); + scale_output_damage(&forward_damage, scene_output->output->scale); + pixman_region32_intersect(&forward_damage, &forward_damage, &scene_output->damage_ring.current); + + if (render_count != 1 || !scene_buffer_try_direct_scanout(scanout, scene_output)) { + scanout = NULL; + } + + if (scene_output->prev_scanout == !scanout) { scene_output->prev_scanout = scanout; wlr_log(WLR_DEBUG, "Direct scan-out %s", scanout ? "enabled" : "disabled"); @@ -1617,11 +1906,12 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { } if (scanout) { - struct wlr_scene_node *node = list_data[0]; - - assert(node->type == WLR_SCENE_NODE_BUFFER); - struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(scanout->node); wl_signal_emit_mutable(&buffer->events.output_present, scene_output); + + wlr_damage_ring_add(&scene_output->damage_ring, &forward_damage); + pixman_region32_fini(&forward_damage); + return true; } @@ -1629,6 +1919,7 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { wlr_damage_ring_add_whole(&scene_output->damage_ring); } + struct timespec now; if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) { struct wl_list *regions = &scene_output->damage_highlight_regions; @@ -1670,6 +1961,7 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { int buffer_age; if (!wlr_output_attach_render(output, &buffer_age)) { + pixman_region32_fini(&forward_damage); return false; } @@ -1679,6 +1971,7 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { buffer_age, &damage); if (!wlr_renderer_begin(renderer, output->width, output->height)) { + pixman_region32_fini(&forward_damage); pixman_region32_fini(&damage); wlr_output_rollback(output); return false; @@ -1695,9 +1988,13 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { float output_scale = scene_output->output->scale; for (int i = list_len - 1; i >= 0; i--) { - struct wlr_scene_node *node = list_data[i]; + struct render_list_entry *entry = &list_data[i]; + if (!entry->visible) { + continue; + } + int x, y; - wlr_scene_node_coords(node, &x, &y); + wlr_scene_node_coords(entry->node, &x, &y); // We must only cull opaque regions that are visible by the node. // The node's visibility will have the knowledge of a black rect @@ -1706,8 +2003,8 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { // rendering in that black rect region, consider the node's visibility. pixman_region32_t opaque; pixman_region32_init(&opaque); - scene_node_opaque_region(node, x, y, &opaque); - pixman_region32_intersect(&opaque, &opaque, &node->visible); + scene_node_opaque_region(entry->node, x, y, &opaque); + pixman_region32_intersect(&opaque, &opaque, &entry->node->visible); pixman_region32_translate(&opaque, -scene_output->x, -scene_output->y); wlr_region_scale(&opaque, &opaque, output_scale); @@ -1733,13 +2030,21 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { pixman_region32_fini(&background); for (int i = list_len - 1; i >= 0; i--) { - struct wlr_scene_node *node = list_data[i]; - scene_node_render(node, scene_output, &damage); + struct render_list_entry *entry = &list_data[i]; + if (!entry->visible) { + continue; + } - if (node->type == WLR_SCENE_NODE_BUFFER) { - struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node); + if (entry->state && entry->state->accepted && entry->state->buffer) { + continue; + } - if (buffer->primary_output == scene_output && !sent_direct_scanout_feedback) { + scene_node_render(entry->node, scene_output, &damage); + + if (entry->node->type == WLR_SCENE_NODE_BUFFER) { + struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(entry->node); + + if (buffer->primary_output == scene_output && !entry->sent_feedback) { struct wlr_linux_dmabuf_feedback_v1_init_options options = { .main_renderer = output->renderer, .scanout_primary_output = NULL, @@ -1797,6 +2102,9 @@ bool wlr_scene_output_commit(struct wlr_scene_output *scene_output) { wlr_output_schedule_frame(scene_output->output); } + wlr_damage_ring_add(&scene_output->damage_ring, &forward_damage); + pixman_region32_fini(&forward_damage); + return success; } diff --git a/util/array.c b/util/array.c index ec16a7b13..427cf725a 100644 --- a/util/array.c +++ b/util/array.c @@ -38,3 +38,26 @@ bool array_realloc(struct wl_array *arr, size_t size) { arr->alloc = alloc; return true; } + +void *array_reversed_start(struct wl_array *arr) { + char *data = arr->data; + return data + arr->alloc - arr->size; +} + +void *array_reversed_add(struct wl_array *arr, size_t size) { + if (arr->size + size > arr->alloc) { + size_t new_alloc = arr->alloc * 2; + char *new = malloc(new_alloc); + if (!new) { + return NULL; + } + + memcpy(new + (new_alloc - arr->size), array_reversed_start(arr), arr->size); + free(arr->data); + arr->data = new; + arr->alloc = new_alloc; + } + + arr->size += size; + return array_reversed_start(arr); +}