summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/tests/test_kernel.cpp
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-15 05:52:20 +0900
committerAdam Malczewski <[email protected]>2026-06-15 05:52:20 +0900
commit12e50166948b7554ccbdd382c8769774033b6e2c (patch)
tree1531853f3e2bbcb59a73dcad163bd2dc83caffc2 /packages/kernel/tests/test_kernel.cpp
parent5437b5e33c542c801bdc557be2ae93bfec6e153d (diff)
downloadunbox-12e50166948b7554ccbdd382c8769774033b6e2c.tar.gz
unbox-12e50166948b7554ccbdd382c8769774033b6e2c.zip
chore: seed feat/rml-compositing from main + RML-compositing design & spike reference
Phase 2 of RML compositing starts here, off main. Brings over from the throwaway spike branch: - design/planning: notes/rml-compositing-phase1.md (the Phase 1 design), notes/plan.md row 71 (ADOPTED/GO + Option-2 contract), GLOSSARY.md terms (RML compositing, surface element), tasks.md, .gitignore. - spike sources as IN-TREE REFERENCE only (packages/kernel/src/spike + build_by_default:false target + the input-core doctest): the proven mechanics Phase-2 ports (live seq-gated import, Element::Project input-back, FBO->dmabuf present, dirty-gate). Deleted once Wave 1 lands. Real Phase-2 implementation lands in subsequent wave commits.
Diffstat (limited to 'packages/kernel/tests/test_kernel.cpp')
-rw-r--r--packages/kernel/tests/test_kernel.cpp98
1 files changed, 98 insertions, 0 deletions
diff --git a/packages/kernel/tests/test_kernel.cpp b/packages/kernel/tests/test_kernel.cpp
index da2cbf0..678bdee 100644
--- a/packages/kernel/tests/test_kernel.cpp
+++ b/packages/kernel/tests/test_kernel.cpp
@@ -15,6 +15,15 @@
#include "../src/ui_core.hpp"
// The VT-switch escape hatch's pure core (keysym -> VT number), no wlroots.
#include "../src/vt_core.hpp"
+// SPIKE (rml-compositing, Phase 0): the throwaway spike's PURE input-inversion
+// core (screen-point -> surface-local through a 3D transform). Header-only, no
+// wlroots/GL/RMLUi, so the criterion-3 geometry is doctest-ed here alongside the
+// runnable target's own headless self-check (src/spike/). Kept in the kernel
+// suite so the spike's geometry stays green with the unit.
+#include "../src/spike/spike_input_core.hpp"
+
+#include <cmath>
+#include <numbers>
#include <cstdlib>
#include <filesystem>
@@ -2555,3 +2564,92 @@ TEST_CASE("ui: transition_timing reads RCSS duration/delay + tween, resolves pro
// (5) Unparseable property name => nullopt (no exact match, no `all` here).
CHECK_FALSE(s->transition_timing("anim", "not-a-real-property").has_value());
}
+
+// ============================================================================
+// SPIKE (rml-compositing, Phase 0) — PURE input-inversion core (criterion 3).
+// The runnable spike target (src/spike/) self-checks the live-texture / 3D
+// transform / present / idle-gate headless; THIS unit-tests the screen-point ->
+// (surface element, surface-local coord) inversion through a known transform —
+// the math the runtime RmlUi-pick -> wl_seat translation rides on. Throwaway,
+// but kept green with the kernel: a regressed inverse would silently mis-route
+// touch on a tilted window, the exact failure criterion 3 guards against.
+// ============================================================================
+
+namespace {
+namespace spk = unbox::kernel::spike;
+
+// Forward-project a surface-local point through `t`, then invert; assert the
+// round trip recovers the original to sub-pixel. err in pixels.
+auto roundtrip_err(const spk::Mat4& t, double lx, double ly) -> double {
+ const spk::ScreenPoint s = spk::project_to_screen(t, lx, ly);
+ const auto back = spk::unproject_to_local(t, s.x, s.y);
+ if (!back) {
+ return 1e9;
+ }
+ return std::hypot(back->x - lx, back->y - ly);
+}
+} // namespace
+
+TEST_CASE("spike(rml-compositing): screen->surface-local inverts an affine transform") {
+ // A plain translate (no perspective): the inverse must be exact everywhere.
+ const spk::Mat4 t = spk::translate(120.0, -40.0);
+ CHECK(roundtrip_err(t, 0.0, 0.0) < 1e-9);
+ CHECK(roundtrip_err(t, 200.0, 150.0) < 1e-9);
+ // The forward map is a pure offset: a local (10,10) lands at (130,-30).
+ const spk::ScreenPoint s = spk::project_to_screen(t, 10.0, 10.0);
+ CHECK(s.x == doctest::Approx(130.0));
+ CHECK(s.y == doctest::Approx(-30.0));
+}
+
+TEST_CASE("spike(rml-compositing): inverts perspective + rotateY about the element origin") {
+ // The criterion-3 case: a 256x256 surface element with perspective(800) +
+ // rotateY, resolved about the 50% origin (what RCSS computes). The inverse is
+ // a ray/plane intersection (non-affine under perspective); assert sub-0.01px
+ // recovery across the element, including off-center points that foreshorten.
+ const double origin = 128.0;
+ for (double deg : {15.0, 35.0, 60.0, -45.0}) {
+ const spk::Mat4 t = spk::rcss_transform_about_origin(
+ spk::mul(spk::perspective(800.0),
+ spk::rotate_y(deg * std::numbers::pi / 180.0)),
+ origin, origin);
+ CHECK(roundtrip_err(t, 128.0, 128.0) < 1e-6); // center: on the rotation axis
+ CHECK(roundtrip_err(t, 32.0, 64.0) < 0.01); // near edge (foreshortened)
+ CHECK(roundtrip_err(t, 224.0, 200.0) < 0.01); // far edge
+ CHECK(roundtrip_err(t, 64.0, 96.0) < 0.01); // arbitrary interior point
+ }
+}
+
+TEST_CASE("spike(rml-compositing): the inverse is the true matrix inverse (M*inv ~ I)") {
+ // The unprojection's correctness rests on invert(): assert inv(M)*M is the
+ // identity for the perspective+rotateY operator (the non-trivial case). This
+ // is the algebraic backstop under the geometric round-trip tests above.
+ const double origin = 128.0;
+ const spk::Mat4 m = spk::rcss_transform_about_origin(
+ spk::mul(spk::perspective(800.0), spk::rotate_y(40.0 * std::numbers::pi / 180.0)), origin,
+ origin);
+ const auto inv = spk::invert(m);
+ REQUIRE(inv.has_value());
+ const spk::Mat4 prod = spk::mul(*inv, m);
+ for (int r = 0; r < 4; ++r) {
+ for (int c = 0; c < 4; ++c) {
+ CHECK(prod.at(r, c) == doctest::Approx(r == c ? 1.0 : 0.0).epsilon(1e-9));
+ }
+ }
+}
+
+TEST_CASE("spike(rml-compositing): an edge-on (90deg) transform collapses the element to a line") {
+ // rotateY(90deg) about the origin turns the element edge-on: its plane
+ // projects to a vertical LINE on screen, so distinct surface-local points
+ // collapse to (nearly) the same screen x — there is no reliable preimage. We
+ // assert the GEOMETRIC truth (the forward map is degenerate) rather than a
+ // particular inverse return: at runtime RmlUi's own transform-aware pick is
+ // what declines an edge-on element, so the spike never has to invert one.
+ const double origin = 128.0;
+ const spk::Mat4 t = spk::rcss_transform_about_origin(
+ spk::mul(spk::perspective(800.0), spk::rotate_y(std::numbers::pi / 2.0)), origin, origin);
+ const spk::ScreenPoint a = spk::project_to_screen(t, 32.0, 64.0);
+ const spk::ScreenPoint b = spk::project_to_screen(t, 224.0, 64.0);
+ // Two points 192px apart in surface-local X land at the same screen X (the
+ // element is edge-on): the map lost its X information.
+ CHECK(std::abs(a.x - b.x) < 0.5);
+}