2034 lines
60 KiB
C
2034 lines
60 KiB
C
#include <assert.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <wlr/backend.h>
|
|
#include <wlr/render/swapchain.h>
|
|
#include <wlr/render/wlr_renderer.h>
|
|
#include <wlr/types/wlr_compositor.h>
|
|
#include <wlr/types/wlr_damage_ring.h>
|
|
#include <wlr/types/wlr_linux_dmabuf_v1.h>
|
|
#include <wlr/types/wlr_presentation_time.h>
|
|
#include <wlr/types/wlr_scene.h>
|
|
#include <wlr/util/log.h>
|
|
#include <wlr/util/region.h>
|
|
#include <wlr/util/transform.h>
|
|
#include "types/wlr_buffer.h"
|
|
#include "types/wlr_output.h"
|
|
#include "types/wlr_scene.h"
|
|
#include "util/array.h"
|
|
#include "util/env.h"
|
|
#include "util/time.h"
|
|
|
|
#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250
|
|
|
|
struct wlr_scene_tree *wlr_scene_tree_from_node(struct wlr_scene_node *node) {
|
|
assert(node->type == WLR_SCENE_NODE_TREE);
|
|
struct wlr_scene_tree *tree = wl_container_of(node, tree, node);
|
|
return tree;
|
|
}
|
|
|
|
struct wlr_scene_rect *wlr_scene_rect_from_node(struct wlr_scene_node *node) {
|
|
assert(node->type == WLR_SCENE_NODE_RECT);
|
|
struct wlr_scene_rect *rect = wl_container_of(node, rect, node);
|
|
return rect;
|
|
}
|
|
|
|
struct wlr_scene_buffer *wlr_scene_buffer_from_node(
|
|
struct wlr_scene_node *node) {
|
|
assert(node->type == WLR_SCENE_NODE_BUFFER);
|
|
struct wlr_scene_buffer *buffer = wl_container_of(node, buffer, node);
|
|
return buffer;
|
|
}
|
|
|
|
struct wlr_scene *scene_node_get_root(struct wlr_scene_node *node) {
|
|
struct wlr_scene_tree *tree;
|
|
if (node->type == WLR_SCENE_NODE_TREE) {
|
|
tree = wlr_scene_tree_from_node(node);
|
|
} else {
|
|
tree = node->parent;
|
|
}
|
|
|
|
while (tree->node.parent != NULL) {
|
|
tree = tree->node.parent;
|
|
}
|
|
struct wlr_scene *scene = wl_container_of(tree, scene, tree);
|
|
return scene;
|
|
}
|
|
|
|
static void scene_node_init(struct wlr_scene_node *node,
|
|
enum wlr_scene_node_type type, struct wlr_scene_tree *parent) {
|
|
*node = (struct wlr_scene_node){
|
|
.type = type,
|
|
.parent = parent,
|
|
.enabled = true,
|
|
};
|
|
|
|
wl_list_init(&node->link);
|
|
|
|
wl_signal_init(&node->events.destroy);
|
|
pixman_region32_init(&node->visible);
|
|
|
|
if (parent != NULL) {
|
|
wl_list_insert(parent->children.prev, &node->link);
|
|
}
|
|
|
|
wlr_addon_set_init(&node->addons);
|
|
}
|
|
|
|
struct highlight_region {
|
|
pixman_region32_t region;
|
|
struct timespec when;
|
|
struct wl_list link;
|
|
};
|
|
|
|
static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
|
|
struct wlr_buffer *buffer);
|
|
|
|
void wlr_scene_node_destroy(struct wlr_scene_node *node) {
|
|
if (node == NULL) {
|
|
return;
|
|
}
|
|
|
|
// We want to call the destroy listeners before we do anything else
|
|
// in case the destroy signal would like to remove children before they
|
|
// are recursively destroyed.
|
|
wl_signal_emit_mutable(&node->events.destroy, NULL);
|
|
wlr_addon_set_finish(&node->addons);
|
|
|
|
wlr_scene_node_set_enabled(node, false);
|
|
|
|
struct wlr_scene *scene = scene_node_get_root(node);
|
|
if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
uint64_t active = scene_buffer->active_outputs;
|
|
if (active) {
|
|
struct wlr_scene_output *scene_output;
|
|
wl_list_for_each(scene_output, &scene->outputs, link) {
|
|
if (active & (1ull << scene_output->index)) {
|
|
wl_signal_emit_mutable(&scene_buffer->events.output_leave,
|
|
scene_output);
|
|
}
|
|
}
|
|
}
|
|
|
|
wlr_texture_destroy(scene_buffer->texture);
|
|
scene_buffer_set_buffer(scene_buffer, NULL);
|
|
pixman_region32_fini(&scene_buffer->opaque_region);
|
|
} else if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
|
|
if (scene_tree == &scene->tree) {
|
|
assert(!node->parent);
|
|
struct wlr_scene_output *scene_output, *scene_output_tmp;
|
|
wl_list_for_each_safe(scene_output, scene_output_tmp, &scene->outputs, link) {
|
|
wlr_scene_output_destroy(scene_output);
|
|
}
|
|
|
|
wl_list_remove(&scene->linux_dmabuf_v1_destroy.link);
|
|
} else {
|
|
assert(node->parent);
|
|
}
|
|
|
|
struct wlr_scene_node *child, *child_tmp;
|
|
wl_list_for_each_safe(child, child_tmp,
|
|
&scene_tree->children, link) {
|
|
wlr_scene_node_destroy(child);
|
|
}
|
|
}
|
|
|
|
wl_list_remove(&node->link);
|
|
pixman_region32_fini(&node->visible);
|
|
free(node);
|
|
}
|
|
|
|
static void scene_tree_init(struct wlr_scene_tree *tree,
|
|
struct wlr_scene_tree *parent) {
|
|
*tree = (struct wlr_scene_tree){0};
|
|
scene_node_init(&tree->node, WLR_SCENE_NODE_TREE, parent);
|
|
wl_list_init(&tree->children);
|
|
}
|
|
|
|
struct wlr_scene *wlr_scene_create(void) {
|
|
struct wlr_scene *scene = calloc(1, sizeof(*scene));
|
|
if (scene == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
scene_tree_init(&scene->tree, NULL);
|
|
|
|
wl_list_init(&scene->outputs);
|
|
wl_list_init(&scene->linux_dmabuf_v1_destroy.link);
|
|
|
|
const char *debug_damage_options[] = {
|
|
"none",
|
|
"rerender",
|
|
"highlight",
|
|
NULL
|
|
};
|
|
|
|
scene->debug_damage_option = env_parse_switch("WLR_SCENE_DEBUG_DAMAGE", debug_damage_options);
|
|
scene->direct_scanout = !env_parse_bool("WLR_SCENE_DISABLE_DIRECT_SCANOUT");
|
|
scene->calculate_visibility = !env_parse_bool("WLR_SCENE_DISABLE_VISIBILITY");
|
|
|
|
return scene;
|
|
}
|
|
|
|
struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent) {
|
|
assert(parent);
|
|
|
|
struct wlr_scene_tree *tree = calloc(1, sizeof(*tree));
|
|
if (tree == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
scene_tree_init(tree, parent);
|
|
return tree;
|
|
}
|
|
|
|
static void scene_node_get_size(struct wlr_scene_node *node, int *lx, int *ly);
|
|
|
|
typedef bool (*scene_node_box_iterator_func_t)(struct wlr_scene_node *node,
|
|
int sx, int sy, void *data);
|
|
|
|
static bool _scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box,
|
|
scene_node_box_iterator_func_t iterator, void *user_data, int lx, int ly) {
|
|
if (!node->enabled) {
|
|
return false;
|
|
}
|
|
|
|
switch (node->type) {
|
|
case WLR_SCENE_NODE_TREE:;
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each_reverse(child, &scene_tree->children, link) {
|
|
if (_scene_nodes_in_box(child, box, iterator, user_data, lx + child->x, ly + child->y)) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
case WLR_SCENE_NODE_RECT:
|
|
case WLR_SCENE_NODE_BUFFER:;
|
|
struct wlr_box node_box = { .x = lx, .y = ly };
|
|
scene_node_get_size(node, &node_box.width, &node_box.height);
|
|
|
|
if (wlr_box_intersection(&node_box, &node_box, box) &&
|
|
iterator(node, lx, ly, user_data)) {
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool scene_nodes_in_box(struct wlr_scene_node *node, struct wlr_box *box,
|
|
scene_node_box_iterator_func_t iterator, void *user_data) {
|
|
int x, y;
|
|
wlr_scene_node_coords(node, &x, &y);
|
|
|
|
return _scene_nodes_in_box(node, box, iterator, user_data, x, y);
|
|
}
|
|
|
|
static void scene_node_opaque_region(struct wlr_scene_node *node, int x, int y,
|
|
pixman_region32_t *opaque) {
|
|
int width, height;
|
|
scene_node_get_size(node, &width, &height);
|
|
|
|
if (node->type == WLR_SCENE_NODE_RECT) {
|
|
struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
|
|
if (scene_rect->color[3] != 1) {
|
|
return;
|
|
}
|
|
} else if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
if (!scene_buffer->buffer) {
|
|
return;
|
|
}
|
|
|
|
if (scene_buffer->opacity != 1) {
|
|
return;
|
|
}
|
|
|
|
if (!scene_buffer->buffer_is_opaque) {
|
|
pixman_region32_copy(opaque, &scene_buffer->opaque_region);
|
|
pixman_region32_intersect_rect(opaque, opaque, 0, 0, width, height);
|
|
pixman_region32_translate(opaque, x, y);
|
|
return;
|
|
}
|
|
}
|
|
|
|
pixman_region32_fini(opaque);
|
|
pixman_region32_init_rect(opaque, x, y, width, height);
|
|
}
|
|
|
|
struct scene_update_data {
|
|
pixman_region32_t *visible;
|
|
pixman_region32_t *update_region;
|
|
struct wl_list *outputs;
|
|
bool calculate_visibility;
|
|
};
|
|
|
|
static uint32_t region_area(pixman_region32_t *region) {
|
|
uint32_t area = 0;
|
|
|
|
int nrects;
|
|
pixman_box32_t *rects = pixman_region32_rectangles(region, &nrects);
|
|
for (int i = 0; i < nrects; ++i) {
|
|
area += (rects[i].x2 - rects[i].x1) * (rects[i].y2 - rects[i].y1);
|
|
}
|
|
|
|
return area;
|
|
}
|
|
|
|
static void scale_output_damage(pixman_region32_t *damage, float scale) {
|
|
wlr_region_scale(damage, damage, scale);
|
|
|
|
if (floor(scale) != scale) {
|
|
wlr_region_expand(damage, damage, 1);
|
|
}
|
|
}
|
|
|
|
struct render_data {
|
|
enum wl_output_transform transform;
|
|
float scale;
|
|
struct wlr_box logical;
|
|
int trans_width, trans_height;
|
|
|
|
struct wlr_scene_output *output;
|
|
|
|
struct wlr_render_pass *render_pass;
|
|
pixman_region32_t damage;
|
|
};
|
|
|
|
static void transform_output_damage(pixman_region32_t *damage, const struct render_data *data) {
|
|
enum wl_output_transform transform = wlr_output_transform_invert(data->transform);
|
|
wlr_region_transform(damage, damage, transform, data->trans_width, data->trans_height);
|
|
}
|
|
|
|
static void transform_output_box(struct wlr_box *box, const struct render_data *data) {
|
|
enum wl_output_transform transform = wlr_output_transform_invert(data->transform);
|
|
wlr_box_transform(box, box, transform, data->trans_width, data->trans_height);
|
|
}
|
|
|
|
static void scene_damage_outputs(struct wlr_scene *scene, pixman_region32_t *damage) {
|
|
if (!pixman_region32_not_empty(damage)) {
|
|
return;
|
|
}
|
|
|
|
struct wlr_scene_output *scene_output;
|
|
wl_list_for_each(scene_output, &scene->outputs, link) {
|
|
pixman_region32_t output_damage;
|
|
pixman_region32_init(&output_damage);
|
|
pixman_region32_copy(&output_damage, damage);
|
|
pixman_region32_translate(&output_damage,
|
|
-scene_output->x, -scene_output->y);
|
|
scale_output_damage(&output_damage, scene_output->output->scale);
|
|
if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) {
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
}
|
|
pixman_region32_fini(&output_damage);
|
|
}
|
|
}
|
|
|
|
static void update_node_update_outputs(struct wlr_scene_node *node,
|
|
struct wl_list *outputs, struct wlr_scene_output *ignore,
|
|
struct wlr_scene_output *force) {
|
|
if (node->type != WLR_SCENE_NODE_BUFFER) {
|
|
return;
|
|
}
|
|
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
uint32_t largest_overlap = 0;
|
|
struct wlr_scene_output *old_primary_output = scene_buffer->primary_output;
|
|
scene_buffer->primary_output = NULL;
|
|
|
|
size_t count = 0;
|
|
uint64_t active_outputs = 0;
|
|
|
|
// let's update the outputs in two steps:
|
|
// - the primary outputs
|
|
// - the enter/leave signals
|
|
// This ensures that the enter/leave signals can rely on the primary output
|
|
// to have a reasonable value. Otherwise, they may get a value that's in
|
|
// the middle of a calculation.
|
|
struct wlr_scene_output *scene_output;
|
|
wl_list_for_each(scene_output, outputs, link) {
|
|
if (scene_output == ignore) {
|
|
continue;
|
|
}
|
|
|
|
if (!scene_output->output->enabled) {
|
|
continue;
|
|
}
|
|
|
|
struct wlr_box output_box = {
|
|
.x = scene_output->x,
|
|
.y = scene_output->y,
|
|
};
|
|
wlr_output_effective_resolution(scene_output->output,
|
|
&output_box.width, &output_box.height);
|
|
|
|
pixman_region32_t intersection;
|
|
pixman_region32_init(&intersection);
|
|
pixman_region32_intersect_rect(&intersection, &node->visible,
|
|
output_box.x, output_box.y, output_box.width, output_box.height);
|
|
|
|
if (pixman_region32_not_empty(&intersection)) {
|
|
uint32_t overlap = region_area(&intersection);
|
|
if (overlap >= largest_overlap) {
|
|
largest_overlap = overlap;
|
|
scene_buffer->primary_output = scene_output;
|
|
}
|
|
|
|
active_outputs |= 1ull << scene_output->index;
|
|
count++;
|
|
}
|
|
|
|
pixman_region32_fini(&intersection);
|
|
}
|
|
|
|
if (old_primary_output != scene_buffer->primary_output) {
|
|
scene_buffer->prev_feedback_options =
|
|
(struct wlr_linux_dmabuf_feedback_v1_init_options){0};
|
|
}
|
|
|
|
uint64_t old_active = scene_buffer->active_outputs;
|
|
scene_buffer->active_outputs = active_outputs;
|
|
|
|
wl_list_for_each(scene_output, outputs, link) {
|
|
uint64_t mask = 1ull << scene_output->index;
|
|
bool intersects = active_outputs & mask;
|
|
bool intersects_before = old_active & mask;
|
|
|
|
if (intersects && !intersects_before) {
|
|
wl_signal_emit_mutable(&scene_buffer->events.output_enter, scene_output);
|
|
} else if (!intersects && intersects_before) {
|
|
wl_signal_emit_mutable(&scene_buffer->events.output_leave, scene_output);
|
|
}
|
|
}
|
|
|
|
// if there are active outputs on this node, we should always have a primary
|
|
// output
|
|
assert(!scene_buffer->active_outputs || scene_buffer->primary_output);
|
|
|
|
// Skip output update event if nothing was updated
|
|
if (old_active == active_outputs &&
|
|
(!force || ((1ull << force->index) & ~active_outputs)) &&
|
|
old_primary_output == scene_buffer->primary_output) {
|
|
return;
|
|
}
|
|
|
|
struct wlr_scene_output *outputs_array[64];
|
|
struct wlr_scene_outputs_update_event event = {
|
|
.active = outputs_array,
|
|
.size = count,
|
|
};
|
|
|
|
size_t i = 0;
|
|
wl_list_for_each(scene_output, outputs, link) {
|
|
if (~active_outputs & (1ull << scene_output->index)) {
|
|
continue;
|
|
}
|
|
|
|
assert(i < count);
|
|
outputs_array[i++] = scene_output;
|
|
}
|
|
|
|
wl_signal_emit_mutable(&scene_buffer->events.outputs_update, &event);
|
|
}
|
|
|
|
static bool scene_node_update_iterator(struct wlr_scene_node *node,
|
|
int lx, int ly, void *_data) {
|
|
struct scene_update_data *data = _data;
|
|
|
|
struct wlr_box box = { .x = lx, .y = ly };
|
|
scene_node_get_size(node, &box.width, &box.height);
|
|
|
|
pixman_region32_subtract(&node->visible, &node->visible, data->update_region);
|
|
pixman_region32_union(&node->visible, &node->visible, data->visible);
|
|
pixman_region32_intersect_rect(&node->visible, &node->visible,
|
|
lx, ly, box.width, box.height);
|
|
|
|
if (data->calculate_visibility) {
|
|
pixman_region32_t opaque;
|
|
pixman_region32_init(&opaque);
|
|
scene_node_opaque_region(node, lx, ly, &opaque);
|
|
pixman_region32_subtract(data->visible, data->visible, &opaque);
|
|
pixman_region32_fini(&opaque);
|
|
}
|
|
|
|
update_node_update_outputs(node, data->outputs, NULL, NULL);
|
|
|
|
return false;
|
|
}
|
|
|
|
static void scene_node_visibility(struct wlr_scene_node *node,
|
|
pixman_region32_t *visible) {
|
|
if (!node->enabled) {
|
|
return;
|
|
}
|
|
|
|
if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_node_visibility(child, visible);
|
|
}
|
|
return;
|
|
}
|
|
|
|
pixman_region32_union(visible, visible, &node->visible);
|
|
}
|
|
|
|
static void scene_node_bounds(struct wlr_scene_node *node,
|
|
int x, int y, pixman_region32_t *visible) {
|
|
if (!node->enabled) {
|
|
return;
|
|
}
|
|
|
|
if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_node_bounds(child, x + child->x, y + child->y, visible);
|
|
}
|
|
return;
|
|
}
|
|
|
|
int width, height;
|
|
scene_node_get_size(node, &width, &height);
|
|
pixman_region32_union_rect(visible, visible, x, y, width, height);
|
|
}
|
|
|
|
static void scene_update_region(struct wlr_scene *scene,
|
|
pixman_region32_t *update_region) {
|
|
pixman_region32_t visible;
|
|
pixman_region32_init(&visible);
|
|
pixman_region32_copy(&visible, update_region);
|
|
|
|
struct scene_update_data data = {
|
|
.visible = &visible,
|
|
.update_region = update_region,
|
|
.outputs = &scene->outputs,
|
|
.calculate_visibility = scene->calculate_visibility,
|
|
};
|
|
|
|
struct pixman_box32 *region_box = pixman_region32_extents(update_region);
|
|
struct wlr_box box = {
|
|
.x = region_box->x1,
|
|
.y = region_box->y1,
|
|
.width = region_box->x2 - region_box->x1,
|
|
.height = region_box->y2 - region_box->y1,
|
|
};
|
|
|
|
// update node visibility and output enter/leave events
|
|
scene_nodes_in_box(&scene->tree.node, &box, scene_node_update_iterator, &data);
|
|
|
|
pixman_region32_fini(&visible);
|
|
}
|
|
|
|
static void scene_node_update(struct wlr_scene_node *node,
|
|
pixman_region32_t *damage) {
|
|
struct wlr_scene *scene = scene_node_get_root(node);
|
|
|
|
int x, y;
|
|
if (!wlr_scene_node_coords(node, &x, &y)) {
|
|
if (damage) {
|
|
scene_update_region(scene, damage);
|
|
scene_damage_outputs(scene, damage);
|
|
pixman_region32_fini(damage);
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
pixman_region32_t visible;
|
|
if (!damage) {
|
|
pixman_region32_init(&visible);
|
|
scene_node_visibility(node, &visible);
|
|
damage = &visible;
|
|
}
|
|
|
|
pixman_region32_t update_region;
|
|
pixman_region32_init(&update_region);
|
|
pixman_region32_copy(&update_region, damage);
|
|
scene_node_bounds(node, x, y, &update_region);
|
|
|
|
scene_update_region(scene, &update_region);
|
|
pixman_region32_fini(&update_region);
|
|
|
|
scene_node_visibility(node, damage);
|
|
scene_damage_outputs(scene, damage);
|
|
pixman_region32_fini(damage);
|
|
}
|
|
|
|
struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent,
|
|
int width, int height, const float color[static 4]) {
|
|
struct wlr_scene_rect *scene_rect = calloc(1, sizeof(*scene_rect));
|
|
if (scene_rect == NULL) {
|
|
return NULL;
|
|
}
|
|
assert(parent);
|
|
scene_node_init(&scene_rect->node, WLR_SCENE_NODE_RECT, parent);
|
|
|
|
scene_rect->width = width;
|
|
scene_rect->height = height;
|
|
memcpy(scene_rect->color, color, sizeof(scene_rect->color));
|
|
|
|
scene_node_update(&scene_rect->node, NULL);
|
|
|
|
return scene_rect;
|
|
}
|
|
|
|
void wlr_scene_rect_set_size(struct wlr_scene_rect *rect, int width, int height) {
|
|
if (rect->width == width && rect->height == height) {
|
|
return;
|
|
}
|
|
|
|
rect->width = width;
|
|
rect->height = height;
|
|
scene_node_update(&rect->node, NULL);
|
|
}
|
|
|
|
void wlr_scene_rect_set_color(struct wlr_scene_rect *rect, const float color[static 4]) {
|
|
if (memcmp(rect->color, color, sizeof(rect->color)) == 0) {
|
|
return;
|
|
}
|
|
|
|
memcpy(rect->color, color, sizeof(rect->color));
|
|
scene_node_update(&rect->node, NULL);
|
|
}
|
|
|
|
static void scene_buffer_handle_buffer_release(struct wl_listener *listener,
|
|
void *data) {
|
|
struct wlr_scene_buffer *scene_buffer =
|
|
wl_container_of(listener, scene_buffer, buffer_release);
|
|
scene_buffer_set_buffer(scene_buffer, NULL);
|
|
}
|
|
|
|
static void scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
|
|
struct wlr_buffer *buffer) {
|
|
wl_list_remove(&scene_buffer->buffer_release.link);
|
|
wl_list_init(&scene_buffer->buffer_release.link);
|
|
if (scene_buffer->own_buffer) {
|
|
wlr_buffer_unlock(scene_buffer->buffer);
|
|
}
|
|
scene_buffer->buffer = NULL;
|
|
scene_buffer->own_buffer = false;
|
|
scene_buffer->buffer_width = scene_buffer->buffer_height = 0;
|
|
scene_buffer->buffer_is_opaque = false;
|
|
|
|
if (!buffer) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->own_buffer = true;
|
|
scene_buffer->buffer = wlr_buffer_lock(buffer);
|
|
scene_buffer->buffer_width = buffer->width;
|
|
scene_buffer->buffer_height = buffer->height;
|
|
scene_buffer->buffer_is_opaque = buffer_is_opaque(buffer);
|
|
|
|
scene_buffer->buffer_release.notify = scene_buffer_handle_buffer_release;
|
|
wl_signal_add(&buffer->events.release, &scene_buffer->buffer_release);
|
|
}
|
|
|
|
struct wlr_scene_buffer *wlr_scene_buffer_create(struct wlr_scene_tree *parent,
|
|
struct wlr_buffer *buffer) {
|
|
struct wlr_scene_buffer *scene_buffer = calloc(1, sizeof(*scene_buffer));
|
|
if (scene_buffer == NULL) {
|
|
return NULL;
|
|
}
|
|
assert(parent);
|
|
scene_node_init(&scene_buffer->node, WLR_SCENE_NODE_BUFFER, parent);
|
|
|
|
wl_signal_init(&scene_buffer->events.outputs_update);
|
|
wl_signal_init(&scene_buffer->events.output_enter);
|
|
wl_signal_init(&scene_buffer->events.output_leave);
|
|
wl_signal_init(&scene_buffer->events.output_sample);
|
|
wl_signal_init(&scene_buffer->events.frame_done);
|
|
pixman_region32_init(&scene_buffer->opaque_region);
|
|
wl_list_init(&scene_buffer->buffer_release.link);
|
|
scene_buffer->opacity = 1;
|
|
|
|
scene_buffer_set_buffer(scene_buffer, buffer);
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
|
|
return scene_buffer;
|
|
}
|
|
|
|
void wlr_scene_buffer_set_buffer_with_damage(struct wlr_scene_buffer *scene_buffer,
|
|
struct wlr_buffer *buffer, const pixman_region32_t *damage) {
|
|
// specifying a region for a NULL buffer doesn't make sense. We need to know
|
|
// about the buffer to scale the buffer local coordinates down to scene
|
|
// coordinates.
|
|
assert(buffer || !damage);
|
|
|
|
bool mapped = buffer != NULL;
|
|
bool prev_mapped = scene_buffer->buffer != NULL || scene_buffer->texture != NULL;
|
|
|
|
if (!mapped && !prev_mapped) {
|
|
// unmapping already unmapped buffer - noop
|
|
return;
|
|
}
|
|
|
|
// if this node used to not be mapped or its previous displayed
|
|
// buffer region will be different from what the new buffer would
|
|
// produce we need to update the node.
|
|
bool update = mapped != prev_mapped;
|
|
if (buffer != NULL && scene_buffer->dst_width == 0 && scene_buffer->dst_height == 0) {
|
|
update = update || scene_buffer->buffer_width != buffer->width ||
|
|
scene_buffer->buffer_height != buffer->height;
|
|
}
|
|
|
|
wlr_texture_destroy(scene_buffer->texture);
|
|
scene_buffer->texture = NULL;
|
|
|
|
scene_buffer_set_buffer(scene_buffer, buffer);
|
|
|
|
if (update) {
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
// updating the node will already damage the whole node for us. Return
|
|
// early to not damage again
|
|
return;
|
|
}
|
|
|
|
int lx, ly;
|
|
if (!wlr_scene_node_coords(&scene_buffer->node, &lx, &ly)) {
|
|
return;
|
|
}
|
|
|
|
pixman_region32_t fallback_damage;
|
|
pixman_region32_init_rect(&fallback_damage, 0, 0, buffer->width, buffer->height);
|
|
if (!damage) {
|
|
damage = &fallback_damage;
|
|
}
|
|
|
|
struct wlr_fbox box = scene_buffer->src_box;
|
|
if (wlr_fbox_empty(&box)) {
|
|
box.x = 0;
|
|
box.y = 0;
|
|
box.width = buffer->width;
|
|
box.height = buffer->height;
|
|
}
|
|
|
|
wlr_fbox_transform(&box, &box, scene_buffer->transform,
|
|
buffer->width, buffer->height);
|
|
|
|
float scale_x, scale_y;
|
|
if (scene_buffer->dst_width || scene_buffer->dst_height) {
|
|
scale_x = scene_buffer->dst_width / box.width;
|
|
scale_y = scene_buffer->dst_height / box.height;
|
|
} else {
|
|
scale_x = buffer->width / box.width;
|
|
scale_y = buffer->height / box.height;
|
|
}
|
|
|
|
pixman_region32_t trans_damage;
|
|
pixman_region32_init(&trans_damage);
|
|
wlr_region_transform(&trans_damage, damage,
|
|
scene_buffer->transform, buffer->width, buffer->height);
|
|
pixman_region32_intersect_rect(&trans_damage, &trans_damage,
|
|
box.x, box.y, box.width, box.height);
|
|
pixman_region32_translate(&trans_damage, -box.x, -box.y);
|
|
|
|
struct wlr_scene *scene = scene_node_get_root(&scene_buffer->node);
|
|
struct wlr_scene_output *scene_output;
|
|
wl_list_for_each(scene_output, &scene->outputs, link) {
|
|
float output_scale = scene_output->output->scale;
|
|
float output_scale_x = output_scale * scale_x;
|
|
float output_scale_y = output_scale * scale_y;
|
|
pixman_region32_t output_damage;
|
|
pixman_region32_init(&output_damage);
|
|
wlr_region_scale_xy(&output_damage, &trans_damage,
|
|
output_scale_x, output_scale_y);
|
|
|
|
// One output pixel will match (buffer_scale_x)x(buffer_scale_y) buffer pixels.
|
|
// If the buffer is upscaled on the given axis (output_scale_* > 1.0,
|
|
// buffer_scale_* < 1.0), its contents will bleed into adjacent
|
|
// (ceil(output_scale_* / 2)) output pixels because of linear filtering.
|
|
// Additionally, if the buffer is downscaled (output_scale_* < 1.0,
|
|
// buffer_scale_* > 1.0), and one output pixel matches a non-integer number of
|
|
// buffer pixels, its contents will bleed into neighboring output pixels.
|
|
// Handle both cases by computing buffer_scale_{x,y} and checking if they are
|
|
// integer numbers; ceilf() is used to ensure that the distance is at least 1.
|
|
float buffer_scale_x = 1.0f / output_scale_x;
|
|
float buffer_scale_y = 1.0f / output_scale_y;
|
|
int dist_x = floor(buffer_scale_x) != buffer_scale_x ?
|
|
(int)ceilf(output_scale_x / 2.0f) : 0;
|
|
int dist_y = floor(buffer_scale_y) != buffer_scale_y ?
|
|
(int)ceilf(output_scale_y / 2.0f) : 0;
|
|
// TODO: expand with per-axis distances
|
|
wlr_region_expand(&output_damage, &output_damage,
|
|
dist_x >= dist_y ? dist_x : dist_y);
|
|
|
|
pixman_region32_t cull_region;
|
|
pixman_region32_init(&cull_region);
|
|
pixman_region32_copy(&cull_region, &scene_buffer->node.visible);
|
|
scale_output_damage(&cull_region, output_scale);
|
|
pixman_region32_translate(&cull_region, -lx * output_scale, -ly * output_scale);
|
|
pixman_region32_intersect(&output_damage, &output_damage, &cull_region);
|
|
pixman_region32_fini(&cull_region);
|
|
|
|
pixman_region32_translate(&output_damage,
|
|
(int)round((lx - scene_output->x) * output_scale),
|
|
(int)round((ly - scene_output->y) * output_scale));
|
|
if (wlr_damage_ring_add(&scene_output->damage_ring, &output_damage)) {
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
}
|
|
pixman_region32_fini(&output_damage);
|
|
}
|
|
|
|
pixman_region32_fini(&trans_damage);
|
|
pixman_region32_fini(&fallback_damage);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_buffer(struct wlr_scene_buffer *scene_buffer,
|
|
struct wlr_buffer *buffer) {
|
|
wlr_scene_buffer_set_buffer_with_damage(scene_buffer, buffer, NULL);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_opaque_region(struct wlr_scene_buffer *scene_buffer,
|
|
const pixman_region32_t *region) {
|
|
if (pixman_region32_equal(&scene_buffer->opaque_region, region)) {
|
|
return;
|
|
}
|
|
|
|
pixman_region32_copy(&scene_buffer->opaque_region, region);
|
|
|
|
int x, y;
|
|
if (!wlr_scene_node_coords(&scene_buffer->node, &x, &y)) {
|
|
return;
|
|
}
|
|
|
|
pixman_region32_t update_region;
|
|
pixman_region32_init(&update_region);
|
|
scene_node_bounds(&scene_buffer->node, x, y, &update_region);
|
|
scene_update_region(scene_node_get_root(&scene_buffer->node), &update_region);
|
|
pixman_region32_fini(&update_region);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_source_box(struct wlr_scene_buffer *scene_buffer,
|
|
const struct wlr_fbox *box) {
|
|
if (wlr_fbox_equal(&scene_buffer->src_box, box)) {
|
|
return;
|
|
}
|
|
|
|
if (box != NULL) {
|
|
scene_buffer->src_box = *box;
|
|
} else {
|
|
scene_buffer->src_box = (struct wlr_fbox){0};
|
|
}
|
|
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_dest_size(struct wlr_scene_buffer *scene_buffer,
|
|
int width, int height) {
|
|
if (scene_buffer->dst_width == width && scene_buffer->dst_height == height) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->dst_width = width;
|
|
scene_buffer->dst_height = height;
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_transform(struct wlr_scene_buffer *scene_buffer,
|
|
enum wl_output_transform transform) {
|
|
if (scene_buffer->transform == transform) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->transform = transform;
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
}
|
|
|
|
void wlr_scene_buffer_send_frame_done(struct wlr_scene_buffer *scene_buffer,
|
|
struct timespec *now) {
|
|
if (pixman_region32_not_empty(&scene_buffer->node.visible)) {
|
|
wl_signal_emit_mutable(&scene_buffer->events.frame_done, now);
|
|
}
|
|
}
|
|
|
|
void wlr_scene_buffer_set_opacity(struct wlr_scene_buffer *scene_buffer,
|
|
float opacity) {
|
|
if (scene_buffer->opacity == opacity) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->opacity = opacity;
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
}
|
|
|
|
void wlr_scene_buffer_set_filter_mode(struct wlr_scene_buffer *scene_buffer,
|
|
enum wlr_scale_filter_mode filter_mode) {
|
|
if (scene_buffer->filter_mode == filter_mode) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->filter_mode = filter_mode;
|
|
scene_node_update(&scene_buffer->node, NULL);
|
|
}
|
|
|
|
static struct wlr_texture *scene_buffer_get_texture(
|
|
struct wlr_scene_buffer *scene_buffer, struct wlr_renderer *renderer) {
|
|
if (scene_buffer->buffer == NULL || scene_buffer->texture != NULL) {
|
|
return scene_buffer->texture;
|
|
}
|
|
|
|
struct wlr_client_buffer *client_buffer =
|
|
wlr_client_buffer_get(scene_buffer->buffer);
|
|
if (client_buffer != NULL) {
|
|
return client_buffer->texture;
|
|
}
|
|
|
|
scene_buffer->texture =
|
|
wlr_texture_from_buffer(renderer, scene_buffer->buffer);
|
|
if (scene_buffer->texture != NULL && scene_buffer->own_buffer) {
|
|
scene_buffer->own_buffer = false;
|
|
wlr_buffer_unlock(scene_buffer->buffer);
|
|
}
|
|
return scene_buffer->texture;
|
|
}
|
|
|
|
static void scene_node_get_size(struct wlr_scene_node *node,
|
|
int *width, int *height) {
|
|
*width = 0;
|
|
*height = 0;
|
|
|
|
switch (node->type) {
|
|
case WLR_SCENE_NODE_TREE:
|
|
return;
|
|
case WLR_SCENE_NODE_RECT:;
|
|
struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
|
|
*width = scene_rect->width;
|
|
*height = scene_rect->height;
|
|
break;
|
|
case WLR_SCENE_NODE_BUFFER:;
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
if (scene_buffer->dst_width > 0 && scene_buffer->dst_height > 0) {
|
|
*width = scene_buffer->dst_width;
|
|
*height = scene_buffer->dst_height;
|
|
} else {
|
|
*width = scene_buffer->buffer_width;
|
|
*height = scene_buffer->buffer_height;
|
|
wlr_output_transform_coords(scene_buffer->transform, width, height);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void wlr_scene_node_set_enabled(struct wlr_scene_node *node, bool enabled) {
|
|
if (node->enabled == enabled) {
|
|
return;
|
|
}
|
|
|
|
int x, y;
|
|
pixman_region32_t visible;
|
|
pixman_region32_init(&visible);
|
|
if (wlr_scene_node_coords(node, &x, &y)) {
|
|
scene_node_visibility(node, &visible);
|
|
}
|
|
|
|
node->enabled = enabled;
|
|
|
|
scene_node_update(node, &visible);
|
|
}
|
|
|
|
void wlr_scene_node_set_position(struct wlr_scene_node *node, int x, int y) {
|
|
if (node->x == x && node->y == y) {
|
|
return;
|
|
}
|
|
|
|
node->x = x;
|
|
node->y = y;
|
|
scene_node_update(node, NULL);
|
|
}
|
|
|
|
void wlr_scene_node_place_above(struct wlr_scene_node *node,
|
|
struct wlr_scene_node *sibling) {
|
|
assert(node != sibling);
|
|
assert(node->parent == sibling->parent);
|
|
|
|
if (node->link.prev == &sibling->link) {
|
|
return;
|
|
}
|
|
|
|
wl_list_remove(&node->link);
|
|
wl_list_insert(&sibling->link, &node->link);
|
|
scene_node_update(node, NULL);
|
|
}
|
|
|
|
void wlr_scene_node_place_below(struct wlr_scene_node *node,
|
|
struct wlr_scene_node *sibling) {
|
|
assert(node != sibling);
|
|
assert(node->parent == sibling->parent);
|
|
|
|
if (node->link.next == &sibling->link) {
|
|
return;
|
|
}
|
|
|
|
wl_list_remove(&node->link);
|
|
wl_list_insert(sibling->link.prev, &node->link);
|
|
scene_node_update(node, NULL);
|
|
}
|
|
|
|
void wlr_scene_node_raise_to_top(struct wlr_scene_node *node) {
|
|
struct wlr_scene_node *current_top = wl_container_of(
|
|
node->parent->children.prev, current_top, link);
|
|
if (node == current_top) {
|
|
return;
|
|
}
|
|
wlr_scene_node_place_above(node, current_top);
|
|
}
|
|
|
|
void wlr_scene_node_lower_to_bottom(struct wlr_scene_node *node) {
|
|
struct wlr_scene_node *current_bottom = wl_container_of(
|
|
node->parent->children.next, current_bottom, link);
|
|
if (node == current_bottom) {
|
|
return;
|
|
}
|
|
wlr_scene_node_place_below(node, current_bottom);
|
|
}
|
|
|
|
void wlr_scene_node_reparent(struct wlr_scene_node *node,
|
|
struct wlr_scene_tree *new_parent) {
|
|
assert(new_parent != NULL);
|
|
|
|
if (node->parent == new_parent) {
|
|
return;
|
|
}
|
|
|
|
/* Ensure that a node cannot become its own ancestor */
|
|
for (struct wlr_scene_tree *ancestor = new_parent; ancestor != NULL;
|
|
ancestor = ancestor->node.parent) {
|
|
assert(&ancestor->node != node);
|
|
}
|
|
|
|
int x, y;
|
|
pixman_region32_t visible;
|
|
pixman_region32_init(&visible);
|
|
if (wlr_scene_node_coords(node, &x, &y)) {
|
|
scene_node_visibility(node, &visible);
|
|
}
|
|
|
|
wl_list_remove(&node->link);
|
|
node->parent = new_parent;
|
|
wl_list_insert(new_parent->children.prev, &node->link);
|
|
scene_node_update(node, &visible);
|
|
}
|
|
|
|
bool wlr_scene_node_coords(struct wlr_scene_node *node,
|
|
int *lx_ptr, int *ly_ptr) {
|
|
assert(node);
|
|
|
|
int lx = 0, ly = 0;
|
|
bool enabled = true;
|
|
while (true) {
|
|
lx += node->x;
|
|
ly += node->y;
|
|
enabled = enabled && node->enabled;
|
|
if (node->parent == NULL) {
|
|
break;
|
|
}
|
|
|
|
node = &node->parent->node;
|
|
}
|
|
|
|
*lx_ptr = lx;
|
|
*ly_ptr = ly;
|
|
return enabled;
|
|
}
|
|
|
|
static void scene_node_for_each_scene_buffer(struct wlr_scene_node *node,
|
|
int lx, int ly, wlr_scene_buffer_iterator_func_t user_iterator,
|
|
void *user_data) {
|
|
if (!node->enabled) {
|
|
return;
|
|
}
|
|
|
|
lx += node->x;
|
|
ly += node->y;
|
|
|
|
if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
user_iterator(scene_buffer, lx, ly, user_data);
|
|
} else if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_node_for_each_scene_buffer(child, lx, ly, user_iterator, user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wlr_scene_node_for_each_buffer(struct wlr_scene_node *node,
|
|
wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) {
|
|
scene_node_for_each_scene_buffer(node, 0, 0, user_iterator, user_data);
|
|
}
|
|
|
|
struct node_at_data {
|
|
double lx, ly;
|
|
double rx, ry;
|
|
struct wlr_scene_node *node;
|
|
};
|
|
|
|
static bool scene_node_at_iterator(struct wlr_scene_node *node,
|
|
int lx, int ly, void *data) {
|
|
struct node_at_data *at_data = data;
|
|
|
|
double rx = at_data->lx - lx;
|
|
double ry = at_data->ly - ly;
|
|
|
|
if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
if (scene_buffer->point_accepts_input &&
|
|
!scene_buffer->point_accepts_input(scene_buffer, &rx, &ry)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
at_data->rx = rx;
|
|
at_data->ry = ry;
|
|
at_data->node = node;
|
|
return true;
|
|
}
|
|
|
|
struct wlr_scene_node *wlr_scene_node_at(struct wlr_scene_node *node,
|
|
double lx, double ly, double *nx, double *ny) {
|
|
struct wlr_box box = {
|
|
.x = floor(lx),
|
|
.y = floor(ly),
|
|
.width = 1,
|
|
.height = 1
|
|
};
|
|
|
|
struct node_at_data data = {
|
|
.lx = lx,
|
|
.ly = ly
|
|
};
|
|
|
|
if (scene_nodes_in_box(node, &box, scene_node_at_iterator, &data)) {
|
|
if (nx) {
|
|
*nx = data.rx;
|
|
}
|
|
if (ny) {
|
|
*ny = data.ry;
|
|
}
|
|
return data.node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct render_list_entry {
|
|
struct wlr_scene_node *node;
|
|
bool sent_dmabuf_feedback;
|
|
int x, y;
|
|
};
|
|
|
|
static void scene_entry_render(struct render_list_entry *entry, const struct render_data *data) {
|
|
struct wlr_scene_node *node = entry->node;
|
|
|
|
pixman_region32_t render_region;
|
|
pixman_region32_init(&render_region);
|
|
pixman_region32_copy(&render_region, &node->visible);
|
|
pixman_region32_translate(&render_region, -data->logical.x, -data->logical.y);
|
|
scale_output_damage(&render_region, data->scale);
|
|
pixman_region32_intersect(&render_region, &render_region, &data->damage);
|
|
if (!pixman_region32_not_empty(&render_region)) {
|
|
pixman_region32_fini(&render_region);
|
|
return;
|
|
}
|
|
|
|
struct wlr_box dst_box = {
|
|
.x = entry->x - data->logical.x,
|
|
.y = entry->y - data->logical.y,
|
|
};
|
|
scene_node_get_size(node, &dst_box.width, &dst_box.height);
|
|
scale_box(&dst_box, data->scale);
|
|
|
|
pixman_region32_t opaque;
|
|
pixman_region32_init(&opaque);
|
|
scene_node_opaque_region(node, dst_box.x, dst_box.y, &opaque);
|
|
scale_output_damage(&opaque, data->scale);
|
|
pixman_region32_subtract(&opaque, &render_region, &opaque);
|
|
|
|
transform_output_box(&dst_box, data);
|
|
transform_output_damage(&render_region, data);
|
|
|
|
switch (node->type) {
|
|
case WLR_SCENE_NODE_TREE:
|
|
assert(false);
|
|
break;
|
|
case WLR_SCENE_NODE_RECT:;
|
|
struct wlr_scene_rect *scene_rect = wlr_scene_rect_from_node(node);
|
|
|
|
wlr_render_pass_add_rect(data->render_pass, &(struct wlr_render_rect_options){
|
|
.box = dst_box,
|
|
.color = {
|
|
.r = scene_rect->color[0],
|
|
.g = scene_rect->color[1],
|
|
.b = scene_rect->color[2],
|
|
.a = scene_rect->color[3],
|
|
},
|
|
.clip = &render_region,
|
|
});
|
|
break;
|
|
case WLR_SCENE_NODE_BUFFER:;
|
|
struct wlr_scene_buffer *scene_buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
struct wlr_texture *texture = scene_buffer_get_texture(scene_buffer,
|
|
data->output->output->renderer);
|
|
if (texture == NULL) {
|
|
break;
|
|
}
|
|
|
|
enum wl_output_transform transform =
|
|
wlr_output_transform_invert(scene_buffer->transform);
|
|
transform = wlr_output_transform_compose(transform, data->transform);
|
|
|
|
wlr_render_pass_add_texture(data->render_pass, &(struct wlr_render_texture_options) {
|
|
.texture = texture,
|
|
.src_box = scene_buffer->src_box,
|
|
.dst_box = dst_box,
|
|
.transform = transform,
|
|
.clip = &render_region,
|
|
.alpha = &scene_buffer->opacity,
|
|
.filter_mode = scene_buffer->filter_mode,
|
|
.blend_mode = pixman_region32_not_empty(&opaque) ?
|
|
WLR_RENDER_BLEND_MODE_PREMULTIPLIED : WLR_RENDER_BLEND_MODE_NONE,
|
|
});
|
|
|
|
struct wlr_scene_output_sample_event sample_event = {
|
|
.output = data->output,
|
|
.direct_scanout = false,
|
|
};
|
|
wl_signal_emit_mutable(&scene_buffer->events.output_sample, &sample_event);
|
|
break;
|
|
}
|
|
|
|
pixman_region32_fini(&opaque);
|
|
pixman_region32_fini(&render_region);
|
|
}
|
|
|
|
static void scene_handle_linux_dmabuf_v1_destroy(struct wl_listener *listener,
|
|
void *data) {
|
|
struct wlr_scene *scene =
|
|
wl_container_of(listener, scene, linux_dmabuf_v1_destroy);
|
|
wl_list_remove(&scene->linux_dmabuf_v1_destroy.link);
|
|
wl_list_init(&scene->linux_dmabuf_v1_destroy.link);
|
|
scene->linux_dmabuf_v1 = NULL;
|
|
}
|
|
|
|
void wlr_scene_set_linux_dmabuf_v1(struct wlr_scene *scene,
|
|
struct wlr_linux_dmabuf_v1 *linux_dmabuf_v1) {
|
|
assert(scene->linux_dmabuf_v1 == NULL);
|
|
scene->linux_dmabuf_v1 = linux_dmabuf_v1;
|
|
scene->linux_dmabuf_v1_destroy.notify = scene_handle_linux_dmabuf_v1_destroy;
|
|
wl_signal_add(&linux_dmabuf_v1->events.destroy, &scene->linux_dmabuf_v1_destroy);
|
|
}
|
|
|
|
static void scene_output_handle_destroy(struct wlr_addon *addon) {
|
|
struct wlr_scene_output *scene_output =
|
|
wl_container_of(addon, scene_output, addon);
|
|
wlr_scene_output_destroy(scene_output);
|
|
}
|
|
|
|
static const struct wlr_addon_interface output_addon_impl = {
|
|
.name = "wlr_scene_output",
|
|
.destroy = scene_output_handle_destroy,
|
|
};
|
|
|
|
static void scene_node_output_update(struct wlr_scene_node *node,
|
|
struct wl_list *outputs, struct wlr_scene_output *ignore,
|
|
struct wlr_scene_output *force) {
|
|
if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_node_output_update(child, outputs, ignore, force);
|
|
}
|
|
return;
|
|
}
|
|
|
|
update_node_update_outputs(node, outputs, ignore, force);
|
|
}
|
|
|
|
static void scene_output_update_geometry(struct wlr_scene_output *scene_output,
|
|
bool force_update) {
|
|
wlr_damage_ring_add_whole(&scene_output->damage_ring);
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
|
|
scene_node_output_update(&scene_output->scene->tree.node,
|
|
&scene_output->scene->outputs, NULL, force_update ? scene_output : NULL);
|
|
}
|
|
|
|
static void scene_output_handle_commit(struct wl_listener *listener, void *data) {
|
|
struct wlr_scene_output *scene_output = wl_container_of(listener,
|
|
scene_output, output_commit);
|
|
struct wlr_output_event_commit *event = data;
|
|
const struct wlr_output_state *state = event->state;
|
|
|
|
bool force_update = state->committed & (
|
|
WLR_OUTPUT_STATE_TRANSFORM |
|
|
WLR_OUTPUT_STATE_SCALE |
|
|
WLR_OUTPUT_STATE_SUBPIXEL);
|
|
|
|
if (force_update || state->committed & (WLR_OUTPUT_STATE_MODE |
|
|
WLR_OUTPUT_STATE_ENABLED)) {
|
|
scene_output_update_geometry(scene_output, force_update);
|
|
}
|
|
|
|
// if the output has been committed with a certain damage, we know that region
|
|
// will be acknowledged by the backend so we don't need to keep track of it
|
|
// anymore
|
|
if (state->committed & WLR_OUTPUT_STATE_DAMAGE) {
|
|
bool tracking_buffer = false;
|
|
struct wlr_damage_ring_buffer *buffer;
|
|
wl_list_for_each(buffer, &scene_output->damage_ring.buffers, link) {
|
|
if (buffer->buffer == state->buffer) {
|
|
tracking_buffer = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (tracking_buffer) {
|
|
pixman_region32_subtract(&scene_output->pending_commit_damage,
|
|
&scene_output->pending_commit_damage, &state->damage);
|
|
} else {
|
|
pixman_region32_union(&scene_output->pending_commit_damage,
|
|
&scene_output->pending_commit_damage, &state->damage);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void scene_output_handle_damage(struct wl_listener *listener, void *data) {
|
|
struct wlr_scene_output *scene_output = wl_container_of(listener,
|
|
scene_output, output_damage);
|
|
struct wlr_output_event_damage *event = data;
|
|
if (wlr_damage_ring_add(&scene_output->damage_ring, event->damage)) {
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
}
|
|
}
|
|
|
|
static void scene_output_handle_needs_frame(struct wl_listener *listener, void *data) {
|
|
struct wlr_scene_output *scene_output = wl_container_of(listener,
|
|
scene_output, output_needs_frame);
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
}
|
|
|
|
struct wlr_scene_output *wlr_scene_output_create(struct wlr_scene *scene,
|
|
struct wlr_output *output) {
|
|
struct wlr_scene_output *scene_output = calloc(1, sizeof(*scene_output));
|
|
if (scene_output == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
scene_output->output = output;
|
|
scene_output->scene = scene;
|
|
wlr_addon_init(&scene_output->addon, &output->addons, scene, &output_addon_impl);
|
|
|
|
wlr_damage_ring_init(&scene_output->damage_ring);
|
|
pixman_region32_init(&scene_output->pending_commit_damage);
|
|
wl_list_init(&scene_output->damage_highlight_regions);
|
|
|
|
int prev_output_index = -1;
|
|
struct wl_list *prev_output_link = &scene->outputs;
|
|
|
|
struct wlr_scene_output *current_output;
|
|
wl_list_for_each(current_output, &scene->outputs, link) {
|
|
if (prev_output_index + 1 != current_output->index) {
|
|
break;
|
|
}
|
|
|
|
prev_output_index = current_output->index;
|
|
prev_output_link = ¤t_output->link;
|
|
}
|
|
|
|
scene_output->index = prev_output_index + 1;
|
|
assert(scene_output->index < 64);
|
|
wl_list_insert(prev_output_link, &scene_output->link);
|
|
|
|
wl_signal_init(&scene_output->events.destroy);
|
|
|
|
scene_output->output_commit.notify = scene_output_handle_commit;
|
|
wl_signal_add(&output->events.commit, &scene_output->output_commit);
|
|
|
|
scene_output->output_damage.notify = scene_output_handle_damage;
|
|
wl_signal_add(&output->events.damage, &scene_output->output_damage);
|
|
|
|
scene_output->output_needs_frame.notify = scene_output_handle_needs_frame;
|
|
wl_signal_add(&output->events.needs_frame, &scene_output->output_needs_frame);
|
|
|
|
scene_output_update_geometry(scene_output, false);
|
|
|
|
return scene_output;
|
|
}
|
|
|
|
static void highlight_region_destroy(struct highlight_region *damage) {
|
|
wl_list_remove(&damage->link);
|
|
pixman_region32_fini(&damage->region);
|
|
free(damage);
|
|
}
|
|
|
|
void wlr_scene_output_destroy(struct wlr_scene_output *scene_output) {
|
|
if (scene_output == NULL) {
|
|
return;
|
|
}
|
|
|
|
wl_signal_emit_mutable(&scene_output->events.destroy, NULL);
|
|
|
|
scene_node_output_update(&scene_output->scene->tree.node,
|
|
&scene_output->scene->outputs, scene_output, NULL);
|
|
|
|
struct highlight_region *damage, *tmp_damage;
|
|
wl_list_for_each_safe(damage, tmp_damage, &scene_output->damage_highlight_regions, link) {
|
|
highlight_region_destroy(damage);
|
|
}
|
|
|
|
wlr_addon_finish(&scene_output->addon);
|
|
wlr_damage_ring_finish(&scene_output->damage_ring);
|
|
pixman_region32_fini(&scene_output->pending_commit_damage);
|
|
wl_list_remove(&scene_output->link);
|
|
wl_list_remove(&scene_output->output_commit.link);
|
|
wl_list_remove(&scene_output->output_damage.link);
|
|
wl_list_remove(&scene_output->output_needs_frame.link);
|
|
|
|
wl_array_release(&scene_output->render_list);
|
|
free(scene_output);
|
|
}
|
|
|
|
struct wlr_scene_output *wlr_scene_get_scene_output(struct wlr_scene *scene,
|
|
struct wlr_output *output) {
|
|
struct wlr_addon *addon =
|
|
wlr_addon_find(&output->addons, scene, &output_addon_impl);
|
|
if (addon == NULL) {
|
|
return NULL;
|
|
}
|
|
struct wlr_scene_output *scene_output =
|
|
wl_container_of(addon, scene_output, addon);
|
|
return scene_output;
|
|
}
|
|
|
|
void wlr_scene_output_set_position(struct wlr_scene_output *scene_output,
|
|
int lx, int ly) {
|
|
if (scene_output->x == lx && scene_output->y == ly) {
|
|
return;
|
|
}
|
|
|
|
scene_output->x = lx;
|
|
scene_output->y = ly;
|
|
|
|
scene_output_update_geometry(scene_output, false);
|
|
}
|
|
|
|
static bool scene_node_invisible(struct wlr_scene_node *node) {
|
|
if (node->type == WLR_SCENE_NODE_TREE) {
|
|
return true;
|
|
} else if (node->type == WLR_SCENE_NODE_RECT) {
|
|
struct wlr_scene_rect *rect = wlr_scene_rect_from_node(node);
|
|
|
|
return rect->color[3] == 0.f;
|
|
} else if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
|
|
|
|
return buffer->buffer == NULL && buffer->texture == 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;
|
|
}
|
|
|
|
// 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) {
|
|
struct wlr_scene_rect *rect = wlr_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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (!pixman_region32_not_empty(&intersection)) {
|
|
pixman_region32_fini(&intersection);
|
|
return false;
|
|
}
|
|
|
|
pixman_region32_fini(&intersection);
|
|
|
|
struct render_list_entry *entry = wl_array_add(data->render_list, sizeof(*entry));
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
|
|
*entry = (struct render_list_entry){
|
|
.node = node,
|
|
.x = lx,
|
|
.y = ly,
|
|
};
|
|
|
|
return false;
|
|
}
|
|
|
|
static void output_state_apply_damage(const struct render_data *data,
|
|
struct wlr_output_state *state) {
|
|
struct wlr_scene_output *output = data->output;
|
|
|
|
pixman_region32_t frame_damage;
|
|
pixman_region32_init(&frame_damage);
|
|
pixman_region32_copy(&frame_damage, &output->damage_ring.current);
|
|
transform_output_damage(&frame_damage, data);
|
|
pixman_region32_union(&output->pending_commit_damage,
|
|
&output->pending_commit_damage, &frame_damage);
|
|
pixman_region32_fini(&frame_damage);
|
|
|
|
wlr_output_state_set_damage(state, &output->pending_commit_damage);
|
|
}
|
|
|
|
static void scene_buffer_send_dmabuf_feedback(const struct wlr_scene *scene,
|
|
struct wlr_scene_buffer *scene_buffer,
|
|
const struct wlr_linux_dmabuf_feedback_v1_init_options *options) {
|
|
if (!scene->linux_dmabuf_v1) {
|
|
return;
|
|
}
|
|
|
|
struct wlr_scene_surface *surface = wlr_scene_surface_try_from_buffer(scene_buffer);
|
|
if (!surface) {
|
|
return;
|
|
}
|
|
|
|
// compare to the previous options so that we don't send
|
|
// duplicate feedback events.
|
|
if (memcmp(options, &scene_buffer->prev_feedback_options, sizeof(*options)) == 0) {
|
|
return;
|
|
}
|
|
|
|
scene_buffer->prev_feedback_options = *options;
|
|
|
|
struct wlr_linux_dmabuf_feedback_v1 feedback = {0};
|
|
if (!wlr_linux_dmabuf_feedback_v1_init_with_options(&feedback, options)) {
|
|
return;
|
|
}
|
|
|
|
wlr_linux_dmabuf_v1_set_surface_feedback(scene->linux_dmabuf_v1,
|
|
surface->surface, &feedback);
|
|
|
|
wlr_linux_dmabuf_feedback_v1_finish(&feedback);
|
|
}
|
|
|
|
static bool scene_entry_try_direct_scanout(struct render_list_entry *entry,
|
|
struct wlr_output_state *state, const struct render_data *data) {
|
|
struct wlr_scene_output *scene_output = data->output;
|
|
struct wlr_scene_node *node = entry->node;
|
|
|
|
if (!scene_output->scene->direct_scanout) {
|
|
return false;
|
|
}
|
|
|
|
if (scene_output->scene->debug_damage_option ==
|
|
WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
|
|
// We don't want to enter direct scan out if we have highlight regions
|
|
// enabled. Otherwise, we won't be able to render the damage regions.
|
|
return false;
|
|
}
|
|
|
|
if (node->type != WLR_SCENE_NODE_BUFFER) {
|
|
return false;
|
|
}
|
|
|
|
if (state->committed & (WLR_OUTPUT_STATE_MODE |
|
|
WLR_OUTPUT_STATE_ENABLED |
|
|
WLR_OUTPUT_STATE_RENDER_FORMAT)) {
|
|
// Legacy DRM will explode if we try to modeset with a direct scanout buffer
|
|
return false;
|
|
}
|
|
|
|
if (!wlr_output_is_direct_scanout_allowed(scene_output->output)) {
|
|
return false;
|
|
}
|
|
|
|
struct wlr_scene_buffer *buffer = wlr_scene_buffer_from_node(node);
|
|
if (buffer->buffer == NULL) {
|
|
return false;
|
|
}
|
|
|
|
int default_width = buffer->buffer->width;
|
|
int default_height = buffer->buffer->height;
|
|
wlr_output_transform_coords(buffer->transform, &default_width, &default_height);
|
|
struct wlr_fbox default_box = {
|
|
.width = default_width,
|
|
.height = default_height,
|
|
};
|
|
|
|
if (!wlr_fbox_empty(&buffer->src_box) &&
|
|
!wlr_fbox_equal(&buffer->src_box, &default_box)) {
|
|
return false;
|
|
}
|
|
|
|
if (buffer->transform != data->transform) {
|
|
return false;
|
|
}
|
|
|
|
struct wlr_box node_box = { .x = entry->x, .y = entry->y };
|
|
scene_node_get_size(node, &node_box.width, &node_box.height);
|
|
|
|
if (!wlr_box_equal(&data->logical, &node_box)) {
|
|
return false;
|
|
}
|
|
|
|
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_dmabuf_feedback = true;
|
|
}
|
|
|
|
struct wlr_output_state pending;
|
|
wlr_output_state_init(&pending);
|
|
if (!wlr_output_state_copy(&pending, state)) {
|
|
return false;
|
|
}
|
|
|
|
wlr_output_state_set_buffer(&pending, buffer->buffer);
|
|
|
|
if (!wlr_output_test_state(scene_output->output, &pending)) {
|
|
wlr_output_state_finish(&pending);
|
|
return false;
|
|
}
|
|
|
|
wlr_output_state_copy(state, &pending);
|
|
wlr_output_state_finish(&pending);
|
|
|
|
struct wlr_scene_output_sample_event sample_event = {
|
|
.output = scene_output,
|
|
.direct_scanout = true,
|
|
};
|
|
wl_signal_emit_mutable(&buffer->events.output_sample, &sample_event);
|
|
return true;
|
|
}
|
|
|
|
bool wlr_scene_output_commit(struct wlr_scene_output *scene_output,
|
|
const struct wlr_scene_output_state_options *options) {
|
|
if (!scene_output->output->needs_frame && !pixman_region32_not_empty(
|
|
&scene_output->pending_commit_damage)) {
|
|
return true;
|
|
}
|
|
|
|
bool ok = false;
|
|
struct wlr_output_state state;
|
|
wlr_output_state_init(&state);
|
|
if (!wlr_scene_output_build_state(scene_output, &state, options)) {
|
|
goto out;
|
|
}
|
|
|
|
ok = wlr_output_commit_state(scene_output->output, &state);
|
|
if (!ok) {
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
wlr_output_state_finish(&state);
|
|
return ok;
|
|
}
|
|
|
|
bool wlr_scene_output_build_state(struct wlr_scene_output *scene_output,
|
|
struct wlr_output_state *state, const struct wlr_scene_output_state_options *options) {
|
|
struct wlr_scene_output_state_options default_options = {0};
|
|
if (!options) {
|
|
options = &default_options;
|
|
}
|
|
struct wlr_scene_timer *timer = options->timer;
|
|
struct timespec start_time;
|
|
if (timer) {
|
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
|
wlr_scene_timer_finish(timer);
|
|
*timer = (struct wlr_scene_timer){0};
|
|
}
|
|
|
|
if ((state->committed & WLR_OUTPUT_STATE_ENABLED) && !state->enabled) {
|
|
// if the state is being disabled, do nothing.
|
|
return true;
|
|
}
|
|
|
|
struct wlr_output *output = scene_output->output;
|
|
enum wlr_scene_debug_damage_option debug_damage =
|
|
scene_output->scene->debug_damage_option;
|
|
|
|
struct render_data render_data = {
|
|
.transform = output->transform,
|
|
.scale = output->scale,
|
|
.logical = { .x = scene_output->x, .y = scene_output->y },
|
|
.output = scene_output,
|
|
};
|
|
|
|
int resolution_width, resolution_height;
|
|
output_pending_resolution(output, state,
|
|
&resolution_width, &resolution_height);
|
|
|
|
if (state->committed & WLR_OUTPUT_STATE_TRANSFORM) {
|
|
if (render_data.transform != state->transform) {
|
|
wlr_damage_ring_add_whole(&scene_output->damage_ring);
|
|
}
|
|
|
|
render_data.transform = state->transform;
|
|
}
|
|
|
|
if (state->committed & WLR_OUTPUT_STATE_SCALE) {
|
|
if (render_data.scale != state->scale) {
|
|
wlr_damage_ring_add_whole(&scene_output->damage_ring);
|
|
}
|
|
|
|
render_data.scale = state->scale;
|
|
}
|
|
|
|
render_data.trans_width = resolution_width;
|
|
render_data.trans_height = resolution_height;
|
|
wlr_output_transform_coords(render_data.transform,
|
|
&render_data.trans_width, &render_data.trans_height);
|
|
|
|
render_data.logical.width = render_data.trans_width / render_data.scale;
|
|
render_data.logical.height = render_data.trans_height / render_data.scale;
|
|
|
|
struct render_list_constructor_data list_con = {
|
|
.box = render_data.logical,
|
|
.render_list = &scene_output->render_list,
|
|
.calculate_visibility = scene_output->scene->calculate_visibility,
|
|
};
|
|
|
|
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 render_list_entry *list_data = list_con.render_list->data;
|
|
int list_len = list_con.render_list->size / sizeof(*list_data);
|
|
|
|
if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_RERENDER) {
|
|
wlr_damage_ring_add_whole(&scene_output->damage_ring);
|
|
}
|
|
|
|
output_state_apply_damage(&render_data, state);
|
|
|
|
bool scanout = list_len == 1 &&
|
|
scene_entry_try_direct_scanout(&list_data[0], state, &render_data);
|
|
|
|
if (scene_output->prev_scanout != scanout) {
|
|
scene_output->prev_scanout = scanout;
|
|
wlr_log(WLR_DEBUG, "Direct scan-out %s",
|
|
scanout ? "enabled" : "disabled");
|
|
}
|
|
|
|
if (scanout) {
|
|
if (timer) {
|
|
struct timespec end_time, duration;
|
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
timespec_sub(&duration, &end_time, &start_time);
|
|
timer->pre_render_duration = timespec_to_nsec(&duration);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
struct timespec now;
|
|
if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
|
|
struct wl_list *regions = &scene_output->damage_highlight_regions;
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
// add the current frame's damage if there is damage
|
|
if (pixman_region32_not_empty(&scene_output->damage_ring.current)) {
|
|
struct highlight_region *current_damage = calloc(1, sizeof(*current_damage));
|
|
if (current_damage) {
|
|
pixman_region32_init(¤t_damage->region);
|
|
pixman_region32_copy(¤t_damage->region,
|
|
&scene_output->damage_ring.current);
|
|
current_damage->when = now;
|
|
wl_list_insert(regions, ¤t_damage->link);
|
|
}
|
|
}
|
|
|
|
pixman_region32_t acc_damage;
|
|
pixman_region32_init(&acc_damage);
|
|
struct highlight_region *damage, *tmp_damage;
|
|
wl_list_for_each_safe(damage, tmp_damage, regions, link) {
|
|
// remove overlaping damage regions
|
|
pixman_region32_subtract(&damage->region, &damage->region, &acc_damage);
|
|
pixman_region32_union(&acc_damage, &acc_damage, &damage->region);
|
|
|
|
// if this damage is too old or has nothing in it, get rid of it
|
|
struct timespec time_diff;
|
|
timespec_sub(&time_diff, &now, &damage->when);
|
|
if (timespec_to_msec(&time_diff) >= HIGHLIGHT_DAMAGE_FADEOUT_TIME ||
|
|
!pixman_region32_not_empty(&damage->region)) {
|
|
highlight_region_destroy(damage);
|
|
}
|
|
}
|
|
|
|
wlr_damage_ring_add(&scene_output->damage_ring, &acc_damage);
|
|
pixman_region32_fini(&acc_damage);
|
|
}
|
|
|
|
wlr_damage_ring_set_bounds(&scene_output->damage_ring,
|
|
render_data.trans_width, render_data.trans_height);
|
|
|
|
struct wlr_swapchain *swapchain = options->swapchain;
|
|
if (!swapchain) {
|
|
if (!wlr_output_configure_primary_swapchain(output, state, &output->swapchain)) {
|
|
return false;
|
|
}
|
|
|
|
swapchain = output->swapchain;
|
|
}
|
|
|
|
struct wlr_buffer *buffer = wlr_swapchain_acquire(swapchain, NULL);
|
|
if (buffer == NULL) {
|
|
return false;
|
|
}
|
|
|
|
assert(buffer->width == resolution_width && buffer->height == resolution_height);
|
|
|
|
if (timer) {
|
|
timer->render_timer = wlr_render_timer_create(output->renderer);
|
|
|
|
struct timespec end_time, duration;
|
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
|
timespec_sub(&duration, &end_time, &start_time);
|
|
timer->pre_render_duration = timespec_to_nsec(&duration);
|
|
}
|
|
|
|
struct wlr_render_pass *render_pass = wlr_renderer_begin_buffer_pass(output->renderer, buffer,
|
|
&(struct wlr_buffer_pass_options){
|
|
.timer = timer ? timer->render_timer : NULL,
|
|
});
|
|
if (render_pass == NULL) {
|
|
wlr_buffer_unlock(buffer);
|
|
return false;
|
|
}
|
|
|
|
render_data.render_pass = render_pass;
|
|
|
|
pixman_region32_init(&render_data.damage);
|
|
wlr_damage_ring_rotate_buffer(&scene_output->damage_ring, buffer,
|
|
&render_data.damage);
|
|
|
|
pixman_region32_t background;
|
|
pixman_region32_init(&background);
|
|
pixman_region32_copy(&background, &render_data.damage);
|
|
|
|
// Cull areas of the background that are occluded by opaque regions of
|
|
// scene nodes above. Those scene nodes will just render atop having us
|
|
// never see the background.
|
|
if (scene_output->scene->calculate_visibility) {
|
|
for (int i = list_len - 1; i >= 0; i--) {
|
|
struct render_list_entry *entry = &list_data[i];
|
|
|
|
// We must only cull opaque regions that are visible by the node.
|
|
// The node's visibility will have the knowledge of a black rect
|
|
// that may have been omitted from the render list via the black
|
|
// rect optimization. In order to ensure we don't cull background
|
|
// rendering in that black rect region, consider the node's visibility.
|
|
pixman_region32_t opaque;
|
|
pixman_region32_init(&opaque);
|
|
scene_node_opaque_region(entry->node, entry->x, entry->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, render_data.scale);
|
|
pixman_region32_subtract(&background, &background, &opaque);
|
|
pixman_region32_fini(&opaque);
|
|
}
|
|
|
|
if (floor(render_data.scale) != render_data.scale) {
|
|
wlr_region_expand(&background, &background, 1);
|
|
|
|
// reintersect with the damage because we never want to render
|
|
// outside of the damage region
|
|
pixman_region32_intersect(&background, &background, &render_data.damage);
|
|
}
|
|
}
|
|
|
|
transform_output_damage(&background, &render_data);
|
|
wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){
|
|
.box = { .width = buffer->width, .height = buffer->height },
|
|
.color = { .r = 0, .g = 0, .b = 0, .a = 1 },
|
|
.clip = &background,
|
|
});
|
|
pixman_region32_fini(&background);
|
|
|
|
for (int i = list_len - 1; i >= 0; i--) {
|
|
struct render_list_entry *entry = &list_data[i];
|
|
scene_entry_render(entry, &render_data);
|
|
|
|
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_dmabuf_feedback) {
|
|
struct wlr_linux_dmabuf_feedback_v1_init_options options = {
|
|
.main_renderer = output->renderer,
|
|
.scanout_primary_output = NULL,
|
|
};
|
|
|
|
scene_buffer_send_dmabuf_feedback(scene_output->scene, buffer, &options);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT) {
|
|
struct highlight_region *damage;
|
|
wl_list_for_each(damage, &scene_output->damage_highlight_regions, link) {
|
|
struct timespec time_diff;
|
|
timespec_sub(&time_diff, &now, &damage->when);
|
|
int64_t time_diff_ms = timespec_to_msec(&time_diff);
|
|
float alpha = 1.0 - (double)time_diff_ms / HIGHLIGHT_DAMAGE_FADEOUT_TIME;
|
|
|
|
wlr_render_pass_add_rect(render_pass, &(struct wlr_render_rect_options){
|
|
.box = { .width = buffer->width, .height = buffer->height },
|
|
.color = { .r = alpha * 0.5, .g = 0, .b = 0, .a = alpha * 0.5 },
|
|
.clip = &damage->region,
|
|
});
|
|
}
|
|
}
|
|
|
|
wlr_output_add_software_cursors_to_render_pass(output, render_pass, &render_data.damage);
|
|
|
|
pixman_region32_fini(&render_data.damage);
|
|
|
|
if (!wlr_render_pass_submit(render_pass)) {
|
|
wlr_buffer_unlock(buffer);
|
|
|
|
// if we failed to render the buffer, it will have undefined contents
|
|
// Trash the damage ring
|
|
wlr_damage_ring_add_whole(&scene_output->damage_ring);
|
|
return false;
|
|
}
|
|
|
|
wlr_output_state_set_buffer(state, buffer);
|
|
wlr_buffer_unlock(buffer);
|
|
|
|
if (debug_damage == WLR_SCENE_DEBUG_DAMAGE_HIGHLIGHT &&
|
|
!wl_list_empty(&scene_output->damage_highlight_regions)) {
|
|
wlr_output_schedule_frame(scene_output->output);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
int64_t wlr_scene_timer_get_duration_ns(struct wlr_scene_timer *timer) {
|
|
int64_t pre_render = timer->pre_render_duration;
|
|
if (!timer->render_timer) {
|
|
return pre_render;
|
|
}
|
|
int64_t render = wlr_render_timer_get_duration_ns(timer->render_timer);
|
|
return render != -1 ? pre_render + render : -1;
|
|
}
|
|
|
|
void wlr_scene_timer_finish(struct wlr_scene_timer *timer) {
|
|
if (timer->render_timer) {
|
|
wlr_render_timer_destroy(timer->render_timer);
|
|
}
|
|
}
|
|
|
|
static void scene_node_send_frame_done(struct wlr_scene_node *node,
|
|
struct wlr_scene_output *scene_output, struct timespec *now) {
|
|
if (!node->enabled) {
|
|
return;
|
|
}
|
|
|
|
if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_scene_buffer *scene_buffer =
|
|
wlr_scene_buffer_from_node(node);
|
|
|
|
if (scene_buffer->primary_output == scene_output) {
|
|
wlr_scene_buffer_send_frame_done(scene_buffer, now);
|
|
}
|
|
} else if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_node_send_frame_done(child, scene_output, now);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wlr_scene_output_send_frame_done(struct wlr_scene_output *scene_output,
|
|
struct timespec *now) {
|
|
scene_node_send_frame_done(&scene_output->scene->tree.node,
|
|
scene_output, now);
|
|
}
|
|
|
|
static void scene_output_for_each_scene_buffer(const struct wlr_box *output_box,
|
|
struct wlr_scene_node *node, int lx, int ly,
|
|
wlr_scene_buffer_iterator_func_t user_iterator, void *user_data) {
|
|
if (!node->enabled) {
|
|
return;
|
|
}
|
|
|
|
lx += node->x;
|
|
ly += node->y;
|
|
|
|
if (node->type == WLR_SCENE_NODE_BUFFER) {
|
|
struct wlr_box node_box = { .x = lx, .y = ly };
|
|
scene_node_get_size(node, &node_box.width, &node_box.height);
|
|
|
|
struct wlr_box intersection;
|
|
if (wlr_box_intersection(&intersection, output_box, &node_box)) {
|
|
struct wlr_scene_buffer *scene_buffer =
|
|
wlr_scene_buffer_from_node(node);
|
|
user_iterator(scene_buffer, lx, ly, user_data);
|
|
}
|
|
} else if (node->type == WLR_SCENE_NODE_TREE) {
|
|
struct wlr_scene_tree *scene_tree = wlr_scene_tree_from_node(node);
|
|
struct wlr_scene_node *child;
|
|
wl_list_for_each(child, &scene_tree->children, link) {
|
|
scene_output_for_each_scene_buffer(output_box, child, lx, ly,
|
|
user_iterator, user_data);
|
|
}
|
|
}
|
|
}
|
|
|
|
void wlr_scene_output_for_each_buffer(struct wlr_scene_output *scene_output,
|
|
wlr_scene_buffer_iterator_func_t iterator, void *user_data) {
|
|
struct wlr_box box = { .x = scene_output->x, .y = scene_output->y };
|
|
wlr_output_effective_resolution(scene_output->output,
|
|
&box.width, &box.height);
|
|
scene_output_for_each_scene_buffer(&box, &scene_output->scene->tree.node, 0, 0,
|
|
iterator, user_data);
|
|
}
|