summaryrefslogtreecommitdiff
path: root/alacritty_terminal/src/renderer/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'alacritty_terminal/src/renderer/mod.rs')
-rw-r--r--alacritty_terminal/src/renderer/mod.rs1629
1 files changed, 1629 insertions, 0 deletions
diff --git a/alacritty_terminal/src/renderer/mod.rs b/alacritty_terminal/src/renderer/mod.rs
new file mode 100644
index 00000000..82c6c2df
--- /dev/null
+++ b/alacritty_terminal/src/renderer/mod.rs
@@ -0,0 +1,1629 @@
+// 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 glutin::dpi::PhysicalSize;
+use notify::{watcher, DebouncedEvent, RecursiveMode, Watcher};
+
+use crate::ansi::CursorStyle;
+use crate::config::{self, Config, Delta};
+use crate::gl;
+use crate::gl::types::*;
+use crate::index::{Column, Line};
+use crate::renderer::rects::{Rect, Rects};
+use crate::term::color::Rgb;
+use crate::term::{self, cell, RenderableCell, RenderableCellContent};
+
+pub mod rects;
+
+// Shader paths for live reload
+static TEXT_SHADER_F_PATH: &'static str =
+ concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl");
+static TEXT_SHADER_V_PATH: &'static str =
+ concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl");
+static RECT_SHADER_F_PATH: &'static str =
+ concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl");
+static RECT_SHADER_V_PATH: &'static 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: &'static str =
+ include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.f.glsl"));
+static TEXT_SHADER_V: &'static str =
+ include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/text.v.glsl"));
+static RECT_SHADER_F: &'static str =
+ include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../res/rect.f.glsl"));
+static RECT_SHADER_V: &'static 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<CursorStyle, Glyph, BuildHasherDefault<FnvHasher>>,
+
+ /// Rasterizer for loading new glyphs
+ rasterizer: Rasterizer,
+
+ /// regular font
+ font_key: FontKey,
+
+ /// italic font
+ italic_key: FontKey,
+
+ /// bold font
+ bold_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) = 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,
+ 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);
+
+ 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..=128u8 {
+ self.get(GlyphKey { font_key: font, c: i as char, size }, loader);
+ }
+ }
+
+ /// Computes font keys for (Regular, Bold, Italic)
+ fn compute_font_keys(
+ font: &config::Font,
+ rasterizer: &mut Rasterizer,
+ ) -> Result<(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);
+
+ Ok((regular, 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 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")
+ }
+
+ 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,
+ size: font::Size,
+ 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 font = font.to_owned().with_size(size);
+ let (regular, 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, 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.metrics = metrics;
+
+ self.load_glyphs_for_font(regular, loader);
+ self.load_glyphs_for_font(bold, loader);
+ self.load_glyphs_for_font(italic, loader);
+
+ Ok(())
+ }
+
+ // Calculate font metrics without access to a glyph cache
+ //
+ // This should only be used *before* OpenGL is initialized and the glyph cache can be filled.
+ pub fn static_metrics(config: &Config, dpr: f32) -> Result<font::Metrics, font::Error> {
+ let font = config.font().clone();
+
+ let mut rasterizer = font::Rasterizer::new(dpr, config.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())
+ }
+}
+
+#[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> {
+ 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,
+}
+
+#[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") {
+ ::std::thread::spawn(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,
+ config: &Config,
+ props: &term::SizeInfo,
+ visual_bell_intensity: f64,
+ cell_line_rects: Rects,
+ ) {
+ // 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::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA);
+
+ // 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 visual bell
+ let color = config.visual_bell().color();
+ let rect = Rect::new(0., 0., props.width, props.height);
+ self.render_rect(&rect, color, visual_bell_intensity as f32, props);
+
+ // Draw underlines and strikeouts
+ for cell_line_rect in cell_line_rects.rects() {
+ self.render_rect(&cell_line_rect.0, cell_line_rect.1, 255., 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>(&mut self, config: &Config, props: &term::SizeInfo, func: F) -> T
+ where
+ F: FnOnce(RenderApi<'_>) -> 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: PhysicalSize, padding_x: f32, padding_y: f32) {
+ let (width, height): (u32, u32) = size.into();
+
+ // viewport
+ unsafe {
+ let width = width as i32;
+ let height = height as i32;
+ let padding_x = padding_x as i32;
+ let padding_y = padding_y as i32;
+ gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
+
+ // update projection
+ gl::UseProgram(self.program.id);
+ self.program.update_projection(
+ width as f32,
+ height as f32,
+ padding_x as f32,
+ padding_y as f32,
+ );
+ gl::UseProgram(0);
+ }
+ }
+
+ // Render a rectangle
+ //
+ // This requires the rectangle program to be activated
+ fn render_rect(&mut self, rect: &Rect<f32>, color: Rgb, alpha: f32, size: &term::SizeInfo) {
+ // Do nothing when alpha is fully transparent
+ if 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(color, alpha);
+
+ // Draw the rectangle
+ gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null());
+ }
+ }
+}
+
+impl<'a> RenderApi<'a> {
+ pub fn clear(&self, color: Rgb) {
+ let alpha = self.config.background_opacity().get();
+ unsafe {
+ 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: cell::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_style, ref raw)) => {
+ // Raw cell pixel buffers like cursors don't need to go through font lookup
+ let glyph = glyph_cache
+ .cursor_cache
+ .entry(cursor_style)
+ .or_insert_with(|| self.load_glyph(raw));
+ self.add_render_item(&cell, &glyph);
+ return;
+ },
+ RenderableCellContent::Chars(chars) => chars,
+ };
+
+ // Get font key for cell
+ // FIXME this is super inefficient.
+ let font_key = if cell.flags.contains(cell::Flags::BOLD) {
+ glyph_cache.bold_key
+ } else if cell.flags.contains(cell::Flags::ITALIC) {
+ glyph_cache.italic_key
+ } else {
+ glyph_cache.font_key
+ };
+
+ // Don't render text of HIDDEN cells
+ let mut chars = if cell.flags.contains(cell::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> 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_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:
+///
+/// ```ignore
+/// (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(())
+ }
+}