mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Optimize glyph cache access
Loading a glyph from the cache is a very hot operation in the renderer. The original implementation would first check if a glyph was loaded and then call `get()` which would have to search a second time. This showed up as a very slow point in profiles. This patch addresses glyph cache access in two ways: by using a faster hasher optimized for small keys (fnv), and by using the entry API for fetching a cached glyph. The `fnv` hasher is faster than the default and is very efficient for small keys. Using the entry API on the HashMap means only 1 lookup instead of two. The entry API has a downside where the key needs to get cloned on fetches. Reducing the GlyphKey width to 64-bits helps in both areas. Copying an 8-byte wide type is very cheap and thus limits downside of the entry API. The small width also helps with the hasher performance. Over all, this patch reduced typical render times by several hundred microseconds on a 2013 MacBook Pro with a full screen terminal full of text.
This commit is contained in:
parent
4ebcbf1c4d
commit
08f348ecea
5 changed files with 80 additions and 48 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -8,6 +8,7 @@ dependencies = [
|
|||
"clippy 0.0.104 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"copypasta 0.0.1",
|
||||
"errno 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"font 0.1.0",
|
||||
"gl_generator 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"glutin 0.6.1 (git+https://github.com/jwilm/glutin?rev=af7fe340bd4a2af53ea521defcb4f377cdc588cf)",
|
||||
|
@ -272,6 +273,11 @@ dependencies = [
|
|||
"libc 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "font"
|
||||
version = "0.1.0"
|
||||
|
@ -1240,6 +1246,7 @@ dependencies = [
|
|||
"checksum euclid 0.6.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7b555729225fcc2aabc1ac951f9346967b35c901f4f03a480c31b6a45824109"
|
||||
"checksum expat-sys 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cef36cd1a8a02d28b91d97347c63247b9e4cb8a8e36df36f8201dc87a1c0859c"
|
||||
"checksum filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "5363ab8e4139b8568a6237db5248646e5a8a2f89bd5ccb02092182b11fd3e922"
|
||||
"checksum fnv 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6cc484842f1e2884faf56f529f960cc12ad8c71ce96cc7abba0a067c98fee344"
|
||||
"checksum freetype-rs 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1e8c93a141b156862ab58d0206fa44a9b20d899c86c3e6260017ab748029aa42"
|
||||
"checksum freetype-sys 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "eccfb6d96cac99921f0c2142a91765f6c219868a2c45bdfe7d65a08775f18127"
|
||||
"checksum fs2 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "bcd414e5a1a979b931bb92f41b7a54106d3f6d2e6c253e9ce943b7cd468251ef"
|
||||
|
|
|
@ -34,6 +34,7 @@ copypasta = { path = "./copypasta" }
|
|||
xdg = "2.0.0"
|
||||
log = "0.3"
|
||||
clap = "2.20"
|
||||
fnv = "1.0.5"
|
||||
|
||||
clippy = { version = "0.0.104", optional = true }
|
||||
|
||||
|
|
|
@ -42,8 +42,9 @@ extern crate ffi_util;
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::fmt;
|
||||
use std::sync::atomic::{AtomicU32, ATOMIC_U32_INIT, Ordering};
|
||||
use std::sync::atomic::{AtomicU16, ATOMIC_U16_INIT, Ordering};
|
||||
|
||||
// If target isn't macos, reexport everything from ft
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
|
@ -114,7 +115,7 @@ impl fmt::Display for FontDesc {
|
|||
/// Identifier for a Font for use in maps/etc
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
pub struct FontKey {
|
||||
token: u32,
|
||||
token: u16,
|
||||
}
|
||||
|
||||
impl FontKey {
|
||||
|
@ -122,7 +123,7 @@ impl FontKey {
|
|||
///
|
||||
/// The generated key will be globally unique
|
||||
pub fn next() -> FontKey {
|
||||
static TOKEN: AtomicU32 = ATOMIC_U32_INIT;
|
||||
static TOKEN: AtomicU16 = ATOMIC_U16_INIT;
|
||||
|
||||
FontKey {
|
||||
token: TOKEN.fetch_add(1, Ordering::SeqCst),
|
||||
|
@ -130,16 +131,28 @@ impl FontKey {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub struct GlyphKey {
|
||||
pub c: char,
|
||||
pub font_key: FontKey,
|
||||
pub size: Size,
|
||||
}
|
||||
|
||||
impl Hash for GlyphKey {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
unsafe {
|
||||
// This transmute is fine:
|
||||
//
|
||||
// - If GlyphKey ever becomes a different size, this will fail to compile
|
||||
// - Result is being used for hashing and has no fields (it's a u64)
|
||||
::std::mem::transmute::<GlyphKey, u64>(*self)
|
||||
}.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
/// Font size stored as integer
|
||||
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
|
||||
pub struct Size(i32);
|
||||
pub struct Size(i16);
|
||||
|
||||
impl Size {
|
||||
/// Scale factor between font "Size" type and point size
|
||||
|
@ -150,7 +163,7 @@ impl Size {
|
|||
|
||||
/// Create a new `Size` from a f32 size in points
|
||||
pub fn new(size: f32) -> Size {
|
||||
Size((size * Size::factor()) as i32)
|
||||
Size((size * Size::factor()) as i16)
|
||||
}
|
||||
|
||||
/// Get the f32 size in points
|
||||
|
@ -168,6 +181,19 @@ pub struct RasterizedGlyph {
|
|||
pub buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Default for RasterizedGlyph {
|
||||
fn default() -> RasterizedGlyph {
|
||||
RasterizedGlyph {
|
||||
c: ' ',
|
||||
width: 0,
|
||||
height: 0,
|
||||
top: 0,
|
||||
left: 0,
|
||||
buf: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BufDebugger<'a>(&'a [u8]);
|
||||
|
||||
impl<'a> fmt::Debug for BufDebugger<'a> {
|
||||
|
|
|
@ -30,6 +30,7 @@ extern crate clap;
|
|||
extern crate copypasta;
|
||||
extern crate errno;
|
||||
extern crate font;
|
||||
extern crate fnv;
|
||||
extern crate glutin;
|
||||
#[macro_use]
|
||||
extern crate lazy_static;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
use std::collections::HashMap;
|
||||
use std::hash::BuildHasherDefault;
|
||||
use std::fs::File;
|
||||
use std::io::{self, Read};
|
||||
use std::mem::size_of;
|
||||
|
@ -20,20 +21,19 @@ use std::ptr;
|
|||
use std::sync::mpsc;
|
||||
|
||||
use cgmath;
|
||||
use fnv::FnvHasher;
|
||||
use font::{self, Rasterizer, Rasterize, RasterizedGlyph, FontDesc, GlyphKey, FontKey};
|
||||
use gl::types::*;
|
||||
use gl;
|
||||
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
|
||||
use index::{Line, Column, RangeInclusive};
|
||||
|
||||
use window::{Size, Pixels};
|
||||
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
|
||||
|
||||
use ansi::{Color, NamedColor};
|
||||
|
||||
use config::{Config, ColorList};
|
||||
use term::{self, cell, IndexedCell, Cell};
|
||||
use window::{Size, Pixels};
|
||||
|
||||
use super::Rgb;
|
||||
use Rgb;
|
||||
|
||||
// Shader paths for live reload
|
||||
static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl");
|
||||
|
@ -136,7 +136,7 @@ pub struct Glyph {
|
|||
/// representations of the same code point.
|
||||
pub struct GlyphCache {
|
||||
/// Cache of buffered glyphs
|
||||
cache: HashMap<GlyphKey, Glyph>,
|
||||
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
|
||||
|
||||
/// Rasterizer for loading new glyphs
|
||||
rasterizer: Rasterizer,
|
||||
|
@ -215,7 +215,7 @@ impl GlyphCache {
|
|||
};
|
||||
|
||||
let mut cache = GlyphCache {
|
||||
cache: HashMap::new(),
|
||||
cache: HashMap::default(),
|
||||
rasterizer: rasterizer,
|
||||
font_size: font.size(),
|
||||
font_key: regular,
|
||||
|
@ -251,26 +251,24 @@ impl GlyphCache {
|
|||
fn load_and_cache_glyph<L>(&mut self, glyph_key: GlyphKey, loader: &mut L)
|
||||
where L: LoadGlyph
|
||||
{
|
||||
if let Ok(rasterized) = self.rasterizer.get_glyph(&glyph_key) {
|
||||
let glyph = loader.load_glyph(&rasterized);
|
||||
self.cache.insert(glyph_key, glyph);
|
||||
}
|
||||
let rasterized = self.rasterizer.get_glyph(&glyph_key)
|
||||
.unwrap_or_else(|_| Default::default());
|
||||
|
||||
let glyph = loader.load_glyph(&rasterized);
|
||||
self.cache.insert(glyph_key, glyph);
|
||||
}
|
||||
|
||||
pub fn get<L>(&mut self, glyph_key: &GlyphKey, loader: &mut L) -> Option<&Glyph>
|
||||
pub fn get<'a, L>(&'a mut self, glyph_key: &GlyphKey, loader: &mut L) -> &'a Glyph
|
||||
where L: LoadGlyph
|
||||
{
|
||||
// Return glyph if it's already loaded
|
||||
// hi borrowck
|
||||
{
|
||||
if self.cache.contains_key(glyph_key) {
|
||||
return self.cache.get(glyph_key);
|
||||
}
|
||||
}
|
||||
|
||||
// Rasterize and load the glyph
|
||||
self.load_and_cache_glyph(glyph_key.to_owned(), loader);
|
||||
self.cache.get(glyph_key)
|
||||
let rasterizer = &mut self.rasterizer;
|
||||
self.cache
|
||||
.entry(*glyph_key)
|
||||
.or_insert_with(|| {
|
||||
let rasterized = rasterizer.get_glyph(&glyph_key)
|
||||
.unwrap_or_else(|_| Default::default());
|
||||
loader.load_glyph(&rasterized)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -817,26 +815,25 @@ impl<'a> RenderApi<'a> {
|
|||
c: cell.c
|
||||
};
|
||||
|
||||
// Add cell to batch if glyph available
|
||||
glyph_cache.get(&glyph_key, self)
|
||||
.map(|glyph| self.add_render_item(&cell, glyph))
|
||||
.and_then(|_| {
|
||||
// FIXME This is a super hacky way to do underlined text. During
|
||||
// a time crunch to release 0.1, this seemed like a really
|
||||
// easy, clean hack.
|
||||
if cell.flags.contains(cell::UNDERLINE) {
|
||||
let glyph_key = GlyphKey {
|
||||
font_key: font_key,
|
||||
size: glyph_cache.font_size,
|
||||
c: '_'
|
||||
};
|
||||
// Add cell to batch
|
||||
{
|
||||
let glyph = glyph_cache.get(&glyph_key, self);
|
||||
self.add_render_item(&cell, glyph);
|
||||
}
|
||||
|
||||
glyph_cache.get(&glyph_key, self)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.map(|underscore| self.add_render_item(&cell, underscore));
|
||||
// FIXME This is a super hacky way to do underlined text. During
|
||||
// a time crunch to release 0.1, this seemed like a really
|
||||
// easy, clean hack.
|
||||
if cell.flags.contains(cell::UNDERLINE) {
|
||||
let glyph_key = GlyphKey {
|
||||
font_key: font_key,
|
||||
size: glyph_cache.font_size,
|
||||
c: '_'
|
||||
};
|
||||
|
||||
let underscore = glyph_cache.get(&glyph_key, self);
|
||||
self.add_render_item(&cell, underscore);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue