summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKirill Chibisov <contact@kchibisov.com>2022-03-02 13:05:12 +0300
committerGitHub <noreply@github.com>2022-03-02 10:05:12 +0000
commit1880522b64d9a5276acea428705c011cbbf8c04c (patch)
tree2f8a662ee0b65fcf6d6fe831949cd2cf78cabdad
parent00383ae967fef6216396c3acaf96a7002b013298 (diff)
downloadalacritty-1880522b64d9a5276acea428705c011cbbf8c04c.tar.gz
alacritty-1880522b64d9a5276acea428705c011cbbf8c04c.zip
Add fallback GLES2 renderer
Currently Alacritty only works on hardware which supports OpenGL 3.3 or more, which can become problematic with older devices. This patch adds a new GLES2 renderer, since it is much more widely supported, especially on weaker hardware like phones or a Raspberry Pi. While the GLES2 renderer is slower than the OpenGL 3.3+ version, it is still significantly faster than software rendering. However because of this performance difference it is only used when necessary and there should be no difference for machines supporting OpenGL 3.3+. The two renderers are largely independent and separated in the `renderer/text/glsl3` and `renderer/text/gles2` modules. Separate shaders are also required for text rendering. The rectangle rendering for underlines and the visual bell works identically for both versions, but does have some version-specific shader code. Fixes #128. Co-authored-by: Christian Duerr <contact@christianduerr.com>
-rw-r--r--CHANGELOG.md1
-rw-r--r--README.md2
-rw-r--r--alacritty/res/gles2/text.f.glsl57
-rw-r--r--alacritty/res/gles2/text.v.glsl46
-rw-r--r--alacritty/res/glsl3/text.f.glsl (renamed from alacritty/res/text.f.glsl)1
-rw-r--r--alacritty/res/glsl3/text.v.glsl (renamed from alacritty/res/text.v.glsl)1
-rw-r--r--alacritty/res/rect.f.glsl118
-rw-r--r--alacritty/res/rect.v.glsl11
-rw-r--r--alacritty/src/display/mod.rs55
-rw-r--r--alacritty/src/renderer/mod.rs1221
-rw-r--r--alacritty/src/renderer/rects.rs10
-rw-r--r--alacritty/src/renderer/shader.rs49
-rw-r--r--alacritty/src/renderer/text/atlas.rs273
-rw-r--r--alacritty/src/renderer/text/builtin_font.rs (renamed from alacritty/src/renderer/builtin_font.rs)0
-rw-r--r--alacritty/src/renderer/text/gles2.rs478
-rw-r--r--alacritty/src/renderer/text/glsl3.rs460
-rw-r--r--alacritty/src/renderer/text/glyph_cache.rs322
-rw-r--r--alacritty/src/renderer/text/mod.rs202
18 files changed, 2066 insertions, 1241 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15b9a41c..188276c6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Track and report surface damage information to Wayland compositors
- Escape sequence for undercurl, dotted and dashed underlines (`CSI 4 : [3-5] m`)
- `ToggleMaximized` key binding action to (un-)maximize the active window, not bound by default
+- Support for OpenGL ES 2.0
### Changed
diff --git a/README.md b/README.md
index e4af703b..9a8546ba 100644
--- a/README.md
+++ b/README.md
@@ -47,7 +47,7 @@ For everyone else, the detailed instructions to install Alacritty can be found
### Requirements
-- OpenGL 3.3 or higher
+- At least OpenGL ES 2.0
- [Windows] ConPTY support (Windows 10 version 1809 or higher)
## Configuration
diff --git a/alacritty/res/gles2/text.f.glsl b/alacritty/res/gles2/text.f.glsl
new file mode 100644
index 00000000..cc5943c9
--- /dev/null
+++ b/alacritty/res/gles2/text.f.glsl
@@ -0,0 +1,57 @@
+varying mediump vec2 TexCoords;
+varying mediump vec3 fg;
+varying highp float colored;
+varying mediump vec4 bg;
+
+uniform highp int renderingPass;
+uniform sampler2D mask;
+
+#define COLORED 1
+
+mediump float max_rgb(mediump vec3 mask) {
+ return max(max(mask.r, mask.g), mask.b);
+}
+
+void render_text() {
+ mediump vec4 mask = texture2D(mask, TexCoords);
+ mediump float m_rgb = max_rgb(mask.rgb);
+
+ if (renderingPass == 1) {
+ gl_FragColor = vec4(mask.rgb, m_rgb);
+ } else if (renderingPass == 2) {
+ gl_FragColor = bg * (vec4(m_rgb) - vec4(mask.rgb, m_rgb));
+ } else {
+ gl_FragColor = vec4(fg, 1.) * vec4(mask.rgb, m_rgb);
+ }
+}
+
+// Render colored bitmaps.
+void render_bitmap() {
+ if (renderingPass == 2) {
+ discard;
+ }
+ mediump vec4 mask = texture2D(mask, TexCoords);
+ if (renderingPass == 1) {
+ gl_FragColor = mask.aaaa;
+ } else {
+ gl_FragColor = mask;
+ }
+}
+
+void main() {
+ // Handle background pass drawing before anything else.
+ if (renderingPass == 0) {
+ if (bg.a == 0.0) {
+ discard;
+ }
+
+ gl_FragColor = vec4(bg.rgb * bg.a, bg.a);
+ return;
+ }
+
+ if (int(colored) == COLORED) {
+ render_bitmap();
+ } else {
+ render_text();
+ }
+}
diff --git a/alacritty/res/gles2/text.v.glsl b/alacritty/res/gles2/text.v.glsl
new file mode 100644
index 00000000..009094cc
--- /dev/null
+++ b/alacritty/res/gles2/text.v.glsl
@@ -0,0 +1,46 @@
+// Cell coords.
+attribute vec2 cellCoords;
+
+// Glyph coords.
+attribute vec2 glyphCoords;
+
+// uv mapping.
+attribute vec2 uv;
+
+// Text foreground rgb packed together with cell flags. textColor.a
+// are the bitflags; consult RenderingGlyphFlags in renderer/mod.rs
+// for the possible values.
+attribute vec4 textColor;
+
+// Background color.
+attribute vec4 backgroundColor;
+
+varying vec2 TexCoords;
+varying vec3 fg;
+varying float colored;
+varying vec4 bg;
+
+uniform highp int renderingPass;
+uniform vec4 projection;
+
+void main() {
+ vec2 projectionOffset = projection.xy;
+ vec2 projectionScale = projection.zw;
+
+ vec2 position;
+ if (renderingPass == 0) {
+ TexCoords = vec2(0, 0);
+ position = cellCoords;
+ } else {
+ TexCoords = uv;
+ position = glyphCoords;
+ }
+
+ fg = vec3(float(textColor.r), float(textColor.g), float(textColor.b)) / 255.;
+ colored = float(textColor.a);
+ bg = vec4(float(backgroundColor.r), float(backgroundColor.g), float(backgroundColor.b),
+ float(backgroundColor.a)) / 255.;
+
+ vec2 finalPosition = projectionOffset + position * projectionScale;
+ gl_Position = vec4(finalPosition, 0., 1.);
+}
diff --git a/alacritty/res/text.f.glsl b/alacritty/res/glsl3/text.f.glsl
index 3b8cd67c..eddc1734 100644
--- a/alacritty/res/text.f.glsl
+++ b/alacritty/res/glsl3/text.f.glsl
@@ -1,4 +1,3 @@
-#version 330 core
in vec2 TexCoords;
flat in vec4 fg;
flat in vec4 bg;
diff --git a/alacritty/res/text.v.glsl b/alacritty/res/glsl3/text.v.glsl
index a4a31382..5c22e0e6 100644
--- a/alacritty/res/text.v.glsl
+++ b/alacritty/res/glsl3/text.v.glsl
@@ -1,4 +1,3 @@
-#version 330 core
// Cell properties.
layout(location = 0) in vec2 gridCoords;
diff --git a/alacritty/res/rect.f.glsl b/alacritty/res/rect.f.glsl
index aad8b418..10c7983a 100644
--- a/alacritty/res/rect.f.glsl
+++ b/alacritty/res/rect.f.glsl
@@ -1,18 +1,32 @@
-#version 330 core
+#if defined(GLES2_RENDERER)
+#define float_t mediump float
+#define color_t mediump vec4
+#define FRAG_COLOR gl_FragColor
+
+varying color_t color;
+
+#else
+#define float_t float
+#define int_t int
+#define color_t vec4
-flat in vec4 color;
out vec4 FragColor;
-uniform int rectKind;
+#define FRAG_COLOR FragColor
-uniform float cellWidth;
-uniform float cellHeight;
-uniform float paddingY;
-uniform float paddingX;
+flat in color_t color;
-uniform float underlinePosition;
-uniform float underlineThickness;
+#endif
-uniform float undercurlPosition;
+uniform int rectKind;
+uniform float_t cellWidth;
+uniform float_t cellHeight;
+uniform float_t paddingY;
+uniform float_t paddingX;
+
+uniform float_t underlinePosition;
+uniform float_t underlineThickness;
+
+uniform float_t undercurlPosition;
#define UNDERCURL 1
#define DOTTED 2
@@ -20,19 +34,18 @@ uniform float undercurlPosition;
#define PI 3.1415926538
-vec4 draw_undercurl(int x, int y) {
+color_t draw_undercurl(float_t x, float_t y) {
// We use `undercurlPosition` as an amplitude, since it's half of the descent
// value.
- float undercurl =
- undercurlPosition / 2. * cos(float(x) * 2 * PI / cellWidth) +
- + undercurlPosition - 1.;
+ float_t undercurl = undercurlPosition / 2. * cos(x * 2. * PI / cellWidth)
+ + undercurlPosition - 1.;
- float undercurlTop = undercurl + max((underlineThickness - 1), 0);
- float undercurlBottom = undercurl - max((underlineThickness - 1), 0);
+ float_t undercurlTop = undercurl + max((underlineThickness - 1.), 0.);
+ float_t undercurlBottom = undercurl - max((underlineThickness - 1.), 0.);
// Compute resulted alpha based on distance from `gl_FragCoord.y` to the
// cosine curve.
- float alpha = 1.;
+ float_t alpha = 1.;
if (y > undercurlTop || y < undercurlBottom) {
alpha = 1. - min(abs(undercurlTop - y), abs(undercurlBottom - y));
}
@@ -43,54 +56,54 @@ vec4 draw_undercurl(int x, int y) {
// When the dot size increases we can use AA to make spacing look even and the
// dots rounded.
-vec4 draw_dotted_aliased(float x, float y) {
- int dotNumber = int(x / underlineThickness);
+color_t draw_dotted_aliased(float_t x, float_t y) {
+ float_t dotNumber = floor(x / underlineThickness);
- float radius = underlineThickness / 2.;
- float centerY = underlinePosition - 1.;
+ float_t radius = underlineThickness / 2.;
+ float_t centerY = underlinePosition - 1.;
- float leftCenter = (dotNumber - dotNumber % 2) * underlineThickness + radius;
- float rightCenter = leftCenter + 2 * underlineThickness;
+ float_t leftCenter = (dotNumber - mod(dotNumber, 2.)) * underlineThickness + radius;
+ float_t rightCenter = leftCenter + 2. * underlineThickness;
- float distanceLeft = sqrt(pow(x - leftCenter, 2) + pow(y - centerY, 2));
- float distanceRight = sqrt(pow(x - rightCenter, 2) + pow(y - centerY, 2));
+ float_t distanceLeft = sqrt(pow(x - leftCenter, 2.) + pow(y - centerY, 2.));
+ float_t distanceRight = sqrt(pow(x - rightCenter, 2.) + pow(y - centerY, 2.));
- float alpha = max(1 - (min(distanceLeft, distanceRight) - radius), 0);
+ float_t alpha = max(1. - (min(distanceLeft, distanceRight) - radius), 0.);
return vec4(color.rgb, alpha);
}
/// Draw dotted line when dot is just a single pixel.
-vec4 draw_dotted(int x, int y) {
- int cellEven = 0;
+color_t draw_dotted(float_t x, float_t y) {
+ float_t cellEven = 0.;
// Since the size of the dot and its gap combined is 2px we should ensure that
// spacing will be even. If the cellWidth is even it'll work since we start
// with dot and end with gap. However if cellWidth is odd, the cell will start
// and end with a dot, creating a dash. To resolve this issue, we invert the
// pattern every two cells.
- if (int(cellWidth) % 2 != 0) {
- cellEven = int((gl_FragCoord.x - paddingX) / cellWidth) % 2;
+ if (int(mod(cellWidth, 2.)) != 0) {
+ cellEven = mod((gl_FragCoord.x - paddingX) / cellWidth, 2.);
}
// Since we use the entire descent area for dotted underlines, we limit its
// height to a single pixel so we don't draw bars instead of dots.
- float alpha = 1. - abs(round(underlinePosition - 1.) - y);
- if (x % 2 != cellEven) {
- alpha = 0;
+ float_t alpha = 1. - abs(floor(underlinePosition - 0.5) - y);
+ if (int(mod(x, 2.)) != int(cellEven)) {
+ alpha = 0.;
}
return vec4(color.rgb, alpha);
}
-vec4 draw_dashed(int x) {
+color_t draw_dashed(float_t x) {
// Since dashes of adjacent cells connect with each other our dash length is
// half of the desired total length.
- int halfDashLen = int(cellWidth) / 4;
+ float_t halfDashLen = floor(cellWidth / 4.);
- float alpha = 1.;
+ float_t alpha = 1.;
// Check if `x` coordinate is where we should draw gap.
- if (x > halfDashLen && x < cellWidth - halfDashLen - 1) {
+ if (x > halfDashLen && x < cellWidth - halfDashLen - 1.) {
alpha = 0.;
}
@@ -98,25 +111,20 @@ vec4 draw_dashed(int x) {
}
void main() {
- int x = int(gl_FragCoord.x - paddingX) % int(cellWidth);
- int y = int(gl_FragCoord.y - paddingY) % int(cellHeight);
-
- switch (rectKind) {
- case UNDERCURL:
- FragColor = draw_undercurl(x, y);
- break;
- case DOTTED:
- if (underlineThickness < 2) {
- FragColor = draw_dotted(x, y);
+ float_t x = floor(mod(gl_FragCoord.x - paddingX, cellWidth));
+ float_t y = floor(mod(gl_FragCoord.y - paddingY, cellHeight));
+
+ if (rectKind == UNDERCURL) {
+ FRAG_COLOR = draw_undercurl(x, y);
+ } else if (rectKind == DOTTED) {
+ if (underlineThickness < 2.) {
+ FRAG_COLOR = draw_dotted(x, y);
} else {
- FragColor = draw_dotted_aliased(x, y);
+ FRAG_COLOR = draw_dotted_aliased(x, y);
}
- break;
- case DASHED:
- FragColor = draw_dashed(x);
- break;
- default:
- FragColor = color;
- break;
+ } else if (rectKind == DASHED) {
+ FRAG_COLOR = draw_dashed(x);
+ } else {
+ FRAG_COLOR = color;
}
}
diff --git a/alacritty/res/rect.v.glsl b/alacritty/res/rect.v.glsl
index bf9a97d3..746eab74 100644
--- a/alacritty/res/rect.v.glsl
+++ b/alacritty/res/rect.v.glsl
@@ -1,11 +1,16 @@
-#version 330 core
+#if defined(GLES2_RENDERER)
+attribute vec2 aPos;
+attribute vec4 aColor;
+
+varying mediump vec4 color;
+#else
layout (location = 0) in vec2 aPos;
layout (location = 1) in vec4 aColor;
flat out vec4 color;
+#endif
-void main()
-{
+void main() {
color = aColor;
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
}
diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs
index dfd43d04..7e8cfc86 100644
--- a/alacritty/src/display/mod.rs
+++ b/alacritty/src/display/mod.rs
@@ -50,7 +50,7 @@ use crate::display::window::Window;
use crate::event::{Mouse, SearchState};
use crate::message_bar::{MessageBuffer, MessageType};
use crate::renderer::rects::{RenderLines, RenderRect};
-use crate::renderer::{self, GlyphCache, QuadRenderer};
+use crate::renderer::{self, GlyphCache, Renderer};
pub mod content;
pub mod cursor;
@@ -204,7 +204,7 @@ pub struct Display {
is_damage_supported: bool,
debug_damage: bool,
damage_rects: Vec<DamageRect>,
- renderer: QuadRenderer,
+ renderer: Renderer,
glyph_cache: GlyphCache,
meter: Meter,
}
@@ -259,7 +259,7 @@ impl Display {
)?;
// Create renderer.
- let mut renderer = QuadRenderer::new()?;
+ let mut renderer = Renderer::new()?;
let scale_factor = window.scale_factor;
info!("Display scale factor: {}", scale_factor);
@@ -308,9 +308,7 @@ impl Display {
// Clear screen.
let background_color = config.colors.primary.background;
- renderer.with_api(config, &size_info, |api| {
- api.clear(background_color);
- });
+ renderer.clear(background_color, config.window_opacity());
// Set subpixel anti-aliasing.
#[cfg(target_os = "macos")]
@@ -325,9 +323,7 @@ impl Display {
#[cfg(not(any(target_os = "macos", windows)))]
if is_x11 {
window.swap_buffers();
- renderer.with_api(config, &size_info, |api| {
- api.finish();
- });
+ renderer.finish();
}
window.set_visible(true);
@@ -376,7 +372,7 @@ impl Display {
///
/// This will return a tuple of the cell width and height.
fn update_glyph_cache(
- renderer: &mut QuadRenderer,
+ renderer: &mut Renderer,
glyph_cache: &mut GlyphCache,
scale_factor: f64,
config: &UiConfig,
@@ -573,10 +569,7 @@ impl Display {
// Make sure this window's OpenGL context is active.
self.window.make_current();
- self.renderer.with_api(config, &size_info, |api| {
- api.clear(background_color);
- });
-
+ self.renderer.clear(background_color, config.window_opacity());
let mut lines = RenderLines::new();
// Draw grid.
@@ -590,9 +583,10 @@ impl Display {
let glyph_cache = &mut self.glyph_cache;
let highlighted_hint = &self.highlighted_hint;
let vi_highlighted_hint = &self.vi_highlighted_hint;
- self.renderer.with_api(config, &size_info, |mut api| {
- // Iterate over all non-empty cells in the grid.
- for mut cell in grid_cells {
+ self.renderer.draw_cells(
+ &size_info,
+ glyph_cache,
+ grid_cells.into_iter().map(|mut cell| {
// Underline hints hovered by mouse or vi mode cursor.
let point = viewport_to_point(display_offset, cell.point);
if highlighted_hint.as_ref().map_or(false, |h| h.bounds.contains(&point))
@@ -604,10 +598,9 @@ impl Display {
// Update underline/strikeout.
lines.update(&cell);
- // Draw the cell.
- api.draw_cell(cell, glyph_cache);
- }
- });
+ cell
+ }),
+ );
}
let mut rects = lines.rects(&metrics, &size_info);
@@ -675,9 +668,7 @@ impl Display {
let fg = config.colors.primary.background;
for (i, message_text) in text.iter().enumerate() {
let point = Point::new(start_line + i, Column(0));
- self.renderer.with_api(config, &size_info, |mut api| {
- api.draw_string(glyph_cache, point, fg, bg, message_text);
- });
+ self.renderer.draw_string(point, fg, bg, message_text, &size_info, glyph_cache);
}
} else {
// Draw rectangles.
@@ -726,9 +717,7 @@ impl Display {
// On X11 `swap_buffers` does not block for vsync. However the next OpenGl command
// will block to synchronize (this is `glClear` in Alacritty), which causes a
// permanent one frame delay.
- self.renderer.with_api(config, &size_info, |api| {
- api.finish();
- });
+ self.renderer.finish();
}
self.damage_rects.clear();
@@ -833,9 +822,7 @@ impl Display {
let fg = config.colors.search_bar_foreground();
let bg = config.colors.search_bar_background();
- self.renderer.with_api(config, size_info, |mut api| {
- api.draw_string(glyph_cache, point, fg, bg, &text);
- });
+ self.renderer.draw_string(point, fg, bg, &text, size_info, glyph_cache);
}
/// Draw render timer.
@@ -853,9 +840,7 @@ impl Display {
self.damage_from_point(point, self.size_info.columns() as u32);
let glyph_cache = &mut self.glyph_cache;
- self.renderer.with_api(config, size_info, |mut api| {
- api.draw_string(glyph_cache, point, fg, bg, &timing);
- });
+ self.renderer.draw_string(point, fg, bg, &timing, size_info, glyph_cache);
}
/// Draw an indicator for the position of a line in history.
@@ -894,9 +879,7 @@ impl Display {
// Do not render anything if it would obscure the vi mode cursor.
if obstructed_column.map_or(true, |obstructed_column| obstructed_column < column) {
let glyph_cache = &mut self.glyph_cache;
- self.renderer.with_api(config, size_info, |mut api| {
- api.draw_string(glyph_cache, point, fg, bg, &text);
- });
+ self.renderer.draw_string(point, fg, bg, &text, size_info, glyph_cache);
}
}
diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs
index 4aa562ad..cf9ee149 100644
--- a/alacritty/src/renderer/mod.rs
+++ b/alacritty/src/renderer/mod.rs
@@ -1,33 +1,27 @@
-use std::collections::HashMap;
-use std::hash::BuildHasherDefault;
-use std::mem::size_of;
-use std::{fmt, ptr};
+use std::ffi::CStr;
+use std::fmt;
-use bitflags::bitflags;
-use crossfont::{
- BitmapBuffer, Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize,
- RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight,
-};
-use fnv::FnvHasher;
-use log::{error, info};
-use unicode_width::UnicodeWidthChar;
+use crossfont::Metrics;
+use log::info;
use alacritty_terminal::index::Point;
use alacritty_terminal::term::cell::Flags;
use alacritty_terminal::term::color::Rgb;
use alacritty_terminal::term::SizeInfo;
-use crate::config::font::{Font, FontDescription};
-use crate::config::ui_config::{Delta, UiConfig};
use crate::display::content::RenderableCell;
use crate::gl;
-use crate::gl::types::*;
use crate::renderer::rects::{RectRenderer, RenderRect};
-use crate::renderer::shader::{ShaderError, ShaderProgram};
+use crate::renderer::shader::ShaderError;
-pub mod builtin_font;
pub mod rects;
mod shader;
+mod text;
+
+pub use text::{GlyphCache, LoaderApi};
+
+use shader::ShaderVersion;
+use text::{Gles2Renderer, Glsl3Renderer, TextRenderer};
macro_rules! cstr {
($s:literal) => {
@@ -37,21 +31,6 @@ macro_rules! cstr {
}
pub(crate) use cstr;
-// Shader source.
-static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl");
-static TEXT_SHADER_V: &str = include_str!("../../res/text.v.glsl");
-
-/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
-pub trait LoadGlyph {
- /// Load the rasterized glyph into GPU memory.
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
-
- /// Clear any state accumulated from previous loaded glyphs.
- ///
- /// This can, for instance, be used to reset the texture Atlas.
- fn clear(&mut self);
-}
-
#[derive(Debug)]
pub enum Error {
/// Shader error.
@@ -82,588 +61,93 @@ impl From<ShaderError> for Error {
}
}
-/// Text drawing program.
-///
-/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
#[derive(Debug)]
-pub struct TextShaderProgram {
- /// Shader program.
- program: ShaderProgram,
-
- /// Projection scale and offset uniform.
- u_projection: GLint,
-
- /// Cell dimensions (pixels).
- u_cell_dim: GLint,
-
- /// Background pass flag.
- ///
- /// Rendering is split into two passes; 1 for backgrounds, and one for text.
- u_background: GLint,
-}
-
-#[derive(Copy, Clone, Debug)]
-pub struct Glyph {
- tex_id: GLuint,
- multicolor: bool,
- top: i16,
- left: i16,
- width: i16,
- height: i16,
- uv_bot: f32,
- uv_left: f32,
- uv_width: f32,
- uv_height: f32,
+enum TextRendererProvider {
+ Gles2(Gles2Renderer),
+ Glsl3(Glsl3Renderer),
}
-/// Naïve glyph cache.
-///
-/// Currently only keyed by `char`, and thus not possible to hold different
-/// representations of the same code point.
-pub struct GlyphCache {
- /// Cache of buffered glyphs.
- cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
-
- /// Rasterizer for loading new glyphs.
- rasterizer: Rasterizer,
-
- /// Regular font.
- font_key: FontKey,
-
- /// Bold font.
- bold_key: FontKey,
-
- /// Italic font.
- italic_key: FontKey,
-
- /// Bold italic font.
- bold_italic_key: FontKey,
-
- /// Font size.
- font_size: crossfont::Size,
-
- /// Font offset.
- font_offset: Delta<i8>,
-
- /// Glyph offset.
- glyph_offset: Delta<i8>,
-
- /// Font metrics.
- metrics: Metrics,
-
- /// Whether to use the built-in font for box drawing characters.
- builtin_box_drawing: bool,
+#[derive(Debug)]
+pub struct Renderer {
+ text_renderer: TextRendererProvider,
+ rect_renderer: RectRenderer,
}
-impl GlyphCache {
- pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> {
- let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
-
- // Need to load at least one glyph for the face before calling metrics.
- // The glyph requested here ('m' at the time of writing) has no special
- // meaning.
- rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
-
- let metrics = rasterizer.metrics(regular, font.size())?;
-
- Ok(Self {
- cache: HashMap::default(),
- rasterizer,
- font_size: font.size(),
- font_key: regular,
- bold_key: bold,
- italic_key: italic,
- bold_italic_key: bold_italic,
- font_offset: font.offset,
- glyph_offset: font.glyph_offset,
- metrics,
- builtin_box_drawing: font.builtin_box_drawing,
- })
- }
-
- fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
- let size = self.font_size;
-
- // Cache all ascii characters.
- for i in 32u8..=126u8 {
- self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
- }
- }
-
- /// Computes font keys for (Regular, Bold, Italic, Bold Italic).
- fn compute_font_keys(
- font: &Font,
- rasterizer: &mut Rasterizer,
- ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
- let size = font.size();
-
- // Load regular font.
- let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
-
- let regular = Self::load_regular_font(rasterizer, &regular_desc, size)?;
-
- // Helper to load a description if it is not the `regular_desc`.
- let mut load_or_regular = |desc: FontDesc| {
- if desc == regular_desc {
- regular
- } else {
- rasterizer.load_font(&desc, size).unwrap_or(regular)
- }
- };
-
- // Load bold font.
- let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
-
- let bold = load_or_regular(bold_desc);
-
- // Load italic font.
- let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
-
- let italic = load_or_regular(italic_desc);
-
- // Load bold italic font.
- let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold);
-
- let bold_italic = load_or_regular(bold_italic_desc);
-
- Ok((regular, bold, italic, bold_italic))
- }
-
- fn load_regular_font(
- rasterizer: &mut Rasterizer,
- description: &FontDesc,
- size: Size,
- ) -> Result<FontKey, crossfont::Error> {
- match rasterizer.load_font(description, size) {
- Ok(font) => Ok(font),
- Err(err) => {
- error!("{}", err);
-
- let fallback_desc =
- Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal);
- rasterizer.load_font(&fallback_desc, size)
- },
- }
- }
-
- fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc {
- let style = if let Some(ref spec) = desc.style {
- Style::Specific(spec.to_owned())
- } else {
- Style::Description { slant, weight }
- };
- FontDesc::new(desc.family.clone(), style)
- }
-
- /// Get a glyph from the font.
+impl Renderer {
+ /// Create a new renderer.
///
- /// If the glyph has never been loaded before, it will be rasterized and inserted into the
- /// cache.
- ///
- /// # Errors
- ///
- /// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
- /// not being present in any font.
- fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L, show_missing: bool) -> Glyph
- where
- L: LoadGlyph,
- {
- // Try to load glyph from cache.
- if let Some(glyph) = self.cache.get(&glyph_key) {
- return *glyph;
+ /// This will automatically pick between the GLES2 and GLSL3 renderer based on the GPU's
+ /// supported OpenGL version.
+ pub fn new() -> Result<Self, Error> {
+ let (version, renderer) = unsafe {
+ let renderer = CStr::from_ptr(gl::GetString(gl::RENDERER) as *mut _);
+ let version = CStr::from_ptr(gl::GetString(gl::SHADING_LANGUAGE_VERSION) as *mut _);
+ (version.to_string_lossy(), renderer.to_string_lossy())
};
- // Rasterize the glyph using the built-in font for special characters or the user's font
- // for everything else.
- let rasterized = self
- .builtin_box_drawing
- .then(|| {
- builtin_font::builtin_glyph(
- glyph_key.character,
- &self.metrics,
- &self.font_offset,
- &self.glyph_offset,
- )
- })
- .flatten()
- .map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok);
-
- let glyph = match rasterized {
- Ok(rasterized) => self.load_glyph(loader, rasterized),
- // Load fallback glyph.
- Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
- // Use `\0` as "missing" glyph to cache it only once.
- let missing_key = GlyphKey { character: '\0', ..glyph_key };
- if let Some(glyph) = self.cache.get(&missing_key) {
- *glyph
- } else {
- // If no missing glyph was loaded yet, insert it as `\0`.
- let glyph = self.load_glyph(loader, rasterized);
- self.cache.insert(missing_key, glyph);
+ info!("Running on {}", renderer);
- glyph
- }
- },
- Err(_) => self.load_glyph(loader, Default::default()),
+ let (text_renderer, rect_renderer) = if version.as_ref() >= "3.3" {
+ let text_renderer = TextRendererProvider::Glsl3(Glsl3Renderer::new()?);
+ let rect_renderer = RectRenderer::new(ShaderVersion::Glsl3)?;
+ (text_renderer, rect_renderer)
+ } else {
+ let text_renderer = TextRendererProvider::Gles2(Gles2Renderer::new()?);
+ let rect_renderer = RectRenderer::new(ShaderVersion::Gles2)?;
+ (text_renderer, rect_renderer)
};
- // Cache rasterized glyph.
- *self.cache.entry(glyph_key).or_insert(glyph)
+ Ok(Self { text_renderer, rect_renderer })
}
- /// Load glyph into the atlas.
- ///
- /// This will apply all transforms defined for the glyph cache to the rasterized glyph before
- /// insertion.
- fn load_glyph<L>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
- where
- L: LoadGlyph,
- {
- glyph.left += i32::from(self.glyph_offset.x);
- glyph.top += i32::from(self.glyph_offset.y);
- glyph.top -= self.metrics.descent as i32;
-
- // The metrics of zero-width characters are based on rendering
- // the character after the current cell, with the anchor at the
- // right side of the preceding character. Since we render the
- // zero-width characters inside the preceding character, the
- // anchor has been moved to the right by one cell.
- if glyph.character.width() == Some(0) {
- glyph.left += self.metrics.average_advance as i32;
+ pub fn draw_cells<I: Iterator<Item = RenderableCell>>(
+ &mut self,
+ size_info: &SizeInfo,
+ glyph_cache: &mut GlyphCache,
+ cells: I,
+ ) {
+ match &mut self.text_renderer {
+ TextRendererProvider::Gles2(renderer) => {
+ renderer.draw_cells(size_info, glyph_cache, cells)
+ },
+ TextRendererProvider::Glsl3(renderer) => {
+ renderer.draw_cells(size_info, glyph_cache, cells)
+ },
}
-
- // Add glyph to cache.
- loader.load_glyph(&glyph)
- }
-
- /// Clear currently cached data in both GL and the registry.
- pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
- loader.clear();
- self.cache = HashMap::default();
-
- self.load_common_glyphs(loader);
}
- pub fn update_font_size<L: LoadGlyph>(
+ /// Draw a string in a variable location. Used for printing the render timer, warnings and
+ /// errors.
+ pub fn draw_string(
&mut self,
- font: &Font,
- scale_factor: f64,
- loader: &mut L,
- ) -> Result<(), crossfont::Error> {
- // Update dpi scaling.
- self.rasterizer.update_dpr(scale_factor as f32);
- self.font_offset = font.offset;
-
- // Recompute font keys.
- let (regular, bold, italic, bold_italic) =
- Self::compute_font_keys(font, &mut self.rasterizer)?;
-
- self.rasterizer.get_glyph(GlyphKey {
- font_key: regular,
- character: 'm',
- size: font.size(),
- })?;
- let metrics = self.rasterizer.metrics(regular, font.size())?;
-
- info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor);
-
- self.font_size = font.size();
- self.font_key = regular;
- self.bold_key = bold;
- self.italic_key = italic;
- self.bold_italic_key = bold_italic;
- self.metrics = metrics;
- self.builtin_box_drawing = font.builtin_box_drawing;
-
- self.clear_glyph_cache(loader);
-
- Ok(())
- }
-
- pub fn font_metrics(&self) -> crossfont::Metrics {
- self.metrics
- }
-
- /// Prefetch glyphs that are almost guaranteed to be loaded anyways.
- pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
- self.load_glyphs_for_font(self.font_key, loader);
- self.load_glyphs_for_font(self.bold_key, loader);
- self.load_glyphs_for_font(self.italic_key, loader);
- self.load_glyphs_for_font(self.bold_italic_key, loader);
- }
-}
-
-// NOTE: These flags must be in sync with their usage in the text.*.glsl shaders.
-bitflags! {
- #[repr(C)]
- struct RenderingGlyphFlags: u8 {
- const WIDE_CHAR = 0b0000_0001;
- const COLORED = 0b0000_0010;
- }
-}
-
-#[derive(Debug)]
-#[repr(C)]
-struct InstanceData {
- // Coords.
- col: u16,
- row: u16,
-
- // Glyph offset.
- left: i16,
- top: i16,
-
- // Glyph size.
- width: i16,
- height: i16,
-
- // UV offset.
- uv_left: f32,
- uv_bot: f32,
-
- // uv scale.
- uv_width: f32,
- uv_height: f32,
-
- // Color.
- r: u8,
- g: u8,
- b: u8,
-
- // Cell flags like multicolor or fullwidth character.
- cell_flags: RenderingGlyphFlags,
-
- // Background color.
- bg_r: u8,
- bg_g: u8,
- bg_b: u8,
- bg_a: u8,
-}
-
-#[derive(Debug)]
-pub struct QuadRenderer {
- program: TextShaderProgram,
- vao: GLuint,
- ebo: GLuint,
- vbo_instance: GLuint,
- atlas: Vec<Atlas>,
- current_atlas: usize,
- active_tex: GLuint,
- batch: Batch,
-
- rect_renderer: RectRenderer,
-}
-
-#[derive(Debug)]
-pub struct RenderApi<'a> {
- active_tex: &'a mut GLuint,
- batch: &'a mut Batch,
- atlas: &'a mut Vec<Atlas>,
- current_atlas: &'a mut usize,
- program: &'a mut TextShaderProgram,
- config: &'a UiConfig,
-}
-
-#[derive(Debug)]
-pub struct LoaderApi<'a> {
- active_tex: &'a mut GLuint,
- atlas: &'a mut Vec<Atlas>,
- current_atlas: &'a mut usize,
-}
-
-#[derive(Debug, Default)]
-pub struct Batch {
- tex: GLuint,
- instances: Vec<InstanceData>,
-}
-
-impl Batch {
- #[inline]
- pub fn new() -> Self {
- Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
- }
-
- pub fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
- if self.is_empty() {
- self.tex = glyph.tex_id;
- }
-
- let mut cell_flags = RenderingGlyphFlags::empty();
- cell_flags.set(RenderingGlyphFlags::COLORED, glyph.multicolor);
- cell_flags.set(RenderingGlyphFlags::WIDE_CHAR, cell.flags.contains(Flags::WIDE_CHAR));
-
- self.instances.push(InstanceData {
- col: cell.point.column.0 as u16,
- row: cell.point.line as u16,
-
- top: glyph.top,
- left: glyph.left,
- width: glyph.width,
- height: glyph.height,
-
- uv_bot: glyph.uv_bot,
- uv_left: glyph.uv_left,
- uv_width: glyph.uv_width,
- uv_height: glyph.uv_height,
-
- r: cell.fg.r,
- g: cell.fg.g,
- b: cell.fg.b,
- cell_flags,
-
- bg_r: cell.bg.r,
- bg_g: cell.bg.g,
- bg_b: cell.bg.b,
- bg_a: (cell.bg_alpha * 255.0) as u8,
+ point: Point<usize>,
+ fg: Rgb,
+ bg: Rgb,
+ string: &str,
+ size_info: &SizeInfo,
+ glyph_cache: &mut GlyphCache,
+ ) {
+ let cells = string.chars().enumerate().map(|(i, character)| RenderableCell {
+ point: Point::new(point.line, point.column + i),
+ character,
+ zerowidth: None,
+ flags: Flags::empty(),
+ bg_alpha: 1.0,
+ fg,
+ bg,
});
- }
- #[inline]
- pub fn full(&self) -> bool {
- self.capacity() == self.len()
+ self.draw_cells(size_info, glyph_cache, cells);
}
- #[inline]
- pub fn len(&self) -> usize {
- self.instances.len()
- }
-
- #[inline]
- pub fn capacity(&self) -> usize {
- BATCH_MAX
- }
-
- #[inline]
- pub fn is_empty(&self) -> bool {
- self.len() == 0
- }
-
- #[inline]
- pub fn size(&self) -> usize {
- self.len() * size_of::<InstanceData>()
- }
-
- pub fn clear(&mut self) {
- self.tex = 0;
- self.instances.clear();
- }
-}
-
-/// Maximum items to be drawn in a batch.
-const BATCH_MAX: usize = 0x1_0000;
-const ATLAS_SIZE: i32 = 1024;
-
-impl QuadRenderer {
- pub fn new() -> Result<QuadRenderer, Error> {
- let program = TextShaderProgram::new()?;
-
- let mut vao: GLuint = 0;
- let mut ebo: GLuint = 0;
-
- let mut vbo_instance: GLuint = 0;
-
- unsafe {
- gl::Enable(gl::BLEND);
- gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
- gl::Enable(gl::MULTISAMPLE);
-
- // Disable depth mask, as the renderer never uses depth tests.
- gl::DepthMask(gl::FALSE);
-
- gl::GenVertexArrays(1, &mut vao);
- gl::GenBuffers(1, &mut ebo);
- gl::GenBuffers(1, &mut vbo_instance);
- gl::BindVertexArray(vao);
-
- // ---------------------
- // Set up element buffer
- // ---------------------
- let indices: [u32; 6] = [0, 1, 3, 1, 2, 3];
-
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
- gl::BufferData(
- gl::ELEMENT_ARRAY_BUFFER,
- (6 * size_of::<u32>()) as isize,
- indices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // ----------------------------
- // Setup vertex instance buffer
- // ----------------------------
- gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance);
- gl::BufferData(
- gl::ARRAY_BUFFER,
- (BATCH_MAX * size_of::<InstanceData>()) as isize,
- ptr::null(),
- gl::STREAM_DRAW,
- );
-
- let mut index = 0;
- let mut size = 0;
-
- macro_rules! add_attr {
- ($count:expr, $gl_type:expr, $type:ty) => {
- gl::VertexAttribPointer(
- index,
- $count,
- $gl_type,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- size as *const _,
- );
- gl::EnableVertexAttribArray(index);
- gl::VertexAttribDivisor(index, 1);
-
- #[allow(unused_assignments)]
- {
- size += $count * size_of::<$type>();
- index += 1;
- }
- };
- }
-
- // Coords.
- add_attr!(2, gl::UNSIGNED_SHORT, u16);
-
- // Glyph offset and size.
- add_attr!(4, gl::SHORT, i16);
-
- // UV offset.
- add_attr!(4, gl::FLOAT, f32);
-
- // Color and cell flags.
- //
- // These are packed together because of an OpenGL driver issue on macOS, which caused a
- // `vec3(u8)` text color and a `u8` cell flags to increase the rendering time by a
- // huge margin.
- add_attr!(4, gl::UNSIGNED_BYTE, u8);
-
- // Background color.
- add_attr!(4, gl::UNSIGNED_BYTE, u8);
-
- // Cleanup.
- gl::BindVertexArray(0);
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ pub fn with_loader<F, T>(&mut self, func: F) -> T
+ where
+ F: FnOnce(LoaderApi<'_>) -> T,
+ {
+ match &mut self.text_renderer {
+ TextRendererProvider::Gles2(renderer) => renderer.with_loader(func),
+ TextRendererProvider::Glsl3(renderer) => renderer.with_loader(func),
}
-
- let mut renderer = Self {
- program,
- rect_renderer: RectRenderer::new()?,
- vao,
- ebo,
- vbo_instance,
- atlas: Vec::new(),
- current_atlas: 0,
- active_tex: 0,
- batch: Batch::new(),
- };
-
- let atlas = Atlas::new(ATLAS_SIZE);
- renderer.atlas.push(atlas);
-
- Ok(renderer)
}
/// Draw all rectangles simultaneously to prevent excessive program swaps.
@@ -691,99 +175,9 @@ impl QuadRenderer {
}
}
- pub fn with_api<F, T>(&mut self, config: &UiConfig, props: &SizeInfo, func: F) -> T
- where
- F: FnOnce(RenderApi<'_>) -> T,
- {
+ /// Fill the window with `color` and `alpha`.
+ pub fn clear(&self, color: Rgb, alpha: f32) {
unsafe {
- gl::UseProgram(self.program.id());
- self.program.set_term_uniforms(props);
-
- gl::BindVertexArray(self.vao);
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
- gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance);
- gl::ActiveTexture(gl::TEXTURE0);
- }
-
- let res = func(RenderApi {
- active_tex: &mut self.active_tex,
- batch: &mut self.batch,
- atlas: &mut self.atlas,
- current_atlas: &mut self.current_atlas,
- program: &mut self.program,
- config,
- });
-
- unsafe {
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindVertexArray(0);
-
- gl::UseProgram(0);
- }
-
- res
- }
-
- pub fn with_loader<F, T>(&mut self, func: F) -> T
- where
- F: FnOnce(LoaderApi<'_>) -> T,
- {
- unsafe {
- gl::ActiveTexture(gl::TEXTURE0);
- }
-
- func(LoaderApi {
- active_tex: &mut self.active_tex,
- atlas: &mut self.atlas,
- current_atlas: &mut self.current_atlas,
- })
- }
-
- pub fn resize(&self, size: &SizeInfo) {
- unsafe {
- self.set_viewport(size);
-
- // Update projection.
- gl::UseProgram(self.program.id());
- self.program.update_projection(
- size.width(),
- size.height(),
- size.padding_x(),
- size.padding_y(),
- );
- gl::UseProgram(0);
- }
- }
-
- /// Set the viewport for cell rendering.
- #[inline]
- pub fn set_viewport(&self, size: &SizeInfo) {
- unsafe {
- gl::Viewport(
- size.padding_x() as i32,
- size.padding_y() as i32,
- size.width() as i32 - 2 * size.padding_x() as i32,
- size.height() as i32 - 2 * size.padding_y() as i32,
- );
- }
- }
-}
-
-impl Drop for QuadRenderer {
- fn drop(&mut self) {
- unsafe {
- gl::DeleteBuffers(1, &self.vbo_instance);
- gl::DeleteBuffers(1, &self.ebo);
- gl::DeleteVertexArrays(1, &self.vao);
- }
- }
-}
-
-impl<'a> RenderApi<'a> {
- pub fn clear(&self, color: Rgb) {
- unsafe {
- let alpha = self.config.window_opacity();
gl::ClearColor(
(f32::from(color.r) / 255.0).min(1.0) * alpha,
(f32::from(color.g) / 255.0).min(1.0) * alpha,
@@ -801,458 +195,25 @@ impl<'a> RenderApi<'a> {
}
}
- fn render_batch(&mut self) {
- unsafe {
- gl::BufferSubData(
- gl::ARRAY_BUFFER,
- 0,
- self.batch.size() as isize,
- self.batch.instances.as_ptr() as *const _,
- );
- }
-
- // Bind texture if necessary.
- if *self.active_tex != self.batch.tex {
- unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.batch.tex);
- }
- *self.active_tex = self.batch.tex;
- }
-
- unsafe {
- self.program.set_background_pass(true);
- gl::DrawElementsInstanced(
- gl::TRIANGLES,
- 6,
- gl::UNSIGNED_INT,
- ptr::null(),
- self.batch.len() as GLsizei,
- );
- self.program.set_background_pass(false);
- gl::DrawElementsInstanced(
- gl::TRIANGLES,
- 6,
- gl::UNSIGNED_INT,
- ptr::null(),
- self.batch.len() as GLsizei,
- );
- }
-
- self.batch.clear();
- }
-
- /// Draw a string in a variable location. Used for printing the render timer, warnings and
- /// errors.
- pub fn draw_string(
- &mut self,
- glyph_cache: &mut GlyphCache,
- point: Point<usize>,
- fg: Rgb,
- bg: Rgb,
- string: &str,
- ) {
- let cells = string
- .chars()
- .enumerate()
- .map(|(i, character)| RenderableCell {
- point: Point::new(point.line, point.column + i),
- character,
- zerowidth: None,
- flags: Flags::empty(),
- bg_alpha: 1.0,
- fg,
- bg,
- })
- .collect::<Vec<_>>();
-
- for cell in cells {
- self.draw_cell(cell, glyph_cache);
- }
- }
-
+ /// Set the viewport for cell rendering.
#[inline]
- fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
- // Flush batch if tex changing.
- if !self.batch.is_empty() && self.batch.tex != glyph.tex_id {
- self.render_batch();
- }
-
- self.batch.add_item(cell, glyph);
-
- // Render batch and clear if it's full.
- if self.batch.full() {
- self.render_batch();
- }
- }
-
- pub fn draw_cell(&mut self, mut cell: RenderableCell, glyph_cache: &mut GlyphCache) {
- // Get font key for cell.
- let font_key = match cell.flags & Flags::BOLD_ITALIC {
- Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
- Flags::ITALIC => glyph_cache.italic_key,
- Flags::BOLD => glyph_cache.bold_key,
- _ => glyph_cache.font_key,
- };
-
- // Ignore hidden cells and render tabs as spaces to prevent font issues.
- let hidden = cell.flags.contains(Flags::HIDDEN);
- if cell.character == '\t' || hidden {
- cell.character = ' ';
- }
-
- let mut glyph_key =
- GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character };
-
- // Add cell to batch.
- let glyph = glyph_cache.get(glyph_key, self, true);
- self.add_render_item(&cell, &glyph);
-
- // Render visible zero-width characters.
- if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) {
- for character in zerowidth {
- glyph_key.character = character;
- let glyph = glyph_cache.get(glyph_key, self, false);
- self.add_render_item(&cell, &glyph);
- }
- }
- }
-}
-
-/// Load a glyph into a texture atlas.
-///
-/// If the current atlas is full, a new one will be created.
-#[inline]
-fn load_glyph(
- active_tex: &mut GLuint,
- atlas: &mut Vec<Atlas>,
- current_atlas: &mut usize,
- rasterized: &RasterizedGlyph,
-) -> Glyph {
- // At least one atlas is guaranteed to be in the `self.atlas` list; thus
- // the unwrap.
- match atlas[*current_atlas].insert(rasterized, active_tex) {
- Ok(glyph) => glyph,
- Err(AtlasInsertError::Full) => {
- *current_atlas += 1;
- if *current_atlas == atlas.len() {
- let new = Atlas::new(ATLAS_SIZE);
- *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
- atlas.push(new);
- }
- load_glyph(active_tex, atlas, current_atlas, rasterized)
- },
- Err(AtlasInsertError::GlyphTooLarge) => Glyph {
- tex_id: atlas[*current_atlas].id,
- multicolor: false,
- top: 0,
- left: 0,
- width: 0,
- height: 0,
- uv_bot: 0.,
- uv_left: 0.,
- uv_width: 0.,
- uv_height: 0.,
- },
- }
-}
-
-#[inline]
-fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) {
- for atlas in atlas.iter_mut() {
- atlas.clear();
- }
- *current_atlas = 0;
-}
-
-impl<'a> LoadGlyph for LoaderApi<'a> {
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
- load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
- }
-
- fn clear(&mut self) {
- clear_atlas(self.atlas, self.current_atlas)
- }
-}
-
-impl<'a> LoadGlyph for RenderApi<'a> {
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
- load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
- }
-
- fn clear(&mut self) {
- clear_atlas(self.atlas, self.current_atlas)
- }
-}
-
-impl<'a> Drop for RenderApi<'a> {
- fn drop(&mut self) {
- if !self.batch.is_empty() {
- self.render_batch();
- }
- }
-}
-
-impl TextShaderProgram {
- pub fn new() -> Result<TextShaderProgram, Error> {
- let program = ShaderProgram::new(TEXT_SHADER_V, TEXT_SHADER_F)?;
- 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"))?,
- program,
- })
- }
-
- fn id(&self) -> GLuint {
- self.program.id()
- }
-
- fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) {
- // Bounds check.
- if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
- return;
- }
-
- // Compute scale and offset factors, from pixel to ndc space. Y is inverted.
- // [0, width - 2 * padding_x] to [-1, 1]
- // [height - 2 * padding_y, 0] to [-1, 1]
- let scale_x = 2. / (width - 2. * padding_x);
- let scale_y = -2. / (height - 2. * padding_y);
- let offset_x = -1.;
- let offset_y = 1.;
-
- unsafe {
- gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y);
- }
- }
-
- fn set_term_uniforms(&self, props: &SizeInfo) {
- unsafe {
- gl::Uniform2f(self.u_cell_dim, props.cell_width(), props.cell_height());
- }
- }
-
- fn set_background_pass(&self, background_pass: bool) {
- let value = if background_pass { 1 } else { 0 };
-
- unsafe {
- gl::Uniform1i(self.u_background, value);
- }
- }
-}
-
-/// Manages a single texture atlas.
-///
-/// The strategy for filling an atlas looks roughly like this:
-///
-/// ```text
-/// (width, height)
-/// ┌─────┬─────┬─────┬─────┬─────┐
-/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
-/// │ │ │ │ │ │ glyph_height < height - row_baseline
-/// ├─────┼─────┼─────┼─────┼─────┤
-/// │ 5 │ 6 │ 7 │ 8 │ 9 │
-/// │ │ │ │ │ │
-/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is
-/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row.
-/// │ │ │ │ │ <- Row considered full when next glyph doesn't
-/// └─────┴─────┴─────┴───────────┘ fit in the row.
-/// (0, 0) x->
-/// ```
-#[derive(Debug)]
-struct Atlas {
- /// Texture id for this atlas.
- id: GLuint,
-
- /// Width of atlas.
- width: i32,
-
- /// Height of atlas.
- height: i32,
-
- /// Left-most free pixel in a row.
- ///
- /// This is called the extent because it is the upper bound of used pixels
- /// in a row.
- row_extent: i32,
-
- /// Baseline for glyphs in the current row.
- row_baseline: i32,
-
- /// Tallest glyph in current row.
- ///
- /// This is used as the advance when end of row is reached.
- row_tallest: i32,
-}
-
-/// Error that can happen when inserting a texture to the Atlas.
-enum AtlasInsertError {
- /// Texture atlas is full.
- Full,
-
- /// The glyph cannot fit within a single texture.
- GlyphTooLarge,
-}
-
-impl Atlas {
- fn new(size: i32) -> Self {
- let mut id: GLuint = 0;
- unsafe {
- gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
- gl::GenTextures(1, &mut id);
- gl::BindTexture(gl::TEXTURE_2D, id);
- // Use RGBA texture for both normal and emoji glyphs, since it has no performance
- // impact.
- gl::TexImage2D(
- gl::TEXTURE_2D,
- 0,
- gl::RGBA as i32,
- size,
- size,
- 0,
- gl::RGBA,
- gl::UNSIGNED_BYTE,
- ptr::null(),
- );
-
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
- gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
-
- gl::BindTexture(gl::TEXTURE_2D, 0);
- }
-
- Self { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 }
- }
-
- pub fn clear(&mut self) {
- self.row_extent = 0;
- self.row_baseline = 0;
- self.row_tallest = 0;
- }
-
- /// Insert a RasterizedGlyph into the texture atlas.
- pub fn insert(
- &mut self,
- glyph: &RasterizedGlyph,
- active_tex: &mut u32,
- ) -> Result<Glyph, AtlasInsertError> {
- if glyph.width > self.width || glyph.height > self.height {
- return Err(AtlasInsertError::GlyphTooLarge);
- }
-
- // If there's not enough room in current row, go onto next one.
- if !self.room_in_row(glyph) {
- self.advance_row()?;
- }
-
- // If there's still not room, there's nothing that can be done here..
- if !self.room_in_row(glyph) {
- return Err(AtlasInsertError::Full);
- }
-
- // There appears to be room; load the glyph.
- Ok(self.insert_inner(glyph, active_tex))
- }
-
- /// Insert the glyph without checking for room.
- ///
- /// Internal function for use once atlas has been checked for space. GL
- /// errors could still occur at this point if we were checking for them;
- /// hence, the Result.
- fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph {
- let offset_y = self.row_baseline;
- let offset_x = self.row_extent;
- let height = glyph.height as i32;
- let width = glyph.width as i32;
- let multicolor;
-
+ pub fn set_viewport(&self, size: &SizeInfo) {
unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.id);
-
- // Load data into OpenGL.
- let (format, buffer) = match &glyph.buffer {
- BitmapBuffer::Rgb(buffer) => {
- multicolor = false;
- (gl::RGB, buffer)
- },
- BitmapBuffer::Rgba(buffer) => {
- multicolor = true;
- (gl::RGBA, buffer)
- },
- };
-
- gl::TexSubImage2D(
- gl::TEXTURE_2D,
- 0,
- offset_x,
- offset_y,
- width,
- height,
- format,
- gl::UNSIGNED_BYTE,
- buffer.as_ptr() as *const _,
+ gl::Viewport(
+ size.padding_x() as i32,
+ size.padding_y() as i32,
+ size.width() as i32 - 2 * size.padding_x() as i32,
+ size.height() as i32 - 2 * size.padding_y() as i32,
);
-
- gl::BindTexture(gl::TEXTURE_2D, 0);
- *active_tex = 0;
- }
-
- // Update Atlas state.
- self.row_extent = offset_x + width;
- if height > self.row_tallest {
- self.row_tallest = height;
}
-
- // Generate UV coordinates.
- let uv_bot = offset_y as f32 / self.height as f32;
- let uv_left = offset_x as f32 / self.width as f32;
- let uv_height = height as f32 / self.height as f32;
- let uv_width = width as f32 / self.width as f32;
-
- Glyph {
- tex_id: self.id,
- multicolor,
- top: glyph.top as i16,
- left: glyph.left as i16,
- width: width as i16,
- height: height as i16,
- uv_bot,
- uv_left,
- uv_width,
- uv_height,
- }
- }
-
- /// Check if there's room in the current row for given glyph.
- fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
- let next_extent = self.row_extent + raw.width as i32;
- let enough_width = next_extent <= self.width;
- let enough_height = (raw.height as i32) < (self.height - self.row_baseline);
-
- enough_width && enough_height
}
- /// Mark current row as finished and prepare to insert into the next row.
- fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
- let advance_to = self.row_baseline + self.row_tallest;
- if self.height - advance_to <= 0 {
- return Err(AtlasInsertError::Full);
- }
-
- self.row_baseline = advance_to;
- self.row_extent = 0;
- self.row_tallest = 0;
-
- Ok(())
- }
-}
-
-impl Drop for Atlas {
- fn drop(&mut self) {
- unsafe {
- gl::DeleteTextures(1, &self.id);
+ /// Resize the renderer.
+ pub fn resize(&self, size_info: &SizeInfo) {
+ self.set_viewport(size_info);
+ match &self.text_renderer {
+ TextRendererProvider::Gles2(renderer) => renderer.resize(size_info),
+ TextRendererProvider::Glsl3(renderer) => renderer.resize(size_info),
}
}
}
diff --git a/alacritty/src/renderer/rects.rs b/alacritty/src/renderer/rects.rs
index 3c74b10b..8c600b6b 100644
--- a/alacritty/src/renderer/rects.rs
+++ b/alacritty/src/renderer/rects.rs
@@ -12,7 +12,7 @@ use alacritty_terminal::term::SizeInfo;
use crate::display::content::RenderableCell;
use crate::gl;
use crate::gl::types::*;
-use crate::renderer::shader::{ShaderError, ShaderProgram};
+use crate::renderer::shader::{ShaderError, ShaderProgram, ShaderVersion};
use crate::renderer::{self, cstr};
#[derive(Debug, Copy, Clone)]
@@ -252,10 +252,10 @@ pub struct RectRenderer {
}
impl RectRenderer {
- pub fn new() -> Result<Self, renderer::Error> {
+ pub fn new(shader_version: ShaderVersion) -> Result<Self, renderer::Error> {
let mut vao: GLuint = 0;
let mut vbo: GLuint = 0;
- let program = RectShaderProgram::new()?;
+ let program = RectShaderProgram::new(shader_version)?;
unsafe {
// Allocate buffers.
@@ -422,8 +422,8 @@ pub struct RectShaderProgram {
}
impl RectShaderProgram {
- pub fn new() -> Result<Self, ShaderError> {
- let program = ShaderProgram::new(RECT_SHADER_V, RECT_SHADER_F)?;
+ pub fn new(shader_version: ShaderVersion) -> Result<Self, ShaderError> {
+ let program = ShaderProgram::new(shader_version, RECT_SHADER_V, RECT_SHADER_F)?;
Ok(Self {
u_rect_kind: program.get_uniform_location(cstr!("rectKind"))?,
diff --git a/alacritty/src/renderer/shader.rs b/alacritty/src/renderer/shader.rs
index edb01277..9326796b 100644
--- a/alacritty/src/renderer/shader.rs
+++ b/alacritty/src/renderer/shader.rs
@@ -8,13 +8,33 @@ use crate::gl::types::*;
#[derive(Debug)]
pub struct ShaderProgram(GLuint);
+#[derive(Copy, Clone, Debug)]
+pub enum ShaderVersion {
+ /// OpenGL 3.3 core shaders.
+ Glsl3,
+
+ /// OpenGL ES 2.0 shaders.
+ Gles2,
+}
+
+impl ShaderVersion {
+ // Header to which we concatenate the entire shader. The newlines are required.
+ fn shader_header(&self) -> &'static str {
+ match self {
+ Self::Glsl3 => "#version 330 core\n",
+ Self::Gles2 => "#version 100\n#define GLES2_RENDERER\n",
+ }
+ }
+}
+
impl ShaderProgram {
pub fn new(
+ shader_version: ShaderVersion,
vertex_shader: &'static str,
fragment_shader: &'static str,
) -> Result<Self, ShaderError> {
- let vertex_shader = Shader::new(gl::VERTEX_SHADER, vertex_shader)?;
- let fragment_shader = Shader::new(gl::FRAGMENT_SHADER, fragment_shader)?;
+ let vertex_shader = Shader::new(shader_version, gl::VERTEX_SHADER, vertex_shader)?;
+ let fragment_shader = Shader::new(shader_version, gl::FRAGMENT_SHADER, fragment_shader)?;
let program = unsafe { Self(gl::CreateProgram()) };
@@ -60,23 +80,34 @@ impl Drop for ShaderProgram {
struct Shader(GLuint);
impl Shader {
- fn new(kind: GLenum, source: &'static str) -> Result<Self, ShaderError> {
- let len: [GLint; 1] = [source.len() as GLint];
+ fn new(
+ shader_version: ShaderVersion,
+ kind: GLenum,
+ source: &'static str,
+ ) -> Result<Self, ShaderError> {
+ let header = shader_version.shader_header();
+ let len: [GLint; 2] = [header.len() as GLint, source.len() as GLint];
+ let source = [header.as_ptr() as *const _, source.as_ptr() as *const _];
let shader = unsafe { Self(gl::CreateShader(kind)) };
let mut success: GLint = 0;
unsafe {
- gl::ShaderSource(shader.id(), 1, &(source.as_ptr() as *const _), len.as_ptr());
+ gl::ShaderSource(
+ shader.id(),
+ len.len() as GLint,
+ source.as_ptr() as *const _,
+ len.as_ptr(),
+ );
gl::CompileShader(shader.id());
gl::GetShaderiv(shader.id(), gl::COMPILE_STATUS, &mut success);
}
- if success != GLint::from(gl::TRUE) {
- return Err(ShaderError::Compile(get_shader_info_log(shader.id())));
+ if success == GLint::from(gl::TRUE) {
+ Ok(shader)
+ } else {
+ Err(ShaderError::Compile(get_shader_info_log(shader.id())))
}
-
- Ok(shader)
}
fn id(&self) -> GLuint {
diff --git a/alacritty/src/renderer/text/atlas.rs b/alacritty/src/renderer/text/atlas.rs
new file mode 100644
index 00000000..97c8b0b4
--- /dev/null
+++ b/alacritty/src/renderer/text/atlas.rs
@@ -0,0 +1,273 @@
+use std::ptr;
+
+use crossfont::{BitmapBuffer, RasterizedGlyph};
+
+use crate::gl;
+use crate::gl::types::*;
+
+use super::Glyph;
+
+/// Size of the Atlas.
+pub const ATLAS_SIZE: i32 = 1024;
+
+/// Manages a single texture atlas.
+///
+/// The strategy for filling an atlas looks roughly like this:
+///
+/// ```text
+/// (width, height)
+/// ┌─────┬─────┬─────┬─────┬─────┐
+/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
+/// │ │ │ │ │ │ glyph_height < height - row_baseline
+/// ├─────┼─────┼─────┼─────┼─────┤
+/// │ 5 │ 6 │ 7 │ 8 │ 9 │
+/// │ │ │ │ │ │
+/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is
+/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row.
+/// │ │ │ │ │ <- Row considered full when next glyph doesn't
+/// └─────┴─────┴─────┴───────────┘ fit in the row.
+/// (0, 0) x->
+/// ```
+#[derive(Debug)]
+pub struct Atlas {
+ /// Texture id for this atlas.
+ id: GLuint,
+
+ /// Width of atlas.
+ width: i32,
+
+ /// Height of atlas.
+ height: i32,
+
+ /// Left-most free pixel in a row.
+ ///
+ /// This is called the extent because it is the upper bound of used pixels
+ /// in a row.
+ row_extent: i32,
+
+ /// Baseline for glyphs in the current row.
+ row_baseline: i32,
+
+ /// Tallest glyph in current row.
+ ///
+ /// This is used as the advance when end of row is reached.
+ row_tallest: i32,
+}
+
+/// Error that can happen when inserting a texture to the Atlas.
+pub enum AtlasInsertError {
+ /// Texture atlas is full.
+ Full,
+
+ /// The glyph cannot fit within a single texture.
+ GlyphTooLarge,
+}
+
+impl Atlas {
+ pub fn new(size: i32) -> Self {
+ let mut id: GLuint = 0;
+ unsafe {
+ gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
+ gl::GenTextures(1, &mut id);
+ gl::BindTexture(gl::TEXTURE_2D, id);
+ // Use RGBA texture for both normal and emoji glyphs, since it has no performance
+ // impact.
+ gl::TexImage2D(
+ gl::TEXTURE_2D,
+ 0,
+ gl::RGBA as i32,
+ size,
+ size,
+ 0,
+ gl::RGBA,
+ gl::UNSIGNED_BYTE,
+ ptr::null(),
+ );
+
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32);
+ gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32);
+
+ gl::BindTexture(gl::TEXTURE_2D, 0);
+ }
+
+ Self { id, width: size, height: size, row_extent: 0, row_baseline: 0, row_tallest: 0 }
+ }
+
+ pub fn clear(&mut self) {
+ self.row_extent = 0;
+ self.row_baseline = 0;
+ self.row_tallest = 0;
+ }
+
+ /// Insert a RasterizedGlyph into the texture atlas.
+ pub fn insert(
+ &mut self,
+ glyph: &RasterizedGlyph,
+ active_tex: &mut u32,
+ ) -> Result<Glyph, AtlasInsertError> {
+ if glyph.width > self.width || glyph.height > self.height {
+ return Err(AtlasInsertError::GlyphTooLarge);
+ }
+
+ // If there's not enough room in current row, go onto next one.
+ if !self.room_in_row(glyph) {
+ self.advance_row()?;
+ }
+
+ // If there's still not room, there's nothing that can be done here..
+ if !self.room_in_row(glyph) {
+ return Err(AtlasInsertError::Full);
+ }
+
+ // There appears to be room; load the glyph.
+ Ok(self.insert_inner(glyph, active_tex))
+ }
+
+ /// Insert the glyph without checking for room.
+ ///
+ /// Internal function for use once atlas has been checked for space. GL
+ /// errors could still occur at this point if we were checking for them;
+ /// hence, the Result.
+ fn insert_inner(&mut self, glyph: &RasterizedGlyph, active_tex: &mut u32) -> Glyph {
+ let offset_y = self.row_baseline;
+ let offset_x = self.row_extent;
+ let height = glyph.height as i32;
+ let width = glyph.width as i32;
+ let multicolor;
+
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.id);
+
+ // Load data into OpenGL.
+ let (format, buffer) = match &glyph.buffer {
+ BitmapBuffer::Rgb(buffer) => {
+ multicolor = false;
+ (gl::RGB, buffer)
+ },
+ BitmapBuffer::Rgba(buffer) => {
+ multicolor = true;
+ (gl::RGBA, buffer)
+ },
+ };
+
+ gl::TexSubImage2D(
+ gl::TEXTURE_2D,
+ 0,
+ offset_x,
+ offset_y,
+ width,
+ height,
+ format,
+ gl::UNSIGNED_BYTE,
+ buffer.as_ptr() as *const _,
+ );
+
+ gl::BindTexture(gl::TEXTURE_2D, 0);
+ *active_tex = 0;
+ }
+
+ // Update Atlas state.
+ self.row_extent = offset_x + width;
+ if height > self.row_tallest {
+ self.row_tallest = height;
+ }
+
+ // Generate UV coordinates.
+ let uv_bot = offset_y as f32 / self.height as f32;
+ let uv_left = offset_x as f32 / self.width as f32;
+ let uv_height = height as f32 / self.height as f32;
+ let uv_width = width as f32 / self.width as f32;
+
+ Glyph {
+ tex_id: self.id,
+ multicolor,
+ top: glyph.top as i16,
+ left: glyph.left as i16,
+ width: width as i16,
+ height: height as i16,
+ uv_bot,
+ uv_left,
+ uv_width,
+ uv_height,
+ }
+ }
+
+ /// Check if there's room in the current row for given glyph.
+ pub fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
+ let next_extent = self.row_extent + raw.width as i32;
+ let enough_width = next_extent <= self.width;
+ let enough_height = (raw.height as i32) < (self.height - self.row_baseline);
+
+ enough_width && enough_height
+ }
+
+ /// Mark current row as finished and prepare to insert into the next row.
+ pub fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
+ let advance_to = self.row_baseline + self.row_tallest;
+ if self.height - advance_to <= 0 {
+ return Err(AtlasInsertError::Full);
+ }
+
+ self.row_baseline = advance_to;
+ self.row_extent = 0;
+ self.row_tallest = 0;
+
+ Ok(())
+ }
+
+ /// Load a glyph into a texture atlas.
+ ///
+ /// If the current atlas is full, a new one will be created.
+ #[inline]
+ pub fn load_glyph(
+ active_tex: &mut GLuint,
+ atlas: &mut Vec<Atlas>,
+ current_atlas: &mut usize,
+ rasterized: &RasterizedGlyph,
+ ) -> Glyph {
+ // At least one atlas is guaranteed to be in the `self.atlas` list; thus
+ // the unwrap.
+ match atlas[*current_atlas].insert(rasterized, active_tex) {
+ Ok(glyph) => glyph,
+ Err(AtlasInsertError::Full) => {
+ *current_atlas += 1;
+ if *current_atlas == atlas.len() {
+ let new = Atlas::new(ATLAS_SIZE);
+ *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
+ atlas.push(new);
+ }
+ Atlas::load_glyph(active_tex, atlas, current_atlas, rasterized)
+ },
+ Err(AtlasInsertError::GlyphTooLarge) => Glyph {
+ tex_id: atlas[*current_atlas].id,
+ multicolor: false,
+ top: 0,
+ left: 0,
+ width: 0,
+ height: 0,
+ uv_bot: 0.,
+ uv_left: 0.,
+ uv_width: 0.,
+ uv_height: 0.,
+ },
+ }
+ }
+
+ #[inline]
+ pub fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) {
+ for atlas in atlas.iter_mut() {
+ atlas.clear();
+ }
+ *current_atlas = 0;
+ }
+}
+
+impl Drop for Atlas {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteTextures(1, &self.id);
+ }
+ }
+}
diff --git a/alacritty/src/renderer/builtin_font.rs b/alacritty/src/renderer/text/builtin_font.rs
index 0922d3ef..0922d3ef 100644
--- a/alacritty/src/renderer/builtin_font.rs
+++ b/alacritty/src/renderer/text/builtin_font.rs
diff --git a/alacritty/src/renderer/text/gles2.rs b/alacritty/src/renderer/text/gles2.rs
new file mode 100644
index 00000000..9b161081
--- /dev/null
+++ b/alacritty/src/renderer/text/gles2.rs
@@ -0,0 +1,478 @@
+use std::mem::size_of;
+use std::ptr;
+
+use crossfont::RasterizedGlyph;
+use log::info;
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+use crate::renderer::shader::{ShaderProgram, ShaderVersion};
+use crate::renderer::{cstr, Error};
+
+use super::atlas::{Atlas, ATLAS_SIZE};
+use super::{
+ Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader,
+};
+
+// Shader source.
+static TEXT_SHADER_F: &str = include_str!("../../../res/gles2/text.f.glsl");
+static TEXT_SHADER_V: &str = include_str!("../../../res/gles2/text.v.glsl");
+
+#[derive(Debug)]
+pub struct Gles2Renderer {
+ program: TextShaderProgram,
+ vao: GLuint,
+ vbo: GLuint,
+ ebo: GLuint,
+ atlas: Vec<Atlas>,
+ batch: Batch,
+ current_atlas: usize,
+ active_tex: GLuint,
+}
+
+impl Gles2Renderer {
+ pub fn new() -> Result<Self, Error> {
+ info!("Using OpenGL ES 2.0 renderer");
+
+ let program = TextShaderProgram::new(ShaderVersion::Gles2)?;
+ let mut vao: GLuint = 0;
+ let mut vbo: GLuint = 0;
+ let mut ebo: GLuint = 0;
+
+ let mut vertex_indices = Vec::with_capacity(BATCH_MAX / 4 * 6);
+ for index in 0..(BATCH_MAX / 4) as u16 {
+ let index = index * 4;
+ vertex_indices.push(index);
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 3);
+
+ vertex_indices.push(index + 1);
+ vertex_indices.push(index + 2);
+ vertex_indices.push(index + 3);
+ }
+
+ unsafe {
+ gl::Enable(gl::BLEND);
+
+ gl::DepthMask(gl::FALSE);
+
+ gl::GenVertexArrays(1, &mut vao);
+ gl::GenBuffers(1, &mut ebo);
+ gl::GenBuffers(1, &mut vbo);
+ gl::BindVertexArray(vao);
+
+ // Elements buffer.
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
+ gl::BufferData(
+ gl::ELEMENT_ARRAY_BUFFER,
+ (vertex_indices.capacity() * size_of::<u16>()) as isize,
+ vertex_indices.as_ptr() as *const _,
+ gl::STATIC_DRAW,
+ );
+
+ // Vertex buffer.
+ gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (BATCH_MAX * size_of::<TextVertex>()) as isize,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+
+ let mut index = 0;
+ let mut size = 0;
+
+ macro_rules! add_attr {
+ ($count:expr, $gl_type:expr, $type:ty) => {
+ gl::VertexAttribPointer(
+ index,
+ $count,
+ $gl_type,
+ gl::FALSE,
+ size_of::<TextVertex>() as i32,
+ size as *const _,
+ );
+ gl::EnableVertexAttribArray(index);
+
+ #[allow(unused_assignments)]
+ {
+ size += $count * size_of::<$type>();
+ index += 1;
+ }
+ };
+ }
+
+ // Cell coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // Glyph coords.
+ add_attr!(2, gl::SHORT, i16);
+
+ // UV.
+ add_attr!(2, gl::FLOAT, u32);
+
+ // Color and bitmap color.
+ //
+ // These are packed together because of an OpenGL driver issue on macOS, which caused a
+ // `vec3(u8)` text color and a `u8` for glyph color to cause performance regressions.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Background color.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Cleanup.
+ gl::BindVertexArray(0);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ }
+
+ Ok(Self {
+ program,
+ vao,
+ vbo,
+ ebo,
+ atlas: vec![Atlas::new(ATLAS_SIZE)],
+ batch: Batch::new(),
+ current_atlas: 0,
+ active_tex: 0,
+ })
+ }
+}
+
+impl Drop for Gles2Renderer {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteBuffers(1, &self.vbo);
+ gl::DeleteBuffers(1, &self.ebo);
+ gl::DeleteVertexArrays(1, &self.vao);
+ }
+ }
+}
+
+impl<'a> TextRenderer<'a> for Gles2Renderer {
+ type RenderApi = RenderApi<'a>;
+ type RenderBatch = Batch;
+ type Shader = TextShaderProgram;
+
+ fn program(&self) -> &Self::Shader {
+ &self.program
+ }
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, _: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T,
+ {
+ unsafe {
+ gl::UseProgram(self.program.id());
+ gl::BindVertexArray(self.vao);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
+ gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ let res = func(RenderApi {
+ active_tex: &mut self.active_tex,
+ batch: &mut self.batch,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ program: &mut self.program,
+ });
+
+ unsafe {
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+
+ gl::UseProgram(0);
+ }
+
+ res
+ }
+
+ fn loader_api(&mut self) -> LoaderApi<'_> {
+ LoaderApi {
+ active_tex: &mut self.active_tex,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ }
+ }
+}
+
+/// 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.
+const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize;
+
+#[derive(Debug)]
+pub struct Batch {
+ tex: GLuint,
+ vertices: Vec<TextVertex>,
+}
+
+impl Batch {
+ fn new() -> Self {
+ Self { tex: 0, vertices: Vec::with_capacity(BATCH_MAX) }
+ }
+
+ #[inline]
+ fn len(&self) -> usize {
+ self.vertices.len()
+ }
+
+ #[inline]
+ fn capacity(&self) -> usize {
+ BATCH_MAX
+ }
+
+ #[inline]
+ fn size(&self) -> usize {
+ self.len() * size_of::<TextVertex>()
+ }
+
+ #[inline]
+ fn clear(&mut self) {
+ self.vertices.clear();
+ }
+}
+
+impl TextRenderBatch for Batch {
+ #[inline]
+ fn tex(&self) -> GLuint {
+ self.tex
+ }
+
+ #[inline]
+ fn full(&self) -> bool {
+ self.capacity() == self.len()
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
+ if self.is_empty() {
+ self.tex = glyph.tex_id;
+ }
+
+ // Calculate the cell position.
+ let x = cell.point.column.0 as i16 * size_info.cell_width() as i16;
+ let y = cell.point.line as i16 * size_info.cell_height() as i16;
+
+ // Calculate the glyph position.
+ 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 is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
+
+ let mut vertex = TextVertex {
+ x,
+ y: y + size_info.cell_height() as i16,
+
+ glyph_x,
+ glyph_y: glyph_y + glyph.height,
+
+ u: glyph.uv_left,
+ v: glyph.uv_bot + glyph.uv_height,
+ r: cell.fg.r,
+ g: cell.fg.g,
+ b: cell.fg.b,
+ colored,
+ bg_r: cell.bg.r,
+ bg_g: cell.bg.g,
+ bg_b: cell.bg.b,
+ bg_a: (cell.bg_alpha * 255.0) as u8,
+ };
+
+ self.vertices.push(vertex);
+
+ vertex.y = y;
+ vertex.glyph_y = glyph_y;
+ vertex.u = glyph.uv_left;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot;
+ self.vertices.push(vertex);
+
+ vertex.x = x + is_wide * size_info.cell_width() as i16;
+ vertex.y = y + size_info.cell_height() as i16;
+ vertex.glyph_x = glyph_x + glyph.width;
+ vertex.glyph_y = glyph_y + glyph.height;
+ vertex.u = glyph.uv_left + glyph.uv_width;
+ vertex.v = glyph.uv_bot + glyph.uv_height;
+ self.vertices.push(vertex);
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderApi<'a> {
+ active_tex: &'a mut GLuint,
+ batch: &'a mut Batch,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+ program: &'a mut TextShaderProgram,
+}
+
+impl<'a> Drop for RenderApi<'a> {
+ fn drop(&mut self) {
+ if !self.batch.is_empty() {
+ self.render_batch();
+ }
+ }
+}
+
+impl<'a> LoadGlyph for RenderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
+ fn batch(&mut self) -> &mut Batch {
+ self.batch
+ }
+
+ fn render_batch(&mut self) {
+ unsafe {
+ gl::BufferSubData(
+ gl::ARRAY_BUFFER,
+ 0,
+ self.batch.size() as isize,
+ self.batch.vertices.as_ptr() as *const _,
+ );
+ }
+
+ if *self.active_tex != self.batch.tex() {
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
+ }
+ *self.active_tex = self.batch.tex();
+ }
+
+ unsafe {
+ let num_indices = (self.batch.len() / 4 * 6) as i32;
+
+ // The rendering is inspired by
+ // https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+
+ // Draw background.
+ self.program.set_rendering_pass(RenderingPass::Background);
+ 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());
+
+ // 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 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());
+ }
+
+ self.batch.clear();
+ }
+}
+
+#[repr(C)]
+#[derive(Debug, Copy, Clone)]
+struct TextVertex {
+ // Cell coordinates.
+ x: i16,
+ y: i16,
+
+ // Glyph coordinates.
+ glyph_x: i16,
+ glyph_y: i16,
+
+ // Offsets into Atlas.
+ u: f32,
+ v: f32,
+
+ // Color.
+ r: u8,
+ g: u8,
+ b: u8,
+
+ // Whether the glyph is colored.
+ colored: u8,
+
+ // Background color.
+ bg_r: u8,
+ bg_g: u8,
+ bg_b: u8,
+ 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.
+ program: ShaderProgram,
+
+ /// Projection scale and offset uniform.
+ u_projection: GLint,
+
+ /// 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
+ /// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
+ ///
+ /// Rendering is split into three passes.
+ u_rendering_pass: GLint,
+}
+
+impl TextShaderProgram {
+ pub fn new(shader_version: ShaderVersion) -> Result<Self, Error> {
+ let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
+ Ok(Self {
+ u_projection: program.get_uniform_location(cstr!("projection"))?,
+ u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?,
+ program,
+ })
+ }
+
+ fn set_rendering_pass(&self, rendering_pass: RenderingPass) {
+ unsafe { gl::Uniform1i(self.u_rendering_pass, rendering_pass as i32) }
+ }
+}
+
+impl TextShader for TextShaderProgram {
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn projection_uniform(&self) -> GLint {
+ self.u_projection
+ }
+}
diff --git a/alacritty/src/renderer/text/glsl3.rs b/alacritty/src/renderer/text/glsl3.rs
new file mode 100644
index 00000000..6701cf3d
--- /dev/null
+++ b/alacritty/src/renderer/text/glsl3.rs
@@ -0,0 +1,460 @@
+use std::mem::size_of;
+use std::ptr;
+
+use crossfont::RasterizedGlyph;
+use log::info;
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+use crate::renderer::shader::{ShaderProgram, ShaderVersion};
+use crate::renderer::{cstr, Error};
+
+use super::atlas::{Atlas, ATLAS_SIZE};
+use super::{
+ Glyph, LoadGlyph, LoaderApi, RenderingGlyphFlags, TextRenderApi, TextRenderBatch, TextRenderer,
+ TextShader,
+};
+
+// Shader source.
+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.
+const BATCH_MAX: usize = 0x1_0000;
+
+#[derive(Debug)]
+pub struct Glsl3Renderer {
+ program: TextShaderProgram,
+ vao: GLuint,
+ ebo: GLuint,
+ vbo_instance: GLuint,
+ atlas: Vec<Atlas>,
+ current_atlas: usize,
+ active_tex: GLuint,
+ batch: Batch,
+}
+
+impl Glsl3Renderer {
+ pub fn new() -> Result<Self, Error> {
+ info!("Using OpenGL 3.3 renderer");
+
+ let program = TextShaderProgram::new(ShaderVersion::Glsl3)?;
+ let mut vao: GLuint = 0;
+ let mut ebo: GLuint = 0;
+ let mut vbo_instance: GLuint = 0;
+
+ unsafe {
+ gl::Enable(gl::BLEND);
+ gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
+ gl::Enable(gl::MULTISAMPLE);
+
+ // Disable depth mask, as the renderer never uses depth tests.
+ gl::DepthMask(gl::FALSE);
+
+ gl::GenVertexArrays(1, &mut vao);
+ gl::GenBuffers(1, &mut ebo);
+ gl::GenBuffers(1, &mut vbo_instance);
+ gl::BindVertexArray(vao);
+
+ // ---------------------
+ // Set up element buffer
+ // ---------------------
+ let indices: [u32; 6] = [0, 1, 3, 1, 2, 3];
+
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
+ gl::BufferData(
+ gl::ELEMENT_ARRAY_BUFFER,
+ (6 * size_of::<u32>()) as isize,
+ indices.as_ptr() as *const _,
+ gl::STATIC_DRAW,
+ );
+
+ // ----------------------------
+ // Setup vertex instance buffer
+ // ----------------------------
+ gl::BindBuffer(gl::ARRAY_BUFFER, vbo_instance);
+ gl::BufferData(
+ gl::ARRAY_BUFFER,
+ (BATCH_MAX * size_of::<InstanceData>()) as isize,
+ ptr::null(),
+ gl::STREAM_DRAW,
+ );
+
+ let mut index = 0;
+ let mut size = 0;
+
+ macro_rules! add_attr {
+ ($count:expr, $gl_type:expr, $type:ty) => {
+ gl::VertexAttribPointer(
+ index,
+ $count,
+ $gl_type,
+ gl::FALSE,
+ size_of::<InstanceData>() as i32,
+ size as *const _,
+ );
+ gl::EnableVertexAttribArray(index);
+ gl::VertexAttribDivisor(index, 1);
+
+ #[allow(unused_assignments)]
+ {
+ size += $count * size_of::<$type>();
+ index += 1;
+ }
+ };
+ }
+
+ // Coords.
+ add_attr!(2, gl::UNSIGNED_SHORT, u16);
+
+ // Glyph offset and size.
+ add_attr!(4, gl::SHORT, i16);
+
+ // UV offset.
+ add_attr!(4, gl::FLOAT, f32);
+
+ // Color and cell flags.
+ //
+ // These are packed together because of an OpenGL driver issue on macOS, which caused a
+ // `vec3(u8)` text color and a `u8` cell flags to increase the rendering time by a
+ // huge margin.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Background color.
+ add_attr!(4, gl::UNSIGNED_BYTE, u8);
+
+ // Cleanup.
+ gl::BindVertexArray(0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ }
+
+ Ok(Self {
+ program,
+ vao,
+ ebo,
+ vbo_instance,
+ atlas: vec![Atlas::new(ATLAS_SIZE)],
+ current_atlas: 0,
+ active_tex: 0,
+ batch: Batch::new(),
+ })
+ }
+}
+
+impl<'a> TextRenderer<'a> for Glsl3Renderer {
+ type RenderApi = RenderApi<'a>;
+ type RenderBatch = Batch;
+ type Shader = TextShaderProgram;
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T,
+ {
+ unsafe {
+ gl::UseProgram(self.program.id());
+ self.program.set_term_uniforms(size_info);
+
+ gl::BindVertexArray(self.vao);
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
+ gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance);
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ let res = func(RenderApi {
+ active_tex: &mut self.active_tex,
+ batch: &mut self.batch,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ program: &mut self.program,
+ });
+
+ unsafe {
+ gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
+ gl::BindBuffer(gl::ARRAY_BUFFER, 0);
+ gl::BindVertexArray(0);
+
+ gl::UseProgram(0);
+ }
+
+ res
+ }
+
+ fn program(&self) -> &Self::Shader {
+ &self.program
+ }
+
+ fn loader_api(&mut self) -> LoaderApi<'_> {
+ LoaderApi {
+ active_tex: &mut self.active_tex,
+ atlas: &mut self.atlas,
+ current_atlas: &mut self.current_atlas,
+ }
+ }
+}
+
+impl Drop for Glsl3Renderer {
+ fn drop(&mut self) {
+ unsafe {
+ gl::DeleteBuffers(1, &self.vbo_instance);
+ gl::DeleteBuffers(1, &self.ebo);
+ gl::DeleteVertexArrays(1, &self.vao);
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct RenderApi<'a> {
+ active_tex: &'a mut GLuint,
+ batch: &'a mut Batch,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+ program: &'a mut TextShaderProgram,
+}
+
+impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
+ fn batch(&mut self) -> &mut Batch {
+ self.batch
+ }
+
+ fn render_batch(&mut self) {
+ unsafe {
+ gl::BufferSubData(
+ gl::ARRAY_BUFFER,
+ 0,
+ self.batch.size() as isize,
+ self.batch.instances.as_ptr() as *const _,
+ );
+ }
+
+ // Bind texture if necessary.
+ if *self.active_tex != self.batch.tex() {
+ unsafe {
+ gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
+ }
+ *self.active_tex = self.batch.tex();
+ }
+
+ unsafe {
+ self.program.set_background_pass(true);
+ gl::DrawElementsInstanced(
+ gl::TRIANGLES,
+ 6,
+ gl::UNSIGNED_INT,
+ ptr::null(),
+ self.batch.len() as GLsizei,
+ );
+ self.program.set_background_pass(false);
+ gl::DrawElementsInstanced(
+ gl::TRIANGLES,
+ 6,
+ gl::UNSIGNED_INT,
+ ptr::null(),
+ self.batch.len() as GLsizei,
+ );
+ }
+
+ self.batch.clear();
+ }
+}
+
+impl<'a> LoadGlyph for RenderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+impl<'a> Drop for RenderApi<'a> {
+ fn drop(&mut self) {
+ if !self.batch.is_empty() {
+ self.render_batch();
+ }
+ }
+}
+
+#[derive(Debug)]
+#[repr(C)]
+struct InstanceData {
+ // Coords.
+ col: u16,
+ row: u16,
+
+ // Glyph offset.
+ left: i16,
+ top: i16,
+
+ // Glyph size.
+ width: i16,
+ height: i16,
+
+ // UV offset.
+ uv_left: f32,
+ uv_bot: f32,
+
+ // uv scale.
+ uv_width: f32,
+ uv_height: f32,
+
+ // Color.
+ r: u8,
+ g: u8,
+ b: u8,
+
+ // Cell flags like multicolor or fullwidth character.
+ cell_flags: RenderingGlyphFlags,
+
+ // Background color.
+ bg_r: u8,
+ bg_g: u8,
+ bg_b: u8,
+ bg_a: u8,
+}
+
+#[derive(Debug, Default)]
+pub struct Batch {
+ tex: GLuint,
+ instances: Vec<InstanceData>,
+}
+
+impl TextRenderBatch for Batch {
+ #[inline]
+ fn tex(&self) -> GLuint {
+ self.tex
+ }
+
+ #[inline]
+ fn full(&self) -> bool {
+ self.capacity() == self.len()
+ }
+
+ #[inline]
+ fn is_empty(&self) -> bool {
+ self.len() == 0
+ }
+
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, _: &SizeInfo) {
+ if self.is_empty() {
+ self.tex = glyph.tex_id;
+ }
+
+ let mut cell_flags = RenderingGlyphFlags::empty();
+ cell_flags.set(RenderingGlyphFlags::COLORED, glyph.multicolor);
+ cell_flags.set(RenderingGlyphFlags::WIDE_CHAR, cell.flags.contains(Flags::WIDE_CHAR));
+
+ self.instances.push(InstanceData {
+ col: cell.point.column.0 as u16,
+ row: cell.point.line as u16,
+
+ top: glyph.top,
+ left: glyph.left,
+ width: glyph.width,
+ height: glyph.height,
+
+ uv_bot: glyph.uv_bot,
+ uv_left: glyph.uv_left,
+ uv_width: glyph.uv_width,
+ uv_height: glyph.uv_height,
+
+ r: cell.fg.r,
+ g: cell.fg.g,
+ b: cell.fg.b,
+ cell_flags,
+
+ bg_r: cell.bg.r,
+ bg_g: cell.bg.g,
+ bg_b: cell.bg.b,
+ bg_a: (cell.bg_alpha * 255.0) as u8,
+ });
+ }
+}
+
+impl Batch {
+ #[inline]
+ pub fn new() -> Self {
+ Self { tex: 0, instances: Vec::with_capacity(BATCH_MAX) }
+ }
+
+ #[inline]
+ pub fn len(&self) -> usize {
+ self.instances.len()
+ }
+
+ #[inline]
+ pub fn capacity(&self) -> usize {
+ BATCH_MAX
+ }
+
+ #[inline]
+ pub fn size(&self) -> usize {
+ self.len() * size_of::<InstanceData>()
+ }
+
+ pub fn clear(&mut self) {
+ self.tex = 0;
+ self.instances.clear();
+ }
+}
+
+/// Text drawing program.
+///
+/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
+#[derive(Debug)]
+pub struct TextShaderProgram {
+ /// Shader program.
+ program: ShaderProgram,
+
+ /// Projection scale and offset uniform.
+ u_projection: GLint,
+
+ /// Cell dimensions (pixels).
+ u_cell_dim: GLint,
+
+ /// Background pass flag.
+ ///
+ /// Rendering is split into two passes; 1 for backgrounds, and one for text.
+ u_background: GLint,
+}
+
+impl TextShaderProgram {
+ pub fn new(shader_version: ShaderVersion) -> Result<TextShaderProgram, Error> {
+ let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
+ 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"))?,
+ program,
+ })
+ }
+
+ fn set_term_uniforms(&self, props: &SizeInfo) {
+ unsafe {
+ gl::Uniform2f(self.u_cell_dim, props.cell_width(), props.cell_height());
+ }
+ }
+
+ fn set_background_pass(&self, background_pass: bool) {
+ let value = if background_pass { 1 } else { 0 };
+
+ unsafe {
+ gl::Uniform1i(self.u_background, value);
+ }
+ }
+}
+
+impl TextShader for TextShaderProgram {
+ fn id(&self) -> GLuint {
+ self.program.id()
+ }
+
+ fn projection_uniform(&self) -> GLint {
+ self.u_projection
+ }
+}
diff --git a/alacritty/src/renderer/text/glyph_cache.rs b/alacritty/src/renderer/text/glyph_cache.rs
new file mode 100644
index 00000000..c75cad7a
--- /dev/null
+++ b/alacritty/src/renderer/text/glyph_cache.rs
@@ -0,0 +1,322 @@
+use std::collections::HashMap;
+use std::hash::BuildHasherDefault;
+
+use crossfont::{
+ Error as RasterizerError, FontDesc, FontKey, GlyphKey, Metrics, Rasterize, RasterizedGlyph,
+ Rasterizer, Size, Slant, Style, Weight,
+};
+use fnv::FnvHasher;
+use log::{error, info};
+use unicode_width::UnicodeWidthChar;
+
+use crate::config::font::{Font, FontDescription};
+use crate::config::ui_config::Delta;
+use crate::gl::types::*;
+
+use super::builtin_font;
+
+/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
+pub trait LoadGlyph {
+ /// Load the rasterized glyph into GPU memory.
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
+
+ /// Clear any state accumulated from previous loaded glyphs.
+ ///
+ /// This can, for instance, be used to reset the texture Atlas.
+ fn clear(&mut self);
+}
+
+#[derive(Copy, Clone, Debug)]
+pub struct Glyph {
+ pub tex_id: GLuint,
+ pub multicolor: bool,
+ pub top: i16,
+ pub left: i16,
+ pub width: i16,
+ pub height: i16,
+ pub uv_bot: f32,
+ pub uv_left: f32,
+ pub uv_width: f32,
+ pub uv_height: f32,
+}
+
+/// Naïve glyph cache.
+///
+/// Currently only keyed by `char`, and thus not possible to hold different
+/// representations of the same code point.
+pub struct GlyphCache {
+ /// Cache of buffered glyphs.
+ cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
+
+ /// Rasterizer for loading new glyphs.
+ rasterizer: Rasterizer,
+
+ /// Regular font.
+ pub font_key: FontKey,
+
+ /// Bold font.
+ pub bold_key: FontKey,
+
+ /// Italic font.
+ pub italic_key: FontKey,
+
+ /// Bold italic font.
+ pub bold_italic_key: FontKey,
+
+ /// Font size.
+ pub font_size: crossfont::Size,
+
+ /// Font offset.
+ font_offset: Delta<i8>,
+
+ /// Glyph offset.
+ glyph_offset: Delta<i8>,
+
+ /// Font metrics.
+ metrics: Metrics,
+
+ /// Whether to use the built-in font for box drawing characters.
+ builtin_box_drawing: bool,
+}
+
+impl GlyphCache {
+ pub fn new(mut rasterizer: Rasterizer, font: &Font) -> Result<GlyphCache, crossfont::Error> {
+ let (regular, bold, italic, bold_italic) = Self::compute_font_keys(font, &mut rasterizer)?;
+
+ // Need to load at least one glyph for the face before calling metrics.
+ // The glyph requested here ('m' at the time of writing) has no special
+ // meaning.
+ rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
+
+ let metrics = rasterizer.metrics(regular, font.size())?;
+
+ Ok(Self {
+ cache: HashMap::default(),
+ rasterizer,
+ font_size: font.size(),
+ font_key: regular,
+ bold_key: bold,
+ italic_key: italic,
+ bold_italic_key: bold_italic,
+ font_offset: font.offset,
+ glyph_offset: font.glyph_offset,
+ metrics,
+ builtin_box_drawing: font.builtin_box_drawing,
+ })
+ }
+
+ fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
+ let size = self.font_size;
+
+ // Cache all ascii characters.
+ for i in 32u8..=126u8 {
+ self.get(GlyphKey { font_key: font, character: i as char, size }, loader, true);
+ }
+ }
+
+ /// Computes font keys for (Regular, Bold, Italic, Bold Italic).
+ fn compute_font_keys(
+ font: &Font,
+ rasterizer: &mut Rasterizer,
+ ) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
+ let size = font.size();
+
+ // Load regular font.
+ let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
+
+ let regular = Self::load_regular_font(rasterizer, &regular_desc, size)?;
+
+ // Helper to load a description if it is not the `regular_desc`.
+ let mut load_or_regular = |desc: FontDesc| {
+ if desc == regular_desc {
+ regular
+ } else {
+ rasterizer.load_font(&desc, size).unwrap_or(regular)
+ }
+ };
+
+ // Load bold font.
+ let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
+
+ let bold = load_or_regular(bold_desc);
+
+ // Load italic font.
+ let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
+
+ let italic = load_or_regular(italic_desc);
+
+ // Load bold italic font.
+ let bold_italic_desc = Self::make_desc(&font.bold_italic(), Slant::Italic, Weight::Bold);
+
+ let bold_italic = load_or_regular(bold_italic_desc);
+
+ Ok((regular, bold, italic, bold_italic))
+ }
+
+ fn load_regular_font(
+ rasterizer: &mut Rasterizer,
+ description: &FontDesc,
+ size: Size,
+ ) -> Result<FontKey, crossfont::Error> {
+ match rasterizer.load_font(description, size) {
+ Ok(font) => Ok(font),
+ Err(err) => {
+ error!("{}", err);
+
+ let fallback_desc =
+ Self::make_desc(Font::default().normal(), Slant::Normal, Weight::Normal);
+ rasterizer.load_font(&fallback_desc, size)
+ },
+ }
+ }
+
+ fn make_desc(desc: &FontDescription, slant: Slant, weight: Weight) -> FontDesc {
+ let style = if let Some(ref spec) = desc.style {
+ Style::Specific(spec.to_owned())
+ } else {
+ Style::Description { slant, weight }
+ };
+ FontDesc::new(desc.family.clone(), style)
+ }
+
+ /// Get a glyph from the font.
+ ///
+ /// If the glyph has never been loaded before, it will be rasterized and inserted into the
+ /// cache.
+ ///
+ /// # Errors
+ ///
+ /// This will fail when the glyph could not be rasterized. Usually this is due to the glyph
+ /// not being present in any font.
+ pub fn get<L: ?Sized>(
+ &mut self,
+ glyph_key: GlyphKey,
+ loader: &mut L,
+ show_missing: bool,
+ ) -> Glyph
+ where
+ L: LoadGlyph,
+ {
+ // Try to load glyph from cache.
+ if let Some(glyph) = self.cache.get(&glyph_key) {
+ return *glyph;
+ };
+
+ // Rasterize the glyph using the built-in font for special characters or the user's font
+ // for everything else.
+ let rasterized = self
+ .builtin_box_drawing
+ .then(|| {
+ builtin_font::builtin_glyph(
+ glyph_key.character,
+ &self.metrics,
+ &self.font_offset,
+ &self.glyph_offset,
+ )
+ })
+ .flatten()
+ .map_or_else(|| self.rasterizer.get_glyph(glyph_key), Ok);
+
+ let glyph = match rasterized {
+ Ok(rasterized) => self.load_glyph(loader, rasterized),
+ // Load fallback glyph.
+ Err(RasterizerError::MissingGlyph(rasterized)) if show_missing => {
+ // Use `\0` as "missing" glyph to cache it only once.
+ let missing_key = GlyphKey { character: '\0', ..glyph_key };
+ if let Some(glyph) = self.cache.get(&missing_key) {
+ *glyph
+ } else {
+ // If no missing glyph was loaded yet, insert it as `\0`.
+ let glyph = self.load_glyph(loader, rasterized);
+ self.cache.insert(missing_key, glyph);
+
+ glyph
+ }
+ },
+ Err(_) => self.load_glyph(loader, Default::default()),
+ };
+
+ // Cache rasterized glyph.
+ *self.cache.entry(glyph_key).or_insert(glyph)
+ }
+
+ /// Load glyph into the atlas.
+ ///
+ /// This will apply all transforms defined for the glyph cache to the rasterized glyph before
+ pub fn load_glyph<L: ?Sized>(&self, loader: &mut L, mut glyph: RasterizedGlyph) -> Glyph
+ where
+ L: LoadGlyph,
+ {
+ glyph.left += i32::from(self.glyph_offset.x);
+ glyph.top += i32::from(self.glyph_offset.y);
+ glyph.top -= self.metrics.descent as i32;
+
+ // The metrics of zero-width characters are based on rendering
+ // the character after the current cell, with the anchor at the
+ // right side of the preceding character. Since we render the
+ // zero-width characters inside the preceding character, the
+ // anchor has been moved to the right by one cell.
+ if glyph.character.width() == Some(0) {
+ glyph.left += self.metrics.average_advance as i32;
+ }
+
+ // Add glyph to cache.
+ loader.load_glyph(&glyph)
+ }
+
+ /// Clear currently cached data in both GL and the registry.
+ pub fn clear_glyph_cache<L: LoadGlyph>(&mut self, loader: &mut L) {
+ loader.clear();
+ self.cache = HashMap::default();
+
+ self.load_common_glyphs(loader);
+ }
+
+ pub fn update_font_size<L: LoadGlyph>(
+ &mut self,
+ font: &Font,
+ scale_factor: f64,
+ loader: &mut L,
+ ) -> Result<(), crossfont::Error> {
+ // Update dpi scaling.
+ self.rasterizer.update_dpr(scale_factor as f32);
+ self.font_offset = font.offset;
+
+ // Recompute font keys.
+ let (regular, bold, italic, bold_italic) =
+ Self::compute_font_keys(font, &mut self.rasterizer)?;
+
+ self.rasterizer.get_glyph(GlyphKey {
+ font_key: regular,
+ character: 'm',
+ size: font.size(),
+ })?;
+ let metrics = self.rasterizer.metrics(regular, font.size())?;
+
+ info!("Font size changed to {:?} with scale factor of {}", font.size(), scale_factor);
+
+ self.font_size = font.size();
+ self.font_key = regular;
+ self.bold_key = bold;
+ self.italic_key = italic;
+ self.bold_italic_key = bold_italic;
+ self.metrics = metrics;
+ self.builtin_box_drawing = font.builtin_box_drawing;
+
+ self.clear_glyph_cache(loader);
+
+ Ok(())
+ }
+
+ pub fn font_metrics(&self) -> crossfont::Metrics {
+ self.metrics
+ }
+
+ /// Prefetch glyphs that are almost guaranteed to be loaded anyways.
+ pub fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
+ self.load_glyphs_for_font(self.font_key, loader);
+ self.load_glyphs_for_font(self.bold_key, loader);
+ self.load_glyphs_for_font(self.italic_key, loader);
+ self.load_glyphs_for_font(self.bold_italic_key, loader);
+ }
+}
diff --git a/alacritty/src/renderer/text/mod.rs b/alacritty/src/renderer/text/mod.rs
new file mode 100644
index 00000000..05ac59e8
--- /dev/null
+++ b/alacritty/src/renderer/text/mod.rs
@@ -0,0 +1,202 @@
+use bitflags::bitflags;
+use crossfont::{GlyphKey, RasterizedGlyph};
+
+use alacritty_terminal::term::cell::Flags;
+use alacritty_terminal::term::SizeInfo;
+
+use crate::display::content::RenderableCell;
+use crate::gl;
+use crate::gl::types::*;
+
+mod atlas;
+mod builtin_font;
+mod gles2;
+mod glsl3;
+pub mod glyph_cache;
+
+use atlas::Atlas;
+pub use gles2::Gles2Renderer;
+pub use glsl3::Glsl3Renderer;
+pub use glyph_cache::GlyphCache;
+use glyph_cache::{Glyph, LoadGlyph};
+
+// NOTE: These flags must be in sync with their usage in the text.*.glsl shaders.
+bitflags! {
+ #[repr(C)]
+ struct RenderingGlyphFlags: u8 {
+ const WIDE_CHAR = 0b0000_0001;
+ const COLORED = 0b0000_0010;
+ }
+}
+
+pub trait TextRenderer<'a> {
+ type Shader: TextShader;
+ type RenderBatch: TextRenderBatch;
+ type RenderApi: TextRenderApi<Self::RenderBatch>;
+
+ /// Get loader API for the renderer.
+ fn loader_api(&mut self) -> LoaderApi<'_>;
+
+ /// Draw cells.
+ fn draw_cells<'b: 'a, I: Iterator<Item = RenderableCell>>(
+ &'b mut self,
+ size_info: &'b SizeInfo,
+ glyph_cache: &'a mut GlyphCache,
+ cells: I,
+ ) {
+ self.with_api(size_info, |mut api| {
+ for cell in cells {
+ api.draw_cell(cell, glyph_cache, size_info);
+ }
+ })
+ }
+
+ fn with_api<'b: 'a, F, T>(&'b mut self, size_info: &'b SizeInfo, func: F) -> T
+ where
+ F: FnOnce(Self::RenderApi) -> T;
+
+ fn program(&self) -> &Self::Shader;
+
+ /// Resize the text rendering.
+ fn resize(&self, size: &SizeInfo) {
+ unsafe {
+ let program = self.program();
+ gl::UseProgram(program.id());
+ update_projection(program.projection_uniform(), size);
+ gl::UseProgram(0);
+ }
+ }
+
+ /// Invoke renderer with the loader.
+ fn with_loader<F: FnOnce(LoaderApi<'_>) -> T, T>(&mut self, func: F) -> T {
+ unsafe {
+ gl::ActiveTexture(gl::TEXTURE0);
+ }
+
+ func(self.loader_api())
+ }
+}
+
+pub trait TextRenderBatch {
+ /// Check if `Batch` is empty.
+ fn is_empty(&self) -> bool;
+
+ /// Check whether the `Batch` is full.
+ fn full(&self) -> bool;
+
+ /// Get texture `Batch` is using.
+ fn tex(&self) -> GLuint;
+
+ /// Add item to the batch.
+ fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo);
+}
+
+pub trait TextRenderApi<T: TextRenderBatch>: LoadGlyph {
+ /// Get `Batch` the api is using.
+ fn batch(&mut self) -> &mut T;
+
+ /// Render the underlying data.
+ fn render_batch(&mut self);
+
+ /// Add item to the rendering queue.
+ #[inline]
+ fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
+ // Flush batch if tex changing.
+ if !self.batch().is_empty() && self.batch().tex() != glyph.tex_id {
+ self.render_batch();
+ }
+
+ self.batch().add_item(cell, glyph, size_info);
+
+ // Render batch and clear if it's full.
+ if self.batch().full() {
+ self.render_batch();
+ }
+ }
+
+ /// Draw cell.
+ fn draw_cell(
+ &mut self,
+ mut cell: RenderableCell,
+ glyph_cache: &mut GlyphCache,
+ size_info: &SizeInfo,
+ ) {
+ // Get font key for cell.
+ let font_key = match cell.flags & Flags::BOLD_ITALIC {
+ Flags::BOLD_ITALIC => glyph_cache.bold_italic_key,
+ Flags::ITALIC => glyph_cache.italic_key,
+ Flags::BOLD => glyph_cache.bold_key,
+ _ => glyph_cache.font_key,
+ };
+
+ // Ignore hidden cells and render tabs as spaces to prevent font issues.
+ let hidden = cell.flags.contains(Flags::HIDDEN);
+ if cell.character == '\t' || hidden {
+ cell.character = ' ';
+ }
+
+ let mut glyph_key =
+ GlyphKey { font_key, size: glyph_cache.font_size, character: cell.character };
+
+ // Add cell to batch.
+ let glyph = glyph_cache.get(glyph_key, self, true);
+ self.add_render_item(&cell, &glyph, size_info);
+
+ // Render visible zero-width characters.
+ if let Some(zerowidth) = cell.zerowidth.take().filter(|_| !hidden) {
+ for character in zerowidth {
+ glyph_key.character = character;
+ let glyph = glyph_cache.get(glyph_key, self, false);
+ self.add_render_item(&cell, &glyph, size_info);
+ }
+ }
+ }
+}
+
+pub trait TextShader {
+ fn id(&self) -> GLuint;
+
+ /// Id of the projection uniform.
+ fn projection_uniform(&self) -> GLint;
+}
+
+#[derive(Debug)]
+pub struct LoaderApi<'a> {
+ active_tex: &'a mut GLuint,
+ atlas: &'a mut Vec<Atlas>,
+ current_atlas: &'a mut usize,
+}
+
+impl<'a> LoadGlyph for LoaderApi<'a> {
+ fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
+ Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
+ }
+
+ fn clear(&mut self) {
+ Atlas::clear_atlas(self.atlas, self.current_atlas)
+ }
+}
+
+fn update_projection(u_projection: GLint, size: &SizeInfo) {
+ let width = size.width();
+ let height = size.height();
+ let padding_x = size.padding_x();
+ let padding_y = size.padding_y();
+
+ // Bounds check.
+ if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
+ return;
+ }
+
+ // Compute scale and offset factors, from pixel to ndc space. Y is inverted.
+ // [0, width - 2 * padding_x] to [-1, 1]
+ // [height - 2 * padding_y, 0] to [-1, 1]
+ let scale_x = 2. / (width - 2. * padding_x);
+ let scale_y = -2. / (height - 2. * padding_y);
+ let offset_x = -1.;
+ let offset_y = 1.;
+
+ unsafe {
+ gl::Uniform4f(u_projection, offset_x, offset_y, scale_x, scale_y);
+ }
+}