summaryrefslogtreecommitdiffhomepage
path: root/packages/kernel/include
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-06-13 21:00:07 +0900
committerAdam Malczewski <[email protected]>2026-06-13 21:00:07 +0900
commitbe5f67f7c7cf2710b0e73df5d92be98c758c47a4 (patch)
tree857e0e9df72675d223b2a2f643c4d49ff01a5b50 /packages/kernel/include
parent4f5779cec17b0e9173d2b1de634c31c516069670 (diff)
downloadunbox-be5f67f7c7cf2710b0e73df5d92be98c758c47a4.tar.gz
unbox-be5f67f7c7cf2710b0e73df5d92be98c758c47a4.zip
kernel: ui surfaces composite with per-pixel alpha + set_size resizes the target
Two substrate capabilities the stage dock forced (both verified real-seat nested and on the gles2 headless path): 1. Per-pixel alpha. A ui surface composited opaque, so any overlay (the dock) occluded the toplevels beneath it. Root cause: a stray opaque render_iface->Clear() (glClearColor 0,0,0,1) in render_surface overrode the transparent BeginFrame clear, and EndFrame's premultiplied composite carried the opaque base to the buffer. Fix: drop the stray Clear(); clear the OUTPUT FBO to (0,0,0,0) once before BeginFrame. Blend was already correct premultiplied; the substrate never sets an opaque region (now guarded by a probe); ARGB8888 alpha survives end to end. A document whose <body> is transparent now shows the scene through its un-painted pixels. 2. set_size resizes the render target. Previously logical-only (the slice-5 documented change-request): set_size re-laid-out the RmlUi document but did NOT realloc the GL target, so a surface created small and grown rendered into its original buffer (the dock, created as a 1px placeholder and grown on minimize, was invisible). Fix: set_size now reallocs the FBO + dmabuf swapchain/shm + EGLImage + texture + scene buffer on an ACTUAL size change (no-op same-size, cheap; set_position still cheap). Grow and shrink both render fully; alpha/upright-flip/blend/fence-sync preserved. ui.hpp documents both. kernel 45 cases/182 assertions green on build + build-asan (no new suppressions). Edits confined to packages/kernel/.
Diffstat (limited to 'packages/kernel/include')
-rw-r--r--packages/kernel/include/unbox/kernel/server.hpp13
-rw-r--r--packages/kernel/include/unbox/kernel/ui.hpp23
2 files changed, 34 insertions, 2 deletions
diff --git a/packages/kernel/include/unbox/kernel/server.hpp b/packages/kernel/include/unbox/kernel/server.hpp
index 4cb5230..4af327a 100644
--- a/packages/kernel/include/unbox/kernel/server.hpp
+++ b/packages/kernel/include/unbox/kernel/server.hpp
@@ -103,6 +103,19 @@ public:
// known source color reached the expected spot inside an <img>.
[[nodiscard]] auto ui_pixel(int x, int y) const -> unsigned int;
+ // Whether the first ui surface's scene_buffer node carries a non-empty
+ // opaque region. The per-pixel-alpha contract requires FALSE: a forced
+ // opaque region would make wlr_scene skip alpha-blending the buffer, so the
+ // scene below would be occluded by un-painted pixels. Test instrumentation;
+ // single-thread only.
+ [[nodiscard]] auto ui_surface_has_opaque_region() const -> bool;
+
+ // Number of UiSurface::set_size GL-target reallocations performed so far.
+ // A same-size set_size is a no-op (does not bump this); a grow/shrink
+ // reallocates the FBO/swapchain/EGLImage/texture and bumps it. Lets the
+ // suite prove the only-on-change guard. Test instrumentation; single-thread.
+ [[nodiscard]] auto ui_resize_realloc_count() const -> int;
+
// Count elements with the given tag name in the first ui surface's loaded
// document. 0 if no surface / no document yet. Lets the suite assert that a
// data-for list rendered the expected number of rows (slice 10 / b2 list
diff --git a/packages/kernel/include/unbox/kernel/ui.hpp b/packages/kernel/include/unbox/kernel/ui.hpp
index 7f7609a..da5986c 100644
--- a/packages/kernel/include/unbox/kernel/ui.hpp
+++ b/packages/kernel/include/unbox/kernel/ui.hpp
@@ -47,6 +47,15 @@ namespace unbox::kernel {
// the document AND its scene node (so hold it as a member; it dies with you,
// in reverse declaration order, while the kernel's scene is still alive).
// All methods are event-loop-thread only.
+//
+// PER-PIXEL ALPHA (transparency). The surface composites with per-pixel alpha:
+// any pixel your document does NOT paint is fully transparent, and the scene
+// BELOW the surface composites through it (the substrate never marks the buffer
+// opaque). So a document with a transparent <body> that paints, say, only a
+// card in one corner shows the windows beneath it everywhere else. If you want
+// a solid panel, paint an opaque background in your RCSS (e.g. body {
+// background-color: #rrggbb; }) — a fully-opaque document is pixel-identical to
+// a fully-opaque surface and occludes whatever it covers, as before.
class UiSurface {
public:
virtual ~UiSurface() = default;
@@ -54,9 +63,19 @@ public:
auto operator=(const UiSurface&) -> UiSurface& = delete;
// ---- Geometry & visibility (layout coordinates) ----
- // Move/resize the surface. The document is laid out to w×h; the node sits
- // at (x,y) in layout space. Cheap; takes effect on the next frame.
+ // Move the surface: the node sits at (x,y) in layout space. Cheap (no
+ // realloc); takes effect on the next frame.
virtual void set_position(int x, int y) = 0;
+ // Resize the surface to w×h. This RESIZES THE RENDER TARGET — the document
+ // is laid out to w×h AND draws into a buffer of matching size, so the
+ // surface renders fully at the new size (grow AND shrink both work; the
+ // composited node and the input hit-test rect track the new size). It is
+ // HEAVIER than set_position: on an actual size change it reallocates GL
+ // resources (the offscreen FBO + dmabuf swapchain / shm buffer), so call it
+ // on size changes, not every frame. A no-op same-size call is cheap (no
+ // realloc). Non-positive w/h is rejected (the surface keeps its size). Takes
+ // effect on the next frame; resizing a hidden surface is fine (it still is
+ // not composited until shown).
virtual void set_size(int width, int height) = 0;
// Show/hide without destroying. Hidden surfaces are not composited and do
// not receive input. Default after create is the spec's `visible`.