mirror of
https://github.com/alacritty/alacritty.git
synced 2024-11-25 14:05:41 -05:00
Render underline and strikeout rects in batches
Currently Alacritty requires a separate `draw` call to OpenGL whenever a new rectangle is rendered to the screen. With many rectangles visible, this has a significant impact on rendering performance. Instead of using separate draw calls, the new `RectRenderer` will build a batch of rectangles for rendering. This makes sure that multiple rectangles can be grouped together for single draw calls allowing a reduced impact on rendering time. Since this change is OpenGL 2 friendly, it should not make it more complicated to transition away from the 3.3+ requirements like an alternative instancing based implementation might have.
This commit is contained in:
parent
4975be29df
commit
5ececc3105
5 changed files with 234 additions and 169 deletions
|
@ -24,6 +24,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Preserve vi mode across terminal `reset`
|
- Preserve vi mode across terminal `reset`
|
||||||
- Escapes `CSI Ps b` and `CSI Ps Z` with large parameters locking up Alacritty
|
- Escapes `CSI Ps b` and `CSI Ps Z` with large parameters locking up Alacritty
|
||||||
- Dimming colors which use the indexed `CSI 38 : 5 : Ps m` notation
|
- Dimming colors which use the indexed `CSI 38 : 5 : Ps m` notation
|
||||||
|
- Slow rendering performance with a lot of cells with underline/strikeout attributes
|
||||||
- Performance of scrolling regions with offset from the bottom
|
- Performance of scrolling regions with offset from the bottom
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#version 330 core
|
#version 330 core
|
||||||
|
|
||||||
uniform vec4 color;
|
flat in vec4 color;
|
||||||
|
|
||||||
out vec4 FragColor;
|
out vec4 FragColor;
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#version 330 core
|
#version 330 core
|
||||||
layout (location = 0) in vec2 aPos;
|
layout (location = 0) in vec2 aPos;
|
||||||
|
layout (location = 1) in vec4 aColor;
|
||||||
|
|
||||||
|
flat out vec4 color;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
|
color = aColor;
|
||||||
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
|
gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,21 +30,17 @@ use crate::config::ui_config::{Delta, UIConfig};
|
||||||
use crate::cursor;
|
use crate::cursor;
|
||||||
use crate::gl;
|
use crate::gl;
|
||||||
use crate::gl::types::*;
|
use crate::gl::types::*;
|
||||||
use crate::renderer::rects::RenderRect;
|
use crate::renderer::rects::{RectRenderer, RectShaderProgram, RenderRect};
|
||||||
|
|
||||||
pub mod rects;
|
pub mod rects;
|
||||||
|
|
||||||
// Shader paths for live reload.
|
// Shader paths for live reload.
|
||||||
static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl");
|
static TEXT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.f.glsl");
|
||||||
static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl");
|
static TEXT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/text.v.glsl");
|
||||||
static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl");
|
|
||||||
static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl");
|
|
||||||
|
|
||||||
// Shader source which is used when live-shader-reload feature is disable.
|
// Shader source which is used when live-shader-reload feature is disable.
|
||||||
static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl");
|
static TEXT_SHADER_F: &str = include_str!("../../res/text.f.glsl");
|
||||||
static TEXT_SHADER_V: &str = include_str!("../../res/text.v.glsl");
|
static TEXT_SHADER_V: &str = include_str!("../../res/text.v.glsl");
|
||||||
static RECT_SHADER_F: &str = include_str!("../../res/rect.f.glsl");
|
|
||||||
static RECT_SHADER_V: &str = include_str!("../../res/rect.v.glsl");
|
|
||||||
|
|
||||||
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
|
/// `LoadGlyph` allows for copying a rasterized glyph into graphics memory.
|
||||||
pub trait LoadGlyph {
|
pub trait LoadGlyph {
|
||||||
|
@ -110,17 +106,6 @@ pub struct TextShaderProgram {
|
||||||
u_background: GLint,
|
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)]
|
#[derive(Copy, Debug, Clone)]
|
||||||
pub struct Glyph {
|
pub struct Glyph {
|
||||||
tex_id: GLuint,
|
tex_id: GLuint,
|
||||||
|
@ -410,17 +395,16 @@ struct InstanceData {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct QuadRenderer {
|
pub struct QuadRenderer {
|
||||||
program: TextShaderProgram,
|
program: TextShaderProgram,
|
||||||
rect_program: RectShaderProgram,
|
|
||||||
vao: GLuint,
|
vao: GLuint,
|
||||||
ebo: GLuint,
|
ebo: GLuint,
|
||||||
vbo_instance: GLuint,
|
vbo_instance: GLuint,
|
||||||
rect_vao: GLuint,
|
|
||||||
rect_vbo: GLuint,
|
|
||||||
atlas: Vec<Atlas>,
|
atlas: Vec<Atlas>,
|
||||||
current_atlas: usize,
|
current_atlas: usize,
|
||||||
active_tex: GLuint,
|
active_tex: GLuint,
|
||||||
batch: Batch,
|
batch: Batch,
|
||||||
rx: mpsc::Receiver<Msg>,
|
rx: mpsc::Receiver<Msg>,
|
||||||
|
|
||||||
|
rect_renderer: RectRenderer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -526,17 +510,12 @@ const ATLAS_SIZE: i32 = 1024;
|
||||||
impl QuadRenderer {
|
impl QuadRenderer {
|
||||||
pub fn new() -> Result<QuadRenderer, Error> {
|
pub fn new() -> Result<QuadRenderer, Error> {
|
||||||
let program = TextShaderProgram::new()?;
|
let program = TextShaderProgram::new()?;
|
||||||
let rect_program = RectShaderProgram::new()?;
|
|
||||||
|
|
||||||
let mut vao: GLuint = 0;
|
let mut vao: GLuint = 0;
|
||||||
let mut ebo: GLuint = 0;
|
let mut ebo: GLuint = 0;
|
||||||
|
|
||||||
let mut vbo_instance: 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 {
|
unsafe {
|
||||||
gl::Enable(gl::BLEND);
|
gl::Enable(gl::BLEND);
|
||||||
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
|
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
|
||||||
|
@ -617,20 +596,6 @@ impl QuadRenderer {
|
||||||
// Background color.
|
// Background color.
|
||||||
add_attr!(4, gl::UNSIGNED_BYTE, u8);
|
add_attr!(4, gl::UNSIGNED_BYTE, u8);
|
||||||
|
|
||||||
// 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.
|
// Cleanup.
|
||||||
gl::BindVertexArray(0);
|
gl::BindVertexArray(0);
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||||
|
@ -651,6 +616,12 @@ impl QuadRenderer {
|
||||||
watcher
|
watcher
|
||||||
.watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive)
|
.watch(TEXT_SHADER_V_PATH, RecursiveMode::NonRecursive)
|
||||||
.expect("watch vertex shader");
|
.expect("watch vertex shader");
|
||||||
|
watcher
|
||||||
|
.watch(rects::RECT_SHADER_V_PATH, RecursiveMode::NonRecursive)
|
||||||
|
.expect("watch rect vertex shader");
|
||||||
|
watcher
|
||||||
|
.watch(rects::RECT_SHADER_F_PATH, RecursiveMode::NonRecursive)
|
||||||
|
.expect("watch rect fragment shader");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
let event = rx.recv().expect("watcher event");
|
let event = rx.recv().expect("watcher event");
|
||||||
|
@ -670,12 +641,10 @@ impl QuadRenderer {
|
||||||
|
|
||||||
let mut renderer = Self {
|
let mut renderer = Self {
|
||||||
program,
|
program,
|
||||||
rect_program,
|
rect_renderer: RectRenderer::new()?,
|
||||||
vao,
|
vao,
|
||||||
ebo,
|
ebo,
|
||||||
vbo_instance,
|
vbo_instance,
|
||||||
rect_vao,
|
|
||||||
rect_vbo,
|
|
||||||
atlas: Vec::new(),
|
atlas: Vec::new(),
|
||||||
current_atlas: 0,
|
current_atlas: 0,
|
||||||
active_tex: 0,
|
active_tex: 0,
|
||||||
|
@ -690,56 +659,31 @@ impl QuadRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draw all rectangles simultaneously to prevent excessive program swaps.
|
/// Draw all rectangles simultaneously to prevent excessive program swaps.
|
||||||
pub fn draw_rects(&mut self, props: &SizeInfo, rects: Vec<RenderRect>) {
|
pub fn draw_rects(&mut self, size_info: &SizeInfo, rects: Vec<RenderRect>) {
|
||||||
// Swap to rectangle rendering program.
|
if rects.is_empty() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare rect rendering state.
|
||||||
unsafe {
|
unsafe {
|
||||||
// Swap program.
|
|
||||||
gl::UseProgram(self.rect_program.id);
|
|
||||||
|
|
||||||
// Remove padding from viewport.
|
// Remove padding from viewport.
|
||||||
gl::Viewport(0, 0, props.width() as i32, props.height() as i32);
|
gl::Viewport(0, 0, size_info.width() as i32, size_info.height() as i32);
|
||||||
|
|
||||||
// Change blending strategy.
|
|
||||||
gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
|
gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE);
|
||||||
|
|
||||||
// Setup data and buffers.
|
|
||||||
gl::BindVertexArray(self.rect_vao);
|
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, self.rect_vbo);
|
|
||||||
|
|
||||||
// Position.
|
|
||||||
gl::VertexAttribPointer(
|
|
||||||
0,
|
|
||||||
2,
|
|
||||||
gl::FLOAT,
|
|
||||||
gl::FALSE,
|
|
||||||
(size_of::<f32>() * 2) as _,
|
|
||||||
ptr::null(),
|
|
||||||
);
|
|
||||||
gl::EnableVertexAttribArray(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw all the rects.
|
self.rect_renderer.draw(size_info, rects);
|
||||||
for rect in rects {
|
|
||||||
self.render_rect(&rect, props);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deactivate rectangle program again.
|
// Activate regular state again.
|
||||||
unsafe {
|
unsafe {
|
||||||
// Reset blending strategy.
|
// Reset blending strategy.
|
||||||
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
|
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
|
||||||
|
|
||||||
// Reset data and buffers.
|
// Restore viewport with padding.
|
||||||
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
let padding_x = size_info.padding_x() as i32;
|
||||||
gl::BindVertexArray(0);
|
let padding_y = size_info.padding_y() as i32;
|
||||||
|
let width = size_info.width() as i32;
|
||||||
let padding_x = props.padding_x() as i32;
|
let height = size_info.height() 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);
|
gl::Viewport(padding_x, padding_y, width - 2 * padding_x, height - 2 * padding_y);
|
||||||
|
|
||||||
// Disable program.
|
|
||||||
gl::UseProgram(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -832,7 +776,7 @@ impl QuadRenderer {
|
||||||
|
|
||||||
self.active_tex = 0;
|
self.active_tex = 0;
|
||||||
self.program = program;
|
self.program = program;
|
||||||
self.rect_program = rect_program;
|
self.rect_renderer.set_program(rect_program);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, size: &SizeInfo) {
|
pub fn resize(&mut self, size: &SizeInfo) {
|
||||||
|
@ -856,43 +800,6 @@ impl QuadRenderer {
|
||||||
gl::UseProgram(0);
|
gl::UseProgram(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render a rectangle.
|
|
||||||
///
|
|
||||||
/// This requires the rectangle program to be activated.
|
|
||||||
fn render_rect(&mut self, rect: &RenderRect, size: &SizeInfo) {
|
|
||||||
// Do nothing when alpha is fully transparent.
|
|
||||||
if rect.alpha == 0. {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate rectangle position.
|
|
||||||
let center_x = size.width() / 2.;
|
|
||||||
let center_y = size.height() / 2.;
|
|
||||||
let x = (rect.x - center_x) / center_x;
|
|
||||||
let y = -(rect.y - center_y) / center_y;
|
|
||||||
let width = rect.width / center_x;
|
|
||||||
let height = rect.height / center_y;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
// Setup vertices.
|
|
||||||
let vertices: [f32; 8] = [x + width, y, x + width, y - height, x, y - height, x, y];
|
|
||||||
|
|
||||||
// Load vertex data into array buffer.
|
|
||||||
gl::BufferData(
|
|
||||||
gl::ARRAY_BUFFER,
|
|
||||||
(size_of::<f32>() * vertices.len()) as _,
|
|
||||||
vertices.as_ptr() as *const _,
|
|
||||||
gl::STATIC_DRAW,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Color.
|
|
||||||
self.rect_program.set_color(rect.color, rect.alpha);
|
|
||||||
|
|
||||||
// Draw the rectangle.
|
|
||||||
gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, ptr::null());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RenderApi<'a> {
|
impl<'a> RenderApi<'a> {
|
||||||
|
@ -1237,55 +1144,7 @@ impl Drop for TextShaderProgram {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RectShaderProgram {
|
pub fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCreationError> {
|
||||||
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 = Self { 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 {
|
unsafe {
|
||||||
let program = gl::CreateProgram();
|
let program = gl::CreateProgram();
|
||||||
gl::AttachShader(program, vertex);
|
gl::AttachShader(program, vertex);
|
||||||
|
@ -1303,7 +1162,7 @@ fn create_program(vertex: GLuint, fragment: GLuint) -> Result<GLuint, ShaderCrea
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_shader(
|
pub fn create_shader(
|
||||||
path: &str,
|
path: &str,
|
||||||
kind: GLenum,
|
kind: GLenum,
|
||||||
source: Option<&'static str>,
|
source: Option<&'static str>,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use crossfont::Metrics;
|
use crossfont::Metrics;
|
||||||
|
|
||||||
|
@ -7,6 +8,10 @@ use alacritty_terminal::term::cell::Flags;
|
||||||
use alacritty_terminal::term::color::Rgb;
|
use alacritty_terminal::term::color::Rgb;
|
||||||
use alacritty_terminal::term::{RenderableCell, SizeInfo};
|
use alacritty_terminal::term::{RenderableCell, SizeInfo};
|
||||||
|
|
||||||
|
use crate::gl;
|
||||||
|
use crate::gl::types::*;
|
||||||
|
use crate::renderer;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct RenderRect {
|
pub struct RenderRect {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
@ -190,3 +195,199 @@ impl RenderLines {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Shader sources for rect rendering program.
|
||||||
|
pub static RECT_SHADER_F_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.f.glsl");
|
||||||
|
pub static RECT_SHADER_V_PATH: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/res/rect.v.glsl");
|
||||||
|
static RECT_SHADER_F: &str = include_str!("../../res/rect.f.glsl");
|
||||||
|
static RECT_SHADER_V: &str = include_str!("../../res/rect.v.glsl");
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct Vertex {
|
||||||
|
// Normalized screen coordinates.
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
|
||||||
|
// Color.
|
||||||
|
r: u8,
|
||||||
|
g: u8,
|
||||||
|
b: u8,
|
||||||
|
a: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RectRenderer {
|
||||||
|
// GL buffer objects.
|
||||||
|
vao: GLuint,
|
||||||
|
vbo: GLuint,
|
||||||
|
|
||||||
|
program: RectShaderProgram,
|
||||||
|
|
||||||
|
vertices: Vec<Vertex>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RectRenderer {
|
||||||
|
/// Update program when doing live-shader-reload.
|
||||||
|
pub fn set_program(&mut self, program: RectShaderProgram) {
|
||||||
|
self.program = program;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new() -> Result<Self, renderer::Error> {
|
||||||
|
let mut vao: GLuint = 0;
|
||||||
|
let mut vbo: GLuint = 0;
|
||||||
|
let program = RectShaderProgram::new()?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Allocate buffers.
|
||||||
|
gl::GenVertexArrays(1, &mut vao);
|
||||||
|
gl::GenBuffers(1, &mut vbo);
|
||||||
|
|
||||||
|
gl::BindVertexArray(vao);
|
||||||
|
|
||||||
|
// VBO binding is not part of VAO itself, but VBO binding is stored in attributes.
|
||||||
|
gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
|
||||||
|
|
||||||
|
let mut attribute_offset = 0;
|
||||||
|
|
||||||
|
// Position.
|
||||||
|
gl::VertexAttribPointer(
|
||||||
|
0,
|
||||||
|
2,
|
||||||
|
gl::FLOAT,
|
||||||
|
gl::FALSE,
|
||||||
|
mem::size_of::<Vertex>() as i32,
|
||||||
|
attribute_offset as *const _,
|
||||||
|
);
|
||||||
|
gl::EnableVertexAttribArray(0);
|
||||||
|
attribute_offset += mem::size_of::<f32>() * 2;
|
||||||
|
|
||||||
|
// Color.
|
||||||
|
gl::VertexAttribPointer(
|
||||||
|
1,
|
||||||
|
4,
|
||||||
|
gl::UNSIGNED_BYTE,
|
||||||
|
gl::TRUE,
|
||||||
|
mem::size_of::<Vertex>() as i32,
|
||||||
|
attribute_offset as *const _,
|
||||||
|
);
|
||||||
|
gl::EnableVertexAttribArray(1);
|
||||||
|
|
||||||
|
// Reset buffer bindings.
|
||||||
|
gl::BindVertexArray(0);
|
||||||
|
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Self { vao, vbo, program, vertices: Vec::new() })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn draw(&mut self, size_info: &SizeInfo, rects: Vec<RenderRect>) {
|
||||||
|
unsafe {
|
||||||
|
// Bind VAO to enable vertex attribute slots.
|
||||||
|
gl::BindVertexArray(self.vao);
|
||||||
|
|
||||||
|
// Bind VBO only once for buffer data upload only.
|
||||||
|
gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo);
|
||||||
|
|
||||||
|
gl::UseProgram(self.program.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
let half_width = size_info.width() / 2.;
|
||||||
|
let half_height = size_info.height() / 2.;
|
||||||
|
|
||||||
|
// Build rect vertices vector.
|
||||||
|
self.vertices.clear();
|
||||||
|
for rect in &rects {
|
||||||
|
self.add_rect(half_width, half_height, rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Upload accumulated vertices.
|
||||||
|
gl::BufferData(
|
||||||
|
gl::ARRAY_BUFFER,
|
||||||
|
(self.vertices.len() * mem::size_of::<Vertex>()) as isize,
|
||||||
|
self.vertices.as_ptr() as *const _,
|
||||||
|
gl::STREAM_DRAW,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Draw all vertices as list of triangles.
|
||||||
|
gl::DrawArrays(gl::TRIANGLES, 0, self.vertices.len() as i32);
|
||||||
|
|
||||||
|
// Disable program.
|
||||||
|
gl::UseProgram(0);
|
||||||
|
|
||||||
|
// Reset buffer bindings to nothing.
|
||||||
|
gl::BindBuffer(gl::ARRAY_BUFFER, 0);
|
||||||
|
gl::BindVertexArray(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_rect(&mut self, half_width: f32, half_height: f32, rect: &RenderRect) {
|
||||||
|
// Calculate rectangle vertices positions in normalized device coordinates.
|
||||||
|
// NDC range from -1 to +1, with Y pointing up.
|
||||||
|
let x = rect.x / half_width - 1.0;
|
||||||
|
let y = -rect.y / half_height + 1.0;
|
||||||
|
let width = rect.width / half_width;
|
||||||
|
let height = rect.height / half_height;
|
||||||
|
let Rgb { r, g, b } = rect.color;
|
||||||
|
let a = (rect.alpha * 255.) as u8;
|
||||||
|
|
||||||
|
// Make quad vertices.
|
||||||
|
let quad = [
|
||||||
|
Vertex { x, y, r, g, b, a },
|
||||||
|
Vertex { x, y: y - height, r, g, b, a },
|
||||||
|
Vertex { x: x + width, y, r, g, b, a },
|
||||||
|
Vertex { x: x + width, y: y - height, r, g, b, a },
|
||||||
|
];
|
||||||
|
|
||||||
|
// Append the vertices to form two triangles.
|
||||||
|
self.vertices.push(quad[0]);
|
||||||
|
self.vertices.push(quad[1]);
|
||||||
|
self.vertices.push(quad[2]);
|
||||||
|
self.vertices.push(quad[2]);
|
||||||
|
self.vertices.push(quad[3]);
|
||||||
|
self.vertices.push(quad[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rectangle drawing program.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RectShaderProgram {
|
||||||
|
/// Program id.
|
||||||
|
id: GLuint,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RectShaderProgram {
|
||||||
|
pub fn new() -> Result<Self, renderer::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 =
|
||||||
|
renderer::create_shader(RECT_SHADER_V_PATH, gl::VERTEX_SHADER, vertex_src)?;
|
||||||
|
let fragment_shader =
|
||||||
|
renderer::create_shader(RECT_SHADER_F_PATH, gl::FRAGMENT_SHADER, fragment_src)?;
|
||||||
|
let program = renderer::create_program(vertex_shader, fragment_shader)?;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
gl::DeleteShader(fragment_shader);
|
||||||
|
gl::DeleteShader(vertex_shader);
|
||||||
|
gl::UseProgram(program);
|
||||||
|
}
|
||||||
|
|
||||||
|
let shader = Self { id: program };
|
||||||
|
|
||||||
|
unsafe { gl::UseProgram(0) }
|
||||||
|
|
||||||
|
Ok(shader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RectShaderProgram {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
gl::DeleteProgram(self.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue