blob: 9a720554a07a647ddfc5b59dd5079e7779458434 [file] [log] [blame]
#![feature(test)]
#![cfg(feature = "gpu_cache")]
extern crate rusttype;
extern crate test;
#[macro_use]
extern crate lazy_static;
extern crate unicode_normalization;
use rusttype::gpu_cache::*;
use rusttype::*;
/// Busy wait 2us
fn mock_gpu_upload(_region: Rect<u32>, _bytes: &[u8]) {
use std::time::{Duration, Instant};
let now = Instant::now();
while now.elapsed() < Duration::from_micros(2) {}
}
fn test_glyphs<'a>(font: &Font<'a>, string: &str) -> Vec<PositionedGlyph<'a>> {
let mut glyphs = vec![];
// Set of scales, found through brute force, to reproduce GlyphNotCached issue
// Cache settings also affect this, it occurs when position_tolerance is < 1.0
for scale in &[25_f32, 24.5, 25.01, 24.7, 24.99] {
for glyph in layout_paragraph(font, Scale::uniform(*scale), 500, string) {
glyphs.push(glyph);
}
}
glyphs
}
fn layout_paragraph<'a>(
font: &Font<'a>,
scale: Scale,
width: u32,
text: &str,
) -> Vec<PositionedGlyph<'a>> {
use unicode_normalization::UnicodeNormalization;
let mut result = Vec::new();
let v_metrics = font.v_metrics(scale);
let advance_height = v_metrics.ascent - v_metrics.descent + v_metrics.line_gap;
let mut caret = point(0.0, v_metrics.ascent);
let mut last_glyph_id = None;
for c in text.nfc() {
if c.is_control() {
if c == '\n' {
caret = point(0.0, caret.y + advance_height)
}
continue;
}
let base_glyph = font.glyph(c);
if let Some(id) = last_glyph_id.take() {
caret.x += font.pair_kerning(scale, id, base_glyph.id());
}
last_glyph_id = Some(base_glyph.id());
let mut glyph = base_glyph.scaled(scale).positioned(caret);
if let Some(bb) = glyph.pixel_bounding_box() {
if bb.max.x > width as i32 {
caret = point(0.0, caret.y + advance_height);
glyph = glyph.into_unpositioned().positioned(caret);
last_glyph_id = None;
}
}
caret.x += glyph.unpositioned().h_metrics().advance_width;
result.push(glyph);
}
result
}
lazy_static! {
static ref FONTS: Vec<Font<'static>> = vec![
include_bytes!("../fonts/wqy-microhei/WenQuanYiMicroHei.ttf") as &[u8],
include_bytes!("../fonts/dejavu/DejaVuSansMono.ttf") as &[u8],
include_bytes!("../fonts/opensans/OpenSans-Italic.ttf") as &[u8],
].into_iter()
.map(|bytes| Font::from_bytes(bytes).unwrap())
.collect();
}
const TEST_STR: &str = include_str!("../tests/lipsum.txt");
/// General use benchmarks.
mod cache {
use super::*;
/// Benchmark using a single font at "don't care" position tolerance
#[bench]
fn high_position_tolerance(b: &mut ::test::Bencher) {
let font_id = 0;
let glyphs = test_glyphs(&FONTS[font_id], TEST_STR);
let mut cache = Cache::builder()
.dimensions(1024, 1024)
.scale_tolerance(0.1)
.position_tolerance(1.0)
.build();
b.iter(|| {
for glyph in &glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
cache.cache_queued(|_, _| {}).expect("cache_queued");
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for glyph index {}, id {}",
rect,
index,
glyph.id().0
);
}
});
}
/// Benchmark using a single font with default tolerances
#[bench]
fn single_font(b: &mut ::test::Bencher) {
let font_id = 0;
let glyphs = test_glyphs(&FONTS[font_id], TEST_STR);
let mut cache = Cache::builder().dimensions(1024, 1024).build();
b.iter(|| {
for glyph in &glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
cache.cache_queued(|_, _| {}).expect("cache_queued");
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for glyph index {}, id {}",
rect,
index,
glyph.id().0
);
}
});
}
/// Benchmark using multiple fonts with default tolerances
#[bench]
fn multi_font(b: &mut ::test::Bencher) {
// Use a smaller amount of the test string, to offset the extra font-glyph
// bench load
let up_to_index = TEST_STR
.char_indices()
.nth(TEST_STR.chars().count() / FONTS.len())
.unwrap()
.0;
let string = &TEST_STR[..up_to_index];
let font_glyphs: Vec<_> = FONTS
.iter()
.enumerate()
.map(|(id, font)| (id, test_glyphs(font, string)))
.collect();
let mut cache = Cache::builder().dimensions(1024, 1024).build();
b.iter(|| {
for &(font_id, ref glyphs) in &font_glyphs {
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
}
cache.cache_queued(|_, _| {}).expect("cache_queued");
for &(font_id, ref glyphs) in &font_glyphs {
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for font {} glyph index {}, id {}",
rect,
font_id,
index,
glyph.id().0
);
}
}
});
}
/// Benchmark using multiple fonts with default tolerances, clears the
/// cache each run to test the population "first run" performance
#[bench]
fn multi_font_population(b: &mut ::test::Bencher) {
// Use a much smaller amount of the test string, to offset the extra font-glyph
// bench load & much slower performance of fresh population each run
let up_to_index = TEST_STR.char_indices().nth(70).unwrap().0;
let string = &TEST_STR[..up_to_index];
let font_glyphs: Vec<_> = FONTS
.iter()
.enumerate()
.map(|(id, font)| (id, test_glyphs(font, string)))
.collect();
b.iter(|| {
let mut cache = Cache::builder().dimensions(1024, 1024).build();
for &(font_id, ref glyphs) in &font_glyphs {
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
}
cache.cache_queued(|_, _| {}).expect("cache_queued");
for &(font_id, ref glyphs) in &font_glyphs {
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for font {} glyph index {}, id {}",
rect,
font_id,
index,
glyph.id().0
);
}
}
});
}
/// Benchmark using multiple fonts and a different text group of glyphs
/// each run
#[bench]
fn moving_text(b: &mut ::test::Bencher) {
let chars: Vec<_> = TEST_STR.chars().collect();
let subsection_len = chars.len() / FONTS.len();
let distinct_subsection: Vec<_> = chars.windows(subsection_len).collect();
let mut first_glyphs = vec![];
let mut middle_glyphs = vec![];
let mut last_glyphs = vec![];
for (id, font) in FONTS.iter().enumerate() {
let first_str: String = distinct_subsection[0].iter().collect();
first_glyphs.push((id, test_glyphs(font, &first_str)));
let middle_str: String = distinct_subsection[distinct_subsection.len() / 2]
.iter()
.collect();
middle_glyphs.push((id, test_glyphs(font, &middle_str)));
let last_str: String = distinct_subsection[distinct_subsection.len() - 1]
.iter()
.collect();
last_glyphs.push((id, test_glyphs(font, &last_str)));
}
let test_variants = [first_glyphs, middle_glyphs, last_glyphs];
let mut test_variants = test_variants.iter().cycle();
let mut cache = Cache::builder()
.dimensions(1500, 1500)
.scale_tolerance(0.1)
.position_tolerance(0.1)
.build();
b.iter(|| {
// switch text variant each run to force cache to deal with moving text
// requirements
let glyphs = test_variants.next().unwrap();
for &(font_id, ref glyphs) in glyphs {
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
}
cache.cache_queued(|_, _| {}).expect("cache_queued");
for &(font_id, ref glyphs) in glyphs {
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for font {} glyph index {}, id {}",
rect,
font_id,
index,
glyph.id().0
);
}
}
});
}
}
/// Benchmarks for cases that should generally be avoided by the cache user if
/// at all possible (ie by picking a better initial cache size).
mod cache_bad_cases {
use super::*;
/// Cache isn't large enough for a queue so a new cache is created to hold
/// the queue.
#[bench]
fn resizing(b: &mut ::test::Bencher) {
let up_to_index = TEST_STR.char_indices().nth(120).unwrap().0;
let string = &TEST_STR[..up_to_index];
let font_glyphs: Vec<_> = FONTS
.iter()
.enumerate()
.map(|(id, font)| (id, test_glyphs(font, string)))
.collect();
b.iter(|| {
let mut cache = Cache::builder().dimensions(256, 256).build();
for &(font_id, ref glyphs) in &font_glyphs {
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
}
cache
.cache_queued(mock_gpu_upload)
.expect_err("shouldn't fit");
cache.to_builder().dimensions(512, 512).rebuild(&mut cache);
cache.cache_queued(mock_gpu_upload).expect("should fit now");
for &(font_id, ref glyphs) in &font_glyphs {
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for font {} glyph index {}, id {}",
rect,
font_id,
index,
glyph.id().0
);
}
}
});
}
/// Benchmark using multiple fonts and a different text group of glyphs
/// each run. The cache is only large enough to fit each run if it is
/// cleared and re-built.
#[bench]
fn moving_text_thrashing(b: &mut ::test::Bencher) {
let chars: Vec<_> = TEST_STR.chars().collect();
let subsection_len = 60;
let distinct_subsection: Vec<_> = chars.windows(subsection_len).collect();
let mut first_glyphs = vec![];
let mut middle_glyphs = vec![];
let mut last_glyphs = vec![];
for (id, font) in FONTS.iter().enumerate() {
let first_str: String = distinct_subsection[0].iter().collect();
first_glyphs.push((id, test_glyphs(font, &first_str)));
let middle_str: String = distinct_subsection[distinct_subsection.len() / 2]
.iter()
.collect();
middle_glyphs.push((id, test_glyphs(font, &middle_str)));
let last_str: String = distinct_subsection[distinct_subsection.len() - 1]
.iter()
.collect();
last_glyphs.push((id, test_glyphs(font, &last_str)));
}
let test_variants = [first_glyphs, middle_glyphs, last_glyphs];
// Cache is only a little larger than each variants size meaning a lot of
// re-ordering, re-rasterization & re-uploading has to occur.
let mut cache = Cache::builder()
.dimensions(450, 450)
.scale_tolerance(0.1)
.position_tolerance(0.1)
.build();
b.iter(|| {
// switch text variant each run to force cache to deal with moving text
// requirements
for glyphs in &test_variants {
for &(font_id, ref glyphs) in glyphs {
for glyph in glyphs {
cache.queue_glyph(font_id, glyph.clone());
}
}
cache.cache_queued(mock_gpu_upload).expect("cache_queued");
for &(font_id, ref glyphs) in glyphs {
for (index, glyph) in glyphs.iter().enumerate() {
let rect = cache.rect_for(font_id, glyph);
assert!(
rect.is_ok(),
"Gpu cache rect lookup failed ({:?}) for font {} glyph index {}, id {}",
rect,
font_id,
index,
glyph.id().0
);
}
}
}
});
}
}