Support dynamic character loading

The glyph cache was previously initialized with a list of glyphs from
INIT_LIST, and never updated again. This meant that code points not
included in that list were not displayed. Now, the glyph cache has
gained the ability to load new glyphs at render time.

This seems to have lightly decreased performance for some reason.
This commit is contained in:
Joe Wilm 2016-06-06 14:31:12 -07:00
parent b977a15187
commit 78f5de4935
No known key found for this signature in database
GPG Key ID: 39B57C6972F518DA
2 changed files with 148 additions and 85 deletions

View File

@ -33,7 +33,7 @@ use std::fs::File;
use std::os::unix::io::{FromRawFd, AsRawFd};
use renderer::{Glyph, QuadRenderer};
use renderer::{QuadRenderer, GlyphCache, LoadGlyph};
use text::FontDesc;
use grid::Grid;
use term::Term;
@ -50,13 +50,6 @@ mod gl {
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
static INIT_LIST: &'static str = "abcdefghijklmnopqrstuvwxyz\
ABCDEFGHIJKLMNOPQRSTUVWXYZ\
01234567890\
~`!@#$%^&*()[]{}-_=+\\|\"'/?.,<>;:█└│├─➜";
type GlyphCache = HashMap<char, renderer::Glyph>;
#[derive(Debug)]
struct TermProps {
width: f32,
@ -108,13 +101,21 @@ fn main() {
let mut grid = Grid::new(num_rows as usize, num_cols as usize);
let props = TermProps {
cell_width: cell_width as f32,
sep_x: sep_x as f32,
cell_height: cell_height as f32,
sep_y: sep_y as f32,
height: height as f32,
width: width as f32,
};
let mut renderer = QuadRenderer::new(width, height);
let mut glyph_cache = HashMap::new();
for c in INIT_LIST.chars() {
let glyph = renderer.load_glyph(&rasterizer.get_glyph(&desc, font_size, c));
glyph_cache.insert(c, glyph);
}
let mut glyph_cache = GlyphCache::new(rasterizer, desc, font_size);
renderer.with_api(&props, |mut api| {
glyph_cache.init(&mut api);
});
unsafe {
gl::Enable(gl::BLEND);
@ -182,24 +183,15 @@ fn main() {
gl::Clear(gl::COLOR_BUFFER_BIT);
}
let props = TermProps {
cell_width: cell_width as f32,
sep_x: sep_x as f32,
cell_height: cell_height as f32,
sep_y: sep_y as f32,
height: height as f32,
width: width as f32,
};
{
let _sampler = meter.sampler();
renderer.with_api(&props, |mut api| {
// Draw the grid
api.render_grid(terminal.grid(), &glyph_cache);
api.render_grid(terminal.grid(), &mut glyph_cache);
// Also draw the cursor
api.render_cursor(terminal.cursor(), &glyph_cache);
api.render_cursor(terminal.cursor(), &mut glyph_cache);
})
}
@ -207,7 +199,7 @@ fn main() {
let timing = format!("{:.3} usec", meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
renderer.with_api(&props, |mut api| {
api.render_string(&timing[..], &glyph_cache, &color);
api.render_string(&timing[..], &mut glyph_cache, &color);
});
window.swap_buffers().unwrap();

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::ffi::CString;
use std::fs::File;
use std::io::{self, Read};
@ -13,15 +14,21 @@ use gl::types::*;
use gl;
use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op};
use text::RasterizedGlyph;
use text::{Rasterizer, RasterizedGlyph, FontDesc};
use grid::Grid;
use term;
use super::{Rgb, TermProps, GlyphCache};
use super::{Rgb, TermProps};
static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl");
static TEXT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl");
/// LoadGlyph allows for copying a rasterized glyph into graphics memory
pub trait LoadGlyph {
/// Load the rasterized glyph into GPU memory
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
}
/// Text drawing program
///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
@ -57,6 +64,67 @@ pub struct Glyph {
uv_height: f32,
}
/// Naïve glyph cache
///
/// Currently only keyed by `char`, and thus not possible to hold different representations of the
/// same code point.
pub struct GlyphCache {
/// Cache of buffered glyphs
cache: HashMap<char, Glyph>,
/// Rasterizer for loading new glyphs
rasterizer: Rasterizer,
/// Font description
desc: FontDesc,
/// Font Size
size: f32,
}
impl GlyphCache {
pub fn new(rasterizer: Rasterizer, desc: FontDesc, font_size: f32) -> GlyphCache {
GlyphCache {
cache: HashMap::new(),
rasterizer: rasterizer,
desc: desc,
size: font_size,
}
}
pub fn init<L>(&mut self, loader: &mut L)
where L: LoadGlyph
{
for i in 32u8...128u8 {
self.load_and_cache_glyph(i as char, loader);
}
}
fn load_and_cache_glyph<L>(&mut self, c: char, loader: &mut L)
where L: LoadGlyph
{
let rasterized = self.rasterizer.get_glyph(&self.desc, self.size, c);
let glyph = loader.load_glyph(&rasterized);
self.cache.insert(c, glyph);
}
pub fn get<L>(&mut self, c: char, loader: &mut L) -> Option<&Glyph>
where L: LoadGlyph
{
// Return glyph if it's already loaded
// hi borrowck
{
if self.cache.contains_key(&c) {
return self.cache.get(&c);
}
}
// Rasterize and load the glyph
self.load_and_cache_glyph(c, loader);
self.cache.get(&c)
}
}
#[derive(Debug)]
struct InstanceData {
// coords
@ -97,6 +165,7 @@ pub struct QuadRenderer {
pub struct RenderApi<'a> {
active_tex: &'a mut GLuint,
batch: &'a mut Batch,
atlas: &'a mut Vec<Atlas>,
}
#[derive(Debug)]
@ -325,7 +394,7 @@ impl QuadRenderer {
batch: Batch::new(),
};
let atlas = renderer.create_atlas(ATLAS_SIZE);
let atlas = Atlas::new(ATLAS_SIZE);
renderer.atlas.push(atlas);
renderer
@ -351,6 +420,7 @@ impl QuadRenderer {
func(RenderApi {
active_tex: &mut self.active_tex,
batch: &mut self.batch,
atlas: &mut self.atlas,
});
unsafe {
@ -384,57 +454,6 @@ impl QuadRenderer {
self.active_tex = 0;
self.program = program;
}
/// Load a glyph into a texture atlas
///
/// If the current atlas is full, a new one will be created.
pub fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
Ok(glyph) => glyph,
Err(_) => {
let atlas = self.create_atlas(ATLAS_SIZE);
self.atlas.push(atlas);
self.load_glyph(rasterized)
}
}
}
fn create_atlas(&mut self, size: i32) -> Atlas {
let mut id: GLuint = 0;
unsafe {
gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
gl::GenTextures(1, &mut id);
gl::BindTexture(gl::TEXTURE_2D, id);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGB as i32,
size,
size,
0,
gl::RGB,
gl::UNSIGNED_BYTE,
ptr::null()
);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::BindTexture(gl::TEXTURE_2D, 0);
self.active_tex = 0;
}
Atlas {
id: id,
width: size,
height: size,
row_extent: 0,
row_baseline: 0,
row_tallest: 0,
}
}
}
impl<'a> RenderApi<'a> {
@ -464,14 +483,14 @@ impl<'a> RenderApi<'a> {
/// optimization.
pub fn render_string(&mut self,
s: &str,
glyph_cache: &GlyphCache,
glyph_cache: &mut GlyphCache,
color: &Rgb)
{
let row = 40.0;
let mut col = 100.0;
for c in s.chars() {
if let Some(glyph) = glyph_cache.get(&c) {
if let Some(glyph) = glyph_cache.get(c, self) {
self.add_render_item(row, col, *color, glyph);
}
@ -496,13 +515,13 @@ impl<'a> RenderApi<'a> {
}
}
pub fn render_cursor(&mut self, cursor: term::Cursor, glyph_cache: &GlyphCache) {
if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) {
pub fn render_cursor(&mut self, cursor: term::Cursor, glyph_cache: &mut GlyphCache) {
if let Some(glyph) = glyph_cache.get(term::CURSOR_SHAPE, self) {
self.add_render_item(cursor.y as f32, cursor.x as f32, term::DEFAULT_FG, glyph);
}
}
pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache) {
pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &mut GlyphCache) {
for (i, row) in grid.rows().enumerate() {
for (j, cell) in row.cells().enumerate() {
// Skip empty cells
@ -511,7 +530,7 @@ impl<'a> RenderApi<'a> {
}
// Add cell to batch if the glyph is laoded
if let Some(glyph) = glyph_cache.get(&cell.c) {
if let Some(glyph) = glyph_cache.get(cell.c, self) {
self.add_render_item(i as f32, j as f32, cell.fg, glyph);
}
}
@ -519,6 +538,23 @@ impl<'a> RenderApi<'a> {
}
}
impl<'a> LoadGlyph for RenderApi<'a> {
/// Load a glyph into a texture atlas
///
/// If the current atlas is full, a new one will be created.
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
Ok(glyph) => glyph,
Err(_) => {
let atlas = Atlas::new(ATLAS_SIZE);
*self.active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
self.atlas.push(atlas);
self.load_glyph(rasterized)
}
}
}
}
impl<'a> Drop for RenderApi<'a> {
fn drop(&mut self) {
if !self.batch.is_empty() {
@ -818,6 +854,41 @@ enum AtlasInsertError {
}
impl Atlas {
fn new(size: i32) -> Atlas {
let mut id: GLuint = 0;
unsafe {
gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
gl::GenTextures(1, &mut id);
gl::BindTexture(gl::TEXTURE_2D, id);
gl::TexImage2D(
gl::TEXTURE_2D,
0,
gl::RGB as i32,
size,
size,
0,
gl::RGB,
gl::UNSIGNED_BYTE,
ptr::null()
);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
gl::BindTexture(gl::TEXTURE_2D, 0);
}
Atlas {
id: id,
width: size,
height: size,
row_extent: 0,
row_baseline: 0,
row_tallest: 0,
}
}
/// Insert a RasterizedGlyph into the texture atlas
pub fn insert(&mut self,