use std::sync::Arc;
use valo::{
Color, Context, DisplayList, DisplayListBuilder, FontCollection, ImageDesc, Paint,
ParagraphBuilder, PersistentCanvas, Rect, TextStyle,
};
const FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Rgba8Unorm;
const CANVASES: usize = 12;
const SIZE: [u32; 2] = [256, 256];
fn fonts() -> FontCollection {
let dir = concat!(env!("CARGO_MANIFEST_DIR"), "/../../assets/fonts");
let mut collection = FontCollection::new();
collection
.register(
"Fira Sans",
std::fs::read(format!("{dir}/fira_sans.ttf")).unwrap(),
)
.unwrap();
collection
}
fn card(collection: &mut FontCollection, image: &valo::Image, seed: usize) -> Arc<DisplayList> {
let mut builder = DisplayListBuilder::new();
builder.draw_rect(
Rect::new(0.0, 0.0, 256.0, 256.0),
&Paint::from_color(Color::rgb(0.1, 0.1, 0.14)),
);
builder.draw_image_rect(
image,
Rect::new(0.0, 0.0, image.width(), image.height()),
Rect::new(16.0, 16.0, 64.0, 64.0),
valo::Sampling::default(),
&Paint::from_color(Color::WHITE),
);
builder.draw_rect(
Rect::new(120.0, 40.0, 90.0, 90.0),
&Paint {
color: Color::rgb(0.9, 0.4, 0.2),
mask_blur: Some(valo::MaskBlur::new(6.0)),
..Paint::default()
},
);
let mut paragraph = ParagraphBuilder::new(collection);
let mut style = TextStyle::new("Fira Sans", 24.0, Color::WHITE);
style.families = vec!["Fira Sans".to_owned()];
paragraph.add_text("Shared device", &style);
let mut paragraph = paragraph.build();
paragraph.layout(240.0);
use valo::DrawParagraphExt;
builder.draw_paragraph(¶graph, (16.0, 150.0 + (seed % 3) as f32));
Arc::new(builder.build())
}
fn swatch(context: &mut Context) -> valo::Image {
context.upload_image(
ImageDesc {
size: [32, 32],
premultiplied: true,
mips: false,
},
&vec![200u8; 32 * 32 * 4],
)
}
fn shared(device: &wgpu::Device, queue: &wgpu::Queue, count: usize) -> (u64, u64, u64, u32) {
let mut context = Context::new(device.clone(), queue.clone());
let mut collection = fonts();
let image = swatch(&mut context);
let mut canvases: Vec<PersistentCanvas> = (0..count)
.map(|_| PersistentCanvas::new(&mut context, SIZE, FORMAT))
.collect();
for (seed, canvas) in canvases.iter_mut().enumerate() {
let list = card(&mut collection, &image, seed);
canvas.draw(&mut context, &list, Some(Color::TRANSPARENT));
}
let report = context.memory_report();
(
report.total_bytes(),
report.atlas.iter().map(|family| family.bytes).sum(),
report.images.bytes,
report.targets.count,
)
}
fn per_canvas(device: &wgpu::Device, queue: &wgpu::Queue, count: usize) -> (u64, u64, u64, u32) {
let mut totals = (0u64, 0u64, 0u64, 0u32);
for seed in 0..count {
let mut context = Context::new(device.clone(), queue.clone());
let mut collection = fonts();
let image = swatch(&mut context);
let mut canvas = PersistentCanvas::new(&mut context, SIZE, FORMAT);
let list = card(&mut collection, &image, seed);
canvas.draw(&mut context, &list, Some(Color::TRANSPARENT));
let report = context.memory_report();
totals.0 += report.total_bytes();
totals.1 += report.atlas.iter().map(|family| family.bytes).sum::<u64>();
totals.2 += report.images.bytes;
totals.3 += report.targets.count;
}
totals
}
#[test]
fn one_device_holds_one_copy_of_what_twelve_would_duplicate() {
let Some((device, queue)) = valo_harness::headless_device() else {
eprintln!("SKIP one_device_holds_one_copy_of_what_twelve_would_duplicate");
return;
};
let (one_total, one_atlas, one_images, _) = shared(&device, &queue, 1);
let (many_total, many_atlas, many_images, many_targets) = shared(&device, &queue, CANVASES);
let (split_total, split_atlas, split_images, split_targets) =
per_canvas(&device, &queue, CANVASES);
let backing_pair = 2 * u64::from(SIZE[0]) * u64::from(SIZE[1]) * 4;
let per_canvas_cost = backing_pair * CANVASES as u64;
let mib = |bytes: u64| bytes as f64 / (1024.0 * 1024.0);
println!(
"\n{CANVASES} canvases, {}x{} each\n\
\x20 shared device : {:.2} MiB device-level + {:.2} MiB backings = {:.2} MiB\n\
\x20 device each : {:.2} MiB device-level + {:.2} MiB backings = {:.2} MiB\n\
\x20 atlas : {:.2} MiB shared vs {:.2} MiB split\n\
\x20 pooled targets: {many_targets} shared vs {split_targets} split\n\
\x20 one canvas : {:.2} MiB device-level\n",
SIZE[0],
SIZE[1],
mib(many_total),
mib(per_canvas_cost),
mib(many_total + per_canvas_cost),
mib(split_total),
mib(per_canvas_cost),
mib(split_total + per_canvas_cost),
mib(many_atlas),
mib(split_atlas),
mib(one_total),
);
assert_eq!(
many_atlas, one_atlas,
"twelve canvases must share one glyph atlas"
);
assert_eq!(
split_atlas,
one_atlas * CANVASES as u64,
"twelve devices each pay for their own atlas — the baseline being beaten"
);
assert_eq!(many_images, one_images, "one image cache, not twelve");
assert_eq!(split_images, one_images * CANVASES as u64);
assert!(
many_targets <= split_targets,
"a shared pool must not hold more targets than separate ones: \
{many_targets} vs {split_targets}"
);
assert!(
many_total < split_total,
"sharing must actually save: {many_total} B shared vs {split_total} B split"
);
}
#[test]
fn canvases_on_one_device_keep_their_own_pixels() {
let Some((device, queue)) = valo_harness::headless_device() else {
eprintln!("SKIP canvases_on_one_device_keep_their_own_pixels");
return;
};
let mut context = Context::new(device.clone(), queue.clone());
let colors = [
Color::rgb(1.0, 0.0, 0.0),
Color::rgb(0.0, 1.0, 0.0),
Color::rgb(0.0, 0.0, 1.0),
];
let mut canvases: Vec<PersistentCanvas> = colors
.iter()
.map(|_| PersistentCanvas::new(&mut context, [32, 32], FORMAT))
.collect();
for (canvas, color) in canvases.iter_mut().zip(colors) {
let mut builder = DisplayListBuilder::new();
builder.draw_rect(Rect::new(0.0, 0.0, 32.0, 32.0), &Paint::from_color(color));
canvas.draw(&mut context, &Arc::new(builder.build()), None);
}
let mut builder = DisplayListBuilder::new();
builder.draw_rect(
Rect::new(0.0, 0.0, 32.0, 32.0),
&Paint::from_color(Color::rgb(1.0, 1.0, 1.0)),
);
canvases[0].draw(&mut context, &Arc::new(builder.build()), None);
let expected = [[255, 255, 255], [0, 255, 0], [0, 0, 255]];
for (canvas, want) in canvases.iter().zip(expected) {
let pixels =
valo_harness::read_texture_rgba(&device, &queue, canvas.front().texture(), [32, 32]);
assert_eq!(&pixels[..3], &want, "each canvas keeps its own pixels");
}
}
#[test]
fn dropping_one_canvas_leaves_the_others_drawing() {
let Some((device, queue)) = valo_harness::headless_device() else {
eprintln!("SKIP dropping_one_canvas_leaves_the_others_drawing");
return;
};
let mut context = Context::new(device.clone(), queue.clone());
let mut keep = PersistentCanvas::new(&mut context, [32, 32], FORMAT);
let discard = PersistentCanvas::new(&mut context, [32, 32], FORMAT);
let mut builder = DisplayListBuilder::new();
builder.draw_rect(
Rect::new(0.0, 0.0, 32.0, 32.0),
&Paint::from_color(Color::rgb(0.0, 1.0, 0.0)),
);
let green = Arc::new(builder.build());
keep.draw(&mut context, &green, None);
drop(discard);
let empty = Arc::new(DisplayListBuilder::new().build());
keep.draw(&mut context, &empty, None);
let pixels = valo_harness::read_texture_rgba(&device, &queue, keep.front().texture(), [32, 32]);
assert_eq!(&pixels[..3], &[0, 255, 0]);
}