479 lines
13 KiB
Rust
479 lines
13 KiB
Rust
use std::mem::size_of;
|
|
use std::ptr;
|
|
|
|
use crossfont::RasterizedGlyph;
|
|
use log::info;
|
|
|
|
use alacritty_terminal::term::cell::Flags;
|
|
use alacritty_terminal::term::SizeInfo;
|
|
|
|
use crate::display::content::RenderableCell;
|
|
use crate::gl;
|
|
use crate::gl::types::*;
|
|
use crate::renderer::shader::{ShaderProgram, ShaderVersion};
|
|
use crate::renderer::{cstr, Error};
|
|
|
|
use super::atlas::{Atlas, ATLAS_SIZE};
|
|
use super::{
|
|
Glyph, LoadGlyph, LoaderApi, TextRenderApi, TextRenderBatch, TextRenderer, TextShader,
|
|
};
|
|
|
|
// Shader source.
|
|
static TEXT_SHADER_F: &str = include_str!("../../../res/gles2/text.f.glsl");
|
|
static TEXT_SHADER_V: &str = include_str!("../../../res/gles2/text.v.glsl");
|
|
|
|
#[derive(Debug)]
|
|
pub struct Gles2Renderer {
|
|
program: TextShaderProgram,
|
|
vao: GLuint,
|
|
vbo: GLuint,
|
|
ebo: GLuint,
|
|
atlas: Vec<Atlas>,
|
|
batch: Batch,
|
|
current_atlas: usize,
|
|
active_tex: GLuint,
|
|
}
|
|
|
|
impl Gles2Renderer {
|
|
pub fn new() -> Result<Self, Error> {
|
|
info!("Using OpenGL ES 2.0 renderer");
|
|
|
|
let program = TextShaderProgram::new(ShaderVersion::Gles2)?;
|
|
let mut vao: GLuint = 0;
|
|
let mut vbo: GLuint = 0;
|
|
let mut ebo: GLuint = 0;
|
|
|
|
let mut vertex_indices = Vec::with_capacity(BATCH_MAX / 4 * 6);
|
|
for index in 0..(BATCH_MAX / 4) as u16 {
|
|
let index = index * 4;
|
|
vertex_indices.push(index);
|
|
vertex_indices.push(index + 1);
|
|
vertex_indices.push(index + 3);
|
|
|
|
vertex_indices.push(index + 1);
|
|
vertex_indices.push(index + 2);
|
|
vertex_indices.push(index + 3);
|
|
}
|
|
|
|
unsafe {
|
|
gl::Enable(gl::BLEND);
|
|
|
|
gl::DepthMask(gl::FALSE);
|
|
|
|
gl::GenVertexArrays(1, &mut vao);
|
|
gl::GenBuffers(1, &mut ebo);
|
|
gl::GenBuffers(1, &mut vbo);
|
|
gl::BindVertexArray(vao);
|
|
|
|
// Elements buffer.
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, ebo);
|
|
gl::BufferData(
|
|
gl::ELEMENT_ARRAY_BUFFER,
|
|
(vertex_indices.capacity() * size_of::<u16>()) as isize,
|
|
vertex_indices.as_ptr() as *const _,
|
|
gl::STATIC_DRAW,
|
|
);
|
|
|
|
// Vertex buffer.
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
|
gl::BufferData(
|
|
gl::ARRAY_BUFFER,
|
|
(BATCH_MAX * size_of::<TextVertex>()) as isize,
|
|
ptr::null(),
|
|
gl::STREAM_DRAW,
|
|
);
|
|
|
|
let mut index = 0;
|
|
let mut size = 0;
|
|
|
|
macro_rules! add_attr {
|
|
($count:expr, $gl_type:expr, $type:ty) => {
|
|
gl::VertexAttribPointer(
|
|
index,
|
|
$count,
|
|
$gl_type,
|
|
gl::FALSE,
|
|
size_of::<TextVertex>() as i32,
|
|
size as *const _,
|
|
);
|
|
gl::EnableVertexAttribArray(index);
|
|
|
|
#[allow(unused_assignments)]
|
|
{
|
|
size += $count * size_of::<$type>();
|
|
index += 1;
|
|
}
|
|
};
|
|
}
|
|
|
|
// Cell coords.
|
|
add_attr!(2, gl::SHORT, i16);
|
|
|
|
// Glyph coords.
|
|
add_attr!(2, gl::SHORT, i16);
|
|
|
|
// UV.
|
|
add_attr!(2, gl::FLOAT, u32);
|
|
|
|
// Color and bitmap color.
|
|
//
|
|
// These are packed together because of an OpenGL driver issue on macOS, which caused a
|
|
// `vec3(u8)` text color and a `u8` for glyph color to cause performance regressions.
|
|
add_attr!(4, gl::UNSIGNED_BYTE, u8);
|
|
|
|
// Background color.
|
|
add_attr!(4, gl::UNSIGNED_BYTE, u8);
|
|
|
|
// Cleanup.
|
|
gl::BindVertexArray(0);
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
|
}
|
|
|
|
Ok(Self {
|
|
program,
|
|
vao,
|
|
vbo,
|
|
ebo,
|
|
atlas: vec![Atlas::new(ATLAS_SIZE)],
|
|
batch: Batch::new(),
|
|
current_atlas: 0,
|
|
active_tex: 0,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Drop for Gles2Renderer {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
gl::DeleteBuffers(1, &self.vbo);
|
|
gl::DeleteBuffers(1, &self.ebo);
|
|
gl::DeleteVertexArrays(1, &self.vao);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> TextRenderer<'a> for Gles2Renderer {
|
|
type RenderApi = RenderApi<'a>;
|
|
type RenderBatch = Batch;
|
|
type Shader = TextShaderProgram;
|
|
|
|
fn program(&self) -> &Self::Shader {
|
|
&self.program
|
|
}
|
|
|
|
fn with_api<'b: 'a, F, T>(&'b mut self, _: &'b SizeInfo, func: F) -> T
|
|
where
|
|
F: FnOnce(Self::RenderApi) -> T,
|
|
{
|
|
unsafe {
|
|
gl::UseProgram(self.program.id());
|
|
gl::BindVertexArray(self.vao);
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
|
gl::ActiveTexture(gl::TEXTURE0);
|
|
}
|
|
|
|
let res = func(RenderApi {
|
|
active_tex: &mut self.active_tex,
|
|
batch: &mut self.batch,
|
|
atlas: &mut self.atlas,
|
|
current_atlas: &mut self.current_atlas,
|
|
program: &mut self.program,
|
|
});
|
|
|
|
unsafe {
|
|
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
|
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
|
gl::BindVertexArray(0);
|
|
|
|
gl::UseProgram(0);
|
|
}
|
|
|
|
res
|
|
}
|
|
|
|
fn loader_api(&mut self) -> LoaderApi<'_> {
|
|
LoaderApi {
|
|
active_tex: &mut self.active_tex,
|
|
atlas: &mut self.atlas,
|
|
current_atlas: &mut self.current_atlas,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Maximum items to be drawn in a batch.
|
|
///
|
|
/// We use the closest number to `u16::MAX` dividable by 4 (amount of vertices we push for a glyph),
|
|
/// since it's the maximum possible index in `glDrawElements` in gles2.
|
|
const BATCH_MAX: usize = (u16::MAX - u16::MAX % 4) as usize;
|
|
|
|
#[derive(Debug)]
|
|
pub struct Batch {
|
|
tex: GLuint,
|
|
vertices: Vec<TextVertex>,
|
|
}
|
|
|
|
impl Batch {
|
|
fn new() -> Self {
|
|
Self { tex: 0, vertices: Vec::with_capacity(BATCH_MAX) }
|
|
}
|
|
|
|
#[inline]
|
|
fn len(&self) -> usize {
|
|
self.vertices.len()
|
|
}
|
|
|
|
#[inline]
|
|
fn capacity(&self) -> usize {
|
|
BATCH_MAX
|
|
}
|
|
|
|
#[inline]
|
|
fn size(&self) -> usize {
|
|
self.len() * size_of::<TextVertex>()
|
|
}
|
|
|
|
#[inline]
|
|
fn clear(&mut self) {
|
|
self.vertices.clear();
|
|
}
|
|
}
|
|
|
|
impl TextRenderBatch for Batch {
|
|
#[inline]
|
|
fn tex(&self) -> GLuint {
|
|
self.tex
|
|
}
|
|
|
|
#[inline]
|
|
fn full(&self) -> bool {
|
|
self.capacity() == self.len()
|
|
}
|
|
|
|
#[inline]
|
|
fn is_empty(&self) -> bool {
|
|
self.len() == 0
|
|
}
|
|
|
|
fn add_item(&mut self, cell: &RenderableCell, glyph: &Glyph, size_info: &SizeInfo) {
|
|
if self.is_empty() {
|
|
self.tex = glyph.tex_id;
|
|
}
|
|
|
|
// Calculate the cell position.
|
|
let x = cell.point.column.0 as i16 * size_info.cell_width() as i16;
|
|
let y = cell.point.line as i16 * size_info.cell_height() as i16;
|
|
|
|
// Calculate the glyph position.
|
|
let glyph_x = cell.point.column.0 as i16 * size_info.cell_width() as i16 + glyph.left;
|
|
let glyph_y = (cell.point.line + 1) as i16 * size_info.cell_height() as i16 - glyph.top;
|
|
|
|
let colored = if glyph.multicolor { 1 } else { 0 };
|
|
let is_wide = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
|
|
|
|
let mut vertex = TextVertex {
|
|
x,
|
|
y: y + size_info.cell_height() as i16,
|
|
|
|
glyph_x,
|
|
glyph_y: glyph_y + glyph.height,
|
|
|
|
u: glyph.uv_left,
|
|
v: glyph.uv_bot + glyph.uv_height,
|
|
r: cell.fg.r,
|
|
g: cell.fg.g,
|
|
b: cell.fg.b,
|
|
colored,
|
|
bg_r: cell.bg.r,
|
|
bg_g: cell.bg.g,
|
|
bg_b: cell.bg.b,
|
|
bg_a: (cell.bg_alpha * 255.0) as u8,
|
|
};
|
|
|
|
self.vertices.push(vertex);
|
|
|
|
vertex.y = y;
|
|
vertex.glyph_y = glyph_y;
|
|
vertex.u = glyph.uv_left;
|
|
vertex.v = glyph.uv_bot;
|
|
self.vertices.push(vertex);
|
|
|
|
vertex.x = x + is_wide * size_info.cell_width() as i16;
|
|
vertex.glyph_x = glyph_x + glyph.width;
|
|
vertex.u = glyph.uv_left + glyph.uv_width;
|
|
vertex.v = glyph.uv_bot;
|
|
self.vertices.push(vertex);
|
|
|
|
vertex.x = x + is_wide * size_info.cell_width() as i16;
|
|
vertex.y = y + size_info.cell_height() as i16;
|
|
vertex.glyph_x = glyph_x + glyph.width;
|
|
vertex.glyph_y = glyph_y + glyph.height;
|
|
vertex.u = glyph.uv_left + glyph.uv_width;
|
|
vertex.v = glyph.uv_bot + glyph.uv_height;
|
|
self.vertices.push(vertex);
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct RenderApi<'a> {
|
|
active_tex: &'a mut GLuint,
|
|
batch: &'a mut Batch,
|
|
atlas: &'a mut Vec<Atlas>,
|
|
current_atlas: &'a mut usize,
|
|
program: &'a mut TextShaderProgram,
|
|
}
|
|
|
|
impl<'a> Drop for RenderApi<'a> {
|
|
fn drop(&mut self) {
|
|
if !self.batch.is_empty() {
|
|
self.render_batch();
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> LoadGlyph for RenderApi<'a> {
|
|
fn load_glyph(&mut self, rasterized: &RasterizedGlyph) -> Glyph {
|
|
Atlas::load_glyph(self.active_tex, self.atlas, self.current_atlas, rasterized)
|
|
}
|
|
|
|
fn clear(&mut self) {
|
|
Atlas::clear_atlas(self.atlas, self.current_atlas)
|
|
}
|
|
}
|
|
|
|
impl<'a> TextRenderApi<Batch> for RenderApi<'a> {
|
|
fn batch(&mut self) -> &mut Batch {
|
|
self.batch
|
|
}
|
|
|
|
fn render_batch(&mut self) {
|
|
unsafe {
|
|
gl::BufferSubData(
|
|
gl::ARRAY_BUFFER,
|
|
0,
|
|
self.batch.size() as isize,
|
|
self.batch.vertices.as_ptr() as *const _,
|
|
);
|
|
}
|
|
|
|
if *self.active_tex != self.batch.tex() {
|
|
unsafe {
|
|
gl::BindTexture(gl::TEXTURE_2D, self.batch.tex());
|
|
}
|
|
*self.active_tex = self.batch.tex();
|
|
}
|
|
|
|
unsafe {
|
|
let num_indices = (self.batch.len() / 4 * 6) as i32;
|
|
|
|
// The rendering is inspired by
|
|
// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
|
|
|
|
// Draw background.
|
|
self.program.set_rendering_pass(RenderingPass::Background);
|
|
gl::BlendFunc(gl::ONE, gl::ZERO);
|
|
gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
|
|
|
|
// First text rendering pass.
|
|
self.program.set_rendering_pass(RenderingPass::SubpixelPass1);
|
|
gl::BlendFuncSeparate(gl::ZERO, gl::ONE_MINUS_SRC_COLOR, gl::ZERO, gl::ONE);
|
|
gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
|
|
|
|
// Second text rendering pass.
|
|
self.program.set_rendering_pass(RenderingPass::SubpixelPass2);
|
|
gl::BlendFuncSeparate(gl::ONE_MINUS_DST_ALPHA, gl::ONE, gl::ZERO, gl::ONE);
|
|
gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
|
|
|
|
// Third pass.
|
|
self.program.set_rendering_pass(RenderingPass::SubpixelPass3);
|
|
gl::BlendFuncSeparate(gl::ONE, gl::ONE, gl::ONE, gl::ONE_MINUS_SRC_ALPHA);
|
|
gl::DrawElements(gl::TRIANGLES, num_indices, gl::UNSIGNED_SHORT, ptr::null());
|
|
}
|
|
|
|
self.batch.clear();
|
|
}
|
|
}
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Copy, Clone)]
|
|
struct TextVertex {
|
|
// Cell coordinates.
|
|
x: i16,
|
|
y: i16,
|
|
|
|
// Glyph coordinates.
|
|
glyph_x: i16,
|
|
glyph_y: i16,
|
|
|
|
// Offsets into Atlas.
|
|
u: f32,
|
|
v: f32,
|
|
|
|
// Color.
|
|
r: u8,
|
|
g: u8,
|
|
b: u8,
|
|
|
|
// Whether the glyph is colored.
|
|
colored: u8,
|
|
|
|
// Background color.
|
|
bg_r: u8,
|
|
bg_g: u8,
|
|
bg_b: u8,
|
|
bg_a: u8,
|
|
}
|
|
|
|
// NOTE: These flags must be in sync with their usage in the gles2/text.*.glsl shaders.
|
|
#[repr(u8)]
|
|
enum RenderingPass {
|
|
Background = 0,
|
|
SubpixelPass1 = 1,
|
|
SubpixelPass2 = 2,
|
|
SubpixelPass3 = 3,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct TextShaderProgram {
|
|
/// Shader program.
|
|
program: ShaderProgram,
|
|
|
|
/// Projection scale and offset uniform.
|
|
u_projection: GLint,
|
|
|
|
/// Rendering pass.
|
|
///
|
|
/// The rendering is split into 4 passes. One is used for the background and the rest to
|
|
/// perform subpixel text rendering according to
|
|
/// https://github.com/servo/webrender/blob/master/webrender/doc/text-rendering.md.
|
|
///
|
|
/// Rendering is split into three passes.
|
|
u_rendering_pass: GLint,
|
|
}
|
|
|
|
impl TextShaderProgram {
|
|
pub fn new(shader_version: ShaderVersion) -> Result<Self, Error> {
|
|
let program = ShaderProgram::new(shader_version, TEXT_SHADER_V, TEXT_SHADER_F)?;
|
|
Ok(Self {
|
|
u_projection: program.get_uniform_location(cstr!("projection"))?,
|
|
u_rendering_pass: program.get_uniform_location(cstr!("renderingPass"))?,
|
|
program,
|
|
})
|
|
}
|
|
|
|
fn set_rendering_pass(&self, rendering_pass: RenderingPass) {
|
|
unsafe { gl::Uniform1i(self.u_rendering_pass, rendering_pass as i32) }
|
|
}
|
|
}
|
|
|
|
impl TextShader for TextShaderProgram {
|
|
fn id(&self) -> GLuint {
|
|
self.program.id()
|
|
}
|
|
|
|
fn projection_uniform(&self) -> GLint {
|
|
self.u_projection
|
|
}
|
|
}
|