GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
fix: the browser build compiles again; CI runs the path tracer's tests
The new wasm job's first run found the browser half broken since
2026-10-06, with nothing building it:
- backend/frame.rs printed through crate::vk::present_debug in portable
code; the print is native-only now.
- backend/driver.rs kept last_input as a std::time::Instant and stamped
it with std's now() — a type error against the pacer's web_time
Instant in the browser, and a panic there at run time had it compiled.
It is web_time's (std's own type natively), like the rest of the file.
the_same_text_in_another_face_is_damage asked for "monospace"; on the
macOS runner that resolves to no installed family, the fallback draws
both styles with one upright face and no fake-italic flag, and the
picture — rightly undamaged — failed the test. It now picks a family the
font system holds upright and italic, and skips with a note if none.
CI also runs the path tracer's #[ignore]d GPU tests on lavapipe's
compute tier (CCE_VK_RT=compute), the tier WebGPU and every device
without RT cores run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
.github/workflows/ci.yml | 6 ++++++
src/backend/driver.rs | 4 ++--
src/backend/frame.rs | 35 +++++++++++++++++++++++++++++++----
3 files changed, 39 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 2ae6466..9aad900 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -55,6 +55,12 @@ jobs:
- name: Test (all features)
run: cargo test --all-features
+ # The path tracer's GPU tests are #[ignore]d for machines without a
+ # device; lavapipe has one, and its compute tier is what every Vulkan
+ # device without RT cores (and WebGPU) runs.
+ - name: Path tracer GPU tests (compute tier)
+ run: CCE_VK_RT=compute cargo test --lib vk::rt -- --ignored
+
# The GPU tests skip with a note when no Vulkan device opens, and a
# skipped test passes. Fail instead if they skipped here, so losing
# lavapipe does not quietly drop the only rendered checks.
diff --git a/src/backend/driver.rs b/src/backend/driver.rs
index c54da97..fe01467 100644
--- a/src/backend/driver.rs
+++ b/src/backend/driver.rs
@@ -153,7 +153,7 @@ pub struct Driver {
/// When the last input event of any kind arrived — what makes a redraw
/// interactive, which is the redraw the runner's warm-down is for
/// (`shell::Pacer`).
- pub last_input: Option<std::time::Instant>,
+ pub last_input: Option<Instant>,
pub pressed_key: Option<PressedKey>,
/// The pointer's last position, window-logical (popup events translated).
pub cursor_pos: (f32, f32),
@@ -288,7 +288,7 @@ impl Driver {
/// window can misroute (e.g. a divider press falling through to the
/// movable-root plate window drag).
fn note_input(&mut self) {
- self.last_input = Some(std::time::Instant::now());
+ self.last_input = Some(Instant::now());
}
pub fn pointer_enter<A: Application>(&mut self, t: Turn<'_, A>, pos: LogicalPosition) {
diff --git a/src/backend/frame.rs b/src/backend/frame.rs
index 6dddeba..2a3919d 100644
--- a/src/backend/frame.rs
+++ b/src/backend/frame.rs
@@ -471,6 +471,8 @@ pub fn derive_damage(frame: &mut BuiltFrame, record: &mut FrameRecord, texts: &[
return;
}
frame.damage = Some((x0 as u32, y0 as u32, (x1 - x0).max(1) as u32, (y1 - y0).max(1) as u32));
+ // The switch lives with the Vulkan renderer, which the browser build does not have.
+ #[cfg(not(target_arch = "wasm32"))]
if crate::vk::present_debug() {
eprintln!("[vk] derived damage {}x{}+{}+{} of {pw}x{ph}", x1 - x0, y1 - y0, x0, y0);
}
@@ -628,16 +630,41 @@ mod damage_tests {
scale: 1.0,
physical: (400, 200),
};
+ // A family the font system holds in both an upright and an italic face, so the two
+ // really are different pictures. Asked for a generic family that resolves to nothing
+ // installed (a macOS runner's "monospace"), the per-glyph fallback can draw both
+ // styles with one upright face and no fake-italic flag: the same picture, and no
+ // damage is then the right answer, not a failure.
+ let family = {
+ use cosmic_text::fontdb::Style;
+ let fs = crate::geometry_font_system().lock().unwrap();
+ let mut styles: std::collections::HashMap<String, (bool, bool)> = Default::default();
+ for f in fs.db().faces() {
+ if let Some((name, _)) = f.families.first() {
+ let e = styles.entry(name.clone()).or_default();
+ match f.style {
+ Style::Normal => e.0 = true,
+ Style::Italic => e.1 = true,
+ Style::Oblique => {}
+ }
+ }
+ }
+ let mut both: Vec<String> = styles.into_iter().filter(|(_, (n, i))| *n && *i).map(|(k, _)| k).collect();
+ both.sort();
+ both.into_iter().next()
+ };
+ let Some(family) = family else {
+ eprintln!("skipping: no font family here has both an upright and an italic face");
+ return;
+ };
let key = |attrs: TextAttrs| {
let mut fs = crate::geometry_font_system().lock().unwrap();
- let buffer = crate::backend::text::shared_text_buffer(&mut fs, "The quick brown fox", 14.0, Some("monospace"), attrs);
+ let buffer = crate::backend::text::shared_text_buffer(&mut fs, "The quick brown fox", 14.0, Some(&family), attrs);
let t = DlText { buffer, x: 10.0, y: 10.0, color: cosmic_text::Color::rgb(255, 255, 255), bounds: None, clip_circle: None, clip_rrect: None };
FrameSig::of(&frame, &[t]).texts[0].0
};
let upright = TextAttrs::default();
assert_eq!(key(upright), key(upright), "an unchanged item is no damage");
- // Italic is a different face or, with none installed, the fake-italic
- // flag: a different picture either way.
- assert_ne!(key(upright), key(TextAttrs { italic: true, ..upright }));
+ assert_ne!(key(upright), key(TextAttrs { italic: true, ..upright }), "{family}: italic is another face");
}
}