1
0
Fork 0
mirror of https://github.com/alacritty/alacritty.git synced 2024-11-25 14:05:41 -05:00

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.
This commit is contained in:
Christian Duerr 2018-01-21 22:16:25 +01:00
parent c720c4d3f4
commit f37dd5c5bc
No known key found for this signature in database
GPG key ID: 85CDAE3C164BA7B4
5 changed files with 258 additions and 72 deletions

22
res/rect.f.glsl Normal file
View file

@ -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;
}

25
res/rect.v.glsl Normal file
View file

@ -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;
}

View file

@ -15,8 +15,6 @@
in vec2 TexCoords; in vec2 TexCoords;
in vec3 fg; in vec3 fg;
in vec4 bg; in vec4 bg;
in vec3 vbc;
flat in float vb;
flat in int background; flat in int background;
layout(location = 0, index = 0) out vec4 color; layout(location = 0, index = 0) out vec4 color;
@ -32,11 +30,7 @@ void main()
discard; discard;
alphaMask = vec4(1.0); alphaMask = vec4(1.0);
if (vb == 0) { color = vec4(bg.rgb, 1.0);
color = vec4(bg.rgb, 1.0);
} else {
color = vec4(vbc, vb);
}
} else { } else {
vec3 textColor = texture(mask, TexCoords).rgb; vec3 textColor = texture(mask, TexCoords).rgb;
alphaMask = vec4(textColor, textColor.r); alphaMask = vec4(textColor, textColor.r);

View file

@ -32,19 +32,15 @@ layout (location = 5) in vec4 backgroundColor;
out vec2 TexCoords; out vec2 TexCoords;
out vec3 fg; out vec3 fg;
out vec4 bg; out vec4 bg;
out vec3 vbc;
// Terminal properties // Terminal properties
uniform vec2 termDim; uniform vec2 termDim;
uniform vec2 cellDim; uniform vec2 cellDim;
uniform float visualBell;
uniform vec3 visualBellColor;
uniform int backgroundPass; uniform int backgroundPass;
// Orthographic projection // Orthographic projection
uniform mat4 projection; uniform mat4 projection;
flat out float vb;
flat out int background; flat out int background;
void main() void main()
@ -76,8 +72,6 @@ void main()
TexCoords = uvOffset + vec2(position.x, 1 - position.y) * uvSize; TexCoords = uvOffset + vec2(position.x, 1 - position.y) * uvSize;
} }
vb = visualBell;
vbc = visualBellColor;
background = backgroundPass; background = backgroundPass;
bg = vec4(backgroundColor.rgb / 255.0, backgroundColor.a); bg = vec4(backgroundColor.rgb / 255.0, backgroundColor.a);
fg = textColor / vec3(255.0, 255.0, 255.0); fg = textColor / vec3(255.0, 255.0, 255.0);

View file

@ -38,6 +38,8 @@ use Rgb;
// Shader paths for live reload // Shader paths for live reload
static TEXT_SHADER_F_PATH: &'static str = concat!(env!("CARGO_MANIFEST_DIR"), "/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"); 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 // Shader source which is used when live-shader-reload feature is disable
static TEXT_SHADER_F: &'static str = include_str!( 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!( static TEXT_SHADER_V: &'static str = include_str!(
concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl") 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 /// `LoadGlyph` allows for copying a rasterized glyph into graphics memory
pub trait LoadGlyph { pub trait LoadGlyph {
@ -106,6 +114,24 @@ pub struct ShaderProgram {
// Program id // Program id
id: GLuint, 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 /// projection matrix uniform
u_projection: GLint, u_projection: GLint,
@ -115,12 +141,6 @@ pub struct ShaderProgram {
/// Cell dimensions (pixels) /// Cell dimensions (pixels)
u_cell_dim: GLint, u_cell_dim: GLint,
/// Visual bell
u_visual_bell: GLint,
/// Visual bell color
u_visual_bell_color: GLint,
/// Background pass flag /// Background pass flag
/// ///
/// Rendering is split into two passes; 1 for backgrounds, and one for text /// Rendering is split into two passes; 1 for backgrounds, and one for text
@ -359,9 +379,10 @@ struct InstanceData {
pub struct QuadRenderer { pub struct QuadRenderer {
program: ShaderProgram, program: ShaderProgram,
vao: GLuint, vao: GLuint,
vbo: GLuint,
ebo: GLuint, ebo: GLuint,
vbo_instance: GLuint, vbo_instance: GLuint,
rect_vao: GLuint,
rect_vbo: GLuint,
atlas: Vec<Atlas>, atlas: Vec<Atlas>,
current_atlas: usize, current_atlas: usize,
active_tex: GLuint, active_tex: GLuint,
@ -377,7 +398,6 @@ pub struct RenderApi<'a> {
current_atlas: &'a mut usize, current_atlas: &'a mut usize,
program: &'a mut ShaderProgram, program: &'a mut ShaderProgram,
config: &'a Config, config: &'a Config,
visual_bell_intensity: f32
} }
#[derive(Debug)] #[derive(Debug)]
@ -488,6 +508,10 @@ impl QuadRenderer {
let mut vbo_instance: GLuint = 0; 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 { unsafe {
gl::Enable(gl::BLEND); gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
@ -578,8 +602,24 @@ impl QuadRenderer {
gl::EnableVertexAttribArray(5); gl::EnableVertexAttribArray(5);
gl::VertexAttribDivisor(5, 1); 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::<i32>() * indices.len()) as _, indices.as_ptr() as *const _, gl::STATIC_DRAW);
// Cleanup
gl::BindVertexArray(0); gl::BindVertexArray(0);
gl::BindBuffer(gl::ARRAY_BUFFER, 0); gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
} }
let (msg_tx, msg_rx) = mpsc::channel(); let (msg_tx, msg_rx) = mpsc::channel();
@ -611,9 +651,10 @@ impl QuadRenderer {
let mut renderer = QuadRenderer { let mut renderer = QuadRenderer {
program: program, program: program,
vao: vao, vao: vao,
vbo: vbo,
ebo: ebo, ebo: ebo,
vbo_instance: vbo_instance, vbo_instance: vbo_instance,
rect_vao: rect_vao,
rect_vbo: rect_vbo,
atlas: Vec::new(), atlas: Vec::new(),
current_atlas: 0, current_atlas: 0,
active_tex: 0, active_tex: 0,
@ -636,23 +677,20 @@ impl QuadRenderer {
) -> T ) -> T
where F: FnOnce(RenderApi) -> T where F: FnOnce(RenderApi) -> T
{ {
while let Ok(msg) = self.rx.try_recv() { let size = Size {
match msg { width: Pixels(props.width as u32),
Msg::ShaderReload => { height: Pixels(props.height as u32),
self.reload_shaders(config, 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 { unsafe {
self.program.activate(); self.program.activate();
self.program.set_term_uniforms(props); self.program.set_term_uniforms(props);
self.program.set_visual_bell(visual_bell_intensity as _, visual_bell_color);
gl::BindVertexArray(self.vao); gl::BindVertexArray(self.vao);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
@ -666,10 +704,14 @@ impl QuadRenderer {
atlas: &mut self.atlas, atlas: &mut self.atlas,
current_atlas: &mut self.current_atlas, current_atlas: &mut self.current_atlas,
program: &mut self.program, program: &mut self.program,
visual_bell_intensity: visual_bell_intensity as _,
config: config, 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 { unsafe {
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
gl::BindBuffer(gl::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.update_projection(width as f32, height as f32);
self.program.deactivate(); 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<u32>, color: Rgb, alpha: f32, size: Size<Pixels<u32>>) {
// 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::<f32>() * vertices.len()) as _, vertices.as_ptr() as *const _, gl::STATIC_DRAW);
// Position
gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, (size_of::<f32>() * 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 { struct Rect<T> {
x * (1.0 - a) + y * a x: T,
y: T,
width: T,
height: T,
}
impl<T> Rect<T> {
fn new(x: T, y: T, width: T, height: T) -> Self {
Rect { x, y, width, height }
}
} }
impl<'a> RenderApi<'a> { impl<'a> RenderApi<'a> {
pub fn clear(&self, color: Rgb) { pub fn clear(&self, color: Rgb) {
let alpha = self.config.background_opacity().get(); let alpha = self.config.background_opacity().get();
let (flash, intensity) = (self.config.visual_bell().color(), self.visual_bell_intensity);
unsafe { unsafe {
gl::ClearColor( gl::ClearColor(
mix(f32::from(color.r) / 255.0, f32::from(flash.r) / 255.0, intensity).min(1.0) * alpha, (f32::from(color.r) / 255.0).min(1.0) * alpha,
mix(f32::from(color.g) / 255.0, f32::from(flash.g) / 255.0, intensity).min(1.0) * alpha, (f32::from(color.g) / 255.0).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.b) / 255.0).min(1.0) * alpha,
alpha alpha
); );
gl::Clear(gl::COLOR_BUFFER_BIT); gl::Clear(gl::COLOR_BUFFER_BIT);
@ -965,31 +1086,37 @@ impl ShaderProgram {
config: &Config, config: &Config,
size: Size<Pixels<u32>> size: Size<Pixels<u32>>
) -> Result<ShaderProgram, ShaderCreationError> { ) -> Result<ShaderProgram, ShaderCreationError> {
let vertex_source = if cfg!(feature = "live-shader-reload") { let (text_vert_src, text_frag_src,
None rect_vert_src, rect_frag_src) = if cfg!(feature = "live-shader-reload")
{
(None, None, None, None)
} else { } 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, TEXT_SHADER_V_PATH,
gl::VERTEX_SHADER, gl::VERTEX_SHADER,
vertex_source text_vert_src
)?; )?;
let frag_source = if cfg!(feature = "live-shader-reload") { let text_fragment = ShaderProgram::create_shader(
None
} else {
Some(TEXT_SHADER_F)
};
let fragment_shader = ShaderProgram::create_shader(
TEXT_SHADER_F_PATH, TEXT_SHADER_F_PATH,
gl::FRAGMENT_SHADER, 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 { unsafe {
gl::DeleteShader(vertex_shader);
gl::DeleteShader(fragment_shader);
gl::UseProgram(program); gl::UseProgram(program);
} }
@ -1008,13 +1135,11 @@ impl ShaderProgram {
} }
// get uniform locations // 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"projection\0")),
gl::GetUniformLocation(program, cptr!(b"termDim\0")), gl::GetUniformLocation(program, cptr!(b"termDim\0")),
gl::GetUniformLocation(program, cptr!(b"cellDim\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")), gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")),
) )
}; };
@ -1023,11 +1148,15 @@ impl ShaderProgram {
let shader = ShaderProgram { let shader = ShaderProgram {
id: program, id: program,
text_vertex,
text_fragment,
rect_vertex,
rect_fragment,
vertex: text_vertex,
fragment: text_fragment,
u_projection: projection, u_projection: projection,
u_term_dim: term_dim, u_term_dim: term_dim,
u_cell_dim: cell_dim, u_cell_dim: cell_dim,
u_visual_bell: visual_bell,
u_visual_bell_color: visual_bell_color,
u_background: background, u_background: background,
padding_x: config.padding().x.floor(), padding_x: config.padding().x.floor(),
padding_y: config.padding().y.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) { fn set_background_pass(&self, background_pass: bool) {
let value = if background_pass { let value = if background_pass {
1 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( fn create_shader(
path: &str, path: &str,
@ -1159,6 +1300,16 @@ impl ShaderProgram {
impl Drop for ShaderProgram { impl Drop for ShaderProgram {
fn drop(&mut self) { fn drop(&mut self) {
unsafe { 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); gl::DeleteProgram(self.id);
} }
} }