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>
135 lines
5.7 KiB
TOML
135 lines
5.7 KiB
TOML
[package]
|
|
name = "iris"
|
|
version.workspace = true
|
|
edition.workspace = true
|
|
|
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
|
|
[dependencies]
|
|
iris-core = { workspace = true }
|
|
iris-macro = { workspace = true }
|
|
parley = { workspace = true }
|
|
swash = { workspace = true }
|
|
pollster = { workspace = true }
|
|
wgpu = { workspace = true }
|
|
image = { workspace = true }
|
|
accesskit = { workspace = true }
|
|
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread"] }
|
|
# For diagnostics visible through android_logger (or whatever logger the
|
|
# app crate installs) -- this crate never installs one itself. Not in the
|
|
# android-only block below any more: the lines that matter most are in
|
|
# shared widget code, which the host backend compiles too.
|
|
log = "0.4.28"
|
|
|
|
# winit everywhere except Android; android-view (below) is what stands in
|
|
# for it there. Both backends live in this crate (see `src/android/mod.rs`'s
|
|
# doc comment) but are never compiled together: winit's own Android support
|
|
# pulls in `android-activity`, which panics at compile time unless one of
|
|
# its own backend features is picked, and picking one is exactly what
|
|
# `iris-core` was kept free of (RUST.md's I0b). Confirmed by trying it
|
|
# 2026-09-05: `cargo ndk -t x86_64 -P 26 build -p iris` failed inside
|
|
# `android-activity` itself with "Either game-activity or native-activity
|
|
# must be enabled" before this split existed.
|
|
[target.'cfg(not(target_os = "android"))'.dependencies]
|
|
winit = { workspace = true }
|
|
arboard = { workspace = true, features = ["wayland-data-control"] }
|
|
# I4 (RUST.md): the desktop half of the AccessKit push, `winit`'s own
|
|
# adapter over `accesskit`. No pin needed the way android-view's rev is
|
|
# pinned -- this is an ordinary crates.io release with no local abort to
|
|
# track (that finding is Android-only, see below).
|
|
accesskit_winit = "0.34.0"
|
|
|
|
# Pinned to the exact commit RUST.md's E1 (2026-09-04) measured on this
|
|
# emulator -- real Vulkan rendering, a working `InputConnection`, and the
|
|
# accesskit-detach abort, all against this rev specifically. Advancing it
|
|
# wants re-running E1's checks, the same reason the nightly toolchain pin
|
|
# is dated rather than floating.
|
|
[target.'cfg(target_os = "android")'.dependencies]
|
|
android-view = { git = "https://github.com/rust-mobile/android-view.git", rev = "bec6c62a96cef8239b0fd7fedeef9b184d02e3a1" }
|
|
# I4 (RUST.md): the Android half of the AccessKit push, over android-view's
|
|
# `AccessibilityNodeProvider`. **0.8.0 carries the same detach-abort E1
|
|
# found on 0.4.0** (the `State` enum still never returns to `Inactive`,
|
|
# and `send_completed_event` still unwraps a Java exception) -- advancing
|
|
# the version is not the fix, so pinning to a specific rev buys nothing
|
|
# here the way it does for android-view itself. `android/view.rs`'s
|
|
# `raise_if_enabled` is the mitigation, carried from E1.
|
|
accesskit_android = "0.8.0"
|
|
# Not re-exported by android-view (only `jni` and `ndk` are), and needed
|
|
# for `android/insets.rs`'s own id -> state map -- the same reason
|
|
# android-view's own `PEER_MAP` carries one.
|
|
send_wrapper = "0.6.0"
|
|
|
|
[features]
|
|
# RUST.md's I5 "Where iris's frame time goes" diagnosis: forces the Android
|
|
# `wgpu::Instance` to `Backends::GL` instead of `Backends::PRIMARY`, so the
|
|
# same build can be measured against SwiftShader's software Vulkan ICD (the
|
|
# default) or virgl's GLES path, without a second env-var plumbing path that
|
|
# nothing on this machine can hand to an already-launched Android process
|
|
# (there is no `am start` environment and no system-property reader here to
|
|
# add one). Read by `android/render.rs` and, so the GLES path can be
|
|
# reproduced on a machine with a real GPU rather than only in the emulator,
|
|
# by `default/render.rs`:
|
|
# ./run-headless.sh transcript --shot /tmp/x.png -- -p transcript-ui \
|
|
# --features iris/force-gles
|
|
force-gles = []
|
|
|
|
[dev-dependencies]
|
|
tokio = { workspace = true, features = ["sync", "rt", "rt-multi-thread", "time"] }
|
|
# The tabs example's widget tree. A dev-dependency cycle back to this
|
|
# package is fine -- cargo excludes dev-dependencies from the graph used
|
|
# to build the library itself, so this only matters for `--examples`.
|
|
tabs-ui = { path = "tabs-ui" }
|
|
# `tests/mask_sdf.rs` only: the grid it hands the GPU and the coverages it
|
|
# reads back. wgpu and pollster are ordinary dependencies already.
|
|
bytemuck = { workspace = true }
|
|
|
|
# Plain Instant-timed binaries, not criterion -- see benches/message_list.rs's
|
|
# header for why. `harness = false` opts out of the unstable `#[bench]`
|
|
# test-crate harness cargo would otherwise want, in favour of an ordinary
|
|
# `fn main()`.
|
|
[[bench]]
|
|
name = "message_list"
|
|
harness = false
|
|
|
|
[workspace]
|
|
members = [
|
|
"core",
|
|
"macro",
|
|
"tabs-ui",
|
|
"transcript-ui",
|
|
"transcript-fixture",
|
|
"rig-input",
|
|
"desktop-app",
|
|
]
|
|
# android-app pulls in android-view, which needs the NDK sysroot to link
|
|
# -- excluded so `cargo build --workspace --all-targets` on the host stays
|
|
# buildable. Cross-compile it from its own directory (its own single-crate
|
|
# workspace, since it has no `[workspace]` table of its own and this
|
|
# exclusion stops it inheriting this one): `cd android-app && cargo ndk
|
|
# -t x86_64 -P 29 build`.
|
|
exclude = ["android-app"]
|
|
|
|
[workspace.package]
|
|
version = "0.1.0"
|
|
edition = "2024"
|
|
|
|
[workspace.dependencies]
|
|
pollster = "0.4.0"
|
|
winit = "0.30.12"
|
|
wgpu = "28.0.0"
|
|
bytemuck = "1.23.1"
|
|
image = "0.25.6"
|
|
parley = "0.11.1"
|
|
swash = "0.2.10"
|
|
fxhash = "0.2.1"
|
|
arboard = "3.6.1"
|
|
accesskit = "0.25.0"
|
|
iris-core = { path = "core" }
|
|
iris-macro = { path = "macro" }
|
|
tokio = "1.49.0"
|
|
# Current stable as of 2026-09-05 (`cargo search`) -- I5's markdown block
|
|
# model, the same crate E2's uncommitted `e2-transcript` experiment used for
|
|
# the identical job (RUST.md), rather than reimplementing a CommonMark
|
|
# parser.
|
|
pulldown-cmark = "0.13.4"
|