Prune commentary and stale Rust port notes
This commit is contained in:
1 parent
3ae034a47b
commit
1e6d3b1edd
84 files changed
+334
-5648
No files matched your search
@@ -1,41 +1,3 @@
|
||||
//! The CPU rounded-rect SDF and the shader's own must agree.
|
||||
//!
|
||||
//! LAYOUT.md's "Masks with a shape" turns on it: the fragment stage clips
|
||||
//! a masked subtree with `shader.wgsl`'s `rounded_rect_coverage`, and the
|
||||
//! hit test (`UiRenderState::mask_admits`) clips the *same* subtree with
|
||||
//! `iris_core::rounded_rect_coverage`, so a corner that cannot be tapped
|
||||
//! and a corner that is not drawn are the same corner only while the two
|
||||
//! functions answer the same. Nothing else checks that: both sides are
|
||||
//! individually plausible and drift shows up as a control that is a pixel
|
||||
//! or two off, which is exactly what nobody notices.
|
||||
//!
|
||||
//! So this runs **the real shader text**, lifted out of
|
||||
//! `iris_core::SHAPE_SHADER` by name rather than copied here, over a grid
|
||||
//! of points, and compares what came back with the Rust function at the
|
||||
//! same points. This is the only test in the workspace that needs a GPU;
|
||||
//! everything else about masks is layer 1 (docs/RUST.md's "Three test
|
||||
//! layers"). It fails rather than skips when there is no adapter, because
|
||||
//! a check that quietly did not run reads exactly like a check that
|
||||
//! passed.
|
||||
//!
|
||||
//! **It is a render pass, and it asks for `iris_core::device_limits()`,
|
||||
//! because those are the two things iris itself does.** The first version
|
||||
//! of this test was a compute pass, which meant asking for compute limits
|
||||
//! that `device_limits()` deliberately zeroes -- docs/RUST.md, 2026-09-05:
|
||||
//! nothing in `iris`/`iris-core` creates a `ComputePipeline` or writes a
|
||||
//! `@compute` stage, so the limits stopped being requested rather than a
|
||||
//! fallback being built for a capability nothing uses. A test that needs
|
||||
//! a capability the thing under test has never needed is testing the
|
||||
//! wrong device, which is reason enough.
|
||||
//!
|
||||
//! It is **not** why that version crashed; see [`vulkan_instance`] for
|
||||
//! what that crash actually was and why nothing here has to work around
|
||||
//! it any more.
|
||||
|
||||
// `OnceLock<wgpu::Instance>` needs `Instance: Sync`, and wgpu's type
|
||||
// graph is deep enough that proving it overflows rustc's default trait
|
||||
// recursion limit of 128. Nothing here is recursive; the limit is a
|
||||
// compile-time budget, and this is the documented way to raise it.
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
use std::sync::OnceLock;
|
||||
@@ -44,35 +6,16 @@ use iris_core::{SHAPE_SHADER, rounded_rect_coverage, util::Vec2};
|
||||
use pollster::FutureExt;
|
||||
use wgpu::util::DeviceExt;
|
||||
|
||||
/// The rect the grid is sampled against, in window pixels. Deliberately
|
||||
/// off the whole-pixel grid: the shader floors a primitive's corners, but
|
||||
/// `rounded_rect_coverage` is handed pixels either side of that and has to
|
||||
/// agree at fractional positions too -- the phone's 2.55 density puts
|
||||
/// nothing on a whole pixel.
|
||||
const TOP_LEFT: Vec2 = Vec2::new(10.5, 20.25);
|
||||
const BOT_RIGHT: Vec2 = Vec2::new(170.75, 90.0);
|
||||
|
||||
/// Radii spanning what the widgets actually ask for, plus the two edges of
|
||||
/// the function's own domain: a square corner, and one large enough that
|
||||
/// `min(edge, radius)` stops mattering.
|
||||
const RADII: [f32; 5] = [0.0, 0.75, 8.0, 20.0, 34.0];
|
||||
|
||||
/// The grid, as an attachment: one texel per probe point. `GRID_W` is a
|
||||
/// multiple of 64 so that a row of `R32Float` is 256-byte aligned, which
|
||||
/// is what `copy_texture_to_buffer` requires; at `STEP` this spans the
|
||||
/// rect above and about four pixels of margin on every side, so the
|
||||
/// feather is sampled rather than stepped over.
|
||||
const GRID_W: u32 = 384;
|
||||
const GRID_H: u32 = 192;
|
||||
const STEP: f32 = 0.5;
|
||||
const ORIGIN: Vec2 = Vec2::new(TOP_LEFT.x - 4.0, TOP_LEFT.y - 4.0);
|
||||
|
||||
/// f32 arithmetic in two compilers, not one: `length`/`sqrt` and
|
||||
/// `smoothstep` are each allowed a unit or two in the last place, and the
|
||||
/// GPU may contract a multiply-add the CPU does not. A coverage is in
|
||||
/// [0, 1], so this is about six decimal digits -- four orders of magnitude
|
||||
/// tighter than the half-pixel feather the hit test reads, which is what
|
||||
/// the agreement is actually for.
|
||||
const TOLERANCE: f32 = 1e-5;
|
||||
|
||||
#[test]
|
||||
@@ -114,7 +57,6 @@ fn mask_sdf_matches_the_shader() {
|
||||
corner stops being tappable where it is drawn.",
|
||||
);
|
||||
|
||||
// The half that would pass on a function returning a constant.
|
||||
assert!(
|
||||
inside > 0 && feather > 0 && outside > 0,
|
||||
"the grid never crossed an edge ({inside} in, {feather} on the feather, {outside} out), \
|
||||
@@ -122,18 +64,10 @@ fn mask_sdf_matches_the_shader() {
|
||||
);
|
||||
}
|
||||
|
||||
/// The probe position of texel `(x, y)` -- the one place the mapping
|
||||
/// lives, so the CPU side and the fragment stage cannot walk different
|
||||
/// grids.
|
||||
fn probe_at(x: u32, y: u32) -> Vec2 {
|
||||
Vec2::new(ORIGIN.x + x as f32 * STEP, ORIGIN.y + y as f32 * STEP)
|
||||
}
|
||||
|
||||
/// `shader.wgsl`'s own `rounded_rect_coverage`, evaluated at every texel
|
||||
/// of an `R32Float` attachment: one fragment per grid point, read back
|
||||
/// whole. A fragment stage because that is the stage the function is
|
||||
/// really called from, so what this compares is the code path that draws
|
||||
/// rather than a second one built to be measurable.
|
||||
fn run_shader(gpu: &Gpu, radius: f32) -> Vec<f32> {
|
||||
let Gpu { device, queue, .. } = gpu;
|
||||
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
@@ -141,10 +75,6 @@ fn run_shader(gpu: &Gpu, radius: f32) -> Vec<f32> {
|
||||
source: wgpu::ShaderSource::Wgsl(probe_source().into()),
|
||||
});
|
||||
|
||||
// R32Float, not an 8-bit colour format: a coverage quantised to 1/255
|
||||
// could not be compared against the CPU's at anything like TOLERANCE,
|
||||
// and the comparison would then be measuring the texture rather than
|
||||
// the two functions.
|
||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("mask sdf coverage"),
|
||||
size: wgpu::Extent3d {
|
||||
@@ -205,9 +135,6 @@ fn run_shader(gpu: &Gpu, radius: f32) -> Vec<f32> {
|
||||
}],
|
||||
});
|
||||
|
||||
// `copy_texture_to_buffer` wants each row 256-byte aligned; GRID_W is
|
||||
// chosen so that it already is, rather than padding and unpicking the
|
||||
// padding on the way out.
|
||||
let row_bytes = GRID_W * 4;
|
||||
assert_eq!(row_bytes % 256, 0, "GRID_W must keep rows 256-byte aligned");
|
||||
let out_size = u64::from(row_bytes) * u64::from(GRID_H);
|
||||
@@ -272,42 +199,17 @@ fn run_shader(gpu: &Gpu, radius: f32) -> Vec<f32> {
|
||||
coverage
|
||||
}
|
||||
|
||||
/// One `wgpu::Instance` for the process, created on first use and never
|
||||
/// destroyed.
|
||||
///
|
||||
/// **Why it is a static rather than a value the test owns.** Destroying
|
||||
/// the last `VkInstance` makes the Vulkan loader `dlclose` the ICD, and
|
||||
/// Mesa's ICD here registers a `pthread_key_create` destructor pointing
|
||||
/// into its own text without being linked `-z nodelete`. glibc then calls
|
||||
/// that destructor when the thread exits -- through an address that is no
|
||||
/// longer mapped. libtest runs every `#[test]` on a spawned thread, so a
|
||||
/// test that opens and closes an instance segfaults *after* printing its
|
||||
/// result, which reads exactly like the test failing. Measured
|
||||
/// 2026-09-08 with `scripts/rigs/gpu-probe`'s `teardown` bin: it
|
||||
/// needs no wgpu (raw `ash` does it too), no GPU work, and no device --
|
||||
/// an instance created and destroyed on a spawned thread is enough, and
|
||||
/// keeping any one instance alive is enough to prevent it.
|
||||
///
|
||||
/// Devices, queues and everything else drop normally; only the instance
|
||||
/// is held, which is what wgpu asks for anyway (one instance per
|
||||
/// process). So this costs one instance for the length of a test binary
|
||||
/// and buys ordinary drops everywhere else.
|
||||
fn vulkan_instance() -> &'static wgpu::Instance {
|
||||
static INSTANCE: OnceLock<wgpu::Instance> = OnceLock::new();
|
||||
INSTANCE.get_or_init(wgpu::Instance::default)
|
||||
}
|
||||
|
||||
/// The device this test draws with.
|
||||
struct Gpu {
|
||||
device: wgpu::Device,
|
||||
queue: wgpu::Queue,
|
||||
}
|
||||
|
||||
impl Gpu {
|
||||
/// Opens the device this test draws with, and reports which adapter
|
||||
/// answered, because that is not a detail here: a run on llvmpipe and
|
||||
/// a run on the host's GPU are otherwise indistinguishable in the
|
||||
/// log, and only one of them is a check of what the phone will do.
|
||||
fn open() -> Self {
|
||||
let instance = vulkan_instance();
|
||||
let adapter = instance
|
||||
@@ -326,7 +228,6 @@ impl Gpu {
|
||||
);
|
||||
let (device, queue) = adapter
|
||||
.request_device(&wgpu::DeviceDescriptor {
|
||||
// What iris itself asks for -- see this file's header.
|
||||
required_limits: iris_core::device_limits(),
|
||||
..Default::default()
|
||||
})
|
||||
@@ -336,8 +237,6 @@ impl Gpu {
|
||||
}
|
||||
}
|
||||
|
||||
/// What the fragment stage needs to turn its own texel into a probe
|
||||
/// position: the rect being sampled, and where texel (0, 0) sits.
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
|
||||
struct Probe {
|
||||
@@ -349,11 +248,6 @@ struct Probe {
|
||||
_pad: [f32; 3],
|
||||
}
|
||||
|
||||
/// The probe module: the two functions **lifted from `shader.wgsl`
|
||||
/// itself**, plus an entry point that calls the outer one. Lifted rather
|
||||
/// than copied so there is nothing to keep in step -- an edit to the
|
||||
/// shader is what this test is for, and a copy here would be edited along
|
||||
/// with it.
|
||||
fn probe_source() -> String {
|
||||
format!(
|
||||
"{}\n{}\n\
|
||||
@@ -380,9 +274,6 @@ fn probe_source() -> String {
|
||||
)
|
||||
}
|
||||
|
||||
/// One WGSL function's whole text, from its `fn` keyword to the `}` that
|
||||
/// closes its body, found by matching braces. Panics by name when the
|
||||
/// function is not there, which is what a rename looks like from here.
|
||||
fn wgsl_fn(name: &str) -> &'static str {
|
||||
let start = SHAPE_SHADER
|
||||
.find(&format!("fn {name}("))
|
||||
|
||||
Reference in new issue
Block a user