All outline text draws with Slug curves on every platform (SDF/MSDF stays selectable per size); a frame never presents a blank glyph and retained draw lists keep theirs. One coverage model for vector feathering, widget families split out of the core crate, fewer steady-state allocations, video keeps its aspect (Contain/Cover), render and SSAO pass unification. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
116 lines
4.7 KiB
Rust
116 lines
4.7 KiB
Rust
//! Native HAL GPU determinism check of the windows_blur example GaussStack.
|
|
//! Run with MAKEPAD_HAL_BACKEND=offscreen and the existing ignored test selection.
|
|
//! MAKEPAD_TEST_OUT_DIR keeps the two compared frames.
|
|
#![cfg(not(target_arch = "wasm32"))]
|
|
|
|
pub use makepad_widgets;
|
|
use makepad_widgets::*;
|
|
use std::{cell::RefCell, rc::Rc};
|
|
|
|
// The example is compiled into this test without its `tweaker` feature.
|
|
#[allow(unexpected_cfgs)]
|
|
#[path = "../../examples/windows_blur/src/main.rs"]
|
|
mod windows_blur;
|
|
|
|
fn decode_png(path: &std::path::Path) -> ((usize, usize), Vec<u8>) {
|
|
use makepad_zune_png::{makepad_zune_core::bytestream::ZCursor, PngDecoder};
|
|
let bytes = std::fs::read(path).unwrap();
|
|
let mut png = PngDecoder::new(ZCursor::new(bytes));
|
|
let pixels = png.decode_raw().unwrap();
|
|
(png.dimensions().unwrap(), pixels)
|
|
}
|
|
|
|
fn render_gauss_frame(out_dir: &std::path::Path) -> usize {
|
|
use windows_blur::App;
|
|
std::fs::create_dir_all(out_dir).unwrap();
|
|
let mut handler = makepad_platform::_app_main_event_closure!(App);
|
|
let cx = Rc::new(RefCell::new(Cx::new(Box::new(move |cx, event| {
|
|
handler(cx, event);
|
|
if matches!(event, Event::Draw(_)) {
|
|
// Warm up window glass registration, then record the capture and
|
|
// consumer together before the HAL submits the frame.
|
|
cx.redraw_all();
|
|
handler(cx, event);
|
|
}
|
|
}))));
|
|
cx.borrow_mut().init_cx_os();
|
|
capture(&cx, &out_dir.join("window_0_frame_000000.png"));
|
|
let cx = cx.borrow();
|
|
cx.passes
|
|
.id_iter()
|
|
.filter(|id| {
|
|
let pass = &cx.passes[*id];
|
|
(pass.debug_name.starts_with("gauss_mip_")
|
|
|| pass.debug_name.starts_with("gauss_smooth_mip_"))
|
|
&& pass.main_draw_list_id.is_some()
|
|
})
|
|
.count()
|
|
}
|
|
|
|
#[test]
|
|
#[ignore = "requires a native HAL GPU release build"]
|
|
fn windows_blur_gauss_stack_render_matches_reference() {
|
|
let keep = std::env::var_os("MAKEPAD_TEST_OUT_DIR").map(std::path::PathBuf::from);
|
|
let scratch = keep.clone().unwrap_or_else(|| {
|
|
std::env::temp_dir().join(format!("makepad-gauss-chain-{}", std::process::id()))
|
|
});
|
|
let first_dir = scratch.join("instance_a");
|
|
let second_dir = scratch.join("instance_b");
|
|
|
|
let filters_a = render_gauss_frame(&first_dir);
|
|
let filters_b = render_gauss_frame(&second_dir);
|
|
assert_eq!(
|
|
filters_a, 12,
|
|
"the example must exercise all six downsample and six tent passes"
|
|
);
|
|
assert_eq!(filters_a, filters_b, "second instance must run the same GaussStack");
|
|
|
|
let first = first_dir.join("window_0_frame_000000.png");
|
|
let second = second_dir.join("window_0_frame_000000.png");
|
|
assert!(first.is_file(), "missing frame {}", first.display());
|
|
assert!(second.is_file(), "missing frame {}", second.display());
|
|
|
|
let (size, pixels) = decode_png(&first);
|
|
let (other_size, other_pixels) = decode_png(&second);
|
|
assert_eq!(size, other_size);
|
|
assert_eq!(pixels.len(), other_pixels.len());
|
|
let components = pixels.len() / (size.0 * size.1);
|
|
let changed = pixels
|
|
.chunks_exact(components)
|
|
.zip(other_pixels.chunks_exact(components))
|
|
.filter(|(a, b)| a != b)
|
|
.count();
|
|
let fraction = changed as f64 / (size.0 * size.1) as f64;
|
|
println!(
|
|
"GaussStack pixel diff: {changed}/{} ({:.6}%)",
|
|
size.0 * size.1,
|
|
fraction * 100.0
|
|
);
|
|
assert!(
|
|
fraction <= 0.005,
|
|
"GaussStack changed more than 0.5% of pixels"
|
|
);
|
|
}
|
|
|
|
fn capture(cx: &Rc<RefCell<Cx>>, path: &std::path::Path) {
|
|
use makepad_widgets::makepad_platform::makepad_hal::{self as hal_api, Hal, OpenReq, open_flags};
|
|
let hal = Hal::global();
|
|
let mut req = OpenReq::from_env(0, open_flags::OFFSCREEN_WINDOWS, 1.0);
|
|
req.flags |= open_flags::OFFSCREEN_WINDOWS | open_flags::RETAIN_WINDOWS;
|
|
let info = hal.open(&req);
|
|
assert_ne!(info.backend, hal_api::backend_id::HEADLESS, "pixel comparisons require a GPU");
|
|
cx.borrow_mut().hal_attach_session(info);
|
|
let deadline = std::time::Instant::now() + std::time::Duration::from_secs(30);
|
|
let pixels = loop {
|
|
assert!(std::time::Instant::now() < deadline, "HAL capture timed out");
|
|
let mut cx = cx.borrow_mut();
|
|
assert!(cx.hal_session_turn(1_000_000, true));
|
|
let submitted = cx.frame_submission_serial();
|
|
if submitted != 0 && cx.frame_completion_serial() == submitted
|
|
&& cx.pipelines_pending() == 0 && cx.pending_async_loads().is_empty() {
|
|
if let Some(pixels) = hal.window_pixels(info.session, None) { break pixels; }
|
|
}
|
|
};
|
|
hal.close(info.session);
|
|
std::fs::write(path, hal_api::automation::png_rgba(pixels.w, pixels.h, &pixels.rgba)).unwrap();
|
|
}
|