275 lines
9.6 KiB
Diff
275 lines
9.6 KiB
Diff
--- a/include/scenefx/types/wlr_scene.h
|
|
+++ b/include/scenefx/types/wlr_scene.h
|
|
@@ -462,6 +462,11 @@
|
|
*/
|
|
struct wlr_scene_tree *wlr_scene_tree_create(struct wlr_scene_tree *parent);
|
|
|
|
+/** Experimental: copy a tree-local clip; NULL removes it. Width/height must be nonnegative. */
|
|
+void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip);
|
|
+/** Experimental: copy the currently applied tree-local clip; false if unset. */
|
|
+bool wlr_scene_tree_get_clip(struct wlr_scene_tree *tree, struct wlr_box *clip);
|
|
+
|
|
/**
|
|
* Add a node displaying a single surface to the scene-graph.
|
|
*
|
|
--- a/types/scene/wlr_scene.c
|
|
+++ b/types/scene/wlr_scene.c
|
|
@@ -1,3 +1,4 @@
|
|
+#include <limits.h>
|
|
#include <assert.h>
|
|
#include <pixman.h>
|
|
#include <stdio.h>
|
|
@@ -40,6 +41,43 @@
|
|
#define DMABUF_FEEDBACK_DEBOUNCE_FRAMES 30
|
|
#define HIGHLIGHT_DAMAGE_FADEOUT_TIME 250
|
|
|
|
+// Experimental sidecar preserves the public scene structs shared with wlroots.
|
|
+// A production primitive needs an upstream-owned, scalable attachment design.
|
|
+struct scene_tree_clip {
|
|
+ struct scene_tree_clip *next;
|
|
+ struct wlr_scene_tree *tree;
|
|
+ struct wlr_box box;
|
|
+ struct wl_listener destroy;
|
|
+ bool active; // Private reservation; public scene ABI is unchanged.
|
|
+};
|
|
+static struct scene_tree_clip *scene_tree_clips;
|
|
+
|
|
+static struct scene_tree_clip *scene_tree_clip_storage(struct wlr_scene_tree *tree) {
|
|
+ for (struct scene_tree_clip *it = scene_tree_clips; it; it = it->next) {
|
|
+ if (it->tree == tree) return it;
|
|
+ }
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+// Inactive reservations are absent to get/render/hit, but survive NULL setters.
|
|
+static struct scene_tree_clip *scene_tree_clip_find(struct wlr_scene_tree *tree) {
|
|
+ struct scene_tree_clip *entry = scene_tree_clip_storage(tree);
|
|
+ return entry && entry->active ? entry : NULL;
|
|
+}
|
|
+
|
|
+static void scene_tree_clip_remove(struct scene_tree_clip *clip) {
|
|
+ struct scene_tree_clip **link = &scene_tree_clips;
|
|
+ while (*link != clip) link = &(*link)->next;
|
|
+ *link = clip->next;
|
|
+ wl_list_remove(&clip->destroy.link);
|
|
+ free(clip);
|
|
+}
|
|
+
|
|
+static void scene_tree_clip_destroy(struct wl_listener *listener, void *data) {
|
|
+ struct scene_tree_clip *clip = wl_container_of(listener, clip, destroy);
|
|
+ scene_tree_clip_remove(clip);
|
|
+}
|
|
+
|
|
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);
|
|
@@ -245,7 +283,20 @@
|
|
return NULL;
|
|
}
|
|
|
|
+ // WHY: admission failure must precede parent publication and listeners.
|
|
+ // CONTRACT: every factory tree has an inactive reservation until destroy.
|
|
+ // INVARIANT: failed reservation leaves no published node or listener.
|
|
+ struct scene_tree_clip *entry = calloc(1, sizeof(*entry));
|
|
+ if (entry == NULL) {
|
|
+ free(tree);
|
|
+ return NULL;
|
|
+ }
|
|
scene_tree_init(tree, parent);
|
|
+ entry->tree = tree;
|
|
+ entry->destroy.notify = scene_tree_clip_destroy;
|
|
+ wl_signal_add(&tree->node.events.destroy, &entry->destroy);
|
|
+ entry->next = scene_tree_clips;
|
|
+ scene_tree_clips = entry;
|
|
return tree;
|
|
}
|
|
|
|
@@ -673,6 +724,73 @@
|
|
}
|
|
#endif
|
|
|
|
+// WHY: int translation or Pixman endpoint overflow must not expose content.
|
|
+// CONTRACT: reject unrepresentable coordinates before narrowing; aliases work.
|
|
+// INVARIANT: render and input share the same fail-closed translation policy.
|
|
+static bool scene_clip_translate(const struct wlr_box *box, int x, int y,
|
|
+ struct wlr_box *result) {
|
|
+ int64_t x1 = (int64_t)box->x + x, y1 = (int64_t)box->y + y;
|
|
+ int64_t x2 = x1 + box->width, y2 = y1 + box->height;
|
|
+ if (box->width < 0 || box->height < 0 || x1 < INT_MIN || x1 > INT_MAX ||
|
|
+ y1 < INT_MIN || y1 > INT_MAX || x2 > INT_MAX || y2 > INT_MAX) {
|
|
+ *result = (struct wlr_box){0};
|
|
+ return false;
|
|
+ }
|
|
+ *result = (struct wlr_box){(int)x1, (int)y1, box->width, box->height};
|
|
+ return true;
|
|
+}
|
|
+
|
|
+// WHY: leaf visibility is also the render/damage boundary for SceneFX effects.
|
|
+// CONTRACT: clips are tree-local and intersect across all ancestors.
|
|
+// INVARIANT: a descendant cannot expose pixels beyond any ancestor clip.
|
|
+static void scene_node_intersect_clip(struct wlr_scene_node *node,
|
|
+ pixman_region32_t *region) {
|
|
+ for (struct wlr_scene_node *ancestor = node; ancestor != NULL;
|
|
+ ancestor = ancestor->parent ? &ancestor->parent->node : NULL) {
|
|
+ if (ancestor->type != WLR_SCENE_NODE_TREE) {
|
|
+ continue;
|
|
+ }
|
|
+ struct scene_tree_clip *clip = scene_tree_clip_find(wlr_scene_tree_from_node(ancestor));
|
|
+ if (!clip) {
|
|
+ continue;
|
|
+ }
|
|
+ int x, y;
|
|
+ if (!wlr_scene_node_coords(ancestor, &x, &y)) {
|
|
+ pixman_region32_clear(region);
|
|
+ return;
|
|
+ }
|
|
+ struct wlr_box translated;
|
|
+ if (!scene_clip_translate(&clip->box, x, y, &translated)) {
|
|
+ pixman_region32_clear(region);
|
|
+ return;
|
|
+ }
|
|
+ pixman_region32_intersect_rect(region, region,
|
|
+ translated.x, translated.y, translated.width, translated.height);
|
|
+ }
|
|
+}
|
|
+
|
|
+static bool scene_node_point_inside_clip(struct wlr_scene_node *node,
|
|
+ double lx, double ly) {
|
|
+ for (struct wlr_scene_node *ancestor = node; ancestor != NULL;
|
|
+ ancestor = ancestor->parent ? &ancestor->parent->node : NULL) {
|
|
+ if (ancestor->type != WLR_SCENE_NODE_TREE) {
|
|
+ continue;
|
|
+ }
|
|
+ struct scene_tree_clip *entry = scene_tree_clip_find(wlr_scene_tree_from_node(ancestor));
|
|
+ if (!entry) {
|
|
+ continue;
|
|
+ }
|
|
+ int x, y;
|
|
+ if (!wlr_scene_node_coords(ancestor, &x, &y)) return false;
|
|
+ struct wlr_box clip;
|
|
+ if (!scene_clip_translate(&entry->box, x, y, &clip) ||
|
|
+ !wlr_box_contains_point(&clip, lx, ly)) {
|
|
+ return false;
|
|
+ }
|
|
+ }
|
|
+ return true;
|
|
+}
|
|
+
|
|
static bool scene_node_update_iterator(struct wlr_scene_node *node,
|
|
int lx, int ly, void *_data) {
|
|
struct scene_update_data *data = _data;
|
|
@@ -695,11 +813,13 @@
|
|
pixman_region32_union(&node->visible, &node->visible, data->visible);
|
|
pixman_region32_intersect_rect(&node->visible, &node->visible,
|
|
lx, ly, box.width, box.height);
|
|
+ scene_node_intersect_clip(node, &node->visible);
|
|
|
|
if (data->calculate_visibility) {
|
|
pixman_region32_t opaque;
|
|
pixman_region32_init(&opaque);
|
|
scene_node_opaque_region(node, lx, ly, &opaque);
|
|
+ scene_node_intersect_clip(node, &opaque);
|
|
pixman_region32_subtract(data->visible, data->visible, &opaque);
|
|
pixman_region32_fini(&opaque);
|
|
}
|
|
@@ -749,7 +869,11 @@
|
|
|
|
int width, height;
|
|
scene_node_get_size(node, &width, &height);
|
|
- pixman_region32_union_rect(visible, visible, x, y, width, height);
|
|
+ pixman_region32_t bounds;
|
|
+ pixman_region32_init_rect(&bounds, x, y, width, height);
|
|
+ scene_node_intersect_clip(node, &bounds);
|
|
+ pixman_region32_union(visible, visible, &bounds);
|
|
+ pixman_region32_fini(&bounds);
|
|
}
|
|
|
|
static void scene_update_region(struct wlr_scene *scene,
|
|
@@ -863,6 +987,37 @@
|
|
pixman_region32_fini(damage);
|
|
}
|
|
|
|
+void wlr_scene_tree_set_clip(struct wlr_scene_tree *tree, const struct wlr_box *clip) {
|
|
+ // WHY: mandatory clip changes must never allocate or silently fail open.
|
|
+ // CONTRACT: only factory-created trees may be passed, never embedded root.
|
|
+ // INVARIANT: NULL deactivates without releasing the reservation.
|
|
+ assert(tree && (!clip || (clip->width >= 0 && clip->height >= 0)));
|
|
+ struct scene_tree_clip *entry = scene_tree_clip_storage(tree);
|
|
+ if (entry == NULL) {
|
|
+ // Keep the precondition enforced even in NDEBUG builds.
|
|
+ abort();
|
|
+ }
|
|
+ if (!clip && !entry->active) return;
|
|
+ if (clip && entry->active && memcmp(&entry->box, clip, sizeof(*clip)) == 0) return;
|
|
+ // WHY: private samples need the clip applied by this setter, including NULL.
|
|
+ // CONTRACT: passive sampled logging only; timing neutrality NOT_PROVEN.
|
|
+ // INVARIANT: no transaction generation, commit/present or GPU identity inferred.
|
|
+ wlr_log(WLR_INFO, "SPIKE_CLIP tree=%p rect=%d,%d %dx%d",
|
|
+ (void *)tree, clip ? clip->x : 0, clip ? clip->y : 0,
|
|
+ clip ? clip->width : 0, clip ? clip->height : 0);
|
|
+ entry->active = clip != NULL;
|
|
+ if (clip) entry->box = *clip;
|
|
+ scene_node_update(&tree->node, NULL);
|
|
+}
|
|
+
|
|
+bool wlr_scene_tree_get_clip(struct wlr_scene_tree *tree, struct wlr_box *clip) {
|
|
+ assert(tree && clip);
|
|
+ struct scene_tree_clip *entry = scene_tree_clip_find(tree);
|
|
+ if (!entry) return false;
|
|
+ *clip = entry->box;
|
|
+ return true;
|
|
+}
|
|
+
|
|
struct wlr_scene_rect *wlr_scene_rect_create(struct wlr_scene_tree *parent,
|
|
int width, int height, const float color[static 4]) {
|
|
assert(parent);
|
|
@@ -1829,7 +1984,7 @@
|
|
int *lx_ptr, int *ly_ptr) {
|
|
assert(node);
|
|
|
|
- int lx = 0, ly = 0;
|
|
+ int64_t lx = 0, ly = 0;
|
|
bool enabled = true;
|
|
while (true) {
|
|
lx += node->x;
|
|
@@ -1842,8 +1997,14 @@
|
|
node = &node->parent->node;
|
|
}
|
|
|
|
- *lx_ptr = lx;
|
|
- *ly_ptr = ly;
|
|
+ // WHY: translated ancestor coordinates can exceed the public int range.
|
|
+ // CONTRACT/INVARIANT: no narrowing overflow; unrepresentable is invisible.
|
|
+ if (lx < INT_MIN || lx > INT_MAX || ly < INT_MIN || ly > INT_MAX) {
|
|
+ *lx_ptr = *ly_ptr = 0;
|
|
+ return false;
|
|
+ }
|
|
+ *lx_ptr = (int)lx;
|
|
+ *ly_ptr = (int)ly;
|
|
return enabled;
|
|
}
|
|
|
|
@@ -1883,6 +2044,9 @@
|
|
static bool scene_node_at_iterator(struct wlr_scene_node *node,
|
|
int lx, int ly, void *data) {
|
|
struct node_at_data *at_data = data;
|
|
+ if (!scene_node_point_inside_clip(node, at_data->lx, at_data->ly)) {
|
|
+ return false;
|
|
+ }
|
|
|
|
double rx = at_data->lx - lx;
|
|
double ry = at_data->ly - ly;
|
|
@@ -2739,6 +2903,13 @@
|
|
const struct render_data *data) {
|
|
struct wlr_scene_output *scene_output = data->output;
|
|
struct wlr_scene_node *node = entry->node;
|
|
+ for (struct wlr_scene_node *ancestor = node; ancestor != NULL;
|
|
+ ancestor = ancestor->parent ? &ancestor->parent->node : NULL) {
|
|
+ if (ancestor->type == WLR_SCENE_NODE_TREE &&
|
|
+ scene_tree_clip_find(wlr_scene_tree_from_node(ancestor))) {
|
|
+ return SCANOUT_INELIGIBLE;
|
|
+ }
|
|
+ }
|
|
|
|
if (!scene_output->scene->direct_scanout) {
|
|
return SCANOUT_INELIGIBLE;
|