use daegun::daecore::daemachine::subpixel::{StripeOrder, SubpixelLayout};
use super::face::Face;
use daegun::daerizer::daegpu::{eval, GpuBatch, SubpixelParams, ffi::{Mode, Renderer}};
fn renderer() -> Option<Renderer> {
Renderer::new().ok()
}
#[test]
fn metal_grayscale_matches_the_reference_evaluator() {
let Some(gpu) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let params = SubpixelParams::default();
let (w, h) = (128u32, 128u32);
let px = 96.0f32;
let inv_px = 1.0f32 / px;
let mut target = gpu.target(w, h).expect("target");
let (mut checked, mut worst, mut inked) = (0usize, 0i32, 0usize);
let (mut sum, mut over) = (0f64, 0usize);
for gid in [36u16, 50, 37, 74, 90, 25] {
let Some(slot) = face.glyph(&mut batch, gid) else { continue };
let geometry = gpu.geometry(&batch).expect("geometry");
let inst = slot.instance([16.0, 16.0], px, [px, px], [1.0, 1.0, 1.0, 1.0]);
gpu.draw(&mut target, &geometry, &[inst], ¶ms, Mode::Grayscale).expect("draw");
let pixels = gpu.read_pixels(&mut target).expect("read").to_vec();
for y in 0..h {
for x in 0..w {
let em = [
(x as f32 + 0.5 - 16.0) * inv_px,
((h - 1 - y) as f32 + 0.5 - 16.0) * inv_px,
];
let want = (eval::coverage(&batch, &slot, em, [px, px]) * 255.0).round() as i32;
let got = i32::from(pixels[((y * w + x) * 4 + 3) as usize]);
let delta = (got - want).abs();
worst = worst.max(delta);
sum += f64::from(delta);
if delta > 16 { over += 1 }
checked += 1;
if got > 0 { inked += 1 }
}
}
}
assert!(checked > 50_000, "only {checked} pixels compared");
assert!(inked > 2_000, "only {inked} pixels took ink, so the draw produced nothing to grade");
let mean = sum / checked as f64;
let rate = over as f64 / checked as f64;
std::eprintln!("metal-vs-eval: mean {mean:.5} over-16 rate {:.4}% worst {worst}", rate * 100.0);
assert!(mean < 0.003, "mean |delta| is {mean:.5} of 255; the interpolated coordinate scored 0.00425 on Apple and 0.00456 on AMD");
assert!(rate < 0.0002, "{over} of {checked} pixels ({:.4}%) differ by more than 16", rate * 100.0);
assert!(worst <= 2, "a pixel differs by {worst} of 255; this should be 0");
}
#[test]
fn pixels_arrive_only_when_read() {
let Some(gpu) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let slot = face.glyph(&mut batch, 37).expect("glyph");
let geometry = gpu.geometry(&batch).expect("geometry");
let inst = slot.instance([8.0, 8.0], 64.0, [64.0, 64.0], [1.0, 1.0, 1.0, 1.0]);
let mut target = gpu.target(96, 96).expect("target");
assert!(target.pixels().iter().all(|&b| b == 0), "a new target is not blank");
gpu.draw(&mut target, &geometry, &[inst], &SubpixelParams::default(), Mode::Grayscale)
.expect("draw");
let ink = gpu.read_pixels(&mut target).expect("read").iter().filter(|&&b| b > 0).count();
assert!(ink > 200, "only {ink} non-zero bytes after a read");
}
#[test]
fn draws_beyond_the_ring_depth_stay_correct() {
let Some(gpu) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let slot = face.glyph(&mut batch, 48).expect("glyph");
let geometry = gpu.geometry(&batch).expect("geometry");
let mut target = gpu.target(96, 96).expect("target");
let params = SubpixelParams::default();
let mut first = None;
for round in 0..24 {
let instances: Vec<_> = (0..=(round % 3))
.map(|i| slot.instance([8.0 + i as f32 * 0.0, 8.0], 64.0, [64.0, 64.0], [1.0, 1.0, 1.0, 1.0]))
.collect();
gpu.draw(&mut target, &geometry, &instances, ¶ms, Mode::Grayscale).expect("draw");
let ink = gpu.read_pixels(&mut target).expect("read").iter().filter(|&&b| b > 0).count();
match first {
None => first = Some(ink),
Some(f) => assert_eq!(ink, f, "round {round} rendered differently from the first"),
}
}
}
#[test]
fn subpixel_and_grayscale_both_render_and_differ() {
let Some(gpu) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let slot = face.glyph(&mut batch, 72).expect("glyph");
let geometry = gpu.geometry(&batch).expect("geometry");
let inst = slot.instance([8.0, 8.0], 32.0, [32.0, 32.0], [1.0, 1.0, 1.0, 1.0]);
let mut target = gpu.target(64, 64).expect("target");
gpu.draw(&mut target, &geometry, &[inst], &SubpixelParams::default(), Mode::Grayscale).expect("draw");
let gray = gpu.read_pixels(&mut target).expect("read").to_vec();
let layout = SubpixelLayout::horizontal(StripeOrder::Rgb);
gpu.draw(&mut target, &geometry, &[inst], &SubpixelParams::from_layout(&layout), Mode::Subpixel).expect("draw");
let sub = gpu.read_pixels(&mut target).expect("read").to_vec();
assert!(gray.iter().any(|&b| b > 0), "grayscale drew nothing");
assert!(sub.iter().any(|&b| b > 0), "subpixel drew nothing");
assert_ne!(gray, sub, "subpixel and grayscale produced identical pixels");
}
#[test]
fn degenerate_targets_are_refused() {
let Some(gpu) = renderer() else { return };
assert!(gpu.target(0, 16).is_err());
assert!(gpu.target(16, 0).is_err());
let mut t = gpu.target(8, 8).expect("target");
assert!(gpu.wait(&mut t).is_ok());
}
#[test]
fn a_second_renderer_on_the_same_device_shares_targets() {
let Some(gpu) = renderer() else { return };
let Some(other) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let slot = face.glyph(&mut batch, 37).expect("glyph");
let geometry = other.geometry(&batch).expect("geometry");
let inst = slot.instance([8.0, 8.0], 48.0, [48.0, 48.0], [1.0; 4]);
let mut target = gpu.target(64, 64).expect("target");
other
.draw(&mut target, &geometry, &[inst], &SubpixelParams::default(), Mode::Grayscale)
.expect("a target from a renderer on the same device must be accepted");
let pixels = other.read_pixels(&mut target).expect("read");
assert!(pixels.iter().any(|&b| b > 0), "drew nothing");
}
#[test]
fn a_target_drawn_by_one_renderer_reads_back_through_another() {
let Some(drawer) = renderer() else { return };
let Some(reader) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let slot = face.glyph(&mut batch, 37).expect("glyph");
let geometry = drawer.geometry(&batch).expect("geometry");
let inst = slot.instance([8.0, 8.0], 48.0, [48.0, 48.0], [1.0; 4]);
let mut target = drawer.target(64, 64).expect("target");
drawer
.draw(&mut target, &geometry, &[inst], &SubpixelParams::default(), Mode::Grayscale)
.expect("draw");
let pixels = reader.read_pixels(&mut target).expect("a target on the same device must read back");
let inked = pixels.iter().filter(|&&b| b > 0).count();
assert!(inked > 200, "only {inked} bytes took ink, so the read raced the draw or drew nothing");
assert!(reader.wait(&mut target).is_ok(), "wait after read_pixels must be a no-op");
assert!(drawer.wait(&mut target).is_ok(), "wait after read_pixels must be a no-op");
let again = drawer.read_pixels(&mut target).expect("read");
assert_eq!(again.iter().filter(|&&b| b > 0).count(), inked, "a second read disagreed");
}
#[test]
fn the_native_handle_is_stable_and_distinct() {
let Some(r) = renderer() else { return };
let (mut a, b) = (r.target(64, 48).expect("target"), r.target(64, 48).expect("target"));
let h = unsafe { a.texture() };
assert!(!h.is_null(), "the target handed out a null texture");
assert_eq!(h, unsafe { a.texture() }, "the handle changed between two calls");
assert_ne!(h, unsafe { b.texture() }, "two targets named one texture");
r.read_pixels(&mut a).expect("read");
assert_eq!(h, unsafe { a.texture() }, "a read replaced the texture");
}
#[test]
fn a_custom_projection_still_draws_the_glyph() {
let Some(gpu) = renderer() else { return };
let face = Face::load("eb-garamond/EBGaramond.ttf");
let mut batch = GpuBatch::new();
let (w, h) = (128u32, 128u32);
let Some(slot) = face.glyph(&mut batch, 36u16) else { return };
let geometry = gpu.geometry(&batch).expect("geometry");
let inst = slot.instance([16.0, 16.0], 48.0, [48.0, 48.0], [1.0; 4]);
let params = SubpixelParams::default();
let mut a = gpu.target(w, h).expect("target");
gpu.draw(&mut a, &geometry, &[inst], ¶ms, Mode::Grayscale).expect("draw");
let default_ink = gpu.read_pixels(&mut a).expect("read").iter().filter(|&&b| b > 0).count();
let mut b = gpu.target(w, h).expect("target");
let same = daegun::daerizer::daegpu::ffi::ortho(w, h);
gpu.draw_with(&mut b, &geometry, &[inst], ¶ms, Mode::Grayscale, &same).expect("draw_with");
let same_ink = gpu.read_pixels(&mut b).expect("read").iter().filter(|&&b| b > 0).count();
assert_eq!(default_ink, same_ink, "the explicit default projection drew something else");
let mut c = gpu.target(w, h).expect("target");
let half = [
1.0 / w as f32, 0.0, 0.0, 0.0,
0.0, 1.0 / h as f32, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
-1.0, -1.0, 0.0, 1.0,
];
gpu.draw_with(&mut c, &geometry, &[inst], ¶ms, Mode::Grayscale, &half).expect("draw_with");
let half_ink = gpu.read_pixels(&mut c).expect("read").iter().filter(|&&b| b > 0).count();
let bbox = |px: &[u8], w: u32| {
let (mut x0, mut y0, mut x1, mut y1) = (u32::MAX, u32::MAX, 0u32, 0u32);
for (i, c) in px.chunks_exact(4).enumerate() {
if c[3] > 0 {
let (x, y) = (i as u32 % w, i as u32 / w);
x0 = x0.min(x); y0 = y0.min(y); x1 = x1.max(x); y1 = y1.max(y);
}
}
(x0, y0, x1, y1)
};
let da = { let mut t = gpu.target(w, h).expect("t");
gpu.draw(&mut t, &geometry, &[inst], ¶ms, Mode::Grayscale).expect("d");
bbox(gpu.read_pixels(&mut t).expect("r"), w) };
let dh = { let mut t = gpu.target(w, h).expect("t");
gpu.draw_with(&mut t, &geometry, &[inst], ¶ms, Mode::Grayscale, &half).expect("d");
bbox(gpu.read_pixels(&mut t).expect("r"), w) };
std::eprintln!("PROJ default ink {default_ink} box {da:?}");
std::eprintln!("PROJ half ink {half_ink} box {dh:?}");
assert!(half_ink > 0, "a half-scale projection drew nothing at all");
for (name, got, want) in [
("x0", dh.0 as f32, da.0 as f32 / 2.0), ("y0", dh.1 as f32, (da.1 as f32 + 128.0) / 2.0),
("x1", dh.2 as f32, da.2 as f32 / 2.0),
] {
assert!(
(got - want).abs() <= 2.0,
"under a half-scale projection {name} landed at {got}, not the {want} the matrix asks \
for — the shader is not honouring the projection it was given",
);
}
}