iris: a mask is a shape, not a rectangle -- .masked_by, and touch obeys it
Iris, on the code fence: "the code block scrolling currently masks in an
inner rectangle. Ideally masks should have a shape associated with them,
rounded rectangle being one of them ... so that the mask becomes the
parent container with rounded edges. Make sure alpha works properly with
it, eg. on the corners where alpha should be decreased / multiplied."
`Mask` is now `{ primitive, parent }` -- the slot of a primitive already
written, plus the mask this one nests inside. The fragment stage
evaluates that primitive's own coverage at the masked pixel, through the
same `rounded_rect_coverage` a drawn rect goes through, and multiplies it
into the alpha along the whole `parent` chain. Nothing about the shape is
copied, so a rounded container's corner and its children's clipped corner
are one piece of arithmetic and cannot drift; two nested feathers dim a
pixel twice, which is the multiply she asked for.
`.masked()` is unchanged for callers: it writes an undrawn rect
(`Drawn::No`/`NOT_DRAWN` -- owned, moved, resized and freed like any
other primitive, simply never rasterized) and points at that, so square
clipping is the same mechanism rather than a special case. New
`.masked_by(shape)` draws `shape` behind the content in its own layer and
clips to the first primitive it drew, with no radius written twice; it
replaces `.masked().background(w)`, which drew both and clipped to the
box. `transcript-ui`'s `BlockFrame::Verbatim` is the first caller.
Hit-testing applies the shape (`SensorUi::run_sensors` ->
`UiRenderState::mask_admits`, coverage above one half, which is where the
drawn edge is), as well as the widget's own box -- the two ask different
questions and both have to hold. `primitive_corners` is a floor-for-floor
transliteration of the shader's `corners_of`, not `region.to_px()`: the
phone's 2.55 density puts nothing on a whole pixel, and skipping the
rounding disagrees with the pixels by up to one along each edge.
A mask's shape must be a rect, asserted by name in `set_mask_to`. A glyph
would need a CPU-side alpha plane before the hit test could agree with
the shader, and a standalone image a bind-group switch the fragment stage
cannot make. So no texture mask exists; the branch where one would go is
in both copies of `mask_coverage`. docs/LAYOUT.md's section end lists this
and the three other places the code is narrower than the design.
Tests. Layer 1, `layout_tests.rs`: the child's coverage swept across the
container's corner arc equals the container's own exactly; nested masks
multiply rather than intersect, asserted where both feathers are partial,
which is the only place the two differ; a press in a rounded-away corner
misses while one inside the curve and one on a straight edge hit; and
`a_plain_mask_still_clips_to_a_square_box`, the half this had no reason to
touch. The first version of the corner test swept the straight chord
between the arc's ends, which lies inside the circle everywhere -- it
proved nothing and said so, which is why it counts both sides now.
`iris/tests/mask_sdf.rs` is the only test here that needs a GPU: it lifts
`distance_from_rect` and `rounded_rect_coverage` out of
`iris_core::SHAPE_SHADER` by name -- lifted, not copied, since a copy
would be edited alongside the shader -- and runs them in a compute pass
over ~200k points at five radii against `iris_core::rounded_rect_coverage`.
Worst disagreement under 1e-5; the negative control (`+ 0.01` inside the
shader's smoothstep) fails it at 0.03.
Layer 2 for looking: `./run-headless.sh phone --phone --shot /tmp/mask.png
--seconds 6 -- -p transcript-fixture` draws the fixture's horizontally
scrolled code fence clipped on the curve at both top corners.
Two things found on the way and fixed here:
- The winit backend had the defect the Android one was fixed for in
85869d0 -- `Backends::PRIMARY` and an `.expect` on the adapter. This
VM's Venus device disappears when the host runs out of virgl contexts,
which happened mid-task, and layer 2 aborted with `Could not get
adapter!` while GL sat there working. It probes and rebuilds the
instance on `Backends::GL` exactly as Android does now, and the request
names the backends it tried. The rule had been written on one member of
a set of two.
- `active_primitive_count` counted mask shapes, so `iris::frame`'s
`primitives=` -- a number Iris reads off a phone report as "how much is
on screen" -- would have gained one per masked widget.
`widget_trait!` now accepts a `///` doc comment on its functions, since
`masked_by` is public API and rustdoc is where a contract is read.
docs/LAYOUT.md, docs/RUST.md (both queue boxes, the commands, and where
the GPU test sits among the three layers), docs/IRIS.md and
docs/IRIS_TODO.md ("Masks defined relative to each other", now closed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3eb0e033d5
commit
38bf6309cb
23 files changed
+1284
-122
No files matched your search
@@ -8,6 +8,48 @@ capability that moved. Small and trivial changes do not go here.
|
||||
An entry gives the date, what changed, why, and a short before/after where
|
||||
it helps judge the change without the session that made it. Newest first.
|
||||
|
||||
## 2026-09-08: masks have a shape -- `.masked_by(shape)`, and clipping applies to touch
|
||||
|
||||
A mask no longer carries a rectangle. It carries **the slot of a
|
||||
primitive already drawn**, and the fragment stage evaluates that
|
||||
primitive's own coverage at each masked pixel and multiplies it into the
|
||||
alpha -- the same rounded-rect SDF the primitive itself is drawn with.
|
||||
Nothing about the shape is copied, so a rounded container's corner and
|
||||
the corner its content is cut to cannot fall out of step, and nested
|
||||
masks multiply rather than intersect: a pixel inside two feathered
|
||||
corners is dimmed by both.
|
||||
|
||||
// before -- the mask clipped to the padded box, the rounding was
|
||||
// only painted behind it, and the two knew nothing of each other
|
||||
field.scrollable_on(Axis::X)
|
||||
.masked()
|
||||
.pad(dp(FRAME_PAD_DP))
|
||||
.background(rect(fill).radius(dp(FRAME_RADIUS_DP)))
|
||||
|
||||
// after -- one rect, drawn and clipped to
|
||||
field.scrollable_on(Axis::X)
|
||||
.pad(dp(FRAME_PAD_DP))
|
||||
.masked_by(rect(fill).radius(dp(FRAME_RADIUS_DP)))
|
||||
|
||||
`.masked()` is unchanged for callers and still clips to the widget's own
|
||||
box; under it, it now writes an undrawn rect primitive and points the
|
||||
mask at that, so square-cornered clipping is the same mechanism rather
|
||||
than a special case. `.masked_by(shape)` draws `shape` behind the
|
||||
content, in its own layer, and clips to the first primitive it drew.
|
||||
There is no radius or shape argument anywhere -- that is the point.
|
||||
|
||||
**A press now has to be inside the shape, not just the box.** A corner
|
||||
the container rounded away is not there to be tapped, which needed the
|
||||
coverage function on the CPU as well as in the shader;
|
||||
`iris/tests/mask_sdf.rs` runs the shader's own text against the Rust one
|
||||
over a grid of points so the two cannot drift apart.
|
||||
|
||||
One limit worth knowing before reaching for it: **a mask's shape must be
|
||||
a rect**, asserted by name. Clipping to a glyph or an image would need,
|
||||
respectively, a CPU-side alpha plane for the hit test and a bind-group
|
||||
switch the fragment stage cannot make. The shader has the branch where
|
||||
either would go.
|
||||
|
||||
## 2026-09-07: iris runs on a GLES-only Android device, and reports the renderer it cannot build
|
||||
|
||||
`AndroidRenderer::new` asked wgpu for `Backends::PRIMARY`, which does not
|
||||
|
||||
+23
-8
@@ -701,12 +701,15 @@ Iris's report, verbatim, with a screenshot. Phone: Mali-G715 (Vulkan),
|
||||
has no rules for stays plain rather than being coloured by the
|
||||
nearest one's.
|
||||
|
||||
- [ ] **Masks defined relative to each other.** Wanted: mask A multiplies
|
||||
by something *and also* applies mask B — a mask can reference a parent
|
||||
mask, the way the move chain references a parent offset. Today masks
|
||||
are independent regions. Design it beside the move chain (same shape:
|
||||
a parent index and a bounded walk in the shader); do it when a real
|
||||
widget needs it, not before.
|
||||
- [x] **Masks defined relative to each other. (Done: chaining
|
||||
2026-09-07 in d507ae4, the multiply 2026-09-08.)** Built exactly
|
||||
beside the move chain, as this asked: `Mask::parent` is a slot index
|
||||
and the fragment stage walks it under the same bound the move chain
|
||||
uses. Each step multiplies the referenced primitive's coverage into
|
||||
the pixel's alpha, so a pixel inside two feathered corners is dimmed
|
||||
by both — the "multiplies by something *and also* applies mask B" half.
|
||||
The real widget that needed it was the transcript's code fence inside
|
||||
the list. See docs/LAYOUT.md's "Masks with a shape".
|
||||
- [ ] **Positions as a single float per scroll.** Iris raised, and half
|
||||
rejected, letting a scroll update one float rather than positions:
|
||||
input handling cares about most elements in a list, so absolute
|
||||
@@ -1076,9 +1079,21 @@ do not duplicate it there.
|
||||
Iris pasted a full Copy report (Mali-G715 Vulkan, 2.55, 120Hz). What it
|
||||
showed, beyond her words:
|
||||
|
||||
- [ ] **"Sometimes when I try to catch it while it's still moving
|
||||
- [x] **"Sometimes when I try to catch it while it's still moving
|
||||
(particularly if I drag) then it fails to stop & snap to where finger
|
||||
is."** The report's release lines show catches ending as
|
||||
is." (done 2026-09-07, b87f5a5.)** Built as described below.
|
||||
`DragArbiter::press_start` takes a `PressState` -- what the target
|
||||
looked like at the moment the press landed -- rather than asking the
|
||||
list later, because by then the fling has already been cancelled and
|
||||
the answer is no. The defect layer 1 found doing it: one touch-down
|
||||
reaches every sensor under the finger, so a block and the tool row
|
||||
containing it deliver the same `PressStart` twice, and re-reading the
|
||||
state on the second delivery turned every catch back into an ordinary
|
||||
slop-waiting press. Tests in
|
||||
`iris/transcript-fixture/tests/catch_a_fling.rs`, with
|
||||
`the_same_small_drag_on_a_settled_list_moves_nothing` as the half the
|
||||
change had no reason to touch. **Not yet confirmed from the phone.**
|
||||
The original reading follows. The report's release lines show catches ending as
|
||||
`v=-41`/`v=-274` pans, so the gesture *does* reach `Panning`, but the
|
||||
content under the finger does not follow it while the fling is still
|
||||
running and the slop has not been crossed. Compose: a down while
|
||||
|
||||
+36
-1
@@ -948,7 +948,7 @@ When this lands, copy this entry into `IRIS.md` (newest first):
|
||||
> design, the move-offset mechanism this shipped alongside, and the file
|
||||
> list.
|
||||
|
||||
## Masks with a shape (decided 2026-09-07, not yet built)
|
||||
## Masks with a shape (decided 2026-09-07, built 2026-09-08)
|
||||
|
||||
Iris, on the code block's scrolling: "the code block scrolling currently
|
||||
masks in an inner rectangle. Ideally masks should have a shape
|
||||
@@ -1039,3 +1039,38 @@ and that the CPU SDF and the shader agree at a grid of points; a
|
||||
`run-headless.sh --phone` screenshot of a scrolled code block shows
|
||||
rounded corners with no square pixels poking out at the top and bottom
|
||||
of the scrolled content. Record the commands in RUST.md when it lands.
|
||||
|
||||
### What was built (2026-09-08), and where it differs
|
||||
|
||||
The commands and the screenshot are in docs/RUST.md's queue entry. Four
|
||||
places the code is narrower than the design above, each deliberate:
|
||||
|
||||
- **No `kind` and no `flags` on `Mask`.** It is `{ primitive, parent }`.
|
||||
The referenced instance already carries its own `binding`, so a copy
|
||||
of it in the mask is a second thing to keep in step; *alpha only* is
|
||||
the only mode there is, so there is nothing to select. Both are a
|
||||
field away if a second mode appears.
|
||||
- **A mask's shape must be a rect.** `Painter::set_mask_to` asserts it,
|
||||
by name, rather than leaving the shader to read a `rects` entry that
|
||||
is not there. A glyph would need a CPU-side alpha plane before the
|
||||
hit test could agree with the shader, and a standalone image needs a
|
||||
bind-group switch the fragment stage cannot make (`masks_layout`'s own
|
||||
comment on why an image's bind group must not name the masks buffer).
|
||||
So **the texture-mask pass condition is not met and no texture mask
|
||||
exists** — the point of the reference design is that adding one is a
|
||||
binding check and a sampled alpha, with no new shader path, and the
|
||||
shader's `mask_coverage` already has the branch where it would go.
|
||||
- **The shape is a primitive of its own, not always a drawn one.** A
|
||||
plain `.masked()` writes an undrawn `RectPrimitive` at its region
|
||||
(`Drawn::No`, `NOT_DRAWN`) and points the mask at that, so "clip to my
|
||||
box" and "clip to that widget's rounded background" are one mechanism
|
||||
and square-cornered clipping did not become a special case.
|
||||
`.masked_by(shape)` draws `shape` behind the content — in its own
|
||||
layer, the way `Stack` puts a background under its content — and
|
||||
clips to the first primitive it drew.
|
||||
- **The CPU/shader agreement is a GPU test**, `iris/tests/mask_sdf.rs`,
|
||||
the only test in the workspace that needs an adapter. It lifts
|
||||
`distance_from_rect` and `rounded_rect_coverage` out of
|
||||
`iris_core::SHAPE_SHADER` *by name* and runs them in a compute pass,
|
||||
so the thing under test is the shader itself rather than a copy of it
|
||||
that would be edited alongside.
|
||||
+137
-20
@@ -609,11 +609,12 @@ In order; two builders at a time. Each is ticked here by the agent that
|
||||
closes it.
|
||||
|
||||
- [x] Test rig, layers 1 and 2 ("Three test layers" below), landed 2026-09-07.
|
||||
- [ ] Fling parity with Compose, and the phone's keyboard push-up, with
|
||||
insets shown in the diagnostics overlay. The worktree note here was
|
||||
stale by 2026-09-07 night: no worktree exists and the keyboard half
|
||||
is ticked in IRIS_TODO's night entry. What remains is the impulse
|
||||
estimator item below.
|
||||
- [x] Fling parity with Compose, and the phone's keyboard push-up, with
|
||||
insets shown in the diagnostics overlay. Every part is ticked in
|
||||
IRIS_TODO's 2026-09-07 entries: the keyboard half in the night entry,
|
||||
the estimator in the "later" one (Lsq2, not Impulse -- see the box
|
||||
below), and the catch in b87f5a5. **Not yet confirmed from the
|
||||
phone**, which is what would close it for Iris rather than for us.
|
||||
- **Orchestrator note, 2026-09-07 late**: the tree was found holding a
|
||||
non-compiling diff from two killed agents (catch-a-fling in
|
||||
`sense.rs`/`selection.rs`; shaped masks in the render files). One
|
||||
@@ -621,6 +622,11 @@ closes it.
|
||||
one sonnet agent owns report hygiene and the bench header in
|
||||
`bench_client.rs` and client-core's log ring. If both boxes below
|
||||
are still open and nothing is running, that work was cut off again.
|
||||
**2026-09-08: it was cut off again** -- the sonnet agent's two boxes
|
||||
had landed (7485d78, b8ea723), catch-a-fling had landed unticked
|
||||
(b87f5a5), and the shaped-mask diff was left uncommitted in the tree
|
||||
with one failing test. Both are closed below, by one session working
|
||||
inline rather than by agents. Nothing is running now.
|
||||
- [x] Rows at the transcript's top edge: culled too early in one state,
|
||||
drawn through the header in the other (docs/IRIS_TODO.md, 2026-09-07).
|
||||
Done 2026-09-07, e922b73 + d507ae4; the root causes and the test names
|
||||
@@ -648,9 +654,17 @@ closes it.
|
||||
`fonts.xml` monospace declaration against fontique's actually-scanned
|
||||
families, Android-only, verified `mono=Some("Droid Sans Mono")` on this
|
||||
checkout's emulator.
|
||||
- [ ] Scroll clamped at both ends, and Compose's impulse velocity
|
||||
estimator with min/max fling velocity (docs/IRIS_TODO.md, 2026-09-07
|
||||
later). After the culling fix lands (same file).
|
||||
- [x] Scroll clamped at both ends (e922b73, `List::clamp_to_content`)
|
||||
and Compose's velocity estimator (docs/IRIS_TODO.md, 2026-09-07
|
||||
later). Ticked 2026-09-08 against those entries, which were already
|
||||
`[x]` while this box was not. Two things this box's own wording had
|
||||
wrong, both corrected there by reading Compose's sources: the touch
|
||||
path is **Lsq2 over absolute positions**, not `Strategy.Impulse`
|
||||
(Impulse is the mouse-wheel/trackpad path), and there is **no minimum
|
||||
fling velocity** on it -- `minimumFlingVelocity` belongs to
|
||||
`NestedScrollInteropConnection`. So iris ports Lsq2, caps at 8000dp/s
|
||||
and floors at 1px/s, and has no 50dp/s threshold Compose does not
|
||||
have.
|
||||
- [x] **APK runtime logs in Dev Updater (Iris, 2026-09-07: "please add
|
||||
android / apk runtime log support to dev updater").** **Done
|
||||
2026-09-07** -- dev-updater `013d711`, and this repo's provider half;
|
||||
@@ -716,9 +730,27 @@ closes it.
|
||||
APK installable (648 MB) -- RUST.md's logging section names both.
|
||||
- [ ] Input-event and timing instrumentation into the log ring, copied
|
||||
by the report button. After the logging route lands (same ring).
|
||||
- [ ] Catch-a-fling: down during a fling stops it at the down and drags
|
||||
with no slop (docs/IRIS_TODO.md, night). Opus, next slot; uses the
|
||||
layer-1 harness.
|
||||
- [x] **Catch-a-fling (done 2026-09-07, b87f5a5; verified and ticked
|
||||
2026-09-08).** A down on a moving list ends the fling on that sample
|
||||
and enters `Panning` with no `DRAG_SLOP` wait, which is Compose's
|
||||
`scrollable(startDragImmediately = isScrollInProgress)`; a catch
|
||||
released without moving is `Released(None)` rather than a `Tapped`,
|
||||
which is also what Compose delivers. `DragArbiter::press_start` takes
|
||||
a `PressState` -- what the target looked like when the press landed
|
||||
(`already_selected`, `scrolling`) -- rather than reading the list
|
||||
again later, because by then the fling it is asking about has already
|
||||
been cancelled. The defect layer 1 found on the way: one touch-down
|
||||
reaches every sensor under the finger, so a transcript row's block and
|
||||
the tool row containing it deliver the same `PressStart` twice, and
|
||||
re-reading `PressState` on the second delivery turned every catch back
|
||||
into an ordinary slop-waiting press. Tests:
|
||||
`transcript-fixture/tests/catch_a_fling.rs` (the recorded 120Hz flick,
|
||||
150ms of fling, a down and three 2px moves -- the content tracks the
|
||||
finger sample for sample, failing at the parent commit with "the
|
||||
content 0.0px"), plus `the_same_small_drag_on_a_settled_list_moves_
|
||||
nothing`, which is the half the change had no reason to touch: 6px is
|
||||
inside `DRAG_SLOP`, so pinning the content on *every* press would pass
|
||||
the first test and quietly take the slop away from every ordinary one.
|
||||
- [x] **Report hygiene (done 2026-09-07).** Ring takes Debug only from
|
||||
`iris`/`client_core` targets, Copy report always copies and trims the
|
||||
log. `client_core::log_ring::ring_accepts` is the one filter (Info+
|
||||
@@ -806,15 +838,87 @@ closes it.
|
||||
renderer with the hook: `iris panic at .../render.rs:140:14: Could not
|
||||
get adapter!: NotFound {...}` in the ring on the run that died, and
|
||||
`iris app log: the previous run died -- ...` on the next one.
|
||||
- [ ] Masks with a shape -- docs/LAYOUT.md "Masks with a shape (decided
|
||||
2026-09-07)". A mask references a primitive already drawn
|
||||
(rect SDF, texture or glyph alpha), chained and multiplied; `.masked()`
|
||||
points at the widget's own primitives; hit-testing applies the shape.
|
||||
**Chaining landed early**, 2026-09-07 (d507ae4): a mask carries the
|
||||
mask it was set inside and the fragment stage walks that chain, so
|
||||
nesting works and `Painter::set_mask` no longer aborts on it. Still
|
||||
rectangles only -- the shape half, and the hit-testing half, are what
|
||||
is left of this item.
|
||||
- [x] **Masks with a shape (done 2026-09-08).** docs/LAYOUT.md's "Masks
|
||||
with a shape" carries the design and, at its end, the four places the
|
||||
code is deliberately narrower than it. **Chaining landed early**,
|
||||
2026-09-07 (d507ae4). What landed now is the shape half and the
|
||||
hit-testing half:
|
||||
|
||||
`Mask` is `{ primitive, parent }` -- the slot of a primitive already
|
||||
written, and the mask this one nests inside. The fragment stage
|
||||
evaluates that primitive's own coverage *at the masked pixel*
|
||||
(`mask_coverage` in `shader.wgsl`, the same `rounded_rect_coverage` a
|
||||
drawn rect goes through) and multiplies it into the pixel's alpha,
|
||||
walking `parent` and multiplying every coverage on the chain. So the
|
||||
container's corner and its children's clipped corner are one piece of
|
||||
arithmetic, and two nested feathers dim a pixel twice -- the "alpha
|
||||
should be decreased / multiplied" Iris asked for.
|
||||
|
||||
`.masked()` is unchanged for callers and writes an undrawn
|
||||
`RectPrimitive` at its own region (`Drawn::No`/`NOT_DRAWN` -- owned,
|
||||
moved, resized and freed like any other primitive, simply never
|
||||
rasterized), so "clip to my box" and "clip to that rounded background"
|
||||
are one mechanism rather than a square-cornered special case. New:
|
||||
**`.masked_by(shape)`** draws `shape` behind the content in its own
|
||||
layer and clips to the first primitive it drew, with no radius passed
|
||||
twice -- it replaces `.masked().background(w)`, which drew the two and
|
||||
clipped to the box. `transcript-ui`'s `BlockFrame::Verbatim` is its
|
||||
first caller, which is the code fence Iris raised this about.
|
||||
|
||||
**Hit-testing applies the shape**: `SensorUi::run_sensors` asks
|
||||
`UiRenderState::mask_admits` (coverage above one half, which is where
|
||||
the drawn edge is) as well as the widget's own box -- the two ask
|
||||
different questions and both have to hold. `primitive_corners` is a
|
||||
`floor`-for-`floor` transliteration of the shader's `corners_of`,
|
||||
which is the whole reason it is not `region.to_px()`: the phone's 2.55
|
||||
density puts nothing on a whole pixel, and skipping the rounding
|
||||
disagrees with the pixels by up to one along each edge.
|
||||
|
||||
A mask's shape **must be a rect**, asserted by name in
|
||||
`Painter::set_mask_to`. A glyph would need a CPU-side alpha plane
|
||||
before the hit test could agree with the shader, and a standalone
|
||||
image a bind-group switch the fragment stage cannot make. So the
|
||||
design's texture-mask pass condition is **not met and no texture mask
|
||||
exists** -- the branch where one would go is in both `mask_coverage`s.
|
||||
|
||||
How it was checked, all four commands:
|
||||
|
||||
cd iris && cargo test --workspace # layer 1 + the GPU test
|
||||
cargo test -p iris --lib layout_tests:: # the four mask tests
|
||||
cargo test -p iris --test mask_sdf # CPU/shader agreement
|
||||
./run-headless.sh phone --phone --shot /tmp/mask.png --seconds 6 \
|
||||
-- -p transcript-fixture # layer 2, for looking
|
||||
|
||||
`iris/tests/mask_sdf.rs` is the only test in the workspace that needs
|
||||
a GPU: it lifts `distance_from_rect` and `rounded_rect_coverage` out
|
||||
of `iris_core::SHAPE_SHADER` **by name** and runs them in a compute
|
||||
pass over a grid of ~200k points at five radii, against the CPU
|
||||
`iris_core::rounded_rect_coverage` -- worst disagreement under 1e-5,
|
||||
and the negative control (a `+ 0.01` inside the shader's smoothstep)
|
||||
fails it at 0.03. It lifts rather than copies because a copy would be
|
||||
edited alongside the shader, which is exactly the drift it exists to
|
||||
catch. The layer-1 tests are in `layout_tests.rs`: the child's
|
||||
coverage swept across the container's corner arc equals the
|
||||
container's own *exactly* (the sweep goes from the arc's centre --
|
||||
the straight chord between the arc's ends lies inside the circle
|
||||
everywhere, so the first version of this test proved nothing and said
|
||||
so); nested masks multiply rather than intersect, asserted where both
|
||||
feathers are partial, which is the only place the two differ; a press
|
||||
in a rounded-away corner misses while one inside the curve and one on
|
||||
a straight edge hit; and `a_plain_mask_still_clips_to_a_square_box`,
|
||||
the half the shape work had no reason to touch. The screenshot shows
|
||||
the fixture's horizontally scrolled code fence clipped on the curve at
|
||||
both top corners with no square pixels outside it.
|
||||
|
||||
**Also landed here, and not on this item's list**: the winit backend
|
||||
had the same defect the Android one was fixed for in `85869d0` -- it
|
||||
asked for a `Backends::PRIMARY` adapter and `.expect`ed one. This VM's
|
||||
Venus device disappears whenever the host runs out of virgl contexts,
|
||||
which happened mid-task, and layer 2 aborted with `Could not get
|
||||
adapter!` while GL sat there working. `default::render::UiRenderer::
|
||||
new` now probes and rebuilds the instance on `Backends::GL` exactly as
|
||||
Android does, and the adapter request names the backends it tried. The
|
||||
rule was written on one member of a set of two; this is the other.
|
||||
- [ ] Compose app: the `Reversed range` crash in `ToolInput.highlighted`
|
||||
(docs/TODO.md). Main branch, not rustify.
|
||||
|
||||
@@ -891,6 +995,19 @@ runs inside `cargo test`.
|
||||
layer 1 records that the platform was asked and layer 2 has no
|
||||
Android platform to ask.
|
||||
|
||||
**The one exception, added 2026-09-08**: `iris/tests/mask_sdf.rs`
|
||||
needs a GPU but no compositor and no window -- it asks wgpu for an
|
||||
adapter, runs two functions lifted out of `shader.wgsl` itself in a
|
||||
compute pass, and compares the answers with the CPU transliteration
|
||||
in `iris_core::render::sdf`. It sits inside `cargo test` because what
|
||||
it checks is arithmetic rather than pixels: the fragment stage and
|
||||
the hit test have to agree about where a rounded edge is, and neither
|
||||
layer 1 (which cannot run the shader) nor layer 2 (where a
|
||||
half-pixel disagreement is invisible) can say whether they do. Reach
|
||||
for this shape only when the question is "do these two
|
||||
implementations of one function agree" -- anything about what is
|
||||
*drawn* is still layer 2.
|
||||
|
||||
2. **A phone-shaped desktop window under headless sway -- for looking.**
|
||||
|
||||
cd iris && ./run-headless.sh phone --phone --shot /tmp/p.png -- -p transcript-fixture
|
||||
|
||||
Reference in new issue
Block a user