From 6dc670cde0c136e28c71d4ebe67c5c8bb9df65b1 Mon Sep 17 00:00:00 2001 From: Vasily Khoruzhick Date: Wed, 8 Jun 2022 02:02:57 -0700 Subject: [PATCH] Support dual source blending in GLES2 renderer GLES2 has GL_EXT_blend_func_extended extension that enables dual-source blending, so essentially we can reuse fragment shader from GLSL3 renderer and do 1 rendering pass instead of 3 for the text. Co-authored-by: Kirill Chibisov Co-authored-by: Christian Duerr --- Cargo.lock | 5 +- alacritty/Cargo.toml | 1 + alacritty/res/glsl3/text.f.glsl | 65 +++++++++++++++++------ alacritty/res/glsl3/text.v.glsl | 28 ++++++---- alacritty/res/rect.f.glsl | 1 - alacritty/src/renderer/mod.rs | 39 ++++++++++++++ alacritty/src/renderer/text/gles2.rs | 79 ++++++++++++++++++---------- alacritty/src/renderer/text/glsl3.rs | 25 +++++---- alacritty/src/renderer/text/mod.rs | 20 ++++++- 9 files changed, 192 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2274c25c..3c80ac8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,7 @@ dependencies = [ "log", "notify", "objc", + "once_cell", "parking_lot", "png", "raw-window-handle", @@ -1191,9 +1192,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.9.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" [[package]] name = "os_str_bytes" diff --git a/alacritty/Cargo.toml b/alacritty/Cargo.toml index b1438c23..888c9490 100644 --- a/alacritty/Cargo.toml +++ b/alacritty/Cargo.toml @@ -34,6 +34,7 @@ libc = "0.2" unicode-width = "0.1" bitflags = "1" dirs = "3.0.1" +once_cell = "1.12" [build-dependencies] gl_generator = "0.14.0" diff --git a/alacritty/res/glsl3/text.f.glsl b/alacritty/res/glsl3/text.f.glsl index eddc1734..1930e354 100644 --- a/alacritty/res/glsl3/text.f.glsl +++ b/alacritty/res/glsl3/text.f.glsl @@ -1,40 +1,73 @@ +#if defined(GLES2_RENDERER) +// Require extension for dual source blending to work on GLES2. +#extension GL_EXT_blend_func_extended: require + +#define int_t highp int +#define float_t highp float +#define vec3_t mediump vec3 +#define texture texture2D + +varying mediump vec2 TexCoords; +varying mediump vec3 fg; +varying highp float colored; +varying mediump vec4 bg; + +#define FRAG_COLOR gl_FragColor +#define ALPHA_MASK gl_SecondaryFragColorEXT +#else + +#define int_t int +#define float_t float +#define vec3_t vec3 + in vec2 TexCoords; flat in vec4 fg; flat in vec4 bg; -uniform int backgroundPass; layout(location = 0, index = 0) out vec4 color; layout(location = 0, index = 1) out vec4 alphaMask; +#define FRAG_COLOR color +#define ALPHA_MASK alphaMask +#endif + +#define COLORED 1 + +uniform int_t renderingPass; uniform sampler2D mask; -#define COLORED 2 - void main() { - if (backgroundPass != 0) { + if (renderingPass == 0) { if (bg.a == 0.0) { discard; } - alphaMask = vec4(1.0); - + ALPHA_MASK = vec4(1.0); // Premultiply background color by alpha. - color = vec4(bg.rgb * bg.a, bg.a); - } else if ((int(fg.a) & COLORED) != 0) { + FRAG_COLOR = vec4(bg.rgb * bg.a, bg.a); + return; + } + +#if !defined(GLES2_RENDERER) + float_t colored = fg.a; +#endif + + // The wide char information is already stripped, so it's safe to check for equality here. + if (int(colored) == COLORED) { // Color glyphs, like emojis. - vec4 glyphColor = texture(mask, TexCoords); - alphaMask = vec4(glyphColor.a); + FRAG_COLOR = texture(mask, TexCoords); + ALPHA_MASK = vec4(FRAG_COLOR.a); // Revert alpha premultiplication. - if (glyphColor.a != 0) { - glyphColor.rgb = vec3(glyphColor.rgb / glyphColor.a); + if (FRAG_COLOR.a != 0.0) { + FRAG_COLOR.rgb = vec3(FRAG_COLOR.rgb / FRAG_COLOR.a); } - color = vec4(glyphColor.rgb, 1.0); + FRAG_COLOR = vec4(FRAG_COLOR.rgb, 1.0); } else { // Regular text glyphs. - vec3 textColor = texture(mask, TexCoords).rgb; - alphaMask = vec4(textColor, textColor.r); - color = vec4(fg.rgb, 1.0); + vec3_t textColor = texture(mask, TexCoords).rgb; + ALPHA_MASK = vec4(textColor, textColor.r); + FRAG_COLOR = vec4(fg.rgb, 1.0); } } diff --git a/alacritty/res/glsl3/text.v.glsl b/alacritty/res/glsl3/text.v.glsl index 5c22e0e6..7e0cd881 100644 --- a/alacritty/res/glsl3/text.v.glsl +++ b/alacritty/res/glsl3/text.v.glsl @@ -23,9 +23,9 @@ flat out vec4 bg; uniform vec2 cellDim; uniform vec4 projection; -uniform int backgroundPass; +uniform int renderingPass; -#define WIDE_CHAR 1 +#define WIDE_CHAR 2 void main() { vec2 projectionOffset = projection.xy; @@ -39,12 +39,23 @@ void main() { // Position of cell from top-left vec2 cellPosition = cellDim * gridCoords; - if (backgroundPass != 0) { + fg = vec4(textColor.rgb / 255.0, textColor.a); + bg = backgroundColor / 255.0; + + float occupiedCells = 1; + if ((int(fg.a) >= WIDE_CHAR)) { + // Update wide char x dimension so it'll cover the following spacer. + occupiedCells = 2; + + // Since we don't perform bitwise operations due to limitations of + // the GLES2 renderer,we subtract wide char bits keeping only colored. + fg.a = round(fg.a - WIDE_CHAR); + } + + if (renderingPass == 0) { vec2 backgroundDim = cellDim; - if ((int(textColor.a) & WIDE_CHAR) != 0) { - // Update wide char x dimension so it'll cover the following spacer. - backgroundDim.x *= 2; - } + backgroundDim.x *= occupiedCells; + vec2 finalPosition = cellPosition + backgroundDim * position; gl_Position = vec4(projectionOffset + projectionScale * finalPosition, 0.0, 1.0); @@ -63,7 +74,4 @@ void main() { vec2 uvSize = uv.zw; TexCoords = uvOffset + position * uvSize; } - - bg = backgroundColor / 255.0; - fg = vec4(textColor.rgb / 255.0, textColor.a); } diff --git a/alacritty/res/rect.f.glsl b/alacritty/res/rect.f.glsl index 4e8fdd01..d29c8b28 100644 --- a/alacritty/res/rect.f.glsl +++ b/alacritty/res/rect.f.glsl @@ -7,7 +7,6 @@ varying color_t color; #else #define float_t float -#define int_t int #define color_t vec4 out vec4 FragColor; diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 0446379e..eb0012bd 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -1,8 +1,10 @@ +use std::collections::HashSet; use std::ffi::CStr; use std::fmt; use crossfont::Metrics; use log::info; +use once_cell::sync::OnceCell; use alacritty_terminal::index::Point; use alacritty_terminal::term::cell::Flags; @@ -218,3 +220,40 @@ impl Renderer { } } } + +struct GlExtensions; + +impl GlExtensions { + /// Check if the given `extension` is supported. + /// + /// This function will lazyly load OpenGL extensions. + fn contains(extension: &str) -> bool { + static OPENGL_EXTENSIONS: OnceCell> = OnceCell::new(); + + OPENGL_EXTENSIONS.get_or_init(Self::load_extensions).contains(extension) + } + + /// Load available OpenGL extensions. + fn load_extensions() -> HashSet<&'static str> { + unsafe { + let extensions = gl::GetString(gl::EXTENSIONS); + + if extensions.is_null() { + let mut extensions_number = 0; + gl::GetIntegerv(gl::NUM_EXTENSIONS, &mut extensions_number); + + (0..extensions_number as gl::types::GLuint) + .flat_map(|i| { + let extension = CStr::from_ptr(gl::GetStringi(gl::EXTENSIONS, i) as *mut _); + extension.to_str() + }) + .collect() + } else { + match CStr::from_ptr(extensions as *mut _).to_str() { + Ok(ext) => ext.split_whitespace().collect(), + Err(_) => HashSet::new(), + } + } + } + } +} diff --git a/alacritty/src/renderer/text/gles2.rs b/alacritty/src/renderer/text/gles2.rs index 4d8347bf..32aaa173 100644 --- a/alacritty/src/renderer/text/gles2.rs +++ b/alacritty/src/renderer/text/gles2.rs @@ -11,11 +11,12 @@ use crate::display::SizeInfo; use crate::gl; use crate::gl::types::*; use crate::renderer::shader::{ShaderProgram, ShaderVersion}; -use crate::renderer::{cstr, Error}; +use crate::renderer::{cstr, Error, GlExtensions}; use super::atlas::{Atlas, ATLAS_SIZE}; use super::{ - Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader, + glsl3, Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, RenderingPass, TextRenderApi, + TextRenderBatch, TextRenderer, TextShader, }; // Shader source. @@ -32,13 +33,21 @@ pub struct Gles2Renderer { batch: Batch, current_atlas: usize, active_tex: GLuint, + dual_source_blending: bool, } impl Gles2Renderer { pub fn new() -> Result { info!("Using OpenGL ES 2.0 renderer"); - let program = TextShaderProgram::new(ShaderVersion::Gles2)?; + let dual_source_blending = GlExtensions::contains("GL_EXT_blend_func_extended") + || GlExtensions::contains("GL_ARB_blend_func_extended"); + + if dual_source_blending { + info!("Using dual source blending"); + } + + let program = TextShaderProgram::new(ShaderVersion::Gles2, dual_source_blending)?; let mut vao: GLuint = 0; let mut vbo: GLuint = 0; let mut ebo: GLuint = 0; @@ -139,6 +148,7 @@ impl Gles2Renderer { batch: Batch::new(), current_atlas: 0, active_tex: 0, + dual_source_blending, }) } } @@ -180,6 +190,7 @@ impl<'a> TextRenderer<'a> for Gles2Renderer { atlas: &mut self.atlas, current_atlas: &mut self.current_atlas, program: &mut self.program, + dual_source_blending: self.dual_source_blending, }); unsafe { @@ -205,7 +216,7 @@ impl<'a> TextRenderer<'a> for Gles2Renderer { /// Maximum items to be drawn in a batch. /// /// We use the closest number to `u16::MAX` dividable by 4 (amount of vertices we push for a glyph), -/// since it's the maximum possible index in `glDrawElements` in gles2. +/// since it's the maximum possible index in `glDrawElements` in GLES2. const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize; #[derive(Debug)] @@ -269,7 +280,12 @@ impl TextRenderBatch for Batch { let glyph_x = cell.point.column.0 as i16 * size_info.cell_width() as i16 + glyph.left; let glyph_y = (cell.point.line + 1) as i16 * size_info.cell_height() as i16 - glyph.top; - let colored = if glyph.multicolor { 1 } else { 0 }; + let colored = if glyph.multicolor { + RenderingGlyphFlags::COLORED + } else { + RenderingGlyphFlags::empty() + }; + let is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 }; let mut vertex = TextVertex { @@ -322,6 +338,7 @@ pub struct RenderApi<'a> { atlas: &'a mut Vec, current_atlas: &'a mut usize, program: &'a mut TextShaderProgram, + dual_source_blending: bool, } impl<'a> Drop for RenderApi<'a> { @@ -375,19 +392,25 @@ impl<'a> TextRenderApi for RenderApi<'a> { gl::BlendFunc(gl::ONE, gl::ZERO); gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); - // First text rendering pass. self.program.set_rendering_pass(RenderingPass::SubpixelPass1); - gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE); - gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + if self.dual_source_blending { + // Text rendering pass. + gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); + } else { + // First text rendering pass. + gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); - // Second text rendering pass. - self.program.set_rendering_pass(RenderingPass::SubpixelPass2); - gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE); - gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + // Second text rendering pass. + self.program.set_rendering_pass(RenderingPass::SubpixelPass2); + gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE); + gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); + + // Third text rendering pass. + self.program.set_rendering_pass(RenderingPass::SubpixelPass3); + gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA); + } - // Third pass. - self.program.set_rendering_pass(RenderingPass::SubpixelPass3); - gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA); gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null()); } @@ -416,7 +439,7 @@ struct TextVertex { b: u8, // Whether the glyph is colored. - colored: u8, + colored: RenderingGlyphFlags, // Background color. bg_r: u8, @@ -425,15 +448,6 @@ struct TextVertex { bg_a: u8, } -// NOTE: These flags must be in sync with their usage in the gles2/text.*.glsl shaders. -#[repr(u8)] -enum RenderingPass { - Background = 0, - SubpixelPass1 = 1, - SubpixelPass2 = 2, - SubpixelPass3 = 3, -} - #[derive(Debug)] pub struct TextShaderProgram { /// Shader program. @@ -444,8 +458,11 @@ pub struct TextShaderProgram { /// Rendering pass. /// - /// The rendering is split into 4 passes. One is used for the background and the rest to - /// perform subpixel text rendering according to + /// For dual source blending, there are 2 passes; one for background, another for text, + /// similar to the GLSL3 renderer. + /// + /// If GL_EXT_blend_func_extended is not available, the rendering is split into 4 passes. + /// One is used for the background and the rest to perform subpixel text rendering according to /// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md. /// /// Rendering is split into three passes. @@ -453,8 +470,12 @@ pub struct TextShaderProgram { } impl TextShaderProgram { - pub fn new(shader_version: ShaderVersion) -> Result { - let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?; + pub fn new(shader_version: ShaderVersion, dual_source_blending: bool) -> Result { + let fragment_shader = + if dual_source_blending { &glsl3::TEXT_SHADER_F } else { &TEXT_SHADER_F }; + + let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, fragment_shader)?; + Ok(Self { u_projection: program.get_uniform_location(cstr!("projection"))?, u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?, diff --git a/alacritty/src/renderer/text/glsl3.rs b/alacritty/src/renderer/text/glsl3.rs index 917a5fb5..d5413d32 100644 --- a/alacritty/src/renderer/text/glsl3.rs +++ b/alacritty/src/renderer/text/glsl3.rs @@ -15,12 +15,12 @@ use crate::renderer::{cstr, Error}; use super::atlas::{Atlas, ATLAS_SIZE}; use super::{ - Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, TextRenderApi, TextRenderBatch, TextRenderer, - TextShader, + Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, RenderingPass, TextRenderApi, + TextRenderBatch, TextRenderer, TextShader, }; // Shader source. -static TEXT_SHADER_F: &str = include_str!("../../../res/glsl3/text.f.glsl"); +pub static TEXT_SHADER_F: &str = include_str!("../../../res/glsl3/text.f.glsl"); static TEXT_SHADER_V: &str = include_str!("../../../res/glsl3/text.v.glsl"); /// Maximum items to be drawn in a batch. @@ -240,7 +240,7 @@ impl<'a> TextRenderApi for RenderApi<'a> { } unsafe { - self.program.set_background_pass(true); + self.program.set_rendering_pass(RenderingPass::Background); gl::DrawElementsInstanced( gl::TRIANGLES, 6, @@ -248,7 +248,7 @@ impl<'a> TextRenderApi for RenderApi<'a> { ptr::null(), self.batch.len() as GLsizei, ); - self.program.set_background_pass(false); + self.program.set_rendering_pass(RenderingPass::SubpixelPass1); gl::DrawElementsInstanced( gl::TRIANGLES, 6, @@ -419,8 +419,8 @@ pub struct TextShaderProgram { /// Background pass flag. /// - /// Rendering is split into two passes; 1 for backgrounds, and one for text. - u_background: GLint, + /// Rendering is split into two passes; one for backgrounds, and one for text. + u_rendering_pass: GLint, } impl TextShaderProgram { @@ -429,7 +429,7 @@ impl TextShaderProgram { Ok(Self { u_projection: program.get_uniform_location(cstr!("projection"))?, u_cell_dim: program.get_uniform_location(cstr!("cellDim"))?, - u_background: program.get_uniform_location(cstr!("backgroundPass"))?, + u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?, program, }) } @@ -440,11 +440,14 @@ impl TextShaderProgram { } } - fn set_background_pass(&self, background_pass: bool) { - let value = if background_pass { 1 } else { 0 }; + fn set_rendering_pass(&self, rendering_pass: RenderingPass) { + let value = match rendering_pass { + RenderingPass::Background | RenderingPass::SubpixelPass1 => rendering_pass as i32, + _ => unreachable!("provided pass is not supported in GLSL3 renderer"), + }; unsafe { - gl::Uniform1i(self.u_background, value); + gl::Uniform1i(self.u_rendering_pass, value); } } } diff --git a/alacritty/src/renderer/text/mod.rs b/alacritty/src/renderer/text/mod.rs index a032ffc7..96945f67 100644 --- a/alacritty/src/renderer/text/mod.rs +++ b/alacritty/src/renderer/text/mod.rs @@ -24,11 +24,27 @@ use glyph_cache::{Glyph, LoadGlyph}; bitflags! { #[repr(C)] struct RenderingGlyphFlags: u8 { - const WIDE_CHAR = 0b0000_0001; - const COLORED = 0b0000_0010; + const COLORED = 0b0000_0001; + const WIDE_CHAR = 0b0000_0010; } } +/// Rendering passes, for both GLES2 and GLSL3 renderer. +#[repr(u8)] +enum RenderingPass { + /// Rendering pass used to render background color in text shaders. + Background = 0, + + /// The first pass to render text with both GLES2 and GLSL3 renderers. + SubpixelPass1 = 1, + + /// The second pass to render text with GLES2 renderer. + SubpixelPass2 = 2, + + /// The third pass to render text with GLES2 renderer. + SubpixelPass3 = 3, +} + pub trait TextRenderer<'a> { type Shader: TextShader; type RenderBatch: TextRenderBatch;