mirror of
https://github.com/wez/wezterm.git
synced 2024-12-25 06:12:16 +03:00
use a single big vertex buffer
This commit is contained in:
parent
4acfd82e87
commit
a4c45fa321
@ -12,8 +12,8 @@ use std::rc::Rc;
|
||||
/// space, then we move up to the logical row above. Since sprites can
|
||||
/// have varying height the height of the rows can also vary.
|
||||
#[derive(Debug)]
|
||||
struct Texture {
|
||||
texture: Rc<glium::texture::SrgbTexture2d>,
|
||||
pub struct Texture {
|
||||
texture: Rc<SrgbTexture2d>,
|
||||
|
||||
// Dimensions of the texture
|
||||
width: u32,
|
||||
@ -134,8 +134,14 @@ pub struct Atlas {
|
||||
const TEX_SIZE: u32 = 2048;
|
||||
|
||||
impl Atlas {
|
||||
pub fn new() -> Self {
|
||||
Default::default()
|
||||
pub fn new<'a, F: Facade>(facade: &F) -> Result<Self, Error> {
|
||||
let tex = Texture::new(facade, TEX_SIZE, TEX_SIZE)?;
|
||||
Ok(Self { textures: vec![tex] })
|
||||
}
|
||||
|
||||
// TODO: this is gross, need to tweak this API
|
||||
pub fn texture(&self) -> Rc<SrgbTexture2d> {
|
||||
Rc::clone(&self.textures[0].texture)
|
||||
}
|
||||
|
||||
pub fn allocate<'a, F: Facade, T: Texture2dDataSource<'a>>(
|
||||
|
370
src/xwin.rs
370
src/xwin.rs
@ -2,7 +2,7 @@ use config::TextStyle;
|
||||
use euclid;
|
||||
use failure::{self, Error};
|
||||
use font::{FontConfiguration, GlyphInfo, ftwrap};
|
||||
use glium::{self, Surface};
|
||||
use glium::{self, IndexBuffer, Surface, VertexBuffer};
|
||||
use pty::MasterPty;
|
||||
use std::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
@ -56,50 +56,86 @@ impl Point {
|
||||
}
|
||||
}
|
||||
|
||||
/// Each cell is composed of two triangles built from 4 vertices.
|
||||
/// The buffer is organized row by row.
|
||||
const VERTICES_PER_CELL: usize = 4;
|
||||
const V_TOP_LEFT: usize = 0;
|
||||
const V_TOP_RIGHT: usize = 1;
|
||||
const V_BOT_LEFT: usize = 2;
|
||||
const V_BOT_RIGHT: usize = 3;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
struct Vertex {
|
||||
// pre-computed by compute_vertices and changed only on resize
|
||||
position: Point,
|
||||
// adjustment for glyph size, recomputed each time the cell changes
|
||||
adjust: Point,
|
||||
// texture coords are updated as the screen contents change
|
||||
tex: (f32, f32),
|
||||
// cell foreground and background color
|
||||
fg_color: (f32, f32, f32, f32),
|
||||
bg_color: (f32, f32, f32, f32),
|
||||
// TODO: underline, strikethrough, cursor, selected
|
||||
}
|
||||
|
||||
implement_vertex!(Vertex, position, tex);
|
||||
implement_vertex!(Vertex, position, adjust, tex, fg_color, bg_color);
|
||||
|
||||
const VERTEX_SHADER: &str = r#"
|
||||
#version 300 es
|
||||
in vec2 position;
|
||||
in vec2 adjust;
|
||||
in vec2 tex;
|
||||
in vec4 fg_color;
|
||||
in vec4 bg_color;
|
||||
|
||||
uniform mat4 projection;
|
||||
uniform mat4 translation;
|
||||
uniform bool bg_fill;
|
||||
|
||||
out vec2 tex_coords;
|
||||
out vec4 o_fg_color;
|
||||
out vec4 o_bg_color;
|
||||
out float o_has_color;
|
||||
|
||||
vec4 cell_pos() {
|
||||
if (bg_fill) {
|
||||
return vec4(position, 0.0, 1.0);
|
||||
} else {
|
||||
return vec4(position + adjust, 0.0, 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
tex_coords = tex;
|
||||
vec4 pos = vec4(position, 0.0, 1.0) * translation;
|
||||
gl_Position = projection * pos;
|
||||
o_fg_color = fg_color;
|
||||
o_bg_color = bg_color;
|
||||
o_has_color = 0.0;
|
||||
|
||||
gl_Position = projection * cell_pos();
|
||||
}
|
||||
"#;
|
||||
|
||||
const FRAGMENT_SHADER: &str = r#"
|
||||
#version 300 es
|
||||
precision mediump float;
|
||||
uniform vec3 fg_color;
|
||||
uniform vec4 bg_color;
|
||||
out vec4 color;
|
||||
in vec2 tex_coords;
|
||||
in vec4 o_fg_color;
|
||||
in vec4 o_bg_color;
|
||||
in float o_has_color;
|
||||
|
||||
out vec4 color;
|
||||
uniform sampler2D glyph_tex;
|
||||
uniform bool has_color;
|
||||
uniform bool bg_fill;
|
||||
|
||||
void main() {
|
||||
if (bg_fill) {
|
||||
color = bg_color;
|
||||
color = o_bg_color;
|
||||
} else {
|
||||
color = texture2D(glyph_tex, tex_coords);
|
||||
if (!has_color) {
|
||||
if (o_has_color == 0.0) {
|
||||
// if it's not a color emoji, tint with the fg_color
|
||||
color = color * vec4(fg_color, 1.0);
|
||||
color = color * o_fg_color;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -139,8 +175,9 @@ pub struct TerminalWindow<'a> {
|
||||
palette: term::color::ColorPalette,
|
||||
program: glium::Program,
|
||||
fill_program: glium::Program,
|
||||
glyph_vertex_buffer: glium::VertexBuffer<Vertex>,
|
||||
line_vertex_buffer: glium::VertexBuffer<Vertex>,
|
||||
glyph_vertex_buffer: RefCell<VertexBuffer<Vertex>>,
|
||||
glyph_index_buffer: IndexBuffer<u32>,
|
||||
line_vertex_buffer: VertexBuffer<Vertex>,
|
||||
projection: Transform3D,
|
||||
atlas: RefCell<Atlas>,
|
||||
}
|
||||
@ -310,29 +347,11 @@ impl<'a> TerminalWindow<'a> {
|
||||
..Default::default()
|
||||
},
|
||||
];
|
||||
glium::VertexBuffer::new(&host.window, &shape)?
|
||||
VertexBuffer::new(&host.window, &shape)?
|
||||
};
|
||||
|
||||
let glyph_vertex_buffer = {
|
||||
let top_left = Vertex {
|
||||
position: Point::new(0.0, 0.0),
|
||||
..Default::default()
|
||||
};
|
||||
let top_right = Vertex {
|
||||
position: Point::new(cw, 0.0),
|
||||
..Default::default()
|
||||
};
|
||||
let bot_left = Vertex {
|
||||
position: Point::new(0.0, ch),
|
||||
..Default::default()
|
||||
};
|
||||
let bot_right = Vertex {
|
||||
position: Point::new(cw, ch),
|
||||
..Default::default()
|
||||
};
|
||||
let shape = [top_left, top_right, bot_left, bot_right];
|
||||
glium::VertexBuffer::new(&host.window, &shape)?
|
||||
};
|
||||
let (glyph_vertex_buffer, glyph_index_buffer) =
|
||||
Self::compute_vertices(&host, cw, ch, width as f32, height as f32)?;
|
||||
|
||||
let program =
|
||||
glium::Program::from_source(&host.window, VERTEX_SHADER, FRAGMENT_SHADER, None)?;
|
||||
@ -340,14 +359,15 @@ impl<'a> TerminalWindow<'a> {
|
||||
let fill_program =
|
||||
glium::Program::from_source(&host.window, VERTEX_SHADER, FILL_RECT_FRAG_SHADER, None)?;
|
||||
|
||||
let atlas = RefCell::new(Atlas::new());
|
||||
let atlas = RefCell::new(Atlas::new(&host.window)?);
|
||||
|
||||
Ok(TerminalWindow {
|
||||
host,
|
||||
atlas,
|
||||
program,
|
||||
fill_program,
|
||||
glyph_vertex_buffer,
|
||||
glyph_vertex_buffer: RefCell::new(glyph_vertex_buffer),
|
||||
glyph_index_buffer,
|
||||
line_vertex_buffer,
|
||||
conn,
|
||||
width,
|
||||
@ -368,6 +388,66 @@ impl<'a> TerminalWindow<'a> {
|
||||
self.host.window.show();
|
||||
}
|
||||
|
||||
fn compute_vertices(
|
||||
host: &Host,
|
||||
cell_width: f32,
|
||||
cell_height: f32,
|
||||
width: f32,
|
||||
height: f32,
|
||||
) -> Result<(VertexBuffer<Vertex>, IndexBuffer<u32>), Error> {
|
||||
let mut verts = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
let num_cols = ((width + 1.0) / cell_width).floor() as usize;
|
||||
let num_rows = ((height + 1.0) / cell_height).floor() as usize;
|
||||
|
||||
for y in 0..num_rows {
|
||||
for x in 0..num_cols {
|
||||
let y_pos = (height / -2.0) + (y as f32 * cell_height);
|
||||
let x_pos = (width / -2.0) + (x as f32 * cell_width);
|
||||
// Remember starting index for this position
|
||||
let idx = verts.len() as u32;
|
||||
verts.push(Vertex {
|
||||
// Top left
|
||||
position: Point::new(x_pos, y_pos),
|
||||
..Default::default()
|
||||
});
|
||||
verts.push(Vertex {
|
||||
// Top Right
|
||||
position: Point::new(x_pos + cell_width, y_pos),
|
||||
..Default::default()
|
||||
});
|
||||
verts.push(Vertex {
|
||||
// Bottom Left
|
||||
position: Point::new(x_pos, y_pos + cell_height),
|
||||
..Default::default()
|
||||
});
|
||||
verts.push(Vertex {
|
||||
// Bottom Right
|
||||
position: Point::new(x_pos + cell_width, y_pos + cell_height),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
// Emit two triangles to form the glyph quad
|
||||
indices.push(idx);
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 1);
|
||||
indices.push(idx + 2);
|
||||
indices.push(idx + 3);
|
||||
}
|
||||
}
|
||||
|
||||
Ok((
|
||||
VertexBuffer::dynamic(&host.window, &verts)?,
|
||||
IndexBuffer::new(
|
||||
&host.window,
|
||||
glium::index::PrimitiveType::TrianglesList,
|
||||
&indices,
|
||||
)?,
|
||||
))
|
||||
}
|
||||
|
||||
fn compute_projection(width: f32, height: f32) -> Transform3D {
|
||||
// The projection corrects for the aspect ratio and flips the y-axis
|
||||
Transform3D::ortho(
|
||||
@ -388,6 +468,16 @@ impl<'a> TerminalWindow<'a> {
|
||||
self.height = height;
|
||||
self.projection = Self::compute_projection(width as f32, height as f32);
|
||||
|
||||
let (glyph_vertex_buffer, glyph_index_buffer) = Self::compute_vertices(
|
||||
&self.host,
|
||||
self.cell_width as f32,
|
||||
self.cell_height as f32,
|
||||
width as f32,
|
||||
height as f32,
|
||||
)?;
|
||||
self.glyph_vertex_buffer = RefCell::new(glyph_vertex_buffer);
|
||||
self.glyph_index_buffer = glyph_index_buffer;
|
||||
|
||||
// The +1 in here is to handle an irritating case.
|
||||
// When we get N rows with a gap of cell_height - 1 left at
|
||||
// the bottom, we can usually squeeze that extra row in there,
|
||||
@ -595,6 +685,7 @@ impl<'a> TerminalWindow<'a> {
|
||||
num_cells_high: u32,
|
||||
color: RgbColor,
|
||||
) -> Result<(), Error> {
|
||||
/*
|
||||
// Translate cell coordinate from top-left origin in cell coords
|
||||
// to center origin pixel coords
|
||||
let xlate_model = Transform2D::create_translation(
|
||||
@ -621,6 +712,7 @@ impl<'a> TerminalWindow<'a> {
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
*/
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -759,21 +851,25 @@ impl<'a> TerminalWindow<'a> {
|
||||
let top_left = Vertex {
|
||||
position: Point::new(0.0, 0.0),
|
||||
tex: image.top_left(),
|
||||
..Default::default()
|
||||
};
|
||||
let top_right = Vertex {
|
||||
position: Point::new(self.cell_width as f32, 0.0),
|
||||
tex: image.top_right(),
|
||||
..Default::default()
|
||||
};
|
||||
let bot_left = Vertex {
|
||||
position: Point::new(0.0, self.cell_height as f32),
|
||||
tex: image.bottom_left(),
|
||||
..Default::default()
|
||||
};
|
||||
let bot_right = Vertex {
|
||||
position: Point::new(self.cell_width as f32, self.cell_height as f32),
|
||||
tex: image.bottom_right(),
|
||||
..Default::default()
|
||||
};
|
||||
let shape = [top_left, top_right, bot_left, bot_right];
|
||||
glium::VertexBuffer::new(&self.host.window, &shape)?
|
||||
VertexBuffer::new(&self.host.window, &shape)?
|
||||
};
|
||||
|
||||
target.draw(
|
||||
@ -801,6 +897,167 @@ impl<'a> TerminalWindow<'a> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_screen_line(
|
||||
&self,
|
||||
target: &mut glium::Frame,
|
||||
line_idx: usize,
|
||||
line: &Line,
|
||||
selection: Range<usize>,
|
||||
cursor: &CursorPosition,
|
||||
) -> Result<(), Error> {
|
||||
let num_cols = self.terminal.screen().physical_cols;
|
||||
let mut vb = self.glyph_vertex_buffer.borrow_mut();
|
||||
let mut vertices = {
|
||||
let per_line = num_cols * VERTICES_PER_CELL;
|
||||
let start = line_idx * per_line;
|
||||
vb.slice_mut(start..start + per_line)
|
||||
.ok_or_else(|| format_err!("we're confused about the screen size"))?
|
||||
.map()
|
||||
};
|
||||
|
||||
let current_highlight = self.terminal.current_highlight();
|
||||
let cell_width = self.cell_width as f32;
|
||||
let cell_height = self.cell_height as f32;
|
||||
|
||||
// Break the line into clusters of cells with the same attributes
|
||||
let cell_clusters = line.cluster();
|
||||
for cluster in cell_clusters {
|
||||
let attrs = &cluster.attrs;
|
||||
let is_highlited_hyperlink = match (&attrs.hyperlink, ¤t_highlight) {
|
||||
(&Some(ref this), &Some(ref highlight)) => this == highlight,
|
||||
_ => false,
|
||||
};
|
||||
let style = self.fonts.match_style(attrs);
|
||||
|
||||
let (fg_color, bg_color) = {
|
||||
let mut fg_color = &attrs.foreground;
|
||||
let mut bg_color = &attrs.background;
|
||||
|
||||
if attrs.reverse() {
|
||||
mem::swap(&mut fg_color, &mut bg_color);
|
||||
}
|
||||
|
||||
(fg_color, bg_color)
|
||||
};
|
||||
|
||||
let bg_color = self.palette.resolve(bg_color).to_linear_tuple_rgba();
|
||||
|
||||
// Shape the printable text from this cluster
|
||||
let glyph_info = self.shape_text(&cluster.text, &style)?;
|
||||
for info in glyph_info.iter() {
|
||||
let cell_idx = cluster.byte_to_cell_idx[info.cluster as usize];
|
||||
let glyph = self.cached_glyph(info, &style)?;
|
||||
|
||||
let glyph_color = match fg_color {
|
||||
&term::color::ColorAttribute::Foreground => {
|
||||
if let Some(fg) = style.foreground {
|
||||
fg
|
||||
} else {
|
||||
self.palette.resolve(fg_color)
|
||||
}
|
||||
}
|
||||
&term::color::ColorAttribute::PaletteIndex(idx) if idx < 8 => {
|
||||
// For compatibility purposes, switch to a brighter version
|
||||
// of one of the standard ANSI colors when Bold is enabled.
|
||||
// This lifts black to dark grey.
|
||||
let idx = if attrs.intensity() == term::Intensity::Bold {
|
||||
idx + 8
|
||||
} else {
|
||||
idx
|
||||
};
|
||||
self.palette.resolve(
|
||||
&term::color::ColorAttribute::PaletteIndex(idx),
|
||||
)
|
||||
}
|
||||
_ => self.palette.resolve(fg_color),
|
||||
}.to_linear_tuple_rgba();
|
||||
|
||||
let glyph_color = match &glyph.texture {
|
||||
&Some(_) => glyph_color,
|
||||
// Whitespace glyph; render with 0 alpha
|
||||
&None => (0.0, 0.0, 0.0, 0.0f32),
|
||||
};
|
||||
|
||||
let left: f32 = glyph.x_offset as f32 + glyph.bearing_x as f32;
|
||||
let top = (self.cell_height as f32 + self.descender as f32) -
|
||||
(glyph.y_offset as f32 + glyph.bearing_y as f32);
|
||||
|
||||
for cur_x in cell_idx..cell_idx + info.num_cells as usize {
|
||||
|
||||
if cur_x >= num_cols {
|
||||
// terminal line data is wider than the window.
|
||||
// This happens for example while live resizing the window
|
||||
// smaller than the terminal.
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: underline and strikethrough
|
||||
|
||||
let selected = term::in_range(cur_x, &selection);
|
||||
let is_cursor = line_idx as i64 == cursor.y && cursor.x == cur_x;
|
||||
|
||||
let (glyph_color, bg_color) = match (selected, is_cursor) {
|
||||
// Normally, render the cell as configured
|
||||
(false, false) => (glyph_color, bg_color),
|
||||
// Cursor cell always renders with background over cursor color
|
||||
(_, true) => (
|
||||
self.palette.background.to_linear_tuple_rgba(),
|
||||
self.palette.cursor.to_linear_tuple_rgba(),
|
||||
),
|
||||
// Selection text colors the background
|
||||
(true, false) => (
|
||||
glyph_color,
|
||||
// TODO: configurable selection color
|
||||
self.palette.cursor.to_linear_tuple_rgba(),
|
||||
),
|
||||
};
|
||||
|
||||
let vert_idx = cur_x * VERTICES_PER_CELL;
|
||||
let vert = &mut vertices[vert_idx..vert_idx + VERTICES_PER_CELL];
|
||||
|
||||
vert[V_TOP_LEFT].fg_color = glyph_color;
|
||||
vert[V_TOP_RIGHT].fg_color = glyph_color;
|
||||
vert[V_BOT_LEFT].fg_color = glyph_color;
|
||||
vert[V_BOT_RIGHT].fg_color = glyph_color;
|
||||
|
||||
vert[V_TOP_LEFT].bg_color = bg_color;
|
||||
vert[V_TOP_RIGHT].bg_color = bg_color;
|
||||
vert[V_BOT_LEFT].bg_color = bg_color;
|
||||
vert[V_BOT_RIGHT].bg_color = bg_color;
|
||||
|
||||
match &glyph.texture {
|
||||
&Some(ref texture) => {
|
||||
let right = (texture.coords.width as f32 + left) - cell_width;
|
||||
let bottom = (texture.coords.height as f32 + top) - cell_height;
|
||||
|
||||
vert[V_TOP_LEFT].tex = texture.top_left();
|
||||
vert[V_TOP_LEFT].adjust = Point::new(left, top);
|
||||
|
||||
vert[V_TOP_RIGHT].tex = texture.top_right();
|
||||
vert[V_TOP_RIGHT].adjust = Point::new(right, top);
|
||||
|
||||
vert[V_BOT_LEFT].tex = texture.bottom_left();
|
||||
vert[V_BOT_LEFT].adjust = Point::new(left, bottom);
|
||||
|
||||
vert[V_BOT_RIGHT].tex = texture.bottom_right();
|
||||
vert[V_BOT_RIGHT].adjust = Point::new(right, bottom);
|
||||
}
|
||||
&None => {
|
||||
// Whitespace; no texture to render
|
||||
let zero = (0.0, 0.0f32);
|
||||
vert[V_TOP_LEFT].tex = zero;
|
||||
vert[V_TOP_RIGHT].tex = zero;
|
||||
vert[V_BOT_LEFT].tex = zero;
|
||||
vert[V_BOT_RIGHT].tex = zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_line(
|
||||
&self,
|
||||
target: &mut glium::Frame,
|
||||
@ -987,10 +1244,10 @@ impl<'a> TerminalWindow<'a> {
|
||||
|
||||
let cursor = self.terminal.cursor_pos();
|
||||
{
|
||||
let dirty_lines = self.terminal.get_dirty_lines(true);
|
||||
let dirty_lines = self.terminal.get_dirty_lines(false);
|
||||
|
||||
for (line_idx, line, selrange) in dirty_lines {
|
||||
self.render_line(
|
||||
self.render_screen_line(
|
||||
&mut target,
|
||||
line_idx,
|
||||
line,
|
||||
@ -1000,6 +1257,43 @@ impl<'a> TerminalWindow<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
let tex = self.atlas.borrow().texture();
|
||||
|
||||
target.draw(
|
||||
&*self.glyph_vertex_buffer.borrow(),
|
||||
&self.glyph_index_buffer,
|
||||
&self.program,
|
||||
&uniform! {
|
||||
projection: self.projection.to_column_arrays(),
|
||||
glyph_tex: &*tex,
|
||||
//has_color: glyph.has_color,
|
||||
//bg_color: bg_color.to_linear_tuple_rgba(),
|
||||
bg_fill: true,
|
||||
},
|
||||
&glium::DrawParameters {
|
||||
blend: glium::Blend::alpha_blending(),
|
||||
dithering: false,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
target.draw(
|
||||
&*self.glyph_vertex_buffer.borrow(),
|
||||
&self.glyph_index_buffer,
|
||||
&self.program,
|
||||
&uniform! {
|
||||
projection: self.projection.to_column_arrays(),
|
||||
glyph_tex: &*tex,
|
||||
//has_color: glyph.has_color,
|
||||
//bg_color: bg_color.to_linear_tuple_rgba(),
|
||||
bg_fill: false,
|
||||
},
|
||||
&glium::DrawParameters {
|
||||
blend: glium::Blend::alpha_blending(),
|
||||
dithering: false,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
||||
self.terminal.clean_dirty_lines();
|
||||
target.finish().unwrap();
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user