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,
/// Bright white
BrightWhite,
/// The foreground color
Foreground,
/// The background color
Background,
}
/// Terminal character attributes
@ -384,10 +388,6 @@ pub enum Attr {
Background(Color),
/// Set specific background color
BackgroundSpec(Rgb),
/// Set default foreground
DefaultForeground,
/// Set default background
DefaultBackground,
}
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;
}
},
39 => Attr::DefaultForeground,
39 => Attr::Foreground(Color::Foreground),
40 => Attr::Background(Color::Black),
41 => Attr::Background(Color::Red),
42 => Attr::Background(Color::Green),
@ -600,7 +600,7 @@ impl<'a, H: Handler + TermInfo + 'a> vte::Perform for Performer<'a, H> {
break;
}
},
49 => Attr::DefaultBackground,
49 => Attr::Background(Color::Background),
90 => Attr::Foreground(Color::BrightBlack),
91 => Attr::Foreground(Color::BrightRed),
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
/// array for performance.
pub fn color_list(&self) -> [Rgb; 16] {
pub fn color_list(&self) -> [Rgb; 18] {
let colors = &self.colors;
[
@ -282,6 +282,10 @@ impl Config {
colors.bright.magenta,
colors.bright.cyan,
colors.bright.white,
// Foreground and background
colors.primary.foreground,
colors.primary.background,
]
}

View File

@ -112,6 +112,7 @@ pub struct Rgb {
}
mod gl {
#![allow(non_upper_case_globals)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
}
@ -155,7 +156,7 @@ fn main() {
let rasterizer = font::Rasterizer::new(dpi.x(), dpi.y(), dpr);
// Create renderer
let mut renderer = QuadRenderer::new(width, height);
let mut renderer = QuadRenderer::new(&config, width, height);
// Initialize glyph cache
let glyph_cache = {
@ -180,7 +181,6 @@ fn main() {
println!("Cell Size: ({} x {})", cell_width, cell_height);
let terminal = Term::new(
&config,
width as f32,
height as f32,
cell_width as f32,
@ -295,11 +295,6 @@ impl Display {
// events into one.
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)
while let Ok(sz) = self.rx.try_recv() {
@ -322,18 +317,16 @@ impl Display {
let size_info = terminal.size_info().clone();
self.renderer.with_api(&size_info, |mut api| {
// Draw the grid
let bg = terminal.bg;
api.render_grid(&bg, &terminal.render_grid(), glyph_cache);
api.render_grid(&terminal.render_grid(), glyph_cache);
});
}
// Draw render timer
if self.render_timer {
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| {
let bg = terminal.bg;
api.render_string(&bg, &timing[..], glyph_cache, &color);
api.render_string(&timing[..], glyph_cache, &color);
});
}
}

View File

@ -20,6 +20,7 @@ use std::path::{PathBuf};
use std::ptr;
use std::sync::Arc;
use std::sync::atomic::{Ordering, AtomicBool};
use std::sync::mpsc;
use cgmath;
use font::{self, Rasterizer, RasterizedGlyph, FontDesc, GlyphKey, FontKey};
@ -42,6 +43,38 @@ pub trait LoadGlyph {
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
///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
@ -222,7 +255,6 @@ struct InstanceData {
#[derive(Debug)]
pub struct QuadRenderer {
program: ShaderProgram,
should_reload: Arc<AtomicBool>,
vao: GLuint,
vbo: GLuint,
ebo: GLuint,
@ -230,6 +262,7 @@ pub struct QuadRenderer {
atlas: Vec<Atlas>,
active_tex: GLuint,
batch: Batch,
rx: mpsc::Receiver<Msg>,
}
#[derive(Debug)]
@ -272,6 +305,18 @@ impl Batch {
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 {
col: col,
row: row,
@ -286,23 +331,23 @@ impl Batch {
uv_width: glyph.uv_width,
uv_height: glyph.uv_height,
r: cell.fg.r as f32,
g: cell.fg.g as f32,
b: cell.fg.b as f32,
r: fg.r as f32,
g: fg.g as f32,
b: fg.b as f32,
bg_r: cell.bg.r as f32,
bg_g: cell.bg.g as f32,
bg_b: cell.bg.b as f32,
bg_r: bg.r as f32,
bg_g: bg.g as f32,
bg_b: bg.b as f32,
};
if cell.flags.contains(cell::INVERSE) {
instance.r = cell.bg.r as f32;
instance.g = cell.bg.g as f32;
instance.b = cell.bg.b as f32;
instance.r = bg.r as f32;
instance.g = bg.g as f32;
instance.b = bg.b as f32;
instance.bg_r = cell.fg.r as f32;
instance.bg_g = cell.fg.g as f32;
instance.bg_b = cell.fg.b as f32;
instance.bg_r = fg.r as f32;
instance.bg_g = fg.g as f32;
instance.bg_b = fg.b as f32;
}
self.instances.push(instance);
@ -345,7 +390,7 @@ const ATLAS_SIZE: i32 = 1024;
impl QuadRenderer {
// 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 mut vao: GLuint = 0;
@ -448,11 +493,14 @@ impl QuadRenderer {
let should_reload = Arc::new(AtomicBool::new(false));
let should_reload2 = should_reload.clone();
let (msg_tx, msg_rx) = mpsc::channel();
::std::thread::spawn(move || {
let (tx, rx) = ::std::sync::mpsc::channel();
let (tx, rx) = mpsc::channel();
let mut watcher = Watcher::new(tx).unwrap();
watcher.watch(TEXT_SHADER_F_PATH).expect("watch fragment shader");
watcher.watch(TEXT_SHADER_V_PATH).expect("watch vertex shader");
watcher.watch("/home/jwilm/.alacritty.yml").expect("watch alacritty yml");
loop {
let event = rx.recv().expect("watcher event");
@ -468,10 +516,17 @@ impl QuadRenderer {
if let Err(err) = watcher.watch(path) {
println!("failed to establish watch on {:?}: {:?}", path, err);
}
}
// This is last event we see after saving in vim
should_reload2.store(true, Ordering::Relaxed);
if path == ::std::path::Path::new("/home/jwilm/.alacritty.yml") {
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 {
program: program,
should_reload: should_reload,
vao: vao,
vbo: vbo,
ebo: ebo,
@ -487,8 +541,13 @@ impl QuadRenderer {
atlas: Vec::new(),
active_tex: 0,
batch: Batch::new(),
rx: msg_rx,
};
unsafe {
COLORS = config.color_list();
}
let atlas = Atlas::new(ATLAS_SIZE);
renderer.atlas.push(atlas);
@ -498,8 +557,17 @@ impl QuadRenderer {
pub fn with_api<F, T>(&mut self, props: &term::SizeInfo, func: F) -> T
where F: FnOnce(RenderApi) -> T
{
if self.should_reload.load(Ordering::Relaxed) {
self.reload_shaders(props.width as u32, props.height as u32);
while let Ok(msg) = self.rx.try_recv() {
match msg {
Msg::ConfigReload(config) => {
unsafe {
COLORS = config.color_list();
}
},
Msg::ShaderReload => {
self.reload_shaders(props.width as u32, props.height as u32);
}
}
}
unsafe {
@ -544,7 +612,6 @@ impl QuadRenderer {
}
pub fn reload_shaders(&mut self, width: u32, height: u32) {
self.should_reload.store(false, Ordering::Relaxed);
let program = match ShaderProgram::new(width, height) {
Ok(program) => program,
Err(err) => {
@ -612,10 +679,9 @@ impl<'a> RenderApi<'a> {
/// optimization.
pub fn render_string(
&mut self,
bg: &Rgb,
s: &str,
glyph_cache: &mut GlyphCache,
color: &Rgb,
color: &::term::cell::Color,
) {
let row = 40.0;
let mut col = 100.0;
@ -630,8 +696,8 @@ impl<'a> RenderApi<'a> {
if let Some(glyph) = glyph_cache.get(&glyph_key, self) {
let cell = Cell {
c: c,
fg: *color,
bg: *bg,
fg: color.clone(),
bg: cell::Color::Rgb(Rgb { r: 0, g: 0, b: 0}),
flags: cell::INVERSE,
};
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 (j, cell) in line.cells().enumerate() {
// Skip empty cells
if cell.c == ' ' &&
cell.bg == *bg &&
if cell.c == ' ' && cell.bg == cell::Color::Ansi(::ansi::Color::Background) &&
!cell.flags.contains(cell::INVERSE)
{
continue;

View File

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