From f37dd5c5bc9a98fa4429f90ca980a7f9f996b06f Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Sun, 21 Jan 2018 22:16:25 +0100 Subject: [PATCH] Draw visual bell as big quad over everything A method `render_rect` has been added that allows drawing a rectangle over anything in the terminal. This is used by the visual bell to draw a big rectangle over everything. To achieve this a new shader has been added. Shaders are now kept around until the program is dropped. Then whenever a rectangle draw call is issued, the shader is swapped from the default shader to the rectangle drawing shader. After the shader has been swapped and the rectangle has been drawn, uniforms (like projection) and blending function are reset to make sure the rendering can be continued on the default shader. All current instances of visual_bell in the rendering have been removed, it is now all done exclusively through the rectangle rendering function. Currently the shaders are swapped every time `render_rect` is called, so when rendering multiple rectangles (potentially for underline / strikethrough) there would be a loads of shader swaps that could be potentially very expensive. So before using `render_rect` for a different application, the shader swap should be removed from the `render_rect` method. As long as the visual bell's alpha value is 0, this has no effect on performance because no rectangle is rendered. --- res/rect.f.glsl | 22 ++++ res/rect.v.glsl | 25 ++++ res/text.f.glsl | 8 +- res/text.v.glsl | 6 - src/renderer/mod.rs | 269 ++++++++++++++++++++++++++++++++++---------- 5 files changed, 258 insertions(+), 72 deletions(-) create mode 100644 res/rect.f.glsl create mode 100644 res/rect.v.glsl diff --git a/res/rect.f.glsl b/res/rect.f.glsl new file mode 100644 index 00000000..907ee858 --- /dev/null +++ b/res/rect.f.glsl @@ -0,0 +1,22 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#version 330 core +in vec4 color; + +out vec4 FragColor; + +void main() +{ + FragColor = color; +} diff --git a/res/rect.v.glsl b/res/rect.v.glsl new file mode 100644 index 00000000..a353b6b0 --- /dev/null +++ b/res/rect.v.glsl @@ -0,0 +1,25 @@ +// Copyright 2016 Joe Wilm, The Alacritty Project Contributors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +#version 330 core +layout (location = 0) in vec3 aPos; + +out vec4 color; + +uniform vec4 col; + +void main() +{ + gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0); + color = col; +} diff --git a/res/text.f.glsl b/res/text.f.glsl index c551179e..532cf929 100644 --- a/res/text.f.glsl +++ b/res/text.f.glsl @@ -15,8 +15,6 @@ in vec2 TexCoords; in vec3 fg; in vec4 bg; -in vec3 vbc; -flat in float vb; flat in int background; layout(location = 0, index = 0) out vec4 color; @@ -32,11 +30,7 @@ void main() discard; alphaMask = vec4(1.0); - if (vb == 0) { - color = vec4(bg.rgb, 1.0); - } else { - color = vec4(vbc, vb); - } + color = vec4(bg.rgb, 1.0); } else { vec3 textColor = texture(mask, TexCoords).rgb; alphaMask = vec4(textColor, textColor.r); diff --git a/res/text.v.glsl b/res/text.v.glsl index a13d99a7..32aee5ac 100644 --- a/res/text.v.glsl +++ b/res/text.v.glsl @@ -32,19 +32,15 @@ layout (location = 5) in vec4 backgroundColor; out vec2 TexCoords; out vec3 fg; out vec4 bg; -out vec3 vbc; // Terminal properties uniform vec2 termDim; uniform vec2 cellDim; -uniform float visualBell; -uniform vec3 visualBellColor; uniform int backgroundPass; // Orthographic projection uniform mat4 projection; -flat out float vb; flat out int background; void main() @@ -76,8 +72,6 @@ void main() TexCoords = uvOffset + vec2(position.x, 1 - position.y) * uvSize; } - vb = visualBell; - vbc = visualBellColor; background = backgroundPass; bg = vec4(backgroundColor.rgb / 255.0, backgroundColor.a); fg = textColor / vec3(255.0, 255.0, 255.0); diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs index 27c52fa0..36ae5b78 100644 --- a/src/renderer/mod.rs +++ b/src/renderer/mod.rs @@ -38,6 +38,8 @@ use Rgb; // Shader paths for live reload 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"); +static RECT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl"); +static RECT_SHADER_V_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl"); // Shader source which is used when live-shader-reload feature is disable static TEXT_SHADER_F: &'static str = include_str!( @@ -46,6 +48,12 @@ static TEXT_SHADER_F: &'static str = include_str!( static TEXT_SHADER_V: &'static str = include_str!( concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl") ); +static RECT_SHADER_F: &'static str = include_str!( + concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl") +); +static RECT_SHADER_V: &'static str = include_str!( + concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl") +); /// `LoadGlyph` allows for copying a rasterized glyph into graphics memory pub trait LoadGlyph { @@ -106,6 +114,24 @@ pub struct ShaderProgram { // Program id id: GLuint, + /// Current vertex shader + vertex: GLuint, + + /// Current fragment shader + fragment: GLuint, + + /// Text vertex shader + text_vertex: GLuint, + + /// Text fragment shader + text_fragment: GLuint, + + /// Rectangle vertex shader + rect_vertex: GLuint, + + /// Rectangle fragment shader + rect_fragment: GLuint, + /// projection matrix uniform u_projection: GLint, @@ -115,12 +141,6 @@ pub struct ShaderProgram { /// Cell dimensions (pixels) u_cell_dim: GLint, - /// Visual bell - u_visual_bell: GLint, - - /// Visual bell color - u_visual_bell_color: GLint, - /// Background pass flag /// /// Rendering is split into two passes; 1 for backgrounds, and one for text @@ -359,9 +379,10 @@ struct InstanceData { pub struct QuadRenderer { program: ShaderProgram, vao: GLuint, - vbo: GLuint, ebo: GLuint, vbo_instance: GLuint, + rect_vao: GLuint, + rect_vbo: GLuint, atlas: Vec, current_atlas: usize, active_tex: GLuint, @@ -377,7 +398,6 @@ pub struct RenderApi<'a> { current_atlas: &'a mut usize, program: &'a mut ShaderProgram, config: &'a Config, - visual_bell_intensity: f32 } #[derive(Debug)] @@ -488,6 +508,10 @@ impl QuadRenderer { let mut vbo_instance: GLuint = 0; + let mut rect_vbo: GLuint = 0; + let mut rect_vao: GLuint = 0; + let mut rect_ebo: GLuint = 0; + unsafe { gl::Enable(gl::BLEND); gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); @@ -578,8 +602,24 @@ impl QuadRenderer { gl::EnableVertexAttribArray(5); gl::VertexAttribDivisor(5, 1); + // Rectangle setup + gl::GenVertexArrays(1, &mut rect_vao); + gl::GenBuffers(1, &mut rect_vbo); + gl::GenBuffers(1, &mut rect_ebo); + gl::BindVertexArray(rect_vao); + + let indices: [i32; 6] = [ + 0, 1, 3, + 1, 2, 3, + ]; + + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo); + gl::BufferData(gl::ELEMENT_ARRAY_BUFFER, (size_of::() * indices.len()) as _, indices.as_ptr() as *const _, gl::STATIC_DRAW); + + // Cleanup gl::BindVertexArray(0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); + gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); } let (msg_tx, msg_rx) = mpsc::channel(); @@ -611,9 +651,10 @@ impl QuadRenderer { let mut renderer = QuadRenderer { program: program, vao: vao, - vbo: vbo, ebo: ebo, vbo_instance: vbo_instance, + rect_vao: rect_vao, + rect_vbo: rect_vbo, atlas: Vec::new(), current_atlas: 0, active_tex: 0, @@ -636,23 +677,20 @@ impl QuadRenderer { ) -> T where F: FnOnce(RenderApi) -> T { - while let Ok(msg) = self.rx.try_recv() { - match msg { - Msg::ShaderReload => { - self.reload_shaders(config, Size { - width: Pixels(props.width as u32), - height: Pixels(props.height as u32) - }); - } - } - } + let size = Size { + width: Pixels(props.width as u32), + height: Pixels(props.height as u32), + }; - let visual_bell_color = config.visual_bell().color(); + // Flush message queue + if let Ok(Msg::ShaderReload) = self.rx.try_recv() { + self.reload_shaders(config, size); + } + while let Ok(_) = self.rx.try_recv() {} unsafe { self.program.activate(); self.program.set_term_uniforms(props); - self.program.set_visual_bell(visual_bell_intensity as _, visual_bell_color); gl::BindVertexArray(self.vao); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); @@ -666,10 +704,14 @@ impl QuadRenderer { atlas: &mut self.atlas, current_atlas: &mut self.current_atlas, program: &mut self.program, - visual_bell_intensity: visual_bell_intensity as _, config: config, }); + // Draw visual bell + let color = config.visual_bell().color(); + let rect = Rect::new(0, 0, *size.width, *size.height); + self.render_rect(rect, color, visual_bell_intensity as f32, size); + unsafe { gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0); @@ -737,21 +779,100 @@ impl QuadRenderer { self.program.update_projection(width as f32, height as f32); self.program.deactivate(); } + + // Render a rectangle + // TODO: When stuff other than visual bell uses render_rect, + // draw calls should be collected to reduce number of shader swaps + fn render_rect(&mut self, rect: Rect, color: Rgb, alpha: f32, size: Size>) { + // Do nothing when alpha is fully transparent + if alpha == 0. { + return; + } + + // Calculate rectangle position + let center_x = *size.width as f32 / 2.; + let center_y = *size.height as f32 / 2.; + let x = (rect.x as f32 - center_x) / center_x; + let y = -(rect.y as f32 - center_y) / center_y; + let width = rect.width as f32 / center_x; + let height = rect.height as f32 / center_y; + + // Swap to rectangle shaders + let (vert, frag) = (self.program.rect_vertex, self.program.rect_fragment); + self.program.swap_shaders(vert, frag).unwrap(); + + unsafe { + // Change blending strategy + gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); + + // Setup vertices + let vertices: [f32; 12] = [ + x + width, y , 0.0, + x + width, y - height, 0.0, + x , y - height, 0.0, + x , y , 0.0, + ]; + + // Load vertex data into array buffer + gl::BindVertexArray(self.rect_vao); + + gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo); + gl::BufferData(gl::ARRAY_BUFFER, (size_of::() * vertices.len()) as _, vertices.as_ptr() as *const _, gl::STATIC_DRAW); + + // Position + gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, (size_of::() * 3) as _, ptr::null()); + gl::EnableVertexAttribArray(0); + + gl::BindBuffer(gl::ARRAY_BUFFER, 0); + + // Color + let u_col = gl::GetUniformLocation(self.program.id, b"col\0".as_ptr() as *const _); + gl::Uniform4f( + u_col, + color.r as f32 / 255., + color.g as f32 / 255., + color.b as f32 / 255., + alpha, + ); + + // Draw the rectangle + self.program.activate(); + gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0 as *const _); + + // Reset state + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + gl::BindVertexArray(0); + } + + // Reset shaders + let (vert, frag) = (self.program.text_vertex, self.program.text_fragment); + self.program.swap_shaders(vert, frag).unwrap(); + self.program.update_projection(*size.width as f32, *size.height as f32); + self.program.deactivate(); + } } -fn mix(x: f32, y: f32, a: f32) -> f32 { - x * (1.0 - a) + y * a +struct Rect { + x: T, + y: T, + width: T, + height: T, +} + +impl Rect { + fn new(x: T, y: T, width: T, height: T) -> Self { + Rect { x, y, width, height } + } } impl<'a> RenderApi<'a> { pub fn clear(&self, color: Rgb) { let alpha = self.config.background_opacity().get(); - let (flash, intensity) = (self.config.visual_bell().color(), self.visual_bell_intensity); unsafe { gl::ClearColor( - mix(f32::from(color.r) / 255.0, f32::from(flash.r) / 255.0, intensity).min(1.0) * alpha, - mix(f32::from(color.g) / 255.0, f32::from(flash.g) / 255.0, intensity).min(1.0) * alpha, - mix(f32::from(color.b) / 255.0, f32::from(flash.b) / 255.0, intensity).min(1.0) * alpha, + (f32::from(color.r) / 255.0).min(1.0) * alpha, + (f32::from(color.g) / 255.0).min(1.0) * alpha, + (f32::from(color.b) / 255.0).min(1.0) * alpha, alpha ); gl::Clear(gl::COLOR_BUFFER_BIT); @@ -965,31 +1086,37 @@ impl ShaderProgram { config: &Config, size: Size> ) -> Result { - let vertex_source = if cfg!(feature = "live-shader-reload") { - None + let (text_vert_src, text_frag_src, + rect_vert_src, rect_frag_src) = if cfg!(feature = "live-shader-reload") + { + (None, None, None, None) } else { - Some(TEXT_SHADER_V) + (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F), + Some(RECT_SHADER_V), Some(RECT_SHADER_F)) }; - let vertex_shader = ShaderProgram::create_shader( + let text_vertex = ShaderProgram::create_shader( TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, - vertex_source + text_vert_src )?; - let frag_source = if cfg!(feature = "live-shader-reload") { - None - } else { - Some(TEXT_SHADER_F) - }; - let fragment_shader = ShaderProgram::create_shader( + let text_fragment = ShaderProgram::create_shader( TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, - frag_source + text_frag_src )?; - let program = ShaderProgram::create_program(vertex_shader, fragment_shader)?; + let rect_vertex = ShaderProgram::create_shader( + RECT_SHADER_V_PATH, + gl::VERTEX_SHADER, + rect_vert_src + )?; + let rect_fragment = ShaderProgram::create_shader( + RECT_SHADER_F_PATH, + gl::FRAGMENT_SHADER, + rect_frag_src + )?; + let program = ShaderProgram::create_program(text_vertex, text_fragment)?; unsafe { - gl::DeleteShader(vertex_shader); - gl::DeleteShader(fragment_shader); gl::UseProgram(program); } @@ -1008,13 +1135,11 @@ impl ShaderProgram { } // get uniform locations - let (projection, term_dim, cell_dim, visual_bell, visual_bell_color, background) = unsafe { + let (projection, term_dim, cell_dim, background) = unsafe { ( gl::GetUniformLocation(program, cptr!(b"projection\0")), gl::GetUniformLocation(program, cptr!(b"termDim\0")), gl::GetUniformLocation(program, cptr!(b"cellDim\0")), - gl::GetUniformLocation(program, cptr!(b"visualBell\0")), - gl::GetUniformLocation(program, cptr!(b"visualBellColor\0")), gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")), ) }; @@ -1023,11 +1148,15 @@ impl ShaderProgram { let shader = ShaderProgram { id: program, + text_vertex, + text_fragment, + rect_vertex, + rect_fragment, + vertex: text_vertex, + fragment: text_fragment, u_projection: projection, u_term_dim: term_dim, u_cell_dim: cell_dim, - u_visual_bell: visual_bell, - u_visual_bell_color: visual_bell_color, u_background: background, padding_x: config.padding().x.floor(), padding_y: config.padding().y.floor(), @@ -1073,17 +1202,6 @@ impl ShaderProgram { } } - fn set_visual_bell(&self, visual_bell: f32, vb_color: Rgb) { - unsafe { - gl::Uniform1f(self.u_visual_bell, visual_bell); - gl::Uniform3f(self.u_visual_bell_color, - vb_color.r as f32 / 255., - vb_color.g as f32 / 255., - vb_color.b as f32 / 255. - ); - } - } - fn set_background_pass(&self, background_pass: bool) { let value = if background_pass { 1 @@ -1114,6 +1232,29 @@ impl ShaderProgram { } } + // Load a new vertex and fragment shader into the program + fn swap_shaders(&mut self, vertex: GLuint, fragment: GLuint) -> Result<(), ShaderCreationError> { + unsafe { + gl::DetachShader(self.id, self.vertex); + gl::DetachShader(self.id, self.fragment); + + gl::AttachShader(self.id, vertex); + gl::AttachShader(self.id, fragment); + + gl::LinkProgram(self.id); + + let mut success: GLint = 0; + gl::GetProgramiv(self.id, gl::LINK_STATUS, &mut success); + if success != i32::from(gl::TRUE) { + return Err(ShaderCreationError::Link(get_program_info_log(self.id))); + } + } + + self.vertex = vertex; + self.fragment = fragment; + + Ok(()) + } fn create_shader( path: &str, @@ -1159,6 +1300,16 @@ impl ShaderProgram { impl Drop for ShaderProgram { fn drop(&mut self) { unsafe { + // Delete shaders + gl::DeleteShader(self.text_vertex); + gl::DeleteShader(self.text_fragment); + gl::DeleteShader(self.rect_vertex); + gl::DeleteShader(self.rect_fragment); + + gl::DetachShader(self.id, self.vertex); + gl::DetachShader(self.id, self.fragment); + + // Delete program gl::DeleteProgram(self.id); } }