summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/tests/test_kernel.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'packages/kernel/tests/test_kernel.cpp')
-rw-r--r--packages/kernel/tests/test_kernel.cpp228
1 files changed, 228 insertions, 0 deletions
diff --git a/packages/kernel/tests/test_kernel.cpp b/packages/kernel/tests/test_kernel.cpp
index 2c9b2fd..89f65f3 100644
--- a/packages/kernel/tests/test_kernel.cpp
+++ b/packages/kernel/tests/test_kernel.cpp
@@ -705,6 +705,234 @@ TEST_CASE("substrate: data-for list renders N rows, re-renders on dirty, routes
}
// ============================================================================
+// slice-10 / ui-surface ALPHA (transparency). A ui surface composites with
+// per-pixel alpha: a pixel the document does NOT paint is transparent (the
+// scene below shows through), while a painted opaque box stays solid. The
+// substrate must (a) clear the surface's output buffer to transparent (0,0,0,0)
+// — NOT opaque black — and (b) never mark the scene_buffer opaque. This is the
+// substrate capability the stage dock needs (its un-painted strip becomes
+// see-through). Proven via the public Host::ui() path + the ui_pixel /
+// ui_surface_has_opaque_region probes.
+// ============================================================================
+
+namespace {
+
+// Transparent <body> with one small OPAQUE box in the top-left corner. The box
+// is #20c040 (an obvious, non-black color). Everything else is unpainted ⇒
+// must read back fully transparent. The box uses position:absolute so its
+// geometry is exact (a 40x40 square at 0,0).
+const char* kAlphaRml = R"RML(<rml>
+<head>
+<style>
+body { background-color: transparent; width: 200px; height: 200px; margin: 0px; }
+#box { display: block; width: 40px; height: 40px; background-color: #20c040; }
+</style>
+</head>
+<body data-model="ui">
+<div id="box"></div>
+</body>
+</rml>)RML";
+
+class AlphaTestExtension : public unbox::kernel::Extension {
+public:
+ auto manifest() const -> const Manifest& override { return manifest_; }
+
+ void activate(Host& host) override {
+ UiSurfaceSpec spec;
+ spec.rml_inline = kAlphaRml;
+ spec.x = 0;
+ spec.y = 0;
+ spec.width = 200;
+ spec.height = 200;
+ spec.layer = unbox::kernel::SceneLayer::overlay;
+ spec.visible = true;
+ surface_ = host.ui().create_surface(spec);
+ }
+
+ [[nodiscard]] auto has_surface() const -> bool { return surface_ != nullptr; }
+
+private:
+ Manifest manifest_{"alpha-test", Tier::standard, {}};
+ std::unique_ptr<UiSurface> surface_;
+};
+
+} // namespace
+
+TEST_CASE("substrate: un-painted pixels are transparent; painted box stays opaque") {
+ setenv("WLR_BACKENDS", "headless", 1);
+ setenv("WLR_RENDERER", "gles2", 1);
+ setenv("WLR_HEADLESS_OUTPUTS", "1", 1);
+ setenv("UNBOX_UI_SUBSTRATE_FORCE_SHM", "1", 1);
+
+ auto server = unbox::kernel::Server::create({});
+ auto* ext = new AlphaTestExtension();
+ server->install(std::unique_ptr<unbox::kernel::Extension>(ext));
+ server->activate_extensions();
+ pump(*server, 60); // load the document + render the surface
+
+ if (!ext->has_surface() || server->ui_frame_count() == 0) {
+ unsetenv("UNBOX_UI_SUBSTRATE_FORCE_SHM"); // no GL path on this box: skip
+ return;
+ }
+
+ // (1) The scene buffer must NOT carry a forced opaque region — otherwise
+ // wlr_scene would skip alpha-blending and occlude the scene below.
+ CHECK(server->ui_surface_has_opaque_region() == false);
+
+ // (2) A pixel in the UN-painted area (center, far from the corner box) is
+ // FULLY TRANSPARENT: premultiplied (0,0,0,0) ⇒ packed 0xRRGGBBAA == 0. This
+ // is the failing-then-passing assertion: before the fix the output FBO was
+ // cleared to opaque black (0,0,0,1) so this read back 0x000000ff.
+ const unsigned int unpainted = server->ui_pixel(100, 100);
+ INFO("un-painted center pixel (RRGGBBAA) = ", unpainted);
+ CHECK((unpainted & 0xffu) == 0u); // alpha == 0
+ CHECK(unpainted == 0u); // fully transparent premultiplied (0,0,0,0)
+
+ // (3) A pixel inside the box reads the box color, fully opaque. The 40x40
+ // box is the first normal-flow block at the document top-left; sample well
+ // inside it (10,10) to avoid antialiased edges.
+ const unsigned int box = server->ui_pixel(10, 10);
+ INFO("box pixel (RRGGBBAA) = ", box);
+ const int br = static_cast<int>((box >> 24) & 0xff);
+ const int bg = static_cast<int>((box >> 16) & 0xff);
+ const int bb = static_cast<int>((box >> 8) & 0xff);
+ const int ba = static_cast<int>(box & 0xff);
+ CHECK(ba == 0xff); // opaque
+ CHECK(bg > br); // green dominates (#20c040)
+ CHECK(bg > bb);
+ CHECK(br < 90); // little red
+ CHECK(bg > 140); // strong green
+
+ unsetenv("UNBOX_UI_SUBSTRATE_FORCE_SHM");
+}
+
+// ============================================================================
+// slice-10 / set_size RENDER-TARGET RESIZE. A surface created SMALL must grow
+// (and shrink) and render fully at the new size — set_size now reallocates the
+// FBO + dmabuf swapchain (or shm buffer) + EGLImage + texture, not just the
+// logical RmlUi layout. The dock creates a 1px placeholder and grows it; before
+// this fix the grown area rendered into the original tiny buffer (invisible).
+// Proven via the public Host::ui() path + the ui_pixel / ui_resize_realloc_count
+// probes. Full-body opaque color so a grown-area pixel reading the color proves
+// the new buffer was actually drawn into.
+// ============================================================================
+
+namespace {
+
+// A full-bleed opaque blue body (#2080e0), no margin, so EVERY pixel of the
+// surface (at whatever current size) is the painted color.
+const char* kResizeRml = R"RML(<rml>
+<head>
+<style>
+body { margin: 0px; }
+#fill { display: block; position: absolute; left: 0px; top: 0px;
+ width: 4000px; height: 4000px; background-color: #2080e0; }
+</style>
+</head>
+<body data-model="ui">
+<div id="fill"></div>
+</body>
+</rml>)RML";
+
+class ResizeTestExtension : public unbox::kernel::Extension {
+public:
+ auto manifest() const -> const Manifest& override { return manifest_; }
+
+ void activate(Host& host) override {
+ UiSurfaceSpec spec;
+ spec.rml_inline = kResizeRml;
+ spec.x = 0;
+ spec.y = 0;
+ spec.width = 40; // created SMALL (the dock starts at a tiny placeholder)
+ spec.height = 40;
+ spec.layer = unbox::kernel::SceneLayer::overlay;
+ spec.visible = true;
+ surface_ = host.ui().create_surface(spec);
+ }
+
+ [[nodiscard]] auto has_surface() const -> bool { return surface_ != nullptr; }
+ auto surface() -> UiSurface* { return surface_.get(); }
+
+private:
+ Manifest manifest_{"resize-test", Tier::standard, {}};
+ std::unique_ptr<UiSurface> surface_;
+};
+
+// True if (RRGGBBAA) is the painted blue #2080e0 (tolerant), opaque.
+auto is_painted_blue(unsigned int px) -> bool {
+ const int r = static_cast<int>((px >> 24) & 0xff);
+ const int g = static_cast<int>((px >> 16) & 0xff);
+ const int b = static_cast<int>((px >> 8) & 0xff);
+ const int a = static_cast<int>(px & 0xff);
+ return a == 0xff && b > 160 && b > r && b > g && r < 90;
+}
+
+} // namespace
+
+TEST_CASE("substrate: set_size resizes the render target (grow renders, shrink renders)") {
+ setenv("WLR_BACKENDS", "headless", 1);
+ setenv("WLR_RENDERER", "gles2", 1);
+ setenv("WLR_HEADLESS_OUTPUTS", "1", 1);
+ unsetenv("UNBOX_UI_SUBSTRATE_FORCE_SHM"); // exercise the real Plan-A path the dock hits
+
+ auto server = unbox::kernel::Server::create({});
+ auto* ext = new ResizeTestExtension();
+ server->install(std::unique_ptr<unbox::kernel::Extension>(ext));
+ server->activate_extensions();
+ pump(*server, 30); // load + render at the small (40x40) size
+
+ if (!ext->has_surface() || server->ui_frame_count() == 0) {
+ return; // no GL path on this box: skip
+ }
+
+ // Small surface paints fully: its center reads the body color.
+ CHECK(is_painted_blue(server->ui_pixel(20, 20)));
+
+ // GROW to 200x200. Tick. A pixel deep in the GROWN region (150,150) — which
+ // does NOT exist in the original 40x40 buffer — must now read the painted
+ // color. Before the fix the document re-laid-out but rendered into the old
+ // 40x40 buffer, so (150,150) was unrendered (clamped/garbage/transparent).
+ const int before = server->ui_resize_realloc_count();
+ ext->surface()->set_size(200, 200);
+ CHECK(server->ui_resize_realloc_count() == before + 1); // grow reallocated
+ pump(*server, 10);
+ const unsigned int grown = server->ui_pixel(150, 150);
+ INFO("grown-region pixel (150,150) (RRGGBBAA) = ", grown);
+ CHECK(is_painted_blue(grown));
+ // The opaque-region invariant survives a resize (still per-pixel-alpha buffer).
+ CHECK(server->ui_surface_has_opaque_region() == false);
+ // The buffer is still upright after the realloc (no flip regression).
+ // (orientation() only inspects shm-path surfaces; this is the dmabuf path, so
+ // we assert upright indirectly: a top-left pixel and a bottom-right pixel of
+ // the full-bleed body both read the color, i.e. no garbled/empty rows.)
+ CHECK(is_painted_blue(server->ui_pixel(5, 5)));
+ CHECK(is_painted_blue(server->ui_pixel(195, 195)));
+
+ // SHRINK to 60x60. Tick. A pixel inside reads the color; out-of-bounds reads
+ // 0 (probe clamps), proving the buffer actually shrank.
+ ext->surface()->set_size(60, 60);
+ CHECK(server->ui_resize_realloc_count() == before + 2); // shrink reallocated
+ pump(*server, 10);
+ CHECK(is_painted_blue(server->ui_pixel(30, 30)));
+ CHECK(server->ui_pixel(150, 150) == 0u); // out of the new 60x60 bounds
+
+ // SAME-size set_size is a no-op realloc (only-on-change guard) and still
+ // renders correctly.
+ const int after_shrink = server->ui_resize_realloc_count();
+ ext->surface()->set_size(60, 60);
+ CHECK(server->ui_resize_realloc_count() == after_shrink); // no extra realloc
+ pump(*server, 5);
+ CHECK(is_painted_blue(server->ui_pixel(30, 30)));
+
+ // Non-positive set_size is rejected (keeps the 60x60 size; no realloc).
+ ext->surface()->set_size(0, 100);
+ ext->surface()->set_size(100, -1);
+ CHECK(server->ui_resize_realloc_count() == after_shrink);
+ pump(*server, 5);
+ CHECK(is_painted_blue(server->ui_pixel(30, 30)));
+}
+
+// ============================================================================
// VT-switch escape hatch — PURE CORE (no wlroots): keysym -> VT number. The
// glue (input.cpp) calls wlr_session_change_vt on a hit and consumes; this
// helper decides the hit. Ctrl+Alt+Fn arrives as XF86Switch_VT_1..12.