| use std::borrow::Cow; |
| use std::collections::HashSet; |
| use std::ffi::{CStr, CString}; |
| use std::sync::atomic::{AtomicBool, Ordering}; |
| use std::sync::OnceLock; |
| use std::{fmt, ptr}; |
| |
| use ahash::RandomState; |
| use crossfont::Metrics; |
| use glutin::context::{ContextApi, GlContext, PossiblyCurrentContext}; |
| use glutin::display::{GetGlDisplay, GlDisplay}; |
| use log::{debug, error, info, warn, LevelFilter}; |
| use unicode_width::UnicodeWidthChar; |
| |
| use alacritty_terminal::index::Point; |
| use alacritty_terminal::term::cell::Flags; |
| |
| use crate::config::debug::RendererPreference; |
| use crate::display::color::Rgb; |
| use crate::display::content::RenderableCell; |
| use crate::display::SizeInfo; |
| use crate::gl; |
| use crate::renderer::rects::{RectRenderer, RenderRect}; |
| use crate::renderer::shader::ShaderError; |
| |
| pub mod platform; |
| pub mod rects; |
| mod shader; |
| mod text; |
| |
| pub use text::{GlyphCache, LoaderApi}; |
| |
| use shader::ShaderVersion; |
| use text::{Gles2Renderer, Glsl3Renderer, TextRenderer}; |
| |
| macro_rules! cstr { |
| ($s:literal) => { |
| // This can be optimized into an no-op with pre-allocated NUL-terminated bytes. |
| unsafe { std::ffi::CStr::from_ptr(concat!($s, "\0").as_ptr().cast()) } |
| }; |
| } |
| pub(crate) use cstr; |
| |
| /// Whether the OpenGL functions have been loaded. |
| pub static GL_FUNS_LOADED: AtomicBool = AtomicBool::new(false); |
| |
| #[derive(Debug)] |
| pub enum Error { |
| /// Shader error. |
| Shader(ShaderError), |
| |
| /// Other error. |
| Other(String), |
| } |
| |
| impl std::error::Error for Error { |
| fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { |
| match self { |
| Error::Shader(err) => err.source(), |
| Error::Other(_) => None, |
| } |
| } |
| } |
| |
| impl fmt::Display for Error { |
| fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| match self { |
| Error::Shader(err) => { |
| write!(f, "There was an error initializing the shaders: {}", err) |
| }, |
| Error::Other(err) => { |
| write!(f, "{}", err) |
| }, |
| } |
| } |
| } |
| |
| impl From<ShaderError> for Error { |
| fn from(val: ShaderError) -> Self { |
| Error::Shader(val) |
| } |
| } |
| |
| impl From<String> for Error { |
| fn from(val: String) -> Self { |
| Error::Other(val) |
| } |
| } |
| |
| #[derive(Debug)] |
| enum TextRendererProvider { |
| Gles2(Gles2Renderer), |
| Glsl3(Glsl3Renderer), |
| } |
| |
| #[derive(Debug)] |
| pub struct Renderer { |
| text_renderer: TextRendererProvider, |
| rect_renderer: RectRenderer, |
| } |
| |
| /// Wrapper around gl::GetString with error checking and reporting. |
| fn gl_get_string( |
| string_id: gl::types::GLenum, |
| description: &str, |
| ) -> Result<Cow<'static, str>, Error> { |
| unsafe { |
| let string_ptr = gl::GetString(string_id); |
| match gl::GetError() { |
| gl::NO_ERROR if !string_ptr.is_null() => { |
| Ok(CStr::from_ptr(string_ptr as *const _).to_string_lossy()) |
| }, |
| gl::INVALID_ENUM => { |
| Err(format!("OpenGL error requesting {}: invalid enum", description).into()) |
| }, |
| error_id => Err(format!("OpenGL error {} requesting {}", error_id, description).into()), |
| } |
| } |
| } |
| |
| impl Renderer { |
| /// Create a new renderer. |
| /// |
| /// This will automatically pick between the GLES2 and GLSL3 renderer based on the GPU's |
| /// supported OpenGL version. |
| pub fn new( |
| context: &PossiblyCurrentContext, |
| renderer_preference: Option<RendererPreference>, |
| ) -> Result<Self, Error> { |
| // We need to load OpenGL functions once per instance, but only after we make our context |
| // current due to WGL limitations. |
| if !GL_FUNS_LOADED.swap(true, Ordering::Relaxed) { |
| let gl_display = context.display(); |
| gl::load_with(|symbol| { |
| let symbol = CString::new(symbol).unwrap(); |
| gl_display.get_proc_address(symbol.as_c_str()).cast() |
| }); |
| } |
| |
| let shader_version = gl_get_string(gl::SHADING_LANGUAGE_VERSION, "shader version")?; |
| let gl_version = gl_get_string(gl::VERSION, "OpenGL version")?; |
| let renderer = gl_get_string(gl::RENDERER, "renderer version")?; |
| |
| info!("Running on {renderer}"); |
| info!("OpenGL version {gl_version}, shader_version {shader_version}"); |
| |
| let is_gles_context = matches!(context.context_api(), ContextApi::Gles(_)); |
| |
| // Use the config option to enforce a particular renderer configuration. |
| let (use_glsl3, allow_dsb) = match renderer_preference { |
| Some(RendererPreference::Glsl3) => (true, true), |
| Some(RendererPreference::Gles2) => (false, true), |
| Some(RendererPreference::Gles2Pure) => (false, false), |
| None => (shader_version.as_ref() >= "3.3" && !is_gles_context, true), |
| }; |
| |
| let (text_renderer, rect_renderer) = if use_glsl3 { |
| let text_renderer = TextRendererProvider::Glsl3(Glsl3Renderer::new()?); |
| let rect_renderer = RectRenderer::new(ShaderVersion::Glsl3)?; |
| (text_renderer, rect_renderer) |
| } else { |
| let text_renderer = |
| TextRendererProvider::Gles2(Gles2Renderer::new(allow_dsb, is_gles_context)?); |
| let rect_renderer = RectRenderer::new(ShaderVersion::Gles2)?; |
| (text_renderer, rect_renderer) |
| }; |
| |
| // Enable debug logging for OpenGL as well. |
| if log::max_level() >= LevelFilter::Debug && GlExtensions::contains("GL_KHR_debug") { |
| debug!("Enabled debug logging for OpenGL"); |
| unsafe { |
| gl::Enable(gl::DEBUG_OUTPUT); |
| gl::Enable(gl::DEBUG_OUTPUT_SYNCHRONOUS); |
| gl::DebugMessageCallback(Some(gl_debug_log), ptr::null_mut()); |
| } |
| } |
| |
| Ok(Self { text_renderer, rect_renderer }) |
| } |
| |
| pub fn draw_cells<I: Iterator<Item = RenderableCell>>( |
| &mut self, |
| size_info: &SizeInfo, |
| glyph_cache: &mut GlyphCache, |
| cells: I, |
| ) { |
| match &mut self.text_renderer { |
| TextRendererProvider::Gles2(renderer) => { |
| renderer.draw_cells(size_info, glyph_cache, cells) |
| }, |
| TextRendererProvider::Glsl3(renderer) => { |
| renderer.draw_cells(size_info, glyph_cache, cells) |
| }, |
| } |
| } |
| |
| /// Draw a string in a variable location. Used for printing the render timer, warnings and |
| /// errors. |
| pub fn draw_string( |
| &mut self, |
| point: Point<usize>, |
| fg: Rgb, |
| bg: Rgb, |
| string_chars: impl Iterator<Item = char>, |
| size_info: &SizeInfo, |
| glyph_cache: &mut GlyphCache, |
| ) { |
| let mut wide_char_spacer = false; |
| let cells = string_chars.enumerate().map(|(i, character)| { |
| let flags = if wide_char_spacer { |
| wide_char_spacer = false; |
| Flags::WIDE_CHAR_SPACER |
| } else if character.width() == Some(2) { |
| // The spacer is always following the wide char. |
| wide_char_spacer = true; |
| Flags::WIDE_CHAR |
| } else { |
| Flags::empty() |
| }; |
| |
| RenderableCell { |
| point: Point::new(point.line, point.column + i), |
| character, |
| extra: None, |
| flags, |
| bg_alpha: 1.0, |
| fg, |
| bg, |
| underline: fg, |
| } |
| }); |
| |
| self.draw_cells(size_info, glyph_cache, cells); |
| } |
| |
| pub fn with_loader<F, T>(&mut self, func: F) -> T |
| where |
| F: FnOnce(LoaderApi<'_>) -> T, |
| { |
| match &mut self.text_renderer { |
| TextRendererProvider::Gles2(renderer) => renderer.with_loader(func), |
| TextRendererProvider::Glsl3(renderer) => renderer.with_loader(func), |
| } |
| } |
| |
| /// Draw all rectangles simultaneously to prevent excessive program swaps. |
| pub fn draw_rects(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec<RenderRect>) { |
| if rects.is_empty() { |
| return; |
| } |
| |
| // Prepare rect rendering state. |
| unsafe { |
| // Remove padding from viewport. |
| gl::Viewport(0, 0, size_info.width() as i32, size_info.height() as i32); |
| gl::BlendFuncSeparate(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA, gl::SRC_ALPHA, gl::ONE); |
| } |
| |
| self.rect_renderer.draw(size_info, metrics, rects); |
| |
| // Activate regular state again. |
| unsafe { |
| // Reset blending strategy. |
| gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR); |
| |
| // Restore viewport with padding. |
| self.set_viewport(size_info); |
| } |
| } |
| |
| /// Fill the window with `color` and `alpha`. |
| pub fn clear(&self, color: Rgb, alpha: f32) { |
| unsafe { |
| gl::ClearColor( |
| (f32::from(color.r) / 255.0).min(1.0) * alpha, |
| (f32::from(color.g) / 255.0).min(1.0) * alpha, |
| (f32::from(color.b) / 255.0).min(1.0) * alpha, |
| alpha, |
| ); |
| gl::Clear(gl::COLOR_BUFFER_BIT); |
| } |
| } |
| |
| pub fn finish(&self) { |
| unsafe { |
| gl::Finish(); |
| } |
| } |
| |
| /// Set the viewport for cell rendering. |
| #[inline] |
| pub fn set_viewport(&self, size: &SizeInfo) { |
| unsafe { |
| gl::Viewport( |
| size.padding_x() as i32, |
| size.padding_y() as i32, |
| size.width() as i32 - 2 * size.padding_x() as i32, |
| size.height() as i32 - 2 * size.padding_y() as i32, |
| ); |
| } |
| } |
| |
| /// Resize the renderer. |
| pub fn resize(&self, size_info: &SizeInfo) { |
| self.set_viewport(size_info); |
| match &self.text_renderer { |
| TextRendererProvider::Gles2(renderer) => renderer.resize(size_info), |
| TextRendererProvider::Glsl3(renderer) => renderer.resize(size_info), |
| } |
| } |
| } |
| |
| struct GlExtensions; |
| |
| impl GlExtensions { |
| /// Check if the given `extension` is supported. |
| /// |
| /// This function will lazily load OpenGL extensions. |
| fn contains(extension: &str) -> bool { |
| static OPENGL_EXTENSIONS: OnceLock<HashSet<&'static str, RandomState>> = OnceLock::new(); |
| |
| OPENGL_EXTENSIONS.get_or_init(Self::load_extensions).contains(extension) |
| } |
| |
| /// Load available OpenGL extensions. |
| fn load_extensions() -> HashSet<&'static str, RandomState> { |
| unsafe { |
| let extensions = gl::GetString(gl::EXTENSIONS); |
| |
| if extensions.is_null() { |
| let mut extensions_number = 0; |
| gl::GetIntegerv(gl::NUM_EXTENSIONS, &mut extensions_number); |
| |
| (0..extensions_number as gl::types::GLuint) |
| .flat_map(|i| { |
| let extension = CStr::from_ptr(gl::GetStringi(gl::EXTENSIONS, i) as *mut _); |
| extension.to_str() |
| }) |
| .collect() |
| } else { |
| match CStr::from_ptr(extensions as *mut _).to_str() { |
| Ok(ext) => ext.split_whitespace().collect(), |
| Err(_) => Default::default(), |
| } |
| } |
| } |
| } |
| } |
| |
| extern "system" fn gl_debug_log( |
| _: gl::types::GLenum, |
| kind: gl::types::GLenum, |
| _: gl::types::GLuint, |
| _: gl::types::GLenum, |
| _: gl::types::GLsizei, |
| msg: *const gl::types::GLchar, |
| _: *mut std::os::raw::c_void, |
| ) { |
| let msg = unsafe { CStr::from_ptr(msg).to_string_lossy() }; |
| match kind { |
| gl::DEBUG_TYPE_ERROR | gl::DEBUG_TYPE_UNDEFINED_BEHAVIOR => { |
| error!("[gl_render] {}", msg) |
| }, |
| gl::DEBUG_TYPE_DEPRECATED_BEHAVIOR => warn!("[gl_render] {}", msg), |
| _ => debug!("[gl_render] {}", msg), |
| } |
| } |