mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-18 13:55:23 -05:00
Refactor Instanced Drawing to use Vertex Arrays
Per-instanced data was previously stored in uniforms. This required several OpenGL calls to upload all of the data, and it was more complex to prepare (several vecs vs one). Additionally, drawing APIs are now accessible through a `RenderApi` (obtained through `QuadRenderer::with_api`) which enables some RAII patterns. Specifically, checks for batch flushing are handled in Drop.
This commit is contained in:
parent
1f3f9add49
commit
ed7aa96907
4 changed files with 283 additions and 278 deletions
|
@ -1,17 +1,14 @@
|
|||
#version 330 core
|
||||
in vec2 TexCoords;
|
||||
flat in int InstanceId;
|
||||
in vec3 fg;
|
||||
|
||||
layout(location = 0, index = 0) out vec4 color;
|
||||
layout(location = 0, index = 1) out vec4 alphaMask;
|
||||
|
||||
uniform sampler2D mask;
|
||||
uniform ivec3 textColor[32];
|
||||
|
||||
void main()
|
||||
{
|
||||
int i = InstanceId;
|
||||
alphaMask = vec4(texture(mask, TexCoords).rgb, 1.0);
|
||||
vec3 textColorF = vec3(textColor[i]) / vec3(255.0, 255.0, 255.0);
|
||||
color = vec4(textColorF, 1.0);
|
||||
color = vec4(fg, 1.0);
|
||||
}
|
||||
|
|
|
@ -1,46 +1,51 @@
|
|||
#version 330 core
|
||||
layout (location = 0) in vec2 position;
|
||||
|
||||
// Cell properties
|
||||
layout (location = 1) in vec2 gridCoords;
|
||||
|
||||
// glyph properties
|
||||
layout (location = 2) in vec4 glyph;
|
||||
|
||||
// uv mapping
|
||||
layout (location = 3) in vec4 uv;
|
||||
|
||||
// text fg color
|
||||
layout (location = 4) in vec3 textColor;
|
||||
|
||||
out vec2 TexCoords;
|
||||
flat out int InstanceId;
|
||||
out vec3 fg;
|
||||
|
||||
// Terminal properties
|
||||
uniform vec2 termDim;
|
||||
uniform vec2 cellDim;
|
||||
uniform vec2 cellSep;
|
||||
|
||||
// Cell properties
|
||||
uniform vec2 gridCoords[32];
|
||||
|
||||
// glyph properties
|
||||
uniform vec2 glyphScale[32];
|
||||
uniform vec2 glyphOffset[32];
|
||||
|
||||
// uv mapping
|
||||
uniform vec2 uvScale[32];
|
||||
uniform vec2 uvOffset[32];
|
||||
|
||||
// Orthographic projection
|
||||
uniform mat4 projection;
|
||||
|
||||
void main()
|
||||
{
|
||||
int i = gl_InstanceID;
|
||||
vec2 glyphOffset = glyph.xy;
|
||||
vec2 glyphSize = glyph.zw;
|
||||
|
||||
vec2 uvOffset = uv.xy;
|
||||
vec2 uvSize = uv.zw;
|
||||
|
||||
// Position of cell from top-left
|
||||
vec2 cellPosition = (cellDim + cellSep) * gridCoords[i];
|
||||
vec2 cellPosition = (cellDim + cellSep) * gridCoords;
|
||||
|
||||
// Invert Y since framebuffer origin is bottom-left
|
||||
cellPosition.y = termDim.y - cellPosition.y - cellDim.y;
|
||||
|
||||
// Glyphs are offset within their cell; account for y-flip
|
||||
vec2 cellOffset = vec2(glyphOffset[i].x,
|
||||
glyphOffset[i].y - glyphScale[i].y);
|
||||
vec2 cellOffset = vec2(glyphOffset.x,
|
||||
glyphOffset.y - glyphSize.y);
|
||||
|
||||
// position coordinates are normalized on [0, 1]
|
||||
vec2 finalPosition = glyphScale[i] * position + cellPosition + cellOffset;
|
||||
vec2 finalPosition = glyphSize * position + cellPosition + cellOffset;
|
||||
|
||||
gl_Position = projection * vec4(finalPosition.xy, 0.0, 1.0);
|
||||
TexCoords = vec2(position.x, 1 - position.y) * uvScale[i] + uvOffset[i];
|
||||
InstanceId = i;
|
||||
TexCoords = uvOffset + vec2(position.x, 1 - position.y) * uvSize;
|
||||
fg = textColor / vec3(255.0, 255.0, 255.0);
|
||||
}
|
||||
|
|
14
src/main.rs
14
src/main.rs
|
@ -194,17 +194,21 @@ fn main() {
|
|||
{
|
||||
let _sampler = meter.sampler();
|
||||
|
||||
// Draw the grid
|
||||
renderer.render_grid(terminal.grid(), &glyph_cache, &props);
|
||||
renderer.with_api(&props, |mut api| {
|
||||
// Draw the grid
|
||||
api.render_grid(terminal.grid(), &glyph_cache);
|
||||
|
||||
// Also draw the cursor
|
||||
renderer.render_cursor(terminal.cursor(), &glyph_cache, &props);
|
||||
// Also draw the cursor
|
||||
api.render_cursor(terminal.cursor(), &glyph_cache);
|
||||
})
|
||||
}
|
||||
|
||||
// Draw render timer
|
||||
let timing = format!("{:.3} usec", meter.average());
|
||||
let color = Rgb { r: 0xd5, g: 0x4e, b: 0x53 };
|
||||
renderer.render_string(&timing[..], &glyph_cache, &props, &color);
|
||||
renderer.with_api(&props, |mut api| {
|
||||
api.render_string(&timing[..], &glyph_cache, &color);
|
||||
});
|
||||
|
||||
window.swap_buffers().unwrap();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ use std::ptr;
|
|||
use std::sync::Arc;
|
||||
use std::sync::atomic::{Ordering, AtomicBool};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use cgmath::{self, Matrix};
|
||||
use euclid::{Rect, Size2D, Point2D};
|
||||
use gl::types::*;
|
||||
|
@ -23,7 +22,29 @@ use super::{Rgb, TermProps, GlyphCache};
|
|||
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");
|
||||
|
||||
/// Text drawing program
|
||||
///
|
||||
/// Uniforms are prefixed with "u", and vertex attributes are prefixed with "a".
|
||||
#[derive(Debug)]
|
||||
pub struct ShaderProgram {
|
||||
// Program id
|
||||
id: GLuint,
|
||||
|
||||
/// projection matrix uniform
|
||||
u_projection: GLint,
|
||||
|
||||
/// Terminal dimensions (pixels)
|
||||
u_term_dim: GLint,
|
||||
|
||||
/// Cell dimensions (pixels)
|
||||
u_cell_dim: GLint,
|
||||
|
||||
/// Cell separation (pixels)
|
||||
u_cell_sep: GLint,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Glyph {
|
||||
tex_id: GLuint,
|
||||
top: f32,
|
||||
|
@ -36,15 +57,46 @@ pub struct Glyph {
|
|||
uv_height: f32,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
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,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct QuadRenderer {
|
||||
program: ShaderProgram,
|
||||
should_reload: Arc<AtomicBool>,
|
||||
vao: GLuint,
|
||||
vbo: GLuint,
|
||||
ebo: GLuint,
|
||||
active_color: Rgb,
|
||||
vbo_instance: GLuint,
|
||||
atlas: Vec<Atlas>,
|
||||
active_tex: GLuint,
|
||||
batch: Batch,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RenderApi<'a> {
|
||||
active_tex: &'a mut GLuint,
|
||||
batch: &'a mut Batch,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -60,44 +112,17 @@ struct ElementIndex {
|
|||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Batch {
|
||||
pub struct Batch {
|
||||
tex: GLuint,
|
||||
coords: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
|
||||
color: ArrayVec<[RgbUpload; BATCH_MAX]>,
|
||||
glyph_scale: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
|
||||
glyph_offset: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
|
||||
uv_scale: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
|
||||
uv_offset: ArrayVec<[Point2D<f32>; BATCH_MAX]>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct RgbUpload {
|
||||
r: i32,
|
||||
g: i32,
|
||||
b: i32,
|
||||
}
|
||||
|
||||
impl From<Rgb> for RgbUpload {
|
||||
#[inline]
|
||||
fn from(color: Rgb) -> RgbUpload {
|
||||
RgbUpload {
|
||||
r: color.r as i32,
|
||||
g: color.g as i32,
|
||||
b: color.b as i32,
|
||||
}
|
||||
}
|
||||
instances: Vec<InstanceData>,
|
||||
}
|
||||
|
||||
impl Batch {
|
||||
#[inline]
|
||||
pub fn new() -> Batch {
|
||||
Batch {
|
||||
tex: 0,
|
||||
coords: ArrayVec::new(),
|
||||
color: ArrayVec::new(),
|
||||
glyph_scale: ArrayVec::new(),
|
||||
glyph_offset: ArrayVec::new(),
|
||||
uv_scale: ArrayVec::new(),
|
||||
uv_offset: ArrayVec::new(),
|
||||
instances: Vec::with_capacity(BATCH_MAX),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -106,12 +131,24 @@ impl Batch {
|
|||
self.tex = glyph.tex_id;
|
||||
}
|
||||
|
||||
self.coords.push(Point2D::new(col, row));
|
||||
self.color.push(RgbUpload::from(color));
|
||||
self.glyph_scale.push(Point2D::new(glyph.width, glyph.height));
|
||||
self.glyph_offset.push(Point2D::new(glyph.left, glyph.top));
|
||||
self.uv_scale.push(Point2D::new(glyph.uv_width, glyph.uv_height));
|
||||
self.uv_offset.push(Point2D::new(glyph.uv_left, glyph.uv_bot));
|
||||
self.instances.push(InstanceData {
|
||||
col: col,
|
||||
row: row,
|
||||
|
||||
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: color.r as f32,
|
||||
g: color.g as f32,
|
||||
b: color.b as f32,
|
||||
});
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -121,7 +158,7 @@ impl Batch {
|
|||
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.color.len()
|
||||
self.instances.len()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
@ -134,24 +171,20 @@ impl Batch {
|
|||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.tex = 0;
|
||||
self.coords.clear();
|
||||
self.color.clear();
|
||||
self.glyph_scale.clear();
|
||||
self.glyph_offset.clear();
|
||||
self.uv_scale.clear();
|
||||
self.uv_offset.clear();
|
||||
#[inline]
|
||||
pub fn size(&self) -> usize {
|
||||
self.len() * size_of::<InstanceData>()
|
||||
}
|
||||
|
||||
pub fn render(&mut self, renderer: &mut QuadRenderer) {
|
||||
renderer.render_batch(self);
|
||||
self.clear();
|
||||
pub fn clear(&mut self) {
|
||||
self.tex = 0;
|
||||
self.instances.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum items to be drawn in a batch.
|
||||
const BATCH_MAX: usize = 32usize;
|
||||
const BATCH_MAX: usize = 4096;
|
||||
const ATLAS_SIZE: i32 = 512;
|
||||
|
||||
impl QuadRenderer {
|
||||
// TODO should probably hand this a transform instead of width/height
|
||||
|
@ -162,14 +195,19 @@ impl QuadRenderer {
|
|||
let mut vbo: GLuint = 0;
|
||||
let mut ebo: GLuint = 0;
|
||||
|
||||
let mut vbo_instance: GLuint = 0;
|
||||
|
||||
|
||||
unsafe {
|
||||
gl::GenVertexArrays(1, &mut vao);
|
||||
gl::GenBuffers(1, &mut vbo);
|
||||
gl::GenBuffers(1, &mut ebo);
|
||||
gl::GenBuffers(1, &mut vbo_instance);
|
||||
gl::BindVertexArray(vao);
|
||||
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
|
||||
// ----------------------------
|
||||
// setup vertex position buffer
|
||||
// ----------------------------
|
||||
// Top right, Bottom right, Bottom left, Top left
|
||||
let vertices = [
|
||||
PackedVertex { x: 1.0, y: 1.0 },
|
||||
|
@ -178,33 +216,69 @@ impl QuadRenderer {
|
|||
PackedVertex { x: 0.0, y: 1.0 },
|
||||
];
|
||||
|
||||
gl::BufferData(
|
||||
gl::ARRAY_BUFFER,
|
||||
(size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr,
|
||||
vertices.as_ptr() as *const _,
|
||||
gl::STATIC_DRAW
|
||||
);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||
|
||||
gl::VertexAttribPointer(0, 2,
|
||||
gl::FLOAT, gl::FALSE,
|
||||
size_of::<PackedVertex>() as i32,
|
||||
ptr::null());
|
||||
gl::EnableVertexAttribArray(0);
|
||||
|
||||
gl::BufferData(gl::ARRAY_BUFFER,
|
||||
(size_of::<PackedVertex>() * vertices.len()) as GLsizeiptr,
|
||||
vertices.as_ptr() as *const _,
|
||||
gl::STATIC_DRAW);
|
||||
|
||||
// ---------------------
|
||||
// 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,
|
||||
(6 * size_of::<u32>()) as isize,
|
||||
indices.as_ptr() as *const _,
|
||||
gl::STATIC_DRAW);
|
||||
|
||||
gl::EnableVertexAttribArray(0);
|
||||
|
||||
// positions
|
||||
gl::VertexAttribPointer(0, 2,
|
||||
// ----------------------------
|
||||
// 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(1, 2,
|
||||
gl::FLOAT, gl::FALSE,
|
||||
size_of::<PackedVertex>() as i32,
|
||||
size_of::<InstanceData>() as i32,
|
||||
ptr::null());
|
||||
gl::EnableVertexAttribArray(1);
|
||||
gl::VertexAttribDivisor(1, 1);
|
||||
// glyphoffset
|
||||
gl::VertexAttribPointer(2, 4,
|
||||
gl::FLOAT, gl::FALSE,
|
||||
size_of::<InstanceData>() as i32,
|
||||
(2 * size_of::<f32>()) as *const _);
|
||||
gl::EnableVertexAttribArray(2);
|
||||
gl::VertexAttribDivisor(2, 1);
|
||||
// uv
|
||||
gl::VertexAttribPointer(3, 4,
|
||||
gl::FLOAT, gl::FALSE,
|
||||
size_of::<InstanceData>() as i32,
|
||||
(6 * size_of::<f32>()) as *const _);
|
||||
gl::EnableVertexAttribArray(3);
|
||||
gl::VertexAttribDivisor(3, 1);
|
||||
// color
|
||||
gl::VertexAttribPointer(4, 3,
|
||||
gl::FLOAT, gl::FALSE,
|
||||
size_of::<InstanceData>() as i32,
|
||||
(10 * size_of::<f32>()) as *const _);
|
||||
gl::EnableVertexAttribArray(4);
|
||||
gl::VertexAttribDivisor(4, 1);
|
||||
|
||||
gl::BindVertexArray(0);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
let should_reload = Arc::new(AtomicBool::new(false));
|
||||
|
@ -245,118 +319,40 @@ impl QuadRenderer {
|
|||
vao: vao,
|
||||
vbo: vbo,
|
||||
ebo: ebo,
|
||||
active_color: Rgb { r: 0, g: 0, b: 0 },
|
||||
vbo_instance: vbo_instance,
|
||||
atlas: Vec::new(),
|
||||
active_tex: 0,
|
||||
batch: Batch::new(),
|
||||
};
|
||||
|
||||
let atlas = renderer.create_atlas(1024);
|
||||
let atlas = renderer.create_atlas(ATLAS_SIZE);
|
||||
renderer.atlas.push(atlas);
|
||||
|
||||
renderer
|
||||
}
|
||||
|
||||
/// Render a string in a predefined location. Used for printing render time for profiling and
|
||||
/// optimization.
|
||||
pub fn render_string(&mut self,
|
||||
s: &str,
|
||||
glyph_cache: &GlyphCache,
|
||||
props: &TermProps,
|
||||
color: &Rgb)
|
||||
pub fn with_api<F>(&mut self, props: &TermProps, mut func: F)
|
||||
where F: FnMut(RenderApi)
|
||||
{
|
||||
self.prepare_render(props);
|
||||
|
||||
let row = 40.0;
|
||||
let mut col = 100.0;
|
||||
|
||||
let mut batch = Batch::new();
|
||||
|
||||
for c in s.chars() {
|
||||
if let Some(glyph) = glyph_cache.get(&c) {
|
||||
batch.add_item(row, col, *color, glyph);
|
||||
}
|
||||
|
||||
col += 1.0;
|
||||
|
||||
// Render batch and clear if it's full
|
||||
if batch.full() {
|
||||
batch.render(self);
|
||||
}
|
||||
if self.should_reload.load(Ordering::Relaxed) {
|
||||
self.reload_shaders(props.width as u32, props.height as u32);
|
||||
}
|
||||
|
||||
if !batch.is_empty() {
|
||||
batch.render(self);
|
||||
}
|
||||
|
||||
self.finish_render();
|
||||
}
|
||||
|
||||
pub fn render_cursor(&mut self,
|
||||
cursor: term::Cursor,
|
||||
glyph_cache: &GlyphCache,
|
||||
props: &TermProps)
|
||||
{
|
||||
self.prepare_render(props);
|
||||
|
||||
if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) {
|
||||
let mut batch = Batch::new();
|
||||
batch.add_item(cursor.y as f32, cursor.x as f32, term::DEFAULT_FG, glyph);
|
||||
batch.render(self);
|
||||
}
|
||||
|
||||
self.finish_render();
|
||||
}
|
||||
|
||||
pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache, props: &TermProps) {
|
||||
self.prepare_render(props);
|
||||
|
||||
// All draws are batched
|
||||
let mut batch = Batch::new();
|
||||
|
||||
for (i, row) in grid.rows().enumerate() {
|
||||
for (j, cell) in row.cells().enumerate() {
|
||||
// Skip empty cells
|
||||
if cell.c == ' ' {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add cell to batch if the glyph is laoded
|
||||
if let Some(glyph) = glyph_cache.get(&cell.c) {
|
||||
batch.add_item(i as f32, j as f32, cell.fg, glyph);
|
||||
}
|
||||
|
||||
// Render batch and clear if it's full
|
||||
if batch.full() {
|
||||
batch.render(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Could have some data in a batch still; render it.
|
||||
if !batch.is_empty() {
|
||||
batch.render(self);
|
||||
}
|
||||
|
||||
self.finish_render();
|
||||
}
|
||||
|
||||
fn prepare_render(&mut self, props: &TermProps) {
|
||||
unsafe {
|
||||
self.program.activate();
|
||||
self.program.set_term_uniforms(props);
|
||||
|
||||
gl::BindVertexArray(self.vao);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo_instance);
|
||||
gl::ActiveTexture(gl::TEXTURE0);
|
||||
}
|
||||
|
||||
if self.should_reload.load(Ordering::Relaxed) {
|
||||
self.reload_shaders(props.width as u32, props.height as u32);
|
||||
}
|
||||
}
|
||||
func(RenderApi {
|
||||
active_tex: &mut self.active_tex,
|
||||
batch: &mut self.batch,
|
||||
});
|
||||
|
||||
fn finish_render(&mut self) {
|
||||
unsafe {
|
||||
gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0);
|
||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||
|
@ -386,27 +382,9 @@ impl QuadRenderer {
|
|||
};
|
||||
|
||||
self.active_tex = 0;
|
||||
self.active_color = Rgb { r: 0, g: 0, b: 0 };
|
||||
self.program = program;
|
||||
}
|
||||
|
||||
fn render_batch(&mut self, batch: &Batch) {
|
||||
self.program.set_uniforms(batch);
|
||||
|
||||
// Bind texture if necessary
|
||||
if self.active_tex != batch.tex {
|
||||
unsafe {
|
||||
gl::BindTexture(gl::TEXTURE_2D, batch.tex);
|
||||
}
|
||||
self.active_tex = batch.tex;
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let count = batch.len() as GLsizei;
|
||||
gl::DrawElementsInstanced(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null(), count);
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a glyph into a texture atlas
|
||||
///
|
||||
/// If the current atlas is full, a new one will be created.
|
||||
|
@ -414,7 +392,7 @@ impl QuadRenderer {
|
|||
match self.atlas.last_mut().unwrap().insert(rasterized, &mut self.active_tex) {
|
||||
Ok(glyph) => glyph,
|
||||
Err(_) => {
|
||||
let atlas = self.create_atlas(1024);
|
||||
let atlas = self.create_atlas(ATLAS_SIZE);
|
||||
self.atlas.push(atlas);
|
||||
self.load_glyph(rasterized)
|
||||
}
|
||||
|
@ -459,6 +437,89 @@ impl QuadRenderer {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> RenderApi<'a> {
|
||||
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 {
|
||||
gl::DrawElementsInstanced(gl::TRIANGLES,
|
||||
6, gl::UNSIGNED_INT, ptr::null(),
|
||||
self.batch.len() as GLsizei);
|
||||
}
|
||||
|
||||
self.batch.clear();
|
||||
}
|
||||
/// Render a string in a predefined location. Used for printing render time for profiling and
|
||||
/// optimization.
|
||||
pub fn render_string(&mut self,
|
||||
s: &str,
|
||||
glyph_cache: &GlyphCache,
|
||||
color: &Rgb)
|
||||
{
|
||||
let row = 40.0;
|
||||
let mut col = 100.0;
|
||||
|
||||
for c in s.chars() {
|
||||
if let Some(glyph) = glyph_cache.get(&c) {
|
||||
self.add_render_item(row, col, *color, glyph);
|
||||
}
|
||||
|
||||
col += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn add_render_item(&mut self, row: f32, col: f32, color: Rgb, glyph: &Glyph) {
|
||||
self.batch.add_item(row, col, color, glyph);
|
||||
|
||||
// Render batch and clear if it's full
|
||||
if self.batch.full() {
|
||||
self.render_batch();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_cursor(&mut self, cursor: term::Cursor, glyph_cache: &GlyphCache) {
|
||||
if let Some(glyph) = glyph_cache.get(&term::CURSOR_SHAPE) {
|
||||
self.add_render_item(cursor.y as f32, cursor.x as f32, term::DEFAULT_FG, glyph);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_grid(&mut self, grid: &Grid, glyph_cache: &GlyphCache) {
|
||||
for (i, row) in grid.rows().enumerate() {
|
||||
for (j, cell) in row.cells().enumerate() {
|
||||
// Skip empty cells
|
||||
if cell.c == ' ' {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Add cell to batch if the glyph is laoded
|
||||
if let Some(glyph) = glyph_cache.get(&cell.c) {
|
||||
self.add_render_item(i as f32, j as f32, cell.fg, glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for RenderApi<'a> {
|
||||
fn drop(&mut self) {
|
||||
if !self.batch.is_empty() {
|
||||
self.render_batch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_rect(glyph: &Glyph, x: f32, y: f32) -> Rect<f32> {
|
||||
Rect::new(
|
||||
Point2D::new(x + glyph.left as f32, y - (glyph.height - glyph.top) as f32),
|
||||
|
@ -466,41 +527,6 @@ fn get_rect(glyph: &Glyph, x: f32, y: f32) -> Rect<f32> {
|
|||
)
|
||||
}
|
||||
|
||||
pub struct ShaderProgram {
|
||||
// Program id
|
||||
id: GLuint,
|
||||
|
||||
/// projection matrix uniform
|
||||
u_projection: GLint,
|
||||
|
||||
/// color uniform
|
||||
u_color: GLint,
|
||||
|
||||
/// Terminal dimensions (pixels)
|
||||
u_term_dim: GLint,
|
||||
|
||||
/// Cell dimensions (pixels)
|
||||
u_cell_dim: GLint,
|
||||
|
||||
/// Cell separation (pixels)
|
||||
u_cell_sep: GLint,
|
||||
|
||||
/// Cell coordinates in grid
|
||||
u_cell_coord: GLint,
|
||||
|
||||
/// Glyph scale
|
||||
u_glyph_scale: GLint,
|
||||
|
||||
/// Glyph offset
|
||||
u_glyph_offset: GLint,
|
||||
|
||||
/// Atlas scale
|
||||
u_uv_scale: GLint,
|
||||
|
||||
/// Atlas offset
|
||||
u_uv_offset: GLint,
|
||||
}
|
||||
|
||||
impl ShaderProgram {
|
||||
pub fn activate(&self) {
|
||||
unsafe {
|
||||
|
@ -541,47 +567,23 @@ impl ShaderProgram {
|
|||
}
|
||||
|
||||
// get uniform locations
|
||||
let (projection, color, term_dim, cell_dim, cell_sep) = unsafe {
|
||||
let (projection, term_dim, cell_dim, cell_sep) = unsafe {
|
||||
(
|
||||
gl::GetUniformLocation(program, cptr!(b"projection\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"textColor\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"termDim\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"cellDim\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"cellSep\0")),
|
||||
)
|
||||
};
|
||||
|
||||
assert_uniform_valid!(projection, color, term_dim, cell_dim, cell_sep);
|
||||
|
||||
let (cell_coord, glyph_scale, glyph_offset, uv_scale, uv_offset) = unsafe {
|
||||
(
|
||||
gl::GetUniformLocation(program, cptr!(b"gridCoords\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"glyphScale\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"glyphOffset\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"uvScale\0")),
|
||||
gl::GetUniformLocation(program, cptr!(b"uvOffset\0")),
|
||||
)
|
||||
};
|
||||
|
||||
assert_uniform_valid!(cell_coord, glyph_scale, glyph_offset, uv_scale, uv_offset);
|
||||
|
||||
// Initialize to known color (black)
|
||||
unsafe {
|
||||
gl::Uniform3i(color, 0, 0, 0);
|
||||
}
|
||||
assert_uniform_valid!(projection, term_dim, cell_dim, cell_sep);
|
||||
|
||||
let shader = ShaderProgram {
|
||||
id: program,
|
||||
u_projection: projection,
|
||||
u_color: color,
|
||||
u_term_dim: term_dim,
|
||||
u_cell_dim: cell_dim,
|
||||
u_cell_sep: cell_sep,
|
||||
u_cell_coord: cell_coord,
|
||||
u_glyph_scale: glyph_scale,
|
||||
u_glyph_offset: glyph_offset,
|
||||
u_uv_scale: uv_scale,
|
||||
u_uv_offset: uv_offset,
|
||||
};
|
||||
|
||||
// set projection uniform
|
||||
|
@ -608,18 +610,6 @@ impl ShaderProgram {
|
|||
}
|
||||
}
|
||||
|
||||
fn set_uniforms(&self, batch: &Batch) {
|
||||
let len = batch.len();
|
||||
unsafe {
|
||||
gl::Uniform2fv(self.u_cell_coord, len as i32, batch.coords.as_ptr() as *const _);
|
||||
gl::Uniform2fv(self.u_glyph_scale, len as i32, batch.glyph_scale.as_ptr() as *const _);
|
||||
gl::Uniform2fv(self.u_glyph_offset, len as i32, batch.glyph_offset.as_ptr() as *const _);
|
||||
gl::Uniform2fv(self.u_uv_scale, len as i32, batch.uv_scale.as_ptr() as *const _);
|
||||
gl::Uniform2fv(self.u_uv_offset, len as i32, batch.uv_offset.as_ptr() as *const _);
|
||||
gl::Uniform3iv(self.u_color, len as i32, batch.color.as_ptr() as *const _);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_program(vertex: GLuint, fragment: GLuint) -> GLuint {
|
||||
unsafe {
|
||||
let program = gl::CreateProgram();
|
||||
|
@ -667,6 +657,14 @@ impl ShaderProgram {
|
|||
}
|
||||
}
|
||||
|
||||
impl Drop for ShaderProgram {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
gl::DeleteProgram(self.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_program_info_log(program: GLuint) -> String {
|
||||
// Get expected log length
|
||||
let mut max_length: GLint = 0;
|
||||
|
@ -780,6 +778,7 @@ impl From<io::Error> for ShaderCreationError {
|
|||
/// │ │ │ │ │ <- 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,
|
||||
|
|
Loading…
Reference in a new issue