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 for Error { fn from(val: ShaderError) -> Self { Error::Shader(val) } } impl From 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, 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, ) -> Result { // 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>( &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, fg: Rgb, bg: Rgb, string_chars: impl Iterator, 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(&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) { 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> = 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), } }