use std::borrow::Cow;
use std::collections::HashSet;
use std::ffi::{CStr, CString};
use std::sync::OnceLock;
use std::sync::atomic::{AtomicBool, Ordering};
use std::{fmt, ptr};
use ahash::RandomState;
use crossfont::Metrics;
use glutin::context::{ContextApi, GlContext, PossiblyCurrentContext};
use glutin::display::{GetGlDisplay, GlDisplay};
use log::{LevelFilter, debug, info};
use unicode_width::UnicodeWidthChar;
use alacritty_terminal::index::Point;
use alacritty_terminal::term::cell::Flags;
use crate::config::debug::RendererPreference;
use crate::display::SizeInfo;
use crate::display::color::Rgb;
use crate::display::content::RenderableCell;
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};
pub static GL_FUNS_LOADED: AtomicBool = AtomicBool::new(false);
#[derive(Debug)]
pub enum Error {
Shader(ShaderError),
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,
robustness: bool,
}
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 {description}: invalid enum").into())
},
error_id => Err(format!("OpenGL error {error_id} requesting {description}").into()),
}
}
}
impl Renderer {
pub fn new(
context: &PossiblyCurrentContext,
renderer_preference: Option<RendererPreference>,
) -> Result<Self, Error> {
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 robustness = Self::supports_robustness();
let is_gles_context = matches!(context.context_api(), ContextApi::Gles(_));
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)
};
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, robustness })
}
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)
},
}
}
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().filter_map(|(i, character)| {
let flags = if wide_char_spacer {
wide_char_spacer = false;
return None;
} else if character.width() == Some(2) {
wide_char_spacer = true;
Flags::WIDE_CHAR
} else {
Flags::empty()
};
Some(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),
}
}
pub fn draw_rects(&mut self, size_info: &SizeInfo, metrics: &Metrics, rects: Vec<RenderRect>) {
if rects.is_empty() {
return;
}
unsafe {
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);
unsafe {
gl::BlendFunc(gl::SRC1_COLOR, gl::ONE_MINUS_SRC1_COLOR);
self.set_viewport(size_info);
}
}
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 was_context_reset(&self) -> bool {
if !self.robustness {
return false;
}
let status = unsafe { gl::GetGraphicsResetStatus() };
if status == gl::NO_ERROR {
false
} else {
let reason = match status {
gl::GUILTY_CONTEXT_RESET_KHR => "guilty",
gl::INNOCENT_CONTEXT_RESET_KHR => "innocent",
gl::UNKNOWN_CONTEXT_RESET_KHR => "unknown",
_ => "invalid",
};
info!("GPU reset ({reason})");
true
}
}
fn supports_robustness() -> bool {
let mut notification_strategy = 0;
if GlExtensions::contains("GL_KHR_robustness") {
unsafe {
gl::GetIntegerv(gl::RESET_NOTIFICATION_STRATEGY_KHR, &mut notification_strategy);
}
} else {
notification_strategy = gl::NO_RESET_NOTIFICATION_KHR as gl::types::GLint;
}
if notification_strategy == gl::LOSE_CONTEXT_ON_RESET_KHR as gl::types::GLint {
info!("GPU reset notifications are enabled");
true
} else {
info!("GPU reset notifications are disabled");
false
}
}
pub fn finish(&self) {
unsafe {
gl::Finish();
}
}
#[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,
);
}
}
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 {
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)
}
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,
_: 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() };
debug!("[gl_render] {msg}");
}