aboutsummaryrefslogtreecommitdiff
path: root/alacritty/src/renderer/graphics/mod.rs
blob: 3fecd54933aa17f77a04746ab2d083d81ee80c44 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
//! This module implements the functionality to support graphics in the grid.

use std::mem;

use alacritty_terminal::graphics::{ColorType, GraphicData, GraphicId, UpdateQueues};
use alacritty_terminal::term::SizeInfo;

use log::trace;
use serde::{Deserialize, Serialize};

use crate::gl;
use crate::gl::types::*;
use crate::renderer;

use std::collections::HashMap;

mod draw;
mod shader;

pub use draw::RenderList;

/// Type for texture names generated in the GPU.
#[derive(Serialize, Deserialize, Eq, PartialEq, Clone, Debug)]
pub struct TextureName(GLuint);

// In debug mode, check if the inner value was set to zero, so we can detect if
// the associated texture was deleted from the GPU.
#[cfg(debug_assertions)]
impl Drop for TextureName {
    fn drop(&mut self) {
        if self.0 != 0 {
            log::error!("Texture {} was not deleted.", self.0);
        }
    }
}

/// Texture for a graphic in the grid.
#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
pub struct GraphicTexture {
    /// Texture in the GPU where the graphic pixels are stored.
    texture: TextureName,

    /// Cell height at the moment graphic was created.
    ///
    /// Used to scale it if the user increases or decreases the font size.
    cell_height: f32,

    /// Width in pixels of the graphic.
    width: u16,

    /// Height in pixels of the graphic.
    height: u16,
}

#[derive(Debug)]
pub struct GraphicsRenderer {
    /// Program in the GPU to render graphics.
    program: shader::GraphicsShaderProgram,

    /// Collection to associate graphic identifiers with their textures.
    graphic_textures: HashMap<GraphicId, GraphicTexture>,
}

impl GraphicsRenderer {
    pub fn new() -> Result<GraphicsRenderer, renderer::Error> {
        let program = shader::GraphicsShaderProgram::new()?;
        Ok(GraphicsRenderer { program, graphic_textures: HashMap::default() })
    }

    /// Run the required actions to apply changes for the graphics in the grid.
    #[inline]
    pub fn run_updates(&mut self, update_queues: UpdateQueues, size_info: &SizeInfo) {
        self.remove_graphics(update_queues.remove_queue);
        self.upload_pending_graphics(update_queues.pending, size_info);
    }

    /// Release resources used by removed graphics.
    fn remove_graphics(&mut self, removed_ids: Vec<GraphicId>) {
        let mut textures = Vec::with_capacity(removed_ids.len());
        for id in removed_ids {
            if let Some(mut graphic_texture) = self.graphic_textures.remove(&id) {
                // Reset the inner value of TextureName, so the Drop implementation
                // (in debug mode) can verify that the texture was deleted.
                textures.push(mem::take(&mut graphic_texture.texture.0));
            }
        }

        trace!("Call glDeleteTextures with {} items", textures.len());

        unsafe {
            gl::DeleteTextures(textures.len() as GLint, textures.as_ptr());
        }
    }

    /// Create new textures in the GPU, and upload the pixels to them.
    fn upload_pending_graphics(&mut self, graphics: Vec<GraphicData>, size_info: &SizeInfo) {
        for graphic in graphics {
            let mut texture = 0;

            unsafe {
                gl::GenTextures(1, &mut texture);
                trace!("Texture generated: {}", texture);

                gl::BindTexture(gl::TEXTURE_2D, texture);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAX_LEVEL, 0);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint);
                gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint);

                let pixel_format = match graphic.color_type {
                    ColorType::Rgb => gl::RGB,
                    ColorType::Rgba => gl::RGBA,
                };

                gl::TexImage2D(
                    gl::TEXTURE_2D,
                    0,
                    gl::RGBA as GLint,
                    graphic.width as GLint,
                    graphic.height as GLint,
                    0,
                    pixel_format,
                    gl::UNSIGNED_BYTE,
                    graphic.pixels.as_ptr().cast(),
                );

                gl::BindTexture(gl::TEXTURE_2D, 0);
            }

            let graphic_texture = GraphicTexture {
                texture: TextureName(texture),
                cell_height: size_info.cell_height(),
                width: graphic.width as u16,
                height: graphic.height as u16,
            };

            self.graphic_textures.insert(graphic.id, graphic_texture);
        }
    }

    /// Draw graphics in the display.
    #[inline]
    pub fn draw(&mut self, render_list: RenderList, size_info: &SizeInfo) {
        if !render_list.is_empty() {
            render_list.draw(self, size_info);
        }
    }
}