alacritty/alacritty/src/renderer/mod.rs

1449 lines
42 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
use std::fmt::{self, Display, Formatter};
use std::hash::BuildHasherDefault;
2016-02-26 04:59:21 +00:00
use std::mem::size_of;
use std::{io, ptr};
2016-02-26 04:59:21 +00:00
use bitflags::bitflags;
2020-07-18 01:27:41 +00:00
use crossfont::{
BitmapBuffer, Error as RasterizerError, FontDesc, FontKey, GlyphKey, Rasterize,
RasterizedGlyph, Rasterizer, Size, Slant, Style, Weight,
};
2020-07-18 01:27:41 +00:00
use fnv::FnvHasher;
use log::{error, info};
use unicode_width::UnicodeWidthChar;
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;
2020-07-18 01:27:41 +00:00
use crate::gl;
use crate::gl::types::*;
use crate::renderer::rects::{RectRenderer, RenderRect};
2020-07-18 01:27:41 +00:00
pub mod builtin_font;
2020-07-18 01:27:41 +00:00
pub mod rects;
// Shader source.
static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl");
static TEXT_SHADER_V: &str = include_str!("../../res/text.v.glsl");
2020-05-05 22:50:23 +00:00
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
pub trait LoadGlyph {
2020-05-05 22:50:23 +00:00
/// Load the rasterized glyph into GPU memory.
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
2020-05-05 22:50:23 +00:00
/// 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 {
ShaderCreation(ShaderCreationError),
}
2020-01-03 00:17:22 +00:00
impl std::error::Error for Error {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Error::ShaderCreation(err) => err.source(),
}
}
}
2020-01-03 00:17:22 +00:00
impl Display for Error {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
Error::ShaderCreation(err) => {
write!(f, "There was an error initializing the shaders: {}", err)
},
}
}
}
impl From<ShaderCreationError> for Error {
2020-01-03 00:17:22 +00:00
fn from(val: ShaderCreationError) -> Self {
Error::ShaderCreation(val)
}
}
2020-05-05 22:50:23 +00:00
/// Text drawing program.
///
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
#[derive(Debug)]
pub struct TextShaderProgram {
2020-05-05 22:50:23 +00:00
/// Program id.
id: GLuint,
2020-05-05 22:50:23 +00:00
/// Projection scale and offset uniform.
u_projection: GLint,
2020-05-05 22:50:23 +00:00
/// Cell dimensions (pixels).
u_cell_dim: GLint,
2020-05-05 22:50:23 +00:00
/// Background pass flag.
///
2020-05-05 22:50:23 +00:00
/// 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,
}
2020-05-05 22:50:23 +00:00
/// 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 {
2020-05-05 22:50:23 +00:00
/// Cache of buffered glyphs.
cache: HashMap<GlyphKey, Glyph, BuildHasherDefault<FnvHasher>>,
2020-05-05 22:50:23 +00:00
/// Rasterizer for loading new glyphs.
rasterizer: Rasterizer,
2020-05-05 22:50:23 +00:00
/// Regular font.
font_key: FontKey,
2020-05-05 22:50:23 +00:00
/// Bold font.
bold_key: FontKey,
2020-05-05 22:50:23 +00:00
/// Italic font.
italic_key: FontKey,
2020-05-05 22:50:23 +00:00
/// Bold italic font.
bold_italic_key: FontKey,
2020-05-05 22:50:23 +00:00
/// Font size.
2020-07-18 01:27:41 +00:00
font_size: crossfont::Size,
/// Font offset.
font_offset: Delta<i8>,
2020-05-05 22:50:23 +00:00
/// Glyph offset.
glyph_offset: Delta<i8>,
2020-05-05 22:50:23 +00:00
/// Font metrics.
2020-07-18 01:27:41 +00:00
metrics: crossfont::Metrics,
/// Whether to use the built-in font for box drawing characters.
builtin_box_drawing: bool,
}
impl GlyphCache {
pub fn new<L>(
mut rasterizer: Rasterizer,
font: &Font,
loader: &mut L,
2020-07-18 01:27:41 +00:00
) -> Result<GlyphCache, crossfont::Error>
where
L: LoadGlyph,
{
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() })?;
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
let metrics = rasterizer.metrics(regular, font.size())?;
2020-01-03 00:17:22 +00:00
let mut cache = Self {
cache: HashMap::default(),
rasterizer,
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
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,
};
cache.load_common_glyphs(loader);
Ok(cache)
}
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);
2017-05-13 09:46:31 +00:00
}
}
2020-05-05 22:50:23 +00:00
/// Computes font keys for (Regular, Bold, Italic, Bold Italic).
fn compute_font_keys(
font: &Font,
rasterizer: &mut Rasterizer,
2020-07-18 01:27:41 +00:00
) -> Result<(FontKey, FontKey, FontKey, FontKey), crossfont::Error> {
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
let size = font.size();
2017-05-13 09:46:31 +00:00
2020-05-05 22:50:23 +00:00
// Load regular font.
2021-07-03 03:06:52 +00:00
let regular_desc = Self::make_desc(font.normal(), Slant::Normal, Weight::Normal);
let regular = Self::load_regular_font(rasterizer, &regular_desc, size)?;
2020-05-05 22:50:23 +00:00
// Helper to load a description if it is not the `regular_desc`.
let mut load_or_regular = |desc: FontDesc| {
2017-05-13 09:46:31 +00:00
if desc == regular_desc {
regular
} else {
rasterizer.load_font(&desc, size).unwrap_or(regular)
2017-05-13 09:46:31 +00:00
}
};
2020-05-05 22:50:23 +00:00
// Load bold font.
let bold_desc = Self::make_desc(&font.bold(), Slant::Normal, Weight::Bold);
2017-05-13 09:46:31 +00:00
let bold = load_or_regular(bold_desc);
2020-05-05 22:50:23 +00:00
// Load italic font.
let italic_desc = Self::make_desc(&font.italic(), Slant::Italic, Weight::Normal);
let italic = load_or_regular(italic_desc);
2020-05-05 22:50:23 +00:00
// 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,
2020-07-18 01:27:41 +00:00
) -> Result<FontKey, crossfont::Error> {
match rasterizer.load_font(description, size) {
Ok(font) => Ok(font),
Err(err) => {
error!("{}", err);
let fallback_desc =
2021-07-03 03:06:52 +00:00
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.
fn get<L>(&mut self, glyph_key: GlyphKey, loader: &mut L, show_missing: bool) -> Glyph
2019-03-30 16:48:36 +00:00
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
/// 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;
}
// Add glyph to cache.
loader.load_glyph(&glyph)
}
2019-03-30 16:48:36 +00:00
/// 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,
Upgrade Glutin to v0.19.0 Some changes include: • Use the with_hardware_acceleration function on the ContextBuilder to not require the discrete GPU • Remove the LMenu and RMenu virtual key codes (winit 0.16.0 removed these because Windows now generates LAlt and RAlt instead • Replace set_cursor_state with hide_cursor (winit 0.16.0 removed the set_cursor_state function) • Replace GlWindow::hidpi_factor with GlWindow::get_hidpi_factor and change to expecting an f64 • Use the glutin/winit dpi size and position types where possible Glutin's dpi change event has been implemented. All size events now return logical sizes. As a result of that, the logical sizes are translated in the `display::handle_rezize` method so DPI scaling works correctly. When the DPI is changed, the glyph cache is updated to make use of the correct font size again. Moving a window to a different screen which is a different DPI caused a racing condition where the logical size of the event was sent to the `handle_resize` method in `src/display.rs`, however if there was a DPI change event before `handle_resize` is able to process this message, it would incorrectly use the new DPI to scale the resize event. To solve this issue instead of sending the logical size to the `handle_resize` method and then converting it to a physical size in there, the `LogicalSize` of the resize event is transformed into a `PhysicalSize` as soon as it's received. This fixes potential racing conditions since all events are processed in order. The padding has been changed so it's also scaled by DPR. The `scale_with_dpi` config option has been removed. If it's not present a warning will be emitted. The `winit` dependency on Windows has been removed. All interactions with winit in Alacritty are handled through glutin.
2018-11-10 16:08:48 +00:00
dpr: f64,
2019-03-30 16:48:36 +00:00
loader: &mut L,
2020-07-18 01:27:41 +00:00
) -> Result<(), crossfont::Error> {
2020-05-05 22:50:23 +00:00
// Update dpi scaling.
Upgrade Glutin to v0.19.0 Some changes include: • Use the with_hardware_acceleration function on the ContextBuilder to not require the discrete GPU • Remove the LMenu and RMenu virtual key codes (winit 0.16.0 removed these because Windows now generates LAlt and RAlt instead • Replace set_cursor_state with hide_cursor (winit 0.16.0 removed the set_cursor_state function) • Replace GlWindow::hidpi_factor with GlWindow::get_hidpi_factor and change to expecting an f64 • Use the glutin/winit dpi size and position types where possible Glutin's dpi change event has been implemented. All size events now return logical sizes. As a result of that, the logical sizes are translated in the `display::handle_rezize` method so DPI scaling works correctly. When the DPI is changed, the glyph cache is updated to make use of the correct font size again. Moving a window to a different screen which is a different DPI caused a racing condition where the logical size of the event was sent to the `handle_resize` method in `src/display.rs`, however if there was a DPI change event before `handle_resize` is able to process this message, it would incorrectly use the new DPI to scale the resize event. To solve this issue instead of sending the logical size to the `handle_resize` method and then converting it to a physical size in there, the `LogicalSize` of the resize event is transformed into a `PhysicalSize` as soon as it's received. This fixes potential racing conditions since all events are processed in order. The padding has been changed so it's also scaled by DPR. The `scale_with_dpi` config option has been removed. If it's not present a warning will be emitted. The `winit` dependency on Windows has been removed. All interactions with winit in Alacritty are handled through glutin.
2018-11-10 16:08:48 +00:00
self.rasterizer.update_dpr(dpr as f32);
self.font_offset = font.offset;
Upgrade Glutin to v0.19.0 Some changes include: • Use the with_hardware_acceleration function on the ContextBuilder to not require the discrete GPU • Remove the LMenu and RMenu virtual key codes (winit 0.16.0 removed these because Windows now generates LAlt and RAlt instead • Replace set_cursor_state with hide_cursor (winit 0.16.0 removed the set_cursor_state function) • Replace GlWindow::hidpi_factor with GlWindow::get_hidpi_factor and change to expecting an f64 • Use the glutin/winit dpi size and position types where possible Glutin's dpi change event has been implemented. All size events now return logical sizes. As a result of that, the logical sizes are translated in the `display::handle_rezize` method so DPI scaling works correctly. When the DPI is changed, the glyph cache is updated to make use of the correct font size again. Moving a window to a different screen which is a different DPI caused a racing condition where the logical size of the event was sent to the `handle_resize` method in `src/display.rs`, however if there was a DPI change event before `handle_resize` is able to process this message, it would incorrectly use the new DPI to scale the resize event. To solve this issue instead of sending the logical size to the `handle_resize` method and then converting it to a physical size in there, the `LogicalSize` of the resize event is transformed into a `PhysicalSize` as soon as it's received. This fixes potential racing conditions since all events are processed in order. The padding has been changed so it's also scaled by DPR. The `scale_with_dpi` config option has been removed. If it's not present a warning will be emitted. The `winit` dependency on Windows has been removed. All interactions with winit in Alacritty are handled through glutin.
2018-11-10 16:08:48 +00:00
2020-05-05 22:50:23 +00:00
// 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(),
})?;
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
let metrics = self.rasterizer.metrics(regular, font.size())?;
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
info!("Font size changed to {:?} with DPR of {}", font.size(), dpr);
Upgrade Glutin to v0.19.0 Some changes include: • Use the with_hardware_acceleration function on the ContextBuilder to not require the discrete GPU • Remove the LMenu and RMenu virtual key codes (winit 0.16.0 removed these because Windows now generates LAlt and RAlt instead • Replace set_cursor_state with hide_cursor (winit 0.16.0 removed the set_cursor_state function) • Replace GlWindow::hidpi_factor with GlWindow::get_hidpi_factor and change to expecting an f64 • Use the glutin/winit dpi size and position types where possible Glutin's dpi change event has been implemented. All size events now return logical sizes. As a result of that, the logical sizes are translated in the `display::handle_rezize` method so DPI scaling works correctly. When the DPI is changed, the glyph cache is updated to make use of the correct font size again. Moving a window to a different screen which is a different DPI caused a racing condition where the logical size of the event was sent to the `handle_resize` method in `src/display.rs`, however if there was a DPI change event before `handle_resize` is able to process this message, it would incorrectly use the new DPI to scale the resize event. To solve this issue instead of sending the logical size to the `handle_resize` method and then converting it to a physical size in there, the `LogicalSize` of the resize event is transformed into a `PhysicalSize` as soon as it's received. This fixes potential racing conditions since all events are processed in order. The padding has been changed so it's also scaled by DPR. The `scale_with_dpi` config option has been removed. If it's not present a warning will be emitted. The `winit` dependency on Windows has been removed. All interactions with winit in Alacritty are handled through glutin.
2018-11-10 16:08:48 +00:00
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
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(())
}
2020-07-18 01:27:41 +00:00
pub fn font_metrics(&self) -> crossfont::Metrics {
2020-01-24 23:42:23 +00:00
self.metrics
}
/// Prefetch glyphs that are almost guaranteed to be loaded anyways.
fn load_common_glyphs<L: LoadGlyph>(&mut self, loader: &mut L) {
self.load_glyphs_for_font(self.font_key, loader);
2021-05-20 19:48:32 +00:00
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);
}
2020-05-05 22:50:23 +00:00
/// Calculate font metrics without access to a glyph cache.
2020-07-18 01:27:41 +00:00
pub fn static_metrics(font: Font, dpr: f64) -> Result<crossfont::Metrics, crossfont::Error> {
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
let mut rasterizer = crossfont::Rasterizer::new(dpr as f32, font.use_thin_strokes)?;
2021-07-03 03:06:52 +00:00
let regular_desc = GlyphCache::make_desc(font.normal(), Slant::Normal, Weight::Normal);
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
let regular = Self::load_regular_font(&mut rasterizer, &regular_desc, font.size())?;
rasterizer.get_glyph(GlyphKey { font_key: regular, character: 'm', size: font.size() })?;
Replace serde's derive with custom proc macro This replaces the existing `Deserialize` derive from serde with a `ConfigDeserialize` derive. The goal of this new proc macro is to allow a more error-friendly deserialization for the Alacritty configuration file without having to manage a lot of boilerplate code inside the configuration modules. The first part of the derive macro is for struct deserialization. This takes structs which have `Default` implemented and will only replace fields which can be successfully deserialized. Otherwise the `log` crate is used for printing errors. Since this deserialization takes the default value from the struct instead of the value, it removes the necessity for creating new types just to implement `Default` on them for deserialization. Additionally, the struct deserialization also checks for `Option` values and makes sure that explicitly specifying `none` as text literal is allowed for all options. The other part of the derive macro is responsible for deserializing enums. While only enums with Unit variants are supported, it will automatically implement a deserializer for these enums which accepts any form of capitalization. Since this custom derive prevents us from using serde's attributes on fields, some of the attributes have been reimplemented for `ConfigDeserialize`. These include `#[config(flatten)]`, `#[config(skip)]` and `#[config(alias = "alias)]`. The flatten attribute is currently limited to at most one per struct. Additionally the `#[config(deprecated = "optional message")]` attribute allows easily defining uniform deprecation messages for fields on structs.
2020-12-21 02:44:38 +00:00
rasterizer.metrics(regular, font.size())
}
}
// 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 {
2020-05-05 22:50:23 +00:00
// Coords.
col: u16,
row: u16,
2020-05-05 22:50:23 +00:00
// Glyph offset.
left: i16,
top: i16,
// Glyph size.
width: i16,
height: i16,
// UV offset.
uv_left: f32,
uv_bot: f32,
2020-05-05 22:50:23 +00:00
// uv scale.
uv_width: f32,
uv_height: f32,
2020-05-05 22:50:23 +00:00
// 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)]
2016-02-26 04:59:21 +00:00
pub struct QuadRenderer {
program: TextShaderProgram,
2016-02-26 04:59:21 +00:00
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,
2016-02-26 04:59:21 +00:00
}
#[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]
2020-01-03 00:17:22 +00:00
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,
});
}
#[inline]
pub fn full(&self) -> bool {
self.capacity() == self.len()
}
#[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;
2016-02-26 04:59:21 +00:00
impl QuadRenderer {
pub fn new() -> Result<QuadRenderer, Error> {
let program = TextShaderProgram::new()?;
2016-02-26 04:59:21 +00:00
let mut vao: GLuint = 0;
let mut ebo: GLuint = 0;
let mut vbo_instance: GLuint = 0;
2016-02-26 04:59:21 +00:00
unsafe {
gl::Enable(gl::BLEND);
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
gl::Enable(gl::MULTISAMPLE);
2020-05-05 22:50:23 +00:00
// Disable depth mask, as the renderer never uses depth tests.
2019-02-04 19:03:25 +00:00
gl::DepthMask(gl::FALSE);
2016-02-26 04:59:21 +00:00
gl::GenVertexArrays(1, &mut vao);
gl::GenBuffers(1, &mut ebo);
gl::GenBuffers(1, &mut vbo_instance);
2016-02-26 04:59:21 +00:00
gl::BindVertexArray(vao);
// ---------------------
// Set up element buffer
// ---------------------
let indices: [u32; 6] = [0, 1, 3, 1, 2, 3];
2016-02-26 04:59:21 +00:00
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,
);
2016-02-26 04:59:21 +00:00
// ----------------------------
// 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;
}
};
}
2020-05-05 22:50:23 +00:00
// 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);
2020-05-05 22:50:23 +00:00
// Background color.
add_attr!(4, gl::UNSIGNED_BYTE, u8);
2016-02-26 04:59:21 +00:00
2020-05-05 22:50:23 +00:00
// Cleanup.
2016-02-26 04:59:21 +00:00
gl::BindVertexArray(0);
2016-02-27 21:08:39 +00:00
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
2016-02-26 04:59:21 +00:00
}
2020-01-03 00:17:22 +00:00
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)
}
2020-05-05 22:50:23 +00:00
/// Draw all rectangles simultaneously to prevent excessive program swaps.
pub fn draw_rects(&mut self, size_info: &SizeInfo, rects: Vec<RenderRect>) {
if rects.is_empty() {
return;
}
// Prepare rect rendering state.
unsafe {
2020-05-05 22:50:23 +00:00
// Remove padding from viewport.
gl::Viewport(0, 0, size_info.width() as i32, size_info.height() as i32);
gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
}
self.rect_renderer.draw(size_info, rects);
// Activate regular state again.
unsafe {
2020-05-05 22:50:23 +00:00
// Reset blending strategy.
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
// Restore viewport with padding.
self.set_viewport(size_info);
}
}
pub fn with_api<F, T>(&mut self, config: &UiConfig, props: &SizeInfo, func: F) -> T
where
F: FnOnce(RenderApi<'_>) -> T,
{
2016-02-26 04:59:21 +00:00
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);
2020-05-05 22:50:23 +00:00
// 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,
(f32::from(color.b) / 255.0).min(1.0) * alpha,
alpha,
);
gl::Clear(gl::COLOR_BUFFER_BIT);
}
}
2020-05-27 14:46:52 +00:00
#[cfg(not(any(target_os = "macos", windows)))]
pub fn finish(&self) {
unsafe {
gl::Finish();
}
}
fn render_batch(&mut self) {
unsafe {
gl::BufferSubData(
gl::ARRAY_BUFFER,
0,
self.batch.size() as isize,
self.batch.instances.as_ptr() as *const _,
);
}
2016-02-26 04:59:21 +00:00
2020-05-05 22:50:23 +00:00
// 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();
}
Display errors and warnings To make sure that all error and information reporting to the user is unified, all instances of `print!`, `eprint!`, `println!` and `eprintln!` have been removed and replaced by logging. When `RUST_LOG` is not specified, the default Alacritty logger now also prints to both the stderr and a log file. The log file is only created when a message is written to it and its name is printed to stdout the first time it is used. Whenever a warning or an error has been written to the log file/stderr, a message is now displayed in Alacritty which points to the log file where the full error is documented. The message is cleared whenever the screen is cleared using either the `clear` command or the `Ctrl+L` key binding. To make sure that log files created by root don't prevent normal users from interacting with them, the Alacritty log file is `/tmp/Alacritty-$PID.log`. Since it's still possible that the log file can't be created, the UI error/warning message now informs the user if the message was only written to stderr. The reason why it couldn't be created is then printed to stderr. To make sure the deletion of the log file at runtime doesn't create any issues, the file is re-created if a write is attempted without the file being present. To help with debugging Alacritty issues, a timestamp and the error level are printed in all log messages. All log messages now follow this format: [YYYY-MM-DD HH:MM] [LEVEL] Message Since it's not unusual to spawn a lot of different terminal emulators without restarting, Alacritty can create a ton of different log files. To combat this problem, logfiles are removed by default after Alacritty has been closed. If the user wants to persist the log of a single session, the `--persistent_logging` option can be used. For persisting all log files, the `persistent_logging` option can be set in the configuration file
2018-11-17 14:39:13 +00:00
/// Draw a string in a variable location. Used for printing the render timer, warnings and
Display errors and warnings To make sure that all error and information reporting to the user is unified, all instances of `print!`, `eprint!`, `println!` and `eprintln!` have been removed and replaced by logging. When `RUST_LOG` is not specified, the default Alacritty logger now also prints to both the stderr and a log file. The log file is only created when a message is written to it and its name is printed to stdout the first time it is used. Whenever a warning or an error has been written to the log file/stderr, a message is now displayed in Alacritty which points to the log file where the full error is documented. The message is cleared whenever the screen is cleared using either the `clear` command or the `Ctrl+L` key binding. To make sure that log files created by root don't prevent normal users from interacting with them, the Alacritty log file is `/tmp/Alacritty-$PID.log`. Since it's still possible that the log file can't be created, the UI error/warning message now informs the user if the message was only written to stderr. The reason why it couldn't be created is then printed to stderr. To make sure the deletion of the log file at runtime doesn't create any issues, the file is re-created if a write is attempted without the file being present. To help with debugging Alacritty issues, a timestamp and the error level are printed in all log messages. All log messages now follow this format: [YYYY-MM-DD HH:MM] [LEVEL] Message Since it's not unusual to spawn a lot of different terminal emulators without restarting, Alacritty can create a ton of different log files. To combat this problem, logfiles are removed by default after Alacritty has been closed. If the user wants to persist the log of a single session, the `--persistent_logging` option can be used. For persisting all log files, the `persistent_logging` option can be set in the configuration file
2018-11-17 14:39:13 +00:00
/// errors.
pub fn draw_string(
Display errors and warnings To make sure that all error and information reporting to the user is unified, all instances of `print!`, `eprint!`, `println!` and `eprintln!` have been removed and replaced by logging. When `RUST_LOG` is not specified, the default Alacritty logger now also prints to both the stderr and a log file. The log file is only created when a message is written to it and its name is printed to stdout the first time it is used. Whenever a warning or an error has been written to the log file/stderr, a message is now displayed in Alacritty which points to the log file where the full error is documented. The message is cleared whenever the screen is cleared using either the `clear` command or the `Ctrl+L` key binding. To make sure that log files created by root don't prevent normal users from interacting with them, the Alacritty log file is `/tmp/Alacritty-$PID.log`. Since it's still possible that the log file can't be created, the UI error/warning message now informs the user if the message was only written to stderr. The reason why it couldn't be created is then printed to stderr. To make sure the deletion of the log file at runtime doesn't create any issues, the file is re-created if a write is attempted without the file being present. To help with debugging Alacritty issues, a timestamp and the error level are printed in all log messages. All log messages now follow this format: [YYYY-MM-DD HH:MM] [LEVEL] Message Since it's not unusual to spawn a lot of different terminal emulators without restarting, Alacritty can create a ton of different log files. To combat this problem, logfiles are removed by default after Alacritty has been closed. If the user wants to persist the log of a single session, the `--persistent_logging` option can be used. For persisting all log files, the `persistent_logging` option can be set in the configuration file
2018-11-17 14:39:13 +00:00
&mut self,
glyph_cache: &mut GlyphCache,
point: Point<usize>,
fg: Rgb,
bg: Rgb,
string: &str,
Display errors and warnings To make sure that all error and information reporting to the user is unified, all instances of `print!`, `eprint!`, `println!` and `eprintln!` have been removed and replaced by logging. When `RUST_LOG` is not specified, the default Alacritty logger now also prints to both the stderr and a log file. The log file is only created when a message is written to it and its name is printed to stdout the first time it is used. Whenever a warning or an error has been written to the log file/stderr, a message is now displayed in Alacritty which points to the log file where the full error is documented. The message is cleared whenever the screen is cleared using either the `clear` command or the `Ctrl+L` key binding. To make sure that log files created by root don't prevent normal users from interacting with them, the Alacritty log file is `/tmp/Alacritty-$PID.log`. Since it's still possible that the log file can't be created, the UI error/warning message now informs the user if the message was only written to stderr. The reason why it couldn't be created is then printed to stderr. To make sure the deletion of the log file at runtime doesn't create any issues, the file is re-created if a write is attempted without the file being present. To help with debugging Alacritty issues, a timestamp and the error level are printed in all log messages. All log messages now follow this format: [YYYY-MM-DD HH:MM] [LEVEL] Message Since it's not unusual to spawn a lot of different terminal emulators without restarting, Alacritty can create a ton of different log files. To combat this problem, logfiles are removed by default after Alacritty has been closed. If the user wants to persist the log of a single session, the `--persistent_logging` option can be used. For persisting all log files, the `persistent_logging` option can be set in the configuration file
2018-11-17 14:39:13 +00:00
) {
let cells = string
.chars()
.enumerate()
.map(|(i, character)| RenderableCell {
point: Point::new(point.line, point.column + i),
character,
zerowidth: None,
2019-09-21 17:54:32 +00:00
flags: Flags::empty(),
bg_alpha: 1.0,
fg,
bg,
})
.collect::<Vec<_>>();
for cell in cells {
self.draw_cell(cell, glyph_cache);
}
}
#[inline]
fn add_render_item(&mut self, cell: &RenderableCell, glyph: &Glyph) {
2020-05-05 22:50:23 +00:00
// 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);
2020-05-05 22:50:23 +00:00
// 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) {
2020-05-05 22:50:23 +00:00
// Get font key for cell.
2019-09-21 17:54:32 +00:00
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 };
2020-05-05 22:50:23 +00:00
// 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);
}
}
}
}
2020-05-05 22:50:23 +00:00
/// 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,
2019-03-30 16:48:36 +00:00
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)
2019-03-30 16:48:36 +00:00
},
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.,
2019-03-30 16:48:36 +00:00
},
}
}
#[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, ShaderCreationError> {
let vertex_shader = create_shader(gl::VERTEX_SHADER, TEXT_SHADER_V)?;
let fragment_shader = create_shader(gl::FRAGMENT_SHADER, TEXT_SHADER_F)?;
let program = create_program(vertex_shader, fragment_shader)?;
2016-02-26 04:59:21 +00:00
unsafe {
gl::DeleteShader(fragment_shader);
gl::DeleteShader(vertex_shader);
gl::UseProgram(program);
}
macro_rules! cptr {
2019-03-30 16:48:36 +00:00
($thing:expr) => {
$thing.as_ptr() as *const _
};
}
macro_rules! assert_uniform_valid {
($uniform:expr) => {
assert!($uniform != gl::INVALID_VALUE as i32);
assert!($uniform != gl::INVALID_OPERATION as i32);
};
( $( $uniform:expr ),* ) => {
$( assert_uniform_valid!($uniform); )*
};
2016-02-26 04:59:21 +00:00
}
// get uniform locations
2019-02-04 19:03:25 +00:00
let (projection, cell_dim, background) = unsafe {
(
gl::GetUniformLocation(program, cptr!(b"projection\0")),
gl::GetUniformLocation(program, cptr!(b"cellDim\0")),
gl::GetUniformLocation(program, cptr!(b"backgroundPass\0")),
)
};
2019-02-04 19:03:25 +00:00
assert_uniform_valid!(projection, cell_dim, background);
2020-01-03 00:17:22 +00:00
let shader = Self {
2016-02-26 04:59:21 +00:00
id: program,
u_projection: projection,
u_cell_dim: cell_dim,
u_background: background,
2016-02-26 04:59:21 +00:00
};
2019-03-30 16:48:36 +00:00
unsafe {
gl::UseProgram(0);
}
Ok(shader)
}
fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) {
2020-05-05 22:50:23 +00:00
// Bounds check.
2019-03-30 16:48:36 +00:00
if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
return;
}
2020-05-05 22:50:23 +00:00
// Compute scale and offset factors, from pixel to ndc space. Y is inverted.
2019-02-04 19:03:25 +00:00
// [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.;
2016-02-26 04:59:21 +00:00
unsafe {
2019-02-04 19:03:25 +00:00
gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y);
2016-02-26 04:59:21 +00:00
}
}
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 Drop for TextShaderProgram {
fn drop(&mut self) {
2016-02-26 04:59:21 +00:00
unsafe {
gl::DeleteProgram(self.id);
2016-02-26 04:59:21 +00:00
}
}
}
2016-02-26 04:59:21 +00:00
pub fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCreationError> {
unsafe {
let program = gl::CreateProgram();
gl::AttachShader(program, vertex);
gl::AttachShader(program, fragment);
gl::LinkProgram(program);
let mut success: GLint = 0;
gl::GetProgramiv(program, gl::LINK_STATUS, &mut success);
if success == i32::from(gl::TRUE) {
Ok(program)
} else {
Err(ShaderCreationError::Link(get_program_info_log(program)))
}
}
}
pub fn create_shader(kind: GLenum, source: &'static str) -> Result<GLuint, ShaderCreationError> {
let len: [GLint; 1] = [source.len() as GLint];
let shader = unsafe {
let shader = gl::CreateShader(kind);
gl::ShaderSource(shader, 1, &(source.as_ptr() as *const _), len.as_ptr());
gl::CompileShader(shader);
shader
};
let mut success: GLint = 0;
unsafe {
gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut success);
}
if success == GLint::from(gl::TRUE) {
Ok(shader)
} else {
2020-05-05 22:50:23 +00:00
// Read log.
let log = get_shader_info_log(shader);
2020-05-05 22:50:23 +00:00
// Cleanup.
2019-03-30 16:48:36 +00:00
unsafe {
gl::DeleteShader(shader);
}
Err(ShaderCreationError::Compile(log))
}
}
fn get_program_info_log(program: GLuint) -> String {
2020-05-05 22:50:23 +00:00
// Get expected log length.
let mut max_length: GLint = 0;
unsafe {
gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length);
}
2020-05-05 22:50:23 +00:00
// Read the info log.
let mut actual_length: GLint = 0;
let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
unsafe {
2019-03-30 16:48:36 +00:00
gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
}
2020-05-05 22:50:23 +00:00
// Build a string.
unsafe {
buf.set_len(actual_length as usize);
}
2020-05-05 22:50:23 +00:00
// XXX should we expect OpenGL to return garbage?
String::from_utf8(buf).unwrap()
}
fn get_shader_info_log(shader: GLuint) -> String {
2020-05-05 22:50:23 +00:00
// Get expected log length.
let mut max_length: GLint = 0;
unsafe {
gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length);
}
2016-02-26 04:59:21 +00:00
2020-05-05 22:50:23 +00:00
// Read the info log.
let mut actual_length: GLint = 0;
let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
unsafe {
2019-03-30 16:48:36 +00:00
gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
}
2016-02-26 04:59:21 +00:00
2020-05-05 22:50:23 +00:00
// Build a string.
unsafe {
buf.set_len(actual_length as usize);
}
2020-05-05 22:50:23 +00:00
// XXX should we expect OpenGL to return garbage?
String::from_utf8(buf).unwrap()
}
#[derive(Debug)]
pub enum ShaderCreationError {
2020-05-05 22:50:23 +00:00
/// Error reading file.
Io(io::Error),
2020-05-05 22:50:23 +00:00
/// Error compiling shader.
Compile(String),
2020-05-05 22:50:23 +00:00
/// Problem linking.
Link(String),
}
2020-01-03 00:17:22 +00:00
impl std::error::Error for ShaderCreationError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
ShaderCreationError::Io(err) => err.source(),
_ => None,
}
}
2016-02-26 04:59:21 +00:00
}
2020-01-03 00:17:22 +00:00
impl Display for ShaderCreationError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
ShaderCreationError::Io(err) => write!(f, "Unable to read shader: {}", err),
ShaderCreationError::Compile(log) => {
write!(f, "Failed compiling shader: {}", log)
},
2020-01-03 00:17:22 +00:00
ShaderCreationError::Link(log) => write!(f, "Failed linking shader: {}", log),
}
}
}
impl From<io::Error> for ShaderCreationError {
2020-01-03 00:17:22 +00:00
fn from(val: io::Error) -> Self {
ShaderCreationError::Io(val)
}
}
2020-05-05 22:50:23 +00:00
/// Manages a single texture atlas.
///
/// The strategy for filling an atlas looks roughly like this:
///
2019-07-07 13:14:04 +00:00
/// ```text
/// (width, height)
/// ┌─────┬─────┬─────┬─────┬─────┐
/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
/// │ │ │ │ │ │ glyph_height < height - row_baseline
2019-07-07 13:14:04 +00:00
/// ├─────┼─────┼─────┼─────┼─────┤
/// │ 5 │ 6 │ 7 │ 8 │ 9 │
/// │ │ │ │ │ │
2019-07-07 13:14:04 +00:00
/// ├─────┼─────┼─────┼─────┴─────┤ <- 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 {
2020-05-05 22:50:23 +00:00
/// Texture id for this atlas.
id: GLuint,
2020-05-05 22:50:23 +00:00
/// Width of atlas.
2016-02-26 04:59:21 +00:00
width: i32,
2020-05-05 22:50:23 +00:00
/// Height of atlas.
2016-02-26 04:59:21 +00:00
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,
2020-05-05 22:50:23 +00:00
/// Baseline for glyphs in the current row.
row_baseline: i32,
2020-05-05 22:50:23 +00:00
/// Tallest glyph in current row.
///
2020-05-05 22:50:23 +00:00
/// This is used as the advance when end of row is reached.
row_tallest: i32,
}
2020-05-05 22:50:23 +00:00
/// Error that can happen when inserting a texture to the Atlas.
enum AtlasInsertError {
2020-05-05 22:50:23 +00:00
/// Texture atlas is full.
Full,
2020-05-05 22:50:23 +00:00
/// The glyph cannot fit within a single texture.
GlyphTooLarge,
2016-02-26 04:59:21 +00:00
}
impl Atlas {
2020-01-03 00:17:22 +00:00
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);
}
2020-01-03 00:17:22 +00:00
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;
}
2020-05-05 22:50:23 +00:00
/// Insert a RasterizedGlyph into the texture atlas.
2017-11-26 19:48:28 +00:00
pub fn insert(
&mut self,
glyph: &RasterizedGlyph,
2019-03-30 16:48:36 +00:00
active_tex: &mut u32,
2017-11-26 19:48:28 +00:00
) -> Result<Glyph, AtlasInsertError> {
if glyph.width > self.width || glyph.height > self.height {
return Err(AtlasInsertError::GlyphTooLarge);
}
2020-05-05 22:50:23 +00:00
// If there's not enough room in current row, go onto next one.
2021-07-03 03:06:52 +00:00
if !self.room_in_row(glyph) {
self.advance_row()?;
}
2020-05-05 22:50:23 +00:00
// If there's still not room, there's nothing that can be done here..
2021-07-03 03:06:52 +00:00
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))
}
2020-05-05 22:50:23 +00:00
/// Insert the glyph without checking for room.
///
2016-12-17 06:48:04 +00:00
/// 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.
2017-11-26 19:48:28 +00:00
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);
2020-05-05 22:50:23 +00:00
// Load data into OpenGL.
let (format, buffer) = match &glyph.buffer {
2021-05-01 20:06:23 +00:00
BitmapBuffer::Rgb(buffer) => {
multicolor = false;
(gl::RGB, buffer)
},
2021-05-01 20:06:23 +00:00
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;
}
2020-05-05 22:50:23 +00:00
// Update Atlas state.
self.row_extent = offset_x + width;
if height > self.row_tallest {
self.row_tallest = height;
}
2020-05-05 22:50:23 +00:00
// 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,
}
2016-02-26 04:59:21 +00:00
}
2020-05-05 22:50:23 +00:00
/// 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
}
2020-05-05 22:50:23 +00:00
/// 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);
}
}
}