mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
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>
This commit is contained in:
parent
00383ae967
commit
1880522b64
18 changed files with 2077 additions and 1252 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
57
alacritty/res/gles2/text.f.glsl
Normal file
57
alacritty/res/gles2/text.f.glsl
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
46
alacritty/res/gles2/text.v.glsl
Normal file
46
alacritty/res/gles2/text.v.glsl
Normal file
|
@ -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.);
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
#version 330 core
|
||||
in vec2 TexCoords;
|
||||
flat in vec4 fg;
|
||||
flat in vec4 bg;
|
|
@ -1,4 +1,3 @@
|
|||
#version 330 core
|
||||
// Cell properties.
|
||||
layout(location = 0) in vec2 gridCoords;
|
||||
|
|
@ -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;
|
||||
#define FRAG_COLOR FragColor
|
||||
|
||||
flat in color_t color;
|
||||
|
||||
#endif
|
||||
|
||||
uniform int rectKind;
|
||||
uniform float_t cellWidth;
|
||||
uniform float_t cellHeight;
|
||||
uniform float_t paddingY;
|
||||
uniform float_t paddingX;
|
||||
|
||||
uniform float cellWidth;
|
||||
uniform float cellHeight;
|
||||
uniform float paddingY;
|
||||
uniform float paddingX;
|
||||
uniform float_t underlinePosition;
|
||||
uniform float_t underlineThickness;
|
||||
|
||||
uniform float underlinePosition;
|
||||
uniform float underlineThickness;
|
||||
|
||||
uniform float undercurlPosition;
|
||||
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);
|
||||
float_t x = floor(mod(gl_FragCoord.x - paddingX, cellWidth));
|
||||
float_t y = floor(mod(gl_FragCoord.y - paddingY, cellHeight));
|
||||
|
||||
switch (rectKind) {
|
||||
case UNDERCURL:
|
||||
FragColor = draw_undercurl(x, y);
|
||||
break;
|
||||
case DOTTED:
|
||||
if (underlineThickness < 2) {
|
||||
FragColor = draw_dotted(x, y);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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"))?,
|
||||
|
|
|
@ -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 {
|
||||
|
|
273
alacritty/src/renderer/text/atlas.rs
Normal file
273
alacritty/src/renderer/text/atlas.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
478
alacritty/src/renderer/text/gles2.rs
Normal file
478
alacritty/src/renderer/text/gles2.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
460
alacritty/src/renderer/text/glsl3.rs
Normal file
460
alacritty/src/renderer/text/glsl3.rs
Normal file
|
@ -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
|
||||
}
|
||||
}
|
322
alacritty/src/renderer/text/glyph_cache.rs
Normal file
322
alacritty/src/renderer/text/glyph_cache.rs
Normal file
|
@ -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, ®ular_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);
|
||||
}
|
||||
}
|
202
alacritty/src/renderer/text/mod.rs
Normal file
202
alacritty/src/renderer/text/mod.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue