blob: f4f1397f733135d8217fb54b1348b9f030299b10 [file] [log] [blame]
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),
}
}