git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit8580ee4a1a63384f810d4e0f21881383a467e89f
parent901699b39b
authorLucas Galante <lsgalante12@gmail.com>
date2026-10-08 08:22
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");
     }
 }