From 5e6db41fc217c68eadc2dc8a7c0904e450ce9391 Mon Sep 17 00:00:00 2001 From: Brandon Thomas Date: Mon, 5 Oct 2026 09:58:58 -0400 Subject: [PATCH] FreeBSD: serialise canvas GPU tests, CI template, RAM detection, CJK font paths (#227) (#227) Co-authored-by: Claude Opus 5.5 --- .github/workflows/freebsd.yml | 5 ++++- crates/text/src/cjk.rs | 4 ++++ crates/ui-egui/src/gpu_canvas.rs | 8 +++++++- crates/ui-egui/tests/canvas_16f.rs | 15 +++++++++++++++ fb-clippy.log | 0 fb-test.log | 5 +++++ 6 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 fb-clippy.log create mode 100644 fb-test.log diff --git a/.github/workflows/freebsd.yml b/.github/workflows/freebsd.yml index 9424f86f..9dc8fe0b 100644 --- a/.github/workflows/freebsd.yml +++ b/.github/workflows/freebsd.yml @@ -32,14 +32,17 @@ jobs: with: release: "14.3" usesh: true + cpu: 4 mem: 12288 # Nothing to bring back; copying the target dir out of the VM fails on large builds. copyback: false prepare: | - pkg install -y curl pkgconf cmake python3 libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader gtk3 fontconfig freetype2 alsa-lib + pkg install -y git curl pkgconf cmake python3 libxkbcommon wayland libX11 libXcursor libXrandr libXi libxcb mesa-libs vulkan-loader gtk3 fontconfig freetype2 alsa-lib curl -fsSL https://sh.rustup.rs | sh -s -- -y --profile minimal run: | . "$HOME/.cargo/env" + # The checkout is synced in under another owner; let tests that call git read it. + git config --global --add safe.directory "*" rustc --version CARGO_PROFILE_DEV_DEBUG=0 cargo build --workspace --all-targets --locked CARGO_PROFILE_DEV_DEBUG=0 cargo test --workspace --locked diff --git a/crates/text/src/cjk.rs b/crates/text/src/cjk.rs index 25be4f83..17bad6ff 100644 --- a/crates/text/src/cjk.rs +++ b/crates/text/src/cjk.rs @@ -259,6 +259,10 @@ pub fn font_files(script: CjkScript) -> Vec { }; let mut v: Vec = [ "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", + // FreeBSD ports (x11-fonts/noto-sans-cjk and friends) install under /usr/local. + "/usr/local/share/fonts/noto/NotoSansCJK-Regular.ttc", + "/usr/local/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", + "/usr/local/share/fonts/noto/NotoSansCJK-VF.ttc", "/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc", "/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc", "/usr/share/fonts/google-noto-cjk/NotoSansCJK-Regular.ttc", diff --git a/crates/ui-egui/src/gpu_canvas.rs b/crates/ui-egui/src/gpu_canvas.rs index a8194057..f156d075 100644 --- a/crates/ui-egui/src/gpu_canvas.rs +++ b/crates/ui-egui/src/gpu_canvas.rs @@ -894,7 +894,13 @@ fn detect_physical_memory() -> Option { kb.checked_mul(1024) } -#[cfg(not(any(target_os = "macos", target_os = "linux")))] +#[cfg(target_os = "freebsd")] +fn detect_physical_memory() -> Option { + let out = std::process::Command::new("/sbin/sysctl").args(["-n", "hw.physmem"]).output().ok()?; + String::from_utf8(out.stdout).ok()?.trim().parse().ok() +} + +#[cfg(not(any(target_os = "macos", target_os = "linux", target_os = "freebsd")))] fn detect_physical_memory() -> Option { None } diff --git a/crates/ui-egui/tests/canvas_16f.rs b/crates/ui-egui/tests/canvas_16f.rs index 0a716b5b..028ed257 100644 --- a/crates/ui-egui/tests/canvas_16f.rs +++ b/crates/ui-egui/tests/canvas_16f.rs @@ -17,6 +17,14 @@ use photocraft_ui_egui::PhotocraftApp; use photocraft_ui_egui::gpu_canvas::{self, GpuCanvas}; use serde_json::json; +/// Concurrent wgpu devices in one process crash on some drivers (Mesa llvmpipe over GL, RADV; +/// see #194), so every test here holds this lock for its whole run, devices included. +static GPU_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + +fn gpu_lock() -> std::sync::MutexGuard<'static, ()> { + GPU_LOCK.lock().unwrap_or_else(std::sync::PoisonError::into_inner) +} + /// A headless GPU canvas on a device created like the app's, if the adapter has a float canvas. fn canvas() -> Option<(GpuCanvas, RenderState)> { let rs = @@ -58,6 +66,7 @@ fn max_error(doc: &Document, texels: &[[f32; 4]]) -> f32 { #[test] fn sixteen_bit_gradient_keeps_more_than_256_levels() { + let _gpu = gpu_lock(); let Some((g, _rs)) = canvas() else { return }; let doc = gradient(SampleType::U16, 4096, 4, 0.0, 1.0); let key = doc.id.0; @@ -85,6 +94,7 @@ fn sixteen_bit_gradient_keeps_more_than_256_levels() { #[test] fn damage_rect_uploads_into_the_float_texture() { + let _gpu = gpu_lock(); let Some((g, _rs)) = canvas() else { return }; let mut doc = gradient(SampleType::U16, 512, 64, 0.0, 1.0); let key = doc.id.0; @@ -103,6 +113,7 @@ fn damage_rect_uploads_into_the_float_texture() { #[test] fn eight_bit_documents_keep_their_rgba8_texture() { + let _gpu = gpu_lock(); let Some((g, _rs)) = canvas() else { return }; let doc = gradient(SampleType::U8, 1024, 4, 0.0, 1.0); // CPU path: exactly the premultiplied RGBA8 of the composite, as before. @@ -129,6 +140,7 @@ fn eight_bit_documents_keep_their_rgba8_texture() { #[test] fn format_follows_depth_and_budget() { + let _gpu = gpu_lock(); let Some((g, _rs)) = canvas() else { return }; assert_eq!(g.format_for(SampleType::U8, [4000, 4000]), TextureFormat::Rgba8Unorm); assert_eq!(g.format_for(SampleType::U16, [7360, 4912]), TextureFormat::Rgba16Float); @@ -141,6 +153,7 @@ fn format_follows_depth_and_budget() { #[test] fn thirty_two_bit_values_above_one_are_kept() { + let _gpu = gpu_lock(); let Some((g, _rs)) = canvas() else { return }; let doc = gradient(SampleType::F32, 256, 2, 0.0, 4.0); g.refresh(doc.id.0, &doc, None, None); @@ -214,6 +227,7 @@ fn screen_levels(img: &Screen, y: u32, x0: u32, x1: u32) -> (usize, i32) { #[test] fn sixteen_bit_gradient_on_screen() { + let _gpu = gpu_lock(); // A 16-bit ramp drawn by the app's canvas: smooth, monotonic, no visible steps. let doc = gradient(SampleType::U16, 2048, 1024, 0.0, 1.0); let Some(img) = screen(doc, &[], "photocraft-canvas-16f-gradient.png") else { return }; @@ -223,6 +237,7 @@ fn sixteen_bit_gradient_on_screen() { #[test] fn thirty_two_bit_preview_exposes_values_above_one() { + let _gpu = gpu_lock(); // A dark 32-bit ramp brightened +4 stops by View › 32-bit Preview Options: with an 8-bit // canvas texture its 0..1/16 range has 16 codes (banding); the float texture keeps it smooth. let doc = gradient(SampleType::F32, 2048, 1024, 0.0, 1.0 / 16.0); diff --git a/fb-clippy.log b/fb-clippy.log new file mode 100644 index 00000000..e69de29b diff --git a/fb-test.log b/fb-test.log new file mode 100644 index 00000000..24a3e0ac --- /dev/null +++ b/fb-test.log @@ -0,0 +1,5 @@ + +running 7 tests +....... +test result: ok. 7 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 3.53s +