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:
parent
b977a15187
commit
78f5de4935
42
src/main.rs
42
src/main.rs
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue