Proof of concept live reloading for colors

The architecture here is really poor. Need to move file watching into a
dedicated location and probably have an spmc broadcast queue. other
modules besides rendering will care about config reloading in the
future.
This commit is contained in:
Joe Wilm 2016-10-23 15:37:06 -07:00
parent ea07f03ac9
commit 5876b4bf7a
5 changed files with 157 additions and 101 deletions

View File

@ -337,6 +337,10 @@ pub enum Color {
BrightCyan, BrightCyan,
/// Bright white /// Bright white
BrightWhite, BrightWhite,
/// The foreground color
Foreground,
/// The background color
Background,
} }
/// Terminal character attributes /// Terminal character attributes
@ -384,10 +388,6 @@ pub enum Attr {
Background(Color), Background(Color),
/// Set specific background color /// Set specific background color
BackgroundSpec(Rgb), BackgroundSpec(Rgb),
/// Set default foreground
DefaultForeground,
/// Set default background
DefaultBackground,
} }
impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> { impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> {
@ -584,7 +584,7 @@ impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> {
break; break;
} }
}, },
39 => Attr::DefaultForeground, 39 => Attr::Foreground(Color::Foreground),
40 => Attr::Background(Color::Black), 40 => Attr::Background(Color::Black),
41 => Attr::Background(Color::Red), 41 => Attr::Background(Color::Red),
42 => Attr::Background(Color::Green), 42 => Attr::Background(Color::Green),
@ -600,7 +600,7 @@ impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> {
break; break;
} }
}, },
49 => Attr::DefaultBackground, 49 => Attr::Background(Color::Background),
90 => Attr::Foreground(Color::BrightBlack), 90 => Attr::Foreground(Color::BrightBlack),
91 => Attr::Foreground(Color::BrightRed), 91 => Attr::Foreground(Color::BrightRed),
92 => Attr::Foreground(Color::BrightGreen), 92 => Attr::Foreground(Color::BrightGreen),

View File

@ -259,7 +259,7 @@ impl Config {
/// ///
/// The ordering returned here is expected by the terminal. Colors are simply indexed in this /// The ordering returned here is expected by the terminal. Colors are simply indexed in this
/// array for performance. /// array for performance.
pub fn color_list(&self) -> [Rgb; 16] { pub fn color_list(&self) -> [Rgb; 18] {
let colors = &self.colors; let colors = &self.colors;
[ [
@ -282,6 +282,10 @@ impl Config {
colors.bright.magenta, colors.bright.magenta,
colors.bright.cyan, colors.bright.cyan,
colors.bright.white, colors.bright.white,
// Foreground and background
colors.primary.foreground,
colors.primary.background,
] ]
} }

View File

@ -112,6 +112,7 @@ pub struct Rgb {
} }
mod gl { mod gl {
#![allow(non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs")); include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
} }
@ -155,7 +156,7 @@ fn main() {
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr); let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
// Create renderer // Create renderer
let mut renderer = QuadRenderer::new(width, height); let mut renderer = QuadRenderer::new(&config, width, height);
// Initialize glyph cache // Initialize glyph cache
let glyph_cache = { let glyph_cache = {
@ -180,7 +181,6 @@ fn main() {
println!("Cell Size: ({} x {})", cell_width, cell_height); println!("Cell Size: ({} x {})", cell_width, cell_height);
let terminal = Term::new( let terminal = Term::new(
&config,
width as f32, width as f32,
height as f32, height as f32,
cell_width as f32, cell_width as f32,
@ -295,11 +295,6 @@ impl Display {
// events into one. // events into one.
let mut new_size = None; let mut new_size = None;
// TODO should be built into renderer
unsafe {
gl::ClearColor(self.clear_red, self.clear_blue, self.clear_green, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
// Check for any out-of-band resize events (mac only) // Check for any out-of-band resize events (mac only)
while let Ok(sz) = self.rx.try_recv() { while let Ok(sz) = self.rx.try_recv() {
@ -322,18 +317,16 @@ impl Display {
let size_info = terminal.size_info().clone(); let size_info = terminal.size_info().clone();
self.renderer.with_api(&size_info, |mut api| { self.renderer.with_api(&size_info, |mut api| {
// Draw the grid // Draw the grid
let bg = terminal.bg; api.render_grid(&terminal.render_grid(), glyph_cache);
api.render_grid(&bg, &terminal.render_grid(), glyph_cache);
}); });
} }
// Draw render timer // Draw render timer
if self.render_timer { if self.render_timer {
let timing = format!("{:.3} usec", self.meter.average()); let timing = format!("{:.3} usec", self.meter.average());
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; let color = ::term::cell::Color::Rgb(Rgb { r: 0xd5, g: 0x4e, b: 0x53 });
self.renderer.with_api(terminal.size_info(), |mut api| { self.renderer.with_api(terminal.size_info(), |mut api| {
let bg = terminal.bg; api.render_string(&timing[..], glyph_cache, &color);
api.render_string(&bg, &timing[..], glyph_cache, &color);
}); });
} }
} }

View File

@ -20,6 +20,7 @@ use std::path::{PathBuf};
use std::ptr; use std::ptr;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{Ordering, AtomicBool}; use std::sync::atomic::{Ordering, AtomicBool};
use std::sync::mpsc;
use cgmath; use cgmath;
use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey}; use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey};
@ -42,6 +43,38 @@ pub trait LoadGlyph {
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph; fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
} }
enum Msg {
ConfigReload(Config),
ShaderReload,
}
/// Colors!
///
/// FIXME this is obviously bad; need static for reload logic for now. Hacking something in with
/// minimal effort and will improve later.
///
/// Only renderer is allowed to access this to prevent race conditions
static mut COLORS: [Rgb; 18] = [
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
Rgb { r: 0, g: 0, b: 0 },
];
/// Text drawing program /// Text drawing program
/// ///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a". /// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
@ -222,7 +255,6 @@ struct InstanceData {
#[derive(Debug)] #[derive(Debug)]
pub struct QuadRenderer { pub struct QuadRenderer {
program: ShaderProgram, program: ShaderProgram,
should_reload: Arc<AtomicBool>,
vao: GLuint, vao: GLuint,
vbo: GLuint, vbo: GLuint,
ebo: GLuint, ebo: GLuint,
@ -230,6 +262,7 @@ pub struct QuadRenderer {
atlas: Vec<Atlas>, atlas: Vec<Atlas>,
active_tex: GLuint, active_tex: GLuint,
batch: Batch, batch: Batch,
rx: mpsc::Receiver<Msg>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -272,6 +305,18 @@ impl Batch {
self.tex = glyph.tex_id; self.tex = glyph.tex_id;
} }
// TODO move colors list to uniform buffer and do this indexing in vertex shader
let fg = match cell.fg {
::term::cell::Color::Rgb(rgb) => rgb,
::term::cell::Color::Ansi(ansi) => unsafe { COLORS[ansi as usize] },
};
// TODO move colors list to uniform buffer and do this indexing in vertex shader
let bg = match cell.bg {
::term::cell::Color::Rgb(rgb) => rgb,
::term::cell::Color::Ansi(ansi) => unsafe { COLORS[ansi as usize] },
};
let mut instance = InstanceData { let mut instance = InstanceData {
col: col, col: col,
row: row, row: row,
@ -286,23 +331,23 @@ impl Batch {
uv_width: glyph.uv_width, uv_width: glyph.uv_width,
uv_height: glyph.uv_height, uv_height: glyph.uv_height,
r: cell.fg.r as f32, r: fg.r as f32,
g: cell.fg.g as f32, g: fg.g as f32,
b: cell.fg.b as f32, b: fg.b as f32,
bg_r: cell.bg.r as f32, bg_r: bg.r as f32,
bg_g: cell.bg.g as f32, bg_g: bg.g as f32,
bg_b: cell.bg.b as f32, bg_b: bg.b as f32,
}; };
if cell.flags.contains(cell::INVERSE) { if cell.flags.contains(cell::INVERSE) {
instance.r = cell.bg.r as f32; instance.r = bg.r as f32;
instance.g = cell.bg.g as f32; instance.g = bg.g as f32;
instance.b = cell.bg.b as f32; instance.b = bg.b as f32;
instance.bg_r = cell.fg.r as f32; instance.bg_r = fg.r as f32;
instance.bg_g = cell.fg.g as f32; instance.bg_g = fg.g as f32;
instance.bg_b = cell.fg.b as f32; instance.bg_b = fg.b as f32;
} }
self.instances.push(instance); self.instances.push(instance);
@ -345,7 +390,7 @@ const ATLAS_SIZE: i32 = 1024;
impl QuadRenderer { impl QuadRenderer {
// TODO should probably hand this a transform instead of width/height // TODO should probably hand this a transform instead of width/height
pub fn new(width: u32, height: u32) -> QuadRenderer { pub fn new(config: &Config, width: u32, height: u32) -> QuadRenderer {
let program = ShaderProgram::new(width, height).unwrap(); let program = ShaderProgram::new(width, height).unwrap();
let mut vao: GLuint = 0; let mut vao: GLuint = 0;
@ -448,11 +493,14 @@ impl QuadRenderer {
let should_reload = Arc::new(AtomicBool::new(false)); let should_reload = Arc::new(AtomicBool::new(false));
let should_reload2 = should_reload.clone(); let should_reload2 = should_reload.clone();
let (msg_tx, msg_rx) = mpsc::channel();
::std::thread::spawn(move || { ::std::thread::spawn(move || {
let (tx, rx) = ::std::sync::mpsc::channel(); let (tx, rx) = mpsc::channel();
let mut watcher = Watcher::new(tx).unwrap(); let mut watcher = Watcher::new(tx).unwrap();
watcher.watch(TEXT_SHADER_F_PATH).expect("watch fragment shader"); watcher.watch(TEXT_SHADER_F_PATH).expect("watch fragment shader");
watcher.watch(TEXT_SHADER_V_PATH).expect("watch vertex shader"); watcher.watch(TEXT_SHADER_V_PATH).expect("watch vertex shader");
watcher.watch("/home/jwilm/.alacritty.yml").expect("watch alacritty yml");
loop { loop {
let event = rx.recv().expect("watcher event"); let event = rx.recv().expect("watcher event");
@ -468,10 +516,17 @@ impl QuadRenderer {
if let Err(err) = watcher.watch(path) { if let Err(err) = watcher.watch(path) {
println!("failed to establish watch on {:?}: {:?}", path, err); println!("failed to establish watch on {:?}: {:?}", path, err);
} }
}
// This is last event we see after saving in vim if path == ::std::path::Path::new("/home/jwilm/.alacritty.yml") {
should_reload2.store(true, Ordering::Relaxed); if let Ok(config) = Config::load() {
msg_tx.send(Msg::ConfigReload(config))
.expect("msg send ok");
};
} else {
msg_tx.send(Msg::ShaderReload)
.expect("msg send ok");
}
}
} }
} }
} }
@ -479,7 +534,6 @@ impl QuadRenderer {
let mut renderer = QuadRenderer { let mut renderer = QuadRenderer {
program: program, program: program,
should_reload: should_reload,
vao: vao, vao: vao,
vbo: vbo, vbo: vbo,
ebo: ebo, ebo: ebo,
@ -487,8 +541,13 @@ impl QuadRenderer {
atlas: Vec::new(), atlas: Vec::new(),
active_tex: 0, active_tex: 0,
batch: Batch::new(), batch: Batch::new(),
rx: msg_rx,
}; };
unsafe {
COLORS = config.color_list();
}
let atlas = Atlas::new(ATLAS_SIZE); let atlas = Atlas::new(ATLAS_SIZE);
renderer.atlas.push(atlas); renderer.atlas.push(atlas);
@ -498,8 +557,17 @@ impl QuadRenderer {
pub fn with_api<F, T>(&mut self, props: &term::SizeInfo, func: F) -> T pub fn with_api<F, T>(&mut self, props: &term::SizeInfo, func: F) -> T
where F: FnOnce(RenderApi) -> T where F: FnOnce(RenderApi) -> T
{ {
if self.should_reload.load(Ordering::Relaxed) { while let Ok(msg) = self.rx.try_recv() {
self.reload_shaders(props.width as u32, props.height as u32); match msg {
Msg::ConfigReload(config) => {
unsafe {
COLORS = config.color_list();
}
},
Msg::ShaderReload => {
self.reload_shaders(props.width as u32, props.height as u32);
}
}
} }
unsafe { unsafe {
@ -544,7 +612,6 @@ impl QuadRenderer {
} }
pub fn reload_shaders(&mut self, width: u32, height: u32) { pub fn reload_shaders(&mut self, width: u32, height: u32) {
self.should_reload.store(false, Ordering::Relaxed);
let program = match ShaderProgram::new(width, height) { let program = match ShaderProgram::new(width, height) {
Ok(program) => program, Ok(program) => program,
Err(err) => { Err(err) => {
@ -612,10 +679,9 @@ impl<'a> RenderApi<'a> {
/// optimization. /// optimization.
pub fn render_string( pub fn render_string(
&mut self, &mut self,
bg: &Rgb,
s: &str, s: &str,
glyph_cache: &mut GlyphCache, glyph_cache: &mut GlyphCache,
color: &Rgb, color: &::term::cell::Color,
) { ) {
let row = 40.0; let row = 40.0;
let mut col = 100.0; let mut col = 100.0;
@ -630,8 +696,8 @@ impl<'a> RenderApi<'a> {
if let Some(glyph) = glyph_cache.get(&glyph_key, self) { if let Some(glyph) = glyph_cache.get(&glyph_key, self) {
let cell = Cell { let cell = Cell {
c: c, c: c,
fg: *color, fg: color.clone(),
bg: *bg, bg: cell::Color::Rgb(Rgb { r: 0, g: 0, b: 0}),
flags: cell::INVERSE, flags: cell::INVERSE,
}; };
self.add_render_item(row, col, &cell, glyph); self.add_render_item(row, col, &cell, glyph);
@ -658,12 +724,27 @@ impl<'a> RenderApi<'a> {
} }
} }
pub fn render_grid(&mut self, bg: &Rgb, grid: &Grid<Cell>, glyph_cache: &mut GlyphCache) { pub fn render_grid(
&mut self,
grid: &Grid<Cell>,
glyph_cache: &mut GlyphCache
) {
// TODO should be built into renderer
let color = unsafe { COLORS[::ansi::Color::Background as usize] };
unsafe {
gl::ClearColor(
color.r as f32 / 255.0,
color.g as f32 / 255.0,
color.b as f32 / 255.0,
1.0
);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
for (i, line) in grid.lines().enumerate() { for (i, line) in grid.lines().enumerate() {
for (j, cell) in line.cells().enumerate() { for (j, cell) in line.cells().enumerate() {
// Skip empty cells // Skip empty cells
if cell.c == ' ' && if cell.c == ' ' && cell.bg == cell::Color::Ansi(::ansi::Color::Background) &&
cell.bg == *bg &&
!cell.flags.contains(cell::INVERSE) !cell.flags.contains(cell::INVERSE)
{ {
continue; continue;

View File

@ -21,9 +21,7 @@ use ansi::{self, Attr, Handler};
use grid::{Grid, ClearRegion}; use grid::{Grid, ClearRegion};
use index::{Cursor, Column, Line}; use index::{Cursor, Column, Line};
use tty; use tty;
use config::Config; use ansi::Color;
use ::Rgb;
/// RAII type which manages grid state for render /// RAII type which manages grid state for render
/// ///
@ -90,20 +88,26 @@ pub mod cell {
} }
} }
#[derive(Clone, Debug, Copy)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Color {
Rgb(Rgb),
Ansi(::ansi::Color),
}
#[derive(Clone, Debug)]
pub struct Cell { pub struct Cell {
pub c: char, pub c: char,
pub fg: Rgb, pub fg: Color,
pub bg: Rgb, pub bg: Color,
pub flags: Flags, pub flags: Flags,
} }
impl Cell { impl Cell {
pub fn new(c: char) -> Cell { pub fn new(c: char, fg: Color, bg: Color) -> Cell {
Cell { Cell {
c: c.into(), c: c.into(),
bg: Default::default(), bg: bg,
fg: Default::default(), fg: fg,
flags: Flags::empty(), flags: Flags::empty(),
} }
} }
@ -111,13 +115,7 @@ pub mod cell {
#[inline] #[inline]
pub fn reset(&mut self, template: &Cell) { pub fn reset(&mut self, template: &Cell) {
// memcpy template to self // memcpy template to self
unsafe { *self = template.clone();
::std::ptr::copy_nonoverlapping(
template as *const Cell,
self as *mut Cell,
1
);
}
} }
} }
} }
@ -165,12 +163,6 @@ pub struct Term {
/// Alt cursor /// Alt cursor
alt_cursor: Cursor, alt_cursor: Cursor,
/// Active foreground color
pub fg: Rgb,
/// Active background color
pub bg: Rgb,
/// Tabstops /// Tabstops
tabs: Vec<bool>, tabs: Vec<bool>,
@ -189,9 +181,6 @@ pub struct Term {
/// Empty cell /// Empty cell
empty_cell: Cell, empty_cell: Cell,
/// Text colors
colors: [Rgb; 16],
pub dirty: bool, pub dirty: bool,
} }
@ -225,7 +214,6 @@ impl SizeInfo {
impl Term { impl Term {
pub fn new( pub fn new(
config: &Config,
width: f32, width: f32,
height: f32, height: f32,
cell_width: f32, cell_width: f32,
@ -238,20 +226,18 @@ impl Term {
cell_height: cell_height as f32, cell_height: cell_height as f32,
}; };
let mut template = Cell::new(' '); let template = Cell::new(
template.flags = cell::Flags::empty(); ' ',
template.bg = config.bg_color(); cell::Color::Ansi(Color::Foreground),
template.fg = config.fg_color(); cell::Color::Ansi(Color::Background)
);
let num_cols = size.cols(); let num_cols = size.cols();
let num_lines = size.lines(); let num_lines = size.lines();
println!("num_cols, num_lines = {}, {}", num_cols, num_lines); println!("num_cols, num_lines = {}, {}", num_cols, num_lines);
println!("bg: {:?}, fg: {:?}", template.bg, template.fg); let grid = Grid::new(num_lines, num_cols, &template);
println!("colors: {:?}", config.color_list());
let grid = Grid::new(num_lines, num_cols, &Cell::new(' '));
let tty = tty::new(*num_lines as u8, *num_cols as u8); let tty = tty::new(*num_lines as u8, *num_cols as u8);
tty.resize(*num_lines as usize, *num_cols as usize, size.width as usize, size.height as usize); tty.resize(*num_lines as usize, *num_cols as usize, size.width as usize, size.height as usize);
@ -272,16 +258,13 @@ impl Term {
alt: false, alt: false,
cursor: Cursor::default(), cursor: Cursor::default(),
alt_cursor: Cursor::default(), alt_cursor: Cursor::default(),
fg: config.fg_color(),
bg: config.bg_color(),
tty: tty, tty: tty,
tabs: tabs, tabs: tabs,
mode: Default::default(), mode: Default::default(),
scroll_region: scroll_region, scroll_region: scroll_region,
size_info: size, size_info: size,
template_cell: template, template_cell: template.clone(),
empty_cell: template, empty_cell: template,
colors: config.color_list(),
} }
} }
@ -323,8 +306,9 @@ impl Term {
println!("num_cols, num_lines = {}, {}", num_cols, num_lines); println!("num_cols, num_lines = {}, {}", num_cols, num_lines);
// Resize grids to new size // Resize grids to new size
self.grid.resize(num_lines, num_cols, &Cell::new(' ')); let template = self.template_cell.clone();
self.alt_grid.resize(num_lines, num_cols, &Cell::new(' ')); self.grid.resize(num_lines, num_cols, &template);
self.alt_grid.resize(num_lines, num_cols, &template);
// Ensure cursor is in-bounds // Ensure cursor is in-bounds
self.cursor.line = limit(self.cursor.line, Line(0), num_lines); self.cursor.line = limit(self.cursor.line, Line(0), num_lines);
@ -462,7 +446,7 @@ impl ansi::Handler for Term {
} }
let cell = &mut self.grid[&self.cursor]; let cell = &mut self.grid[&self.cursor];
*cell = self.template_cell; *cell = self.template_cell.clone();
cell.c = c; cell.c = c;
self.cursor.col += 1; self.cursor.col += 1;
} }
@ -779,27 +763,21 @@ impl ansi::Handler for Term {
fn terminal_attribute(&mut self, attr: Attr) { fn terminal_attribute(&mut self, attr: Attr) {
debug_println!("Set Attribute: {:?}", attr); debug_println!("Set Attribute: {:?}", attr);
match attr { match attr {
Attr::DefaultForeground => {
self.template_cell.fg = self.fg;
},
Attr::DefaultBackground => {
self.template_cell.bg = self.bg;
},
Attr::Foreground(named_color) => { Attr::Foreground(named_color) => {
self.template_cell.fg = self.colors[named_color as usize]; self.template_cell.fg = cell::Color::Ansi(named_color);
}, },
Attr::Background(named_color) => { Attr::Background(named_color) => {
self.template_cell.bg = self.colors[named_color as usize]; self.template_cell.bg = cell::Color::Ansi(named_color);
}, },
Attr::ForegroundSpec(rgb) => { Attr::ForegroundSpec(rgb) => {
self.template_cell.fg = rgb; self.template_cell.fg = cell::Color::Rgb(rgb);
}, },
Attr::BackgroundSpec(rgb) => { Attr::BackgroundSpec(rgb) => {
self.template_cell.bg = rgb; self.template_cell.bg = cell::Color::Rgb(rgb);
}, },
Attr::Reset => { Attr::Reset => {
self.template_cell.fg = self.fg; self.template_cell.fg = cell::Color::Ansi(Color::Foreground);
self.template_cell.bg = self.bg; self.template_cell.bg = cell::Color::Ansi(Color::Background);
self.template_cell.flags = cell::Flags::empty(); self.template_cell.flags = cell::Flags::empty();
}, },
Attr::Reverse => self.template_cell.flags.insert(cell::INVERSE), Attr::Reverse => self.template_cell.flags.insert(cell::INVERSE),