summaryrefslogtreecommitdiff
path: root/alacritty_terminal
diff options
context:
space:
mode:
authorKirill Chibisov <wchibisovkirill@gmail.com>2019-11-23 20:08:52 +0300
committerChristian Duerr <contact@christianduerr.com>2019-11-23 18:08:52 +0100
commit474032742b3bf325e655eb6cddb19632cb989c1a (patch)
tree9ae4ed52a0fe756474329c5c57076690525d511a /alacritty_terminal
parent624b3e2189df93145559ebba96e0a7754845c033 (diff)
downloadalacritty-474032742b3bf325e655eb6cddb19632cb989c1a.tar.gz
alacritty-474032742b3bf325e655eb6cddb19632cb989c1a.zip
Move renderer from alacritty_terminal to alacritty
Diffstat (limited to 'alacritty_terminal')
-rw-r--r--alacritty_terminal/Cargo.toml5
-rw-r--r--alacritty_terminal/build.rs28
-rw-r--r--alacritty_terminal/src/cursor.rs108
-rw-r--r--alacritty_terminal/src/lib.rs7
-rw-r--r--alacritty_terminal/src/renderer/mod.rs1651
-rw-r--r--alacritty_terminal/src/renderer/rects.rs149
-rw-r--r--alacritty_terminal/src/term/mod.rs8
7 files changed, 7 insertions, 1949 deletions
diff --git a/alacritty_terminal/Cargo.toml b/alacritty_terminal/Cargo.toml
index 36efde99..00598357 100644
--- a/alacritty_terminal/Cargo.toml
+++ b/alacritty_terminal/Cargo.toml
@@ -3,7 +3,6 @@ name = "alacritty_terminal"
version = "0.4.1-dev"
authors = ["Joe Wilm <joe@jwilm.com>"]
license = "Apache-2.0"
-build = "build.rs"
description = "Library for writing terminal emulators"
readme = "../README.md"
homepage = "https://github.com/jwilm/alacritty"
@@ -21,7 +20,6 @@ vte = "0.3"
mio = "0.6"
mio-extras = "2"
log = "0.4"
-fnv = "1"
unicode-width = "0.1"
base64 = "0.10.0"
terminfo = "0.6.1"
@@ -51,8 +49,5 @@ live-shader-reload = []
nightly = []
bench = []
-[build-dependencies]
-gl_generator = "0.14.0"
-
[dev-dependencies]
serde_json = "1.0.0"
diff --git a/alacritty_terminal/build.rs b/alacritty_terminal/build.rs
deleted file mode 100644
index 5d5bc451..00000000
--- a/alacritty_terminal/build.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-use gl_generator::{Api, Fallbacks, GlobalGenerator, Profile, Registry};
-
-use std::env;
-use std::fs::File;
-use std::path::Path;
-
-fn main() {
- let dest = env::var("OUT_DIR").unwrap();
- let mut file = File::create(&Path::new(&dest).join("gl_bindings.rs")).unwrap();
-
- Registry::new(Api::Gl, (4, 5), Profile::Core, Fallbacks::All, ["GL_ARB_blend_func_extended"])
- .write_bindings(GlobalGenerator, &mut file)
- .unwrap();
-}
diff --git a/alacritty_terminal/src/cursor.rs b/alacritty_terminal/src/cursor.rs
deleted file mode 100644
index d1df14e2..00000000
--- a/alacritty_terminal/src/cursor.rs
+++ /dev/null
@@ -1,108 +0,0 @@
-// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-//! Helpers for creating different cursor glyphs from font metrics
-
-use std::cmp;
-
-use serde::Deserialize;
-
-use font::{Metrics, RasterizedGlyph};
-
-use crate::ansi::CursorStyle;
-
-/// Width/Height of the cursor relative to the font width
-pub const CURSOR_WIDTH_PERCENTAGE: i32 = 15;
-
-/// A key for caching cursor glyphs
-#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
-pub struct CursorKey {
- pub style: CursorStyle,
- pub is_wide: bool,
-}
-
-pub fn get_cursor_glyph(
- cursor: CursorStyle,
- metrics: Metrics,
- offset_x: i8,
- offset_y: i8,
- is_wide: bool,
-) -> RasterizedGlyph {
- // Calculate the cell metrics
- let height = metrics.line_height as i32 + i32::from(offset_y);
- let mut width = metrics.average_advance as i32 + i32::from(offset_x);
- let line_width = cmp::max(width * CURSOR_WIDTH_PERCENTAGE / 100, 1);
-
- // Double the cursor width if it's above a double-width glyph
- if is_wide {
- width *= 2;
- }
-
- match cursor {
- CursorStyle::HollowBlock => get_box_cursor_glyph(height, width, line_width),
- CursorStyle::Underline => get_underline_cursor_glyph(width, line_width),
- CursorStyle::Beam => get_beam_cursor_glyph(height, line_width),
- CursorStyle::Block => get_block_cursor_glyph(height, width),
- CursorStyle::Hidden => RasterizedGlyph::default(),
- }
-}
-
-// Returns a custom underline cursor character
-pub fn get_underline_cursor_glyph(width: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new rectangle, the height is relative to the font width
- let buf = vec![255u8; (width * line_width * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: line_width, left: 0, height: line_width, width, buf }
-}
-
-// Returns a custom beam cursor character
-pub fn get_beam_cursor_glyph(height: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new rectangle that is at least one pixel wide
- let buf = vec![255u8; (line_width * height * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: height, left: 0, height, width: line_width, buf }
-}
-
-// Returns a custom box cursor character
-pub fn get_box_cursor_glyph(height: i32, width: i32, line_width: i32) -> RasterizedGlyph {
- // Create a new box outline rectangle
- let mut buf = Vec::with_capacity((width * height * 3) as usize);
- for y in 0..height {
- for x in 0..width {
- if y < line_width
- || y >= height - line_width
- || x < line_width
- || x >= width - line_width
- {
- buf.append(&mut vec![255u8; 3]);
- } else {
- buf.append(&mut vec![0u8; 3]);
- }
- }
- }
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf }
-}
-
-// Returns a custom block cursor character
-pub fn get_block_cursor_glyph(height: i32, width: i32) -> RasterizedGlyph {
- // Create a completely filled glyph
- let buf = vec![255u8; (width * height * 3) as usize];
-
- // Create a custom glyph with the rectangle data attached to it
- RasterizedGlyph { c: ' ', top: height, left: 0, height, width, buf }
-}
diff --git a/alacritty_terminal/src/lib.rs b/alacritty_terminal/src/lib.rs
index 82c83856..039f2b81 100644
--- a/alacritty_terminal/src/lib.rs
+++ b/alacritty_terminal/src/lib.rs
@@ -24,7 +24,6 @@ extern crate objc;
pub mod ansi;
pub mod clipboard;
pub mod config;
-mod cursor;
pub mod event;
pub mod event_loop;
pub mod grid;
@@ -33,7 +32,6 @@ pub mod locale;
pub mod message_bar;
pub mod meter;
pub mod panic;
-pub mod renderer;
pub mod selection;
pub mod sync;
pub mod term;
@@ -42,8 +40,3 @@ pub mod util;
pub use crate::grid::Grid;
pub use crate::term::Term;
-
-pub mod gl {
- #![allow(clippy::all)]
- include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
-}
diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs
deleted file mode 100644
index b200743a..00000000
--- a/alacritty_terminal/src/renderer/mod.rs
+++ /dev/null
@@ -1,1651 +0,0 @@
-// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-use std::collections::HashMap;
-use std::fs::File;
-use std::hash::BuildHasherDefault;
-use std::io::{self, Read};
-use std::mem::size_of;
-use std::path::PathBuf;
-use std::ptr;
-use std::sync::mpsc;
-use std::time::Duration;
-
-use fnv::FnvHasher;
-use font::{self, FontDesc, FontKey, GlyphKey, Rasterize, RasterizedGlyph, Rasterizer};
-use log::{error, info};
-use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
-
-use crate::config::{self, Config, Delta, Font, StartupMode};
-use crate::cursor::{get_cursor_glyph, CursorKey};
-use crate::gl;
-use crate::gl::types::*;
-use crate::index::{Column, Line};
-use crate::renderer::rects::RenderRect;
-use crate::term::cell::{self, Flags};
-use crate::term::color::Rgb;
-use crate::term::SizeInfo;
-use crate::term::{self, RenderableCell, RenderableCellContent};
-use crate::util;
-
-pub mod rects;
-
-// Shader paths for live reload
-static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl");
-static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl");
-static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl");
-static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl");
-
-// Shader source which is used when live-shader-reload feature is disable
-static TEXT_SHADER_F: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"));
-static TEXT_SHADER_V: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"));
-static RECT_SHADER_F: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl"));
-static RECT_SHADER_V: &str =
- include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.v.glsl"));
-
-/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory
-pub trait LoadGlyph {
- /// Load the rasterized glyph into GPU memory
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph;
-
- /// Clear any state accumulated from previous loaded glyphs
- ///
- /// This can, for instance, be used to reset the texture Atlas.
- fn clear(&mut self);
-}
-
-enum Msg {
- ShaderReload,
-}
-
-#[derive(Debug)]
-pub enum Error {
- ShaderCreation(ShaderCreationError),
-}
-
-impl ::std::error::Error for Error {
- fn cause(&self) -> Option<&dyn (::std::error::Error)> {
- match *self {
- Error::ShaderCreation(ref err) => Some(err),
- }
- }
-
- fn description(&self) -> &str {
- match *self {
- Error::ShaderCreation(ref err) => err.description(),
- }
- }
-}
-
-impl ::std::fmt::Display for Error {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match *self {
- Error::ShaderCreation(ref err) => {
- write!(f, "There was an error initializing the shaders: {}", err)
- },
- }
- }
-}
-
-impl From<ShaderCreationError> for Error {
- fn from(val: ShaderCreationError) -> Error {
- Error::ShaderCreation(val)
- }
-}
-
-/// Text drawing program
-///
-/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
-#[derive(Debug)]
-pub struct TextShaderProgram {
- // Program id
- id: GLuint,
-
- /// 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,
-}
-
-/// Rectangle drawing program
-///
-/// Uniforms are prefixed with "u"
-#[derive(Debug)]
-pub struct RectShaderProgram {
- // Program id
- id: GLuint,
- /// Rectangle color
- u_color: GLint,
-}
-
-#[derive(Copy, Debug, Clone)]
-pub struct Glyph {
- tex_id: GLuint,
- top: f32,
- left: f32,
- width: f32,
- height: f32,
- uv_bot: f32,
- uv_left: f32,
- uv_width: f32,
- 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>>,
-
- /// Cache of buffered cursor glyphs
- cursor_cache: HashMap<CursorKey, Glyph, BuildHasherDefault<FnvHasher>>,
-
- /// Rasterizer for loading new glyphs
- rasterizer: Rasterizer,
-
- /// regular font
- font_key: FontKey,
-
- /// bold font
- bold_key: FontKey,
-
- /// italic font
- italic_key: FontKey,
-
- /// bold italic font
- bold_italic_key: FontKey,
-
- /// font size
- font_size: font::Size,
-
- /// glyph offset
- glyph_offset: Delta<i8>,
-
- metrics: ::font::Metrics,
-}
-
-impl GlyphCache {
- pub fn new<L>(
- mut rasterizer: Rasterizer,
- font: &config::Font,
- loader: &mut L,
- ) -> Result<GlyphCache, font::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, c: 'm', size: font.size })?;
-
- let metrics = rasterizer.metrics(regular, font.size)?;
-
- let mut cache = GlyphCache {
- cache: HashMap::default(),
- cursor_cache: HashMap::default(),
- rasterizer,
- font_size: font.size,
- font_key: regular,
- bold_key: bold,
- italic_key: italic,
- bold_italic_key: bold_italic,
- glyph_offset: font.glyph_offset,
- metrics,
- };
-
- cache.load_glyphs_for_font(regular, loader);
- cache.load_glyphs_for_font(bold, loader);
- cache.load_glyphs_for_font(italic, loader);
- cache.load_glyphs_for_font(bold_italic, loader);
-
- Ok(cache)
- }
-
- fn load_glyphs_for_font<L: LoadGlyph>(&mut self, font: FontKey, loader: &mut L) {
- let size = self.font_size;
- for i in 32u8..=126u8 {
- self.get(GlyphKey { font_key: font, c: i as char, size }, loader);
- }
- }
-
- /// Computes font keys for (Regular, Bold, Italic, Bold Italic)
- fn compute_font_keys(
- font: &config::Font,
- rasterizer: &mut Rasterizer,
- ) -> Result<(FontKey, FontKey, FontKey, FontKey), font::Error> {
- let size = font.size;
-
- // Load regular font
- let regular_desc =
- Self::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
-
- let regular = rasterizer.load_font(&regular_desc, size)?;
-
- // helper to load a description if it is not the regular_desc
- let mut load_or_regular = |desc: FontDesc| {
- if desc == regular_desc {
- regular
- } else {
- rasterizer.load_font(&desc, size).unwrap_or_else(|_| regular)
- }
- };
-
- // Load bold font
- let bold_desc = Self::make_desc(&font.bold(), font::Slant::Normal, font::Weight::Bold);
-
- let bold = load_or_regular(bold_desc);
-
- // Load italic font
- let italic_desc =
- Self::make_desc(&font.italic(), font::Slant::Italic, font::Weight::Normal);
-
- let italic = load_or_regular(italic_desc);
-
- // Load bold italic font
- let bold_italic_desc =
- Self::make_desc(&font.bold_italic(), font::Slant::Italic, font::Weight::Bold);
-
- let bold_italic = load_or_regular(bold_italic_desc);
-
- Ok((regular, bold, italic, bold_italic))
- }
-
- fn make_desc(
- desc: &config::FontDescription,
- slant: font::Slant,
- weight: font::Weight,
- ) -> FontDesc {
- let style = if let Some(ref spec) = desc.style {
- font::Style::Specific(spec.to_owned())
- } else {
- font::Style::Description { slant, weight }
- };
- FontDesc::new(desc.family.clone(), style)
- }
-
- pub fn get<'a, L>(&'a mut self, glyph_key: GlyphKey, loader: &mut L) -> &'a Glyph
- where
- L: LoadGlyph,
- {
- let glyph_offset = self.glyph_offset;
- let rasterizer = &mut self.rasterizer;
- let metrics = &self.metrics;
- self.cache.entry(glyph_key).or_insert_with(|| {
- let mut rasterized =
- rasterizer.get_glyph(glyph_key).unwrap_or_else(|_| Default::default());
-
- rasterized.left += i32::from(glyph_offset.x);
- rasterized.top += i32::from(glyph_offset.y);
- rasterized.top -= metrics.descent as i32;
-
- loader.load_glyph(&rasterized)
- })
- }
-
- pub fn update_font_size<L: LoadGlyph>(
- &mut self,
- font: config::Font,
- dpr: f64,
- loader: &mut L,
- ) -> Result<(), font::Error> {
- // Clear currently cached data in both GL and the registry
- loader.clear();
- self.cache = HashMap::default();
- self.cursor_cache = HashMap::default();
-
- // Update dpi scaling
- self.rasterizer.update_dpr(dpr as f32);
-
- // 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, c: 'm', size: font.size })?;
- let metrics = self.rasterizer.metrics(regular, font.size)?;
-
- info!("Font size changed to {:?} with DPR of {}", font.size, dpr);
-
- 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.load_glyphs_for_font(regular, loader);
- self.load_glyphs_for_font(bold, loader);
- self.load_glyphs_for_font(italic, loader);
- self.load_glyphs_for_font(bold_italic, loader);
-
- Ok(())
- }
-
- pub fn font_metrics(&self) -> font::Metrics {
- self.rasterizer
- .metrics(self.font_key, self.font_size)
- .expect("metrics load since font is loaded at glyph cache creation")
- }
-
- // Calculate font metrics without access to a glyph cache
- pub fn static_metrics(font: Font, dpr: f64) -> Result<font::Metrics, font::Error> {
- let mut rasterizer = font::Rasterizer::new(dpr as f32, font.use_thin_strokes())?;
- let regular_desc =
- GlyphCache::make_desc(&font.normal(), font::Slant::Normal, font::Weight::Normal);
- let regular = rasterizer.load_font(&regular_desc, font.size)?;
- rasterizer.get_glyph(GlyphKey { font_key: regular, c: 'm', size: font.size })?;
-
- rasterizer.metrics(regular, font.size)
- }
-
- pub fn calculate_dimensions<C>(
- config: &Config<C>,
- dpr: f64,
- cell_width: f32,
- cell_height: f32,
- ) -> Option<(f64, f64)> {
- let dimensions = config.window.dimensions;
-
- if dimensions.columns_u32() == 0
- || dimensions.lines_u32() == 0
- || config.window.startup_mode() != StartupMode::Windowed
- {
- return None;
- }
-
- let padding_x = f64::from(config.window.padding.x) * dpr;
- let padding_y = f64::from(config.window.padding.y) * dpr;
-
- // Calculate new size based on cols/lines specified in config
- let grid_width = cell_width as u32 * dimensions.columns_u32();
- let grid_height = cell_height as u32 * dimensions.lines_u32();
-
- let width = padding_x.mul_add(2., f64::from(grid_width)).floor();
- let height = padding_y.mul_add(2., f64::from(grid_height)).floor();
-
- Some((width, height))
- }
-}
-
-#[derive(Debug)]
-#[repr(C)]
-struct InstanceData {
- // coords
- col: f32,
- row: f32,
- // glyph offset
- left: f32,
- top: f32,
- // glyph scale
- width: f32,
- height: f32,
- // uv offset
- uv_left: f32,
- uv_bot: f32,
- // uv scale
- uv_width: f32,
- uv_height: f32,
- // color
- r: f32,
- g: f32,
- b: f32,
- // background color
- bg_r: f32,
- bg_g: f32,
- bg_b: f32,
- bg_a: f32,
-}
-
-#[derive(Debug)]
-pub struct QuadRenderer {
- program: TextShaderProgram,
- rect_program: RectShaderProgram,
- vao: GLuint,
- ebo: GLuint,
- vbo_instance: GLuint,
- rect_vao: GLuint,
- rect_vbo: GLuint,
- atlas: Vec<Atlas>,
- current_atlas: usize,
- active_tex: GLuint,
- batch: Batch,
- rx: mpsc::Receiver<Msg>,
-}
-
-#[derive(Debug)]
-pub struct RenderApi<'a, C> {
- 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 Config<C>,
-}
-
-#[derive(Debug)]
-pub struct LoaderApi<'a> {
- active_tex: &'a mut GLuint,
- atlas: &'a mut Vec<Atlas>,
- current_atlas: &'a mut usize,
-}
-
-#[derive(Debug)]
-pub struct PackedVertex {
- x: f32,
- y: f32,
-}
-
-#[derive(Debug, Default)]
-pub struct Batch {
- tex: GLuint,
- instances: Vec<InstanceData>,
-}
-
-impl Batch {
- #[inline]
- pub fn new() -> Batch {
- Batch { 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;
- }
-
- self.instances.push(InstanceData {
- col: cell.column.0 as f32,
- row: cell.line.0 as f32,
-
- 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: f32::from(cell.fg.r),
- g: f32::from(cell.fg.g),
- b: f32::from(cell.fg.b),
-
- bg_r: f32::from(cell.bg.r),
- bg_g: f32::from(cell.bg.g),
- bg_b: f32::from(cell.bg.b),
- bg_a: cell.bg_alpha,
- });
- }
-
- #[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;
-
-impl QuadRenderer {
- pub fn new() -> Result<QuadRenderer, Error> {
- let program = TextShaderProgram::new()?;
- let rect_program = RectShaderProgram::new()?;
-
- let mut vao: GLuint = 0;
- let mut ebo: GLuint = 0;
-
- let mut vbo_instance: GLuint = 0;
-
- let mut rect_vao: GLuint = 0;
- let mut rect_vbo: GLuint = 0;
- let mut rect_ebo: 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,
- );
- // coords
- gl::VertexAttribPointer(
- 0,
- 2,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- ptr::null(),
- );
- gl::EnableVertexAttribArray(0);
- gl::VertexAttribDivisor(0, 1);
- // glyphoffset
- gl::VertexAttribPointer(
- 1,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (2 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(1);
- gl::VertexAttribDivisor(1, 1);
- // uv
- gl::VertexAttribPointer(
- 2,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (6 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(2);
- gl::VertexAttribDivisor(2, 1);
- // color
- gl::VertexAttribPointer(
- 3,
- 3,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (10 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(3);
- gl::VertexAttribDivisor(3, 1);
- // color
- gl::VertexAttribPointer(
- 4,
- 4,
- gl::FLOAT,
- gl::FALSE,
- size_of::<InstanceData>() as i32,
- (13 * size_of::<f32>()) as *const _,
- );
- gl::EnableVertexAttribArray(4);
- gl::VertexAttribDivisor(4, 1);
-
- // Rectangle setup
- gl::GenVertexArrays(1, &mut rect_vao);
- gl::GenBuffers(1, &mut rect_vbo);
- gl::GenBuffers(1, &mut rect_ebo);
- gl::BindVertexArray(rect_vao);
- let indices: [i32; 6] = [0, 1, 3, 1, 2, 3];
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, rect_ebo);
- gl::BufferData(
- gl::ELEMENT_ARRAY_BUFFER,
- (size_of::<i32>() * indices.len()) as _,
- indices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // Cleanup
- gl::BindVertexArray(0);
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
- }
-
- let (msg_tx, msg_rx) = mpsc::channel();
-
- if cfg!(feature = "live-shader-reload") {
- util::thread::spawn_named("live shader reload", move || {
- let (tx, rx) = ::std::sync::mpsc::channel();
- // The Duration argument is a debouncing period.
- let mut watcher =
- watcher(tx, Duration::from_millis(10)).expect("create file watcher");
- watcher
- .watch(TEXT_SHADER_F_PATH, RecursiveMode::NonRecursive)
- .expect("watch fragment shader");
- watcher
- .watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive)
- .expect("watch vertex shader");
-
- loop {
- let event = rx.recv().expect("watcher event");
-
- match event {
- DebouncedEvent::Rename(..) => continue,
- DebouncedEvent::Create(_)
- | DebouncedEvent::Write(_)
- | DebouncedEvent::Chmod(_) => {
- msg_tx.send(Msg::ShaderReload).expect("msg send ok");
- },
- _ => {},
- }
- }
- });
- }
-
- let mut renderer = QuadRenderer {
- program,
- rect_program,
- vao,
- ebo,
- vbo_instance,
- rect_vao,
- rect_vbo,
- atlas: Vec::new(),
- current_atlas: 0,
- active_tex: 0,
- batch: Batch::new(),
- rx: msg_rx,
- };
-
- let atlas = Atlas::new(ATLAS_SIZE);
- renderer.atlas.push(atlas);
-
- Ok(renderer)
- }
-
- // Draw all rectangles simultaneously to prevent excessive program swaps
- pub fn draw_rects(&mut self, props: &term::SizeInfo, rects: Vec<RenderRect>) {
- // Swap to rectangle rendering program
- unsafe {
- // Swap program
- gl::UseProgram(self.rect_program.id);
-
- // Remove padding from viewport
- gl::Viewport(0, 0, props.width as i32, props.height as i32);
-
- // Change blending strategy
- gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
-
- // Setup data and buffers
- gl::BindVertexArray(self.rect_vao);
- gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo);
-
- // Position
- gl::VertexAttribPointer(
- 0,
- 2,
- gl::FLOAT,
- gl::FALSE,
- (size_of::<f32>() * 2) as _,
- ptr::null(),
- );
- gl::EnableVertexAttribArray(0);
- }
-
- // Draw all the rects
- for rect in rects {
- self.render_rect(&rect, props);
- }
-
- // Deactivate rectangle program again
- unsafe {
- // Reset blending strategy
- gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
-
- // Reset data and buffers
- gl::BindBuffer(gl::ARRAY_BUFFER, 0);
- gl::BindVertexArray(0);
-
- let padding_x = props.padding_x as i32;
- let padding_y = props.padding_y as i32;
- let width = props.width as i32;
- let height = props.height as i32;
- gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
-
- // Disable program
- gl::UseProgram(0);
- }
- }
-
- pub fn with_api<F, T, C>(&mut self, config: &Config<C>, props: &term::SizeInfo, func: F) -> T
- where
- F: FnOnce(RenderApi<'_, C>) -> T,
- {
- // Flush message queue
- if let Ok(Msg::ShaderReload) = self.rx.try_recv() {
- self.reload_shaders(props);
- }
- while let Ok(_) = self.rx.try_recv() {}
-
- 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 reload_shaders(&mut self, props: &term::SizeInfo) {
- info!("Reloading shaders...");
- let result = (TextShaderProgram::new(), RectShaderProgram::new());
- let (program, rect_program) = match result {
- (Ok(program), Ok(rect_program)) => {
- unsafe {
- gl::UseProgram(program.id);
- program.update_projection(
- props.width,
- props.height,
- props.padding_x,
- props.padding_y,
- );
- gl::UseProgram(0);
- }
-
- info!("... successfully reloaded shaders");
- (program, rect_program)
- },
- (Err(err), _) | (_, Err(err)) => {
- error!("{}", err);
- return;
- },
- };
-
- self.active_tex = 0;
- self.program = program;
- self.rect_program = rect_program;
- }
-
- pub fn resize(&mut self, size: &SizeInfo) {
- // viewport
- 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,
- );
-
- // update projection
- gl::UseProgram(self.program.id);
- self.program.update_projection(size.width, size.height, size.padding_x, size.padding_y);
- gl::UseProgram(0);
- }
- }
-
- // Render a rectangle
- //
- // This requires the rectangle program to be activated
- fn render_rect(&mut self, rect: &RenderRect, size: &term::SizeInfo) {
- // Do nothing when alpha is fully transparent
- if rect.alpha == 0. {
- return;
- }
-
- // Calculate rectangle position
- let center_x = size.width / 2.;
- let center_y = size.height / 2.;
- let x = (rect.x - center_x) / center_x;
- let y = -(rect.y - center_y) / center_y;
- let width = rect.width / center_x;
- let height = rect.height / center_y;
-
- unsafe {
- // Setup vertices
- let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y];
-
- // Load vertex data into array buffer
- gl::BufferData(
- gl::ARRAY_BUFFER,
- (size_of::<f32>() * vertices.len()) as _,
- vertices.as_ptr() as *const _,
- gl::STATIC_DRAW,
- );
-
- // Color
- self.rect_program.set_color(rect.color, rect.alpha);
-
- // Draw the rectangle
- gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null());
- }
- }
-}
-
-impl<'a, C> RenderApi<'a, C> {
- pub fn clear(&self, color: Rgb) {
- unsafe {
- let alpha = self.config.background_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);
- }
- }
-
- 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();
- }
-
- /// Render a string in a variable location. Used for printing the render timer, warnings and
- /// errors.
- pub fn render_string(
- &mut self,
- string: &str,
- line: Line,
- glyph_cache: &mut GlyphCache,
- color: Option<Rgb>,
- ) {
- let bg_alpha = color.map(|_| 1.0).unwrap_or(0.0);
- let col = Column(0);
-
- let cells = string
- .chars()
- .enumerate()
- .map(|(i, c)| RenderableCell {
- line,
- column: col + i,
- inner: RenderableCellContent::Chars({
- let mut chars = [' '; cell::MAX_ZEROWIDTH_CHARS + 1];
- chars[0] = c;
- chars
- }),
- bg: color.unwrap_or(Rgb { r: 0, g: 0, b: 0 }),
- fg: Rgb { r: 0, g: 0, b: 0 },
- flags: Flags::empty(),
- bg_alpha,
- })
- .collect::<Vec<_>>();
-
- for cell in cells {
- self.render_cell(cell, glyph_cache);
- }
- }
-
- #[inline]
- fn add_render_item(&mut self, cell: RenderableCell, glyph: &Glyph) {
- // Flush batch if tex changing
- if !self.batch.is_empty() && self.batch.tex != glyph.tex_id {
- self.render_batch();
- }
-
- self.batch.add_item(cell, glyph);
-
- // Render batch and clear if it's full
- if self.batch.full() {
- self.render_batch();
- }
- }
-
- pub fn render_cell(&mut self, cell: RenderableCell, glyph_cache: &mut GlyphCache) {
- let chars = match cell.inner {
- RenderableCellContent::Cursor(cursor_key) => {
- // Raw cell pixel buffers like cursors don't need to go through font lookup
- let metrics = glyph_cache.metrics;
- let glyph = glyph_cache.cursor_cache.entry(cursor_key).or_insert_with(|| {
- self.load_glyph(&get_cursor_glyph(
- cursor_key.style,
- metrics,
- self.config.font.offset.x,
- self.config.font.offset.y,
- cursor_key.is_wide,
- ))
- });
- self.add_render_item(cell, &glyph);
- return;
- },
- RenderableCellContent::Chars(chars) => chars,
- };
-
- // 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,
- };
-
- // Don't render text of HIDDEN cells
- let mut chars = if cell.flags.contains(Flags::HIDDEN) {
- [' '; cell::MAX_ZEROWIDTH_CHARS + 1]
- } else {
- chars
- };
-
- // Render tabs as spaces in case the font doesn't support it
- if chars[0] == '\t' {
- chars[0] = ' ';
- }
-
- let mut glyph_key = GlyphKey { font_key, size: glyph_cache.font_size, c: chars[0] };
-
- // Add cell to batch
- let glyph = glyph_cache.get(glyph_key, self);
- self.add_render_item(cell, glyph);
-
- // Render zero-width characters
- for c in (&chars[1..]).iter().filter(|c| **c != ' ') {
- glyph_key.c = *c;
- let mut glyph = *glyph_cache.get(glyph_key, self);
-
- // 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.
- glyph.left += glyph_cache.metrics.average_advance as f32;
-
- self.add_render_item(cell, &glyph);
- }
- }
-}
-
-/// Load a glyph into a texture atlas
-///
-/// If the current atlas is full, a new one will be created.
-#[inline]
-fn load_glyph(
- active_tex: &mut GLuint,
- atlas: &mut Vec<Atlas>,
- current_atlas: &mut usize,
- rasterized: &RasterizedGlyph,
-) -> Glyph {
- // At least one atlas is guaranteed to be in the `self.atlas` list; thus
- // the unwrap.
- match atlas[*current_atlas].insert(rasterized, active_tex) {
- Ok(glyph) => glyph,
- Err(AtlasInsertError::Full) => {
- *current_atlas += 1;
- if *current_atlas == atlas.len() {
- let new = Atlas::new(ATLAS_SIZE);
- *active_tex = 0; // Atlas::new binds a texture. Ugh this is sloppy.
- atlas.push(new);
- }
- load_glyph(active_tex, atlas, current_atlas, rasterized)
- },
- Err(AtlasInsertError::GlyphTooLarge) => Glyph {
- tex_id: atlas[*current_atlas].id,
- top: 0.0,
- left: 0.0,
- width: 0.0,
- height: 0.0,
- uv_bot: 0.0,
- uv_left: 0.0,
- uv_width: 0.0,
- uv_height: 0.0,
- },
- }
-}
-
-#[inline]
-fn clear_atlas(atlas: &mut Vec<Atlas>, current_atlas: &mut usize) {
- for atlas in atlas.iter_mut() {
- atlas.clear();
- }
- *current_atlas = 0;
-}
-
-impl<'a> LoadGlyph for LoaderApi<'a> {
- fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
- load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
- }
-
- fn clear(&mut self) {
- clear_atlas(self.atlas, self.current_atlas)
- }
-}
-
-impl<'a, C> LoadGlyph for RenderApi<'a, C> {
- 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, C> Drop for RenderApi<'a, C> {
- fn drop(&mut self) {
- if !self.batch.is_empty() {
- self.render_batch();
- }
- }
-}
-
-impl TextShaderProgram {
- pub fn new() -> Result<TextShaderProgram, ShaderCreationError> {
- let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") {
- (None, None)
- } else {
- (Some(TEXT_SHADER_V), Some(TEXT_SHADER_F))
- };
- let vertex_shader = create_shader(TEXT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?;
- let fragment_shader = create_shader(TEXT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?;
- let program = create_program(vertex_shader, fragment_shader)?;
-
- unsafe {
- gl::DeleteShader(fragment_shader);
- gl::DeleteShader(vertex_shader);
- gl::UseProgram(program);
- }
-
- macro_rules! cptr {
- ($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); )*
- };
- }
-
- // get uniform locations
- 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")),
- )
- };
-
- assert_uniform_valid!(projection, cell_dim, background);
-
- let shader = TextShaderProgram {
- id: program,
- u_projection: projection,
- u_cell_dim: cell_dim,
- u_background: background,
- };
-
- unsafe {
- gl::UseProgram(0);
- }
-
- Ok(shader)
- }
-
- fn update_projection(&self, width: f32, height: f32, padding_x: f32, padding_y: f32) {
- // Bounds check
- if (width as u32) < (2 * padding_x as u32) || (height as u32) < (2 * padding_y as u32) {
- return;
- }
-
- // Compute scale and offset factors, from pixel to ndc space. Y is inverted
- // [0, width - 2 * padding_x] to [-1, 1]
- // [height - 2 * padding_y, 0] to [-1, 1]
- let scale_x = 2. / (width - 2. * padding_x);
- let scale_y = -2. / (height - 2. * padding_y);
- let offset_x = -1.;
- let offset_y = 1.;
-
- info!("Width: {}, Height: {}", width, height);
-
- unsafe {
- gl::Uniform4f(self.u_projection, offset_x, offset_y, scale_x, scale_y);
- }
- }
-
- fn set_term_uniforms(&self, props: &term::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) {
- unsafe {
- gl::DeleteProgram(self.id);
- }
- }
-}
-
-impl RectShaderProgram {
- pub fn new() -> Result<Self, ShaderCreationError> {
- let (vertex_src, fragment_src) = if cfg!(feature = "live-shader-reload") {
- (None, None)
- } else {
- (Some(RECT_SHADER_V), Some(RECT_SHADER_F))
- };
- let vertex_shader = create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?;
- let fragment_shader = create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?;
- let program = create_program(vertex_shader, fragment_shader)?;
-
- unsafe {
- gl::DeleteShader(fragment_shader);
- gl::DeleteShader(vertex_shader);
- gl::UseProgram(program);
- }
-
- // get uniform locations
- let u_color = unsafe { gl::GetUniformLocation(program, b"color\0".as_ptr() as *const _) };
-
- let shader = RectShaderProgram { id: program, u_color };
-
- unsafe { gl::UseProgram(0) }
-
- Ok(shader)
- }
-
- fn set_color(&self, color: Rgb, alpha: f32) {
- unsafe {
- gl::Uniform4f(
- self.u_color,
- f32::from(color.r) / 255.,
- f32::from(color.g) / 255.,
- f32::from(color.b) / 255.,
- alpha,
- );
- }
- }
-}
-
-impl Drop for RectShaderProgram {
- fn drop(&mut self) {
- unsafe {
- gl::DeleteProgram(self.id);
- }
- }
-}
-
-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)))
- }
- }
-}
-
-fn create_shader(
- path: &str,
- kind: GLenum,
- source: Option<&'static str>,
-) -> Result<GLuint, ShaderCreationError> {
- let from_disk;
- let source = if let Some(src) = source {
- src
- } else {
- from_disk = read_file(path)?;
- &from_disk[..]
- };
-
- 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 {
- // Read log
- let log = get_shader_info_log(shader);
-
- // Cleanup
- unsafe {
- gl::DeleteShader(shader);
- }
-
- Err(ShaderCreationError::Compile(PathBuf::from(path), log))
- }
-}
-
-fn get_program_info_log(program: GLuint) -> String {
- // Get expected log length
- let mut max_length: GLint = 0;
- unsafe {
- gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut max_length);
- }
-
- // Read the info log
- let mut actual_length: GLint = 0;
- let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
- unsafe {
- gl::GetProgramInfoLog(program, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
- }
-
- // Build a string
- unsafe {
- buf.set_len(actual_length as usize);
- }
-
- // XXX should we expect opengl to return garbage?
- String::from_utf8(buf).unwrap()
-}
-
-fn get_shader_info_log(shader: GLuint) -> String {
- // Get expected log length
- let mut max_length: GLint = 0;
- unsafe {
- gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut max_length);
- }
-
- // Read the info log
- let mut actual_length: GLint = 0;
- let mut buf: Vec<u8> = Vec::with_capacity(max_length as usize);
- unsafe {
- gl::GetShaderInfoLog(shader, max_length, &mut actual_length, buf.as_mut_ptr() as *mut _);
- }
-
- // Build a string
- unsafe {
- buf.set_len(actual_length as usize);
- }
-
- // XXX should we expect opengl to return garbage?
- String::from_utf8(buf).unwrap()
-}
-
-fn read_file(path: &str) -> Result<String, io::Error> {
- let mut f = File::open(path)?;
- let mut buf = String::new();
- f.read_to_string(&mut buf)?;
-
- Ok(buf)
-}
-
-#[derive(Debug)]
-pub enum ShaderCreationError {
- /// Error reading file
- Io(io::Error),
-
- /// Error compiling shader
- Compile(PathBuf, String),
-
- /// Problem linking
- Link(String),
-}
-
-impl ::std::error::Error for ShaderCreationError {
- fn cause(&self) -> Option<&dyn (::std::error::Error)> {
- match *self {
- ShaderCreationError::Io(ref err) => Some(err),
- _ => None,
- }
- }
-
- fn description(&self) -> &str {
- match *self {
- ShaderCreationError::Io(ref err) => err.description(),
- ShaderCreationError::Compile(ref _path, ref s) => s.as_str(),
- ShaderCreationError::Link(ref s) => s.as_str(),
- }
- }
-}
-
-impl ::std::fmt::Display for ShaderCreationError {
- fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- match *self {
- ShaderCreationError::Io(ref err) => write!(f, "Couldn't read shader: {}", err),
- ShaderCreationError::Compile(ref path, ref log) => {
- write!(f, "Failed compiling shader at {}: {}", path.display(), log)
- },
- ShaderCreationError::Link(ref log) => write!(f, "Failed linking shader: {}", log),
- }
- }
-}
-
-impl From<io::Error> for ShaderCreationError {
- fn from(val: io::Error) -> ShaderCreationError {
- ShaderCreationError::Io(val)
- }
-}
-
-/// Manages a single texture atlas
-///
-/// The strategy for filling an atlas looks roughly like this:
-///
-/// ```text
-/// (width, height)
-/// ┌─────┬─────┬─────┬─────┬─────┐
-/// │ 10 │ │ │ │ │ <- Empty spaces; can be filled while
-/// │ │ │ │ │ │ glyph_height < height - row_baseline
-/// ├─────┼─────┼─────┼─────┼─────┤
-/// │ 5 │ 6 │ 7 │ 8 │ 9 │
-/// │ │ │ │ │ │
-/// ├─────┼─────┼─────┼─────┴─────┤ <- Row height is tallest glyph in row; this is
-/// │ 1 │ 2 │ 3 │ 4 │ used as the baseline for the following row.
-/// │ │ │ │ │ <- Row considered full when next glyph doesn't
-/// └─────┴─────┴─────┴───────────┘ fit in the row.
-/// (0, 0) x->
-/// ```
-#[derive(Debug)]
-struct Atlas {
- /// Texture id for this atlas
- id: GLuint,
-
- /// Width of atlas
- width: i32,
-
- /// Height of atlas
- height: i32,
-
- /// Left-most free pixel in a row.
- ///
- /// This is called the extent because it is the upper bound of used pixels
- /// in a row.
- row_extent: i32,
-
- /// Baseline for glyphs in the current row
- row_baseline: i32,
-
- /// Tallest glyph in current row
- ///
- /// This is used as the advance when end of row is reached
- row_tallest: i32,
-}
-
-/// Error that can happen when inserting a texture to the Atlas
-enum AtlasInsertError {
- /// Texture atlas is full
- Full,
-
- /// The glyph cannot fit within a single texture
- GlyphTooLarge,
-}
-
-impl Atlas {
- fn new(size: i32) -> Atlas {
- let mut id: GLuint = 0;
- unsafe {
- gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1);
- gl::GenTextures(1, &mut id);
- gl::BindTexture(gl::TEXTURE_2D, id);
- gl::TexImage2D(
- gl::TEXTURE_2D,
- 0,
- gl::RGB as i32,
- size,
- size,
- 0,
- gl::RGB,
- 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);
- }
-
- Atlas { 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;
-
- unsafe {
- gl::BindTexture(gl::TEXTURE_2D, self.id);
-
- // Load data into OpenGL
- gl::TexSubImage2D(
- gl::TEXTURE_2D,
- 0,
- offset_x,
- offset_y,
- width,
- height,
- gl::RGB,
- gl::UNSIGNED_BYTE,
- glyph.buf.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,
- top: glyph.top as f32,
- width: width as f32,
- height: height as f32,
- left: glyph.left as f32,
- uv_bot,
- uv_left,
- uv_width,
- uv_height,
- }
- }
-
- /// Check if there's room in the current row for given glyph
- fn room_in_row(&self, raw: &RasterizedGlyph) -> bool {
- let next_extent = self.row_extent + raw.width as i32;
- let enough_width = next_extent <= self.width;
- let enough_height = (raw.height as i32) < (self.height - self.row_baseline);
-
- enough_width && enough_height
- }
-
- /// Mark current row as finished and prepare to insert into the next row
- fn advance_row(&mut self) -> Result<(), AtlasInsertError> {
- let advance_to = self.row_baseline + self.row_tallest;
- if self.height - advance_to <= 0 {
- return Err(AtlasInsertError::Full);
- }
-
- self.row_baseline = advance_to;
- self.row_extent = 0;
- self.row_tallest = 0;
-
- Ok(())
- }
-}
diff --git a/alacritty_terminal/src/renderer/rects.rs b/alacritty_terminal/src/renderer/rects.rs
deleted file mode 100644
index d5645828..00000000
--- a/alacritty_terminal/src/renderer/rects.rs
+++ /dev/null
@@ -1,149 +0,0 @@
-// Copyright 2016 Joe Wilm, The Alacritty Project Contributors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-use std::collections::HashMap;
-
-use font::Metrics;
-
-use crate::index::{Column, Point};
-use crate::term::cell::Flags;
-use crate::term::color::Rgb;
-use crate::term::{RenderableCell, SizeInfo};
-
-#[derive(Debug, Copy, Clone)]
-pub struct RenderRect {
- pub x: f32,
- pub y: f32,
- pub width: f32,
- pub height: f32,
- pub color: Rgb,
- pub alpha: f32,
-}
-
-impl RenderRect {
- pub fn new(x: f32, y: f32, width: f32, height: f32, color: Rgb, alpha: f32) -> Self {
- RenderRect { x, y, width, height, color, alpha }
- }
-}
-
-#[derive(Copy, Clone, Debug, PartialEq, Eq)]
-pub struct RenderLine {
- pub start: Point,
- pub end: Point,
- pub color: Rgb,
-}
-
-impl RenderLine {
- pub fn rects(&self, flag: Flags, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
- let mut rects = Vec::new();
-
- let mut start = self.start;
- while start.line < self.end.line {
- let mut end = start;
- end.col = size.cols() - 1;
- rects.push(Self::create_rect(metrics, size, flag, start, end, self.color));
-
- start.col = Column(0);
- start.line += 1;
- }
-
- rects.push(Self::create_rect(metrics, size, flag, start, self.end, self.color));
-
- rects
- }
-
- fn create_rect(
- metrics: &Metrics,
- size: &SizeInfo,
- flag: Flags,
- start: Point,
- end: Point,
- color: Rgb,
- ) -> RenderRect {
- let start_x = start.col.0 as f32 * size.cell_width;
- let end_x = (end.col.0 + 1) as f32 * size.cell_width;
- let width = end_x - start_x;
-
- let (position, mut height) = match flag {
- Flags::UNDERLINE => (metrics.underline_position, metrics.underline_thickness),
- Flags::STRIKEOUT => (metrics.strikeout_position, metrics.strikeout_thickness),
- _ => unimplemented!("Invalid flag for cell line drawing specified"),
- };
-
- // Make sure lines are always visible
- height = height.max(1.);
-
- let line_bottom = (start.line.0 as f32 + 1.) * size.cell_height;
- let baseline = line_bottom + metrics.descent;
-
- let mut y = baseline - position - height / 2.;
- let max_y = line_bottom - height;
- if y > max_y {
- y = max_y;
- }
-
- RenderRect::new(start_x + size.padding_x, y + size.padding_y, width, height, color, 1.)
- }
-}
-
-/// Lines for underline and strikeout.
-#[derive(Default)]
-pub struct RenderLines {
- inner: HashMap<Flags, Vec<RenderLine>>,
-}
-
-impl RenderLines {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn rects(&self, metrics: &Metrics, size: &SizeInfo) -> Vec<RenderRect> {
- self.inner
- .iter()
- .map(|(flag, lines)| -> Vec<RenderRect> {
- lines.iter().map(|line| line.rects(*flag, metrics, size)).flatten().collect()
- })
- .flatten()
- .collect()
- }
-
- /// Update the stored lines with the next cell info.
- pub fn update(&mut self, cell: RenderableCell) {
- for flag in &[Flags::UNDERLINE, Flags::STRIKEOUT] {
- if !cell.flags.contains(*flag) {
- continue;
- }
-
- // Check if there's an active line
- if let Some(line) = self.inner.get_mut(flag).and_then(|lines| lines.last_mut()) {
- if cell.fg == line.color
- && cell.column == line.end.col + 1
- && cell.line == line.end.line
- {
- // Update the length of the line
- line.end = cell.into();
- continue;
- }
- }
-
- // Start new line if there currently is none
- let line = RenderLine { start: cell.into(), end: cell.into(), color: cell.fg };
- match self.inner.get_mut(flag) {
- Some(lines) => lines.push(line),
- None => {
- self.inner.insert(*flag, vec![line]);
- },
- }
- }
- }
-}
diff --git a/alacritty_terminal/src/term/mod.rs b/alacritty_terminal/src/term/mod.rs
index 6a51ba35..a5bc8313 100644
--- a/alacritty_terminal/src/term/mod.rs
+++ b/alacritty_terminal/src/term/mod.rs
@@ -27,7 +27,6 @@ use crate::ansi::{
};
use crate::clipboard::{Clipboard, ClipboardType};
use crate::config::{Config, VisualBellAnimation, DEFAULT_NAME};
-use crate::cursor::CursorKey;
use crate::event::{Event, EventListener};
use crate::grid::{
BidirectionalIterator, DisplayIter, Grid, GridCell, IndexRegion, Indexed, Scroll,
@@ -161,6 +160,13 @@ impl<T> selection::Dimensions for Term<T> {
}
}
+/// A key for caching cursor glyphs
+#[derive(Debug, Eq, PartialEq, Copy, Clone, Hash, Deserialize)]
+pub struct CursorKey {
+ pub style: CursorStyle,
+ pub is_wide: bool,
+}
+
/// Iterator that yields cells needing render
///
/// Yields cells that require work to be displayed (that is, not a an empty