diff --git a/Cargo.lock b/Cargo.lock index 16457c20..565684bc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,7 @@ dependencies = [ "gl_generator 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "glutin 0.4.9 (git+https://github.com/jwilm/glutin?rev=c95e6973ace3cbf321123a64588b27f032675be9)", "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "notify 2.5.5 (git+https://github.com/jwilm/rsnotify?branch=add-ignore-op)", "servo-fontconfig 0.2.0 (git+https://github.com/jwilm/rust-fontconfig)", ] @@ -21,6 +22,11 @@ name = "bitflags" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bitflags" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "bitflags" version = "0.6.0" @@ -36,6 +42,16 @@ name = "byteorder" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cfg-if" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cgl" version = "0.1.5" @@ -142,6 +158,14 @@ dependencies = [ "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "filetime" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "freetype-rs" version = "0.9.0" @@ -172,6 +196,24 @@ dependencies = [ "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "fsevent" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fsevent-sys" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "gcc" version = "0.3.28" @@ -242,6 +284,14 @@ dependencies = [ "x11-dl 2.4.0 (git+https://github.com/jwilm/x11-rs?rev=40f08df7b4408980b922b3c6e258c9c6765c2c24)", ] +[[package]] +name = "inotify" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "kernel32-sys" version = "0.2.2" @@ -320,6 +370,73 @@ dependencies = [ "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mio" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "nix 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.23 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "nix" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "notify" +version = "2.5.5" +source = "git+https://github.com/jwilm/rsnotify?branch=add-ignore-op#0ca41a4807c427e6cf47d7e75735df62d2e86708" +dependencies = [ + "bitflags 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "filetime 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", + "fsevent-sys 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "inotify 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "num" version = "0.1.32" @@ -477,6 +594,11 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "tempfile" version = "2.1.3" @@ -489,6 +611,16 @@ dependencies = [ "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "time" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "user32-sys" version = "0.1.2" @@ -498,6 +630,15 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "walkdir" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wayland-client" version = "0.5.12" @@ -560,6 +701,15 @@ name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "x11-dl" version = "2.4.0" diff --git a/Cargo.toml b/Cargo.toml index ef2cdf32..de8c09cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ freetype-rs = "0.9.0" libc = "*" cgmath = "0.7" euclid = "0.6" +notify = { git = "https://github.com/jwilm/rsnotify", branch = "add-ignore-op" } [build-dependencies] gl_generator = "0.5" diff --git a/src/main.rs b/src/main.rs index b6a36330..e5f9e0b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,6 +11,7 @@ extern crate libc; extern crate glutin; extern crate cgmath; extern crate euclid; +extern crate notify; #[macro_use] mod macros; @@ -61,6 +62,7 @@ struct TermProps { cell_height: f32, sep_y: f32, height: f32, + width: f32, } fn main() { @@ -178,17 +180,18 @@ 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(); - 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, - }; - // Draw the grid renderer.render_grid(terminal.grid(), &glyph_cache, &props); @@ -199,7 +202,7 @@ fn main() { // Draw render timer let timing = format!("{:.3} usec", meter.average()); let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 }; - renderer.render_string(&timing[..], &glyph_cache, cell_width, &color); + renderer.render_string(&timing[..], &glyph_cache, &props, &color); window.swap_buffers().unwrap(); } diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 2f364482..6161191f 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -1,23 +1,32 @@ use std::ffi::CString; +use std::fs::File; +use std::io::{self, Read}; use std::mem::size_of; +use std::path::{PathBuf, Path}; use std::ptr; +use std::sync::Arc; +use std::sync::atomic::{Ordering, AtomicBool}; use cgmath::{self, Matrix}; use euclid::{Rect, Size2D, Point2D}; use gl::types::*; use gl; - +use notify::{Watcher as WatcherApi, RecommendedWatcher as Watcher, op}; use text::RasterizedGlyph; use grid::Grid; use term; use super::{Rgb, TermProps, GlyphCache}; -static TEXT_SHADER_V: &'static str = include_str!("../../res/text.v.glsl"); -static TEXT_SHADER_F: &'static str = include_str!("../../res/text.f.glsl"); +// static TEXT_SHADER_V: &'static str = include_str!("../../res/text.v.glsl"); +// static TEXT_SHADER_F: &'static str = include_str!("../../res/text.f.glsl"); + +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"); pub struct QuadRenderer { program: ShaderProgram, + should_reload: Arc, vao: GLuint, vbo: GLuint, ebo: GLuint, @@ -39,7 +48,7 @@ pub struct PackedVertex { impl QuadRenderer { // TODO should probably hand this a transform instead of width/height pub fn new(width: u32, height: u32) -> QuadRenderer { - let program = ShaderProgram::new(width, height); + let program = ShaderProgram::new(width, height).unwrap(); let mut vao: GLuint = 0; let mut vbo: GLuint = 0; @@ -88,8 +97,41 @@ impl QuadRenderer { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); } + let should_reload = Arc::new(AtomicBool::new(false)); + let should_reload2 = should_reload.clone(); + + ::std::thread::spawn(move || { + let (tx, rx) = ::std::sync::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"); + + loop { + let event = rx.recv().expect("watcher event"); + let ::notify::Event { path, op } = event; + + if let Ok(op) = op { + if op.contains(op::RENAME) { + continue; + } + + if op.contains(op::IGNORED) { + if let Some(path) = path.as_ref() { + 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); + } + } + } + }); + let mut renderer = QuadRenderer { program: program, + should_reload: should_reload, vao: vao, vbo: vbo, ebo: ebo, @@ -109,10 +151,10 @@ impl QuadRenderer { pub fn render_string(&mut self, s: &str, glyph_cache: &GlyphCache, - cell_width: u32, + props: &TermProps, color: &Rgb) { - self.prepare_render(); + self.prepare_render(props); let (mut x, mut y) = (800f32, 20f32); @@ -121,7 +163,7 @@ impl QuadRenderer { self.render(glyph, x, y, color); } - x += cell_width as f32 + 2f32; + x += props.cell_width as f32 + 2f32; } self.finish_render(); @@ -132,7 +174,7 @@ impl QuadRenderer { glyph_cache: &GlyphCache, props: &TermProps) { - self.prepare_render(); + self.prepare_render(props); if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) { let y = (props.cell_height + props.sep_y) * (cursor.y as f32); let x = (props.cell_width + props.sep_x) * (cursor.x as f32); @@ -146,7 +188,7 @@ impl QuadRenderer { } pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache, props: &TermProps) { - self.prepare_render(); + self.prepare_render(props); for i in 0..grid.rows() { let row = &grid[i]; for j in 0..row.cols() { @@ -166,7 +208,7 @@ impl QuadRenderer { self.finish_render(); } - fn prepare_render(&self) { + fn prepare_render(&mut self, props: &TermProps) { unsafe { self.program.activate(); @@ -175,6 +217,10 @@ impl QuadRenderer { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); gl::ActiveTexture(gl::TEXTURE0); } + + if self.should_reload.load(Ordering::Relaxed) { + self.reload_shaders(props.width as u32, props.height as u32); + } } fn finish_render(&mut self) { @@ -187,6 +233,30 @@ 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) => { + match err { + ShaderCreationError::Io(err) => { + println!("Error reading shader file: {}", err); + }, + ShaderCreationError::Compile(path, log) => { + println!("Error compiling shader at {:?}", path); + io::copy(&mut log.as_bytes(), &mut io::stdout()).unwrap(); + } + } + + return; + } + }; + + self.active_tex = 0; + self.active_color = Rgb { r: 0, g: 0, b: 0 }; + self.program = program; + } + fn render(&mut self, glyph: &Glyph, x: f32, y: f32, color: &Rgb) { if &self.active_color != color { unsafe { @@ -308,9 +378,10 @@ impl ShaderProgram { } } - pub fn new(width: u32, height: u32) -> ShaderProgram { - let vertex_shader = ShaderProgram::create_vertex_shader(); - let fragment_shader = ShaderProgram::create_fragment_shader(); + pub fn new(width: u32, height: u32) -> Result { + let vertex_shader = ShaderProgram::create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER)?; + let fragment_shader = ShaderProgram::create_shader(TEXT_SHADER_F_PATH, + gl::FRAGMENT_SHADER)?; let program = ShaderProgram::create_program(vertex_shader, fragment_shader); unsafe { @@ -358,11 +429,10 @@ impl ShaderProgram { } shader.deactivate(); - shader + Ok(shader) } fn create_program(vertex: GLuint, fragment: GLuint) -> GLuint { - unsafe { let program = gl::CreateProgram(); gl::AttachShader(program, vertex); @@ -379,41 +449,109 @@ impl ShaderProgram { } } - fn create_fragment_shader() -> GLuint { + + fn create_shader(path: &str, kind: GLenum) -> Result { + let source = CString::new(read_file(path)?).unwrap(); + let shader = unsafe { + let shader = gl::CreateShader(kind); + gl::ShaderSource(shader, 1, &source.as_ptr(), ptr::null()); + gl::CompileShader(shader); + shader + }; + + let mut success: GLint = 0; unsafe { - let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER); - let fragment_source = CString::new(TEXT_SHADER_F).unwrap(); - gl::ShaderSource(fragment_shader, 1, &fragment_source.as_ptr(), ptr::null()); - gl::CompileShader(fragment_shader); - - let mut success: GLint = 0; - gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success); - - if success != (gl::TRUE as GLint) { - panic!("failed to compiler fragment shader"); - } - fragment_shader + gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success); } - } - fn create_vertex_shader() -> GLuint { - unsafe { - let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER); - let vertex_source = CString::new(TEXT_SHADER_V).unwrap(); - gl::ShaderSource(vertex_shader, 1, &vertex_source.as_ptr(), ptr::null()); - gl::CompileShader(vertex_shader); + if success == (gl::TRUE as GLint) { + Ok(shader) + } else { + // Read log + let log = get_shader_info_log(shader); - let mut success: GLint = 0; - gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success); + // Cleanup + unsafe { gl::DeleteShader(shader); } - if success != (gl::TRUE as GLint) { - panic!("failed to compiler vertex shader"); - } - vertex_shader + Err(ShaderCreationError::Compile(PathBuf::from(path), log)) } } } +fn get_shader_info_log(shader: GLuint) -> String { + // Get expected log length + let mut max_length: GLint = 0; + unsafe { + gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length); + } + + // Read the info log + let mut actual_length: GLint = 0; + let mut buf: Vec = Vec::with_capacity(max_length as usize); + unsafe { + gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _); + } + + // Build a string + unsafe { + buf.set_len(actual_length as usize); + } + + // XXX should we expect opengl to return garbage? + String::from_utf8(buf).unwrap() +} + +fn read_file(path: &str) -> Result { + let mut f = File::open(path)?; + let mut buf = String::new(); + f.read_to_string(&mut buf)?; + + Ok(buf) +} + +#[derive(Debug)] +pub enum ShaderCreationError { + /// Error reading file + Io(io::Error), + + /// Error compiling shader + Compile(PathBuf, String), +} + +impl ::std::error::Error for ShaderCreationError { + fn cause(&self) -> Option<&::std::error::Error> { + match *self { + ShaderCreationError::Io(ref err) => Some(err), + ShaderCreationError::Compile(_, _) => None, + } + } + + fn description(&self) -> &str { + match *self { + ShaderCreationError::Io(ref err) => err.description(), + ShaderCreationError::Compile(ref _path, ref s) => s.as_str(), + } + } +} + +impl ::std::fmt::Display for ShaderCreationError { + fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + match *self { + ShaderCreationError::Io(ref err) => write!(f, "Error creating shader: {}", err), + ShaderCreationError::Compile(ref _path, ref s) => { + write!(f, "Error compiling shader: {}", s) + }, + } + } +} + +impl From for ShaderCreationError { + fn from(val: io::Error) -> ShaderCreationError { + ShaderCreationError::Io(val) + } +} + + /// Manages a single texture atlas /// /// The strategy for filling an atlas looks roughly like this: