diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab157db..c8666916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ Notable changes to the `alacritty_terminal` crate are documented in its - Report of Enter/Tab/Backspace in kitty keyboard's report event types mode - Crash when pressing certain modifier keys on macOS 15+ - Cut off wide characters in preedit string +- Crash when OpenGL context resets ## 0.14.0 diff --git a/alacritty/build.rs b/alacritty/build.rs index 7c33040e..0856e3a5 100644 --- a/alacritty/build.rs +++ b/alacritty/build.rs @@ -17,6 +17,7 @@ fn main() { Registry::new(Api::Gl, (3, 3), Profile::Core, Fallbacks::All, [ "GL_ARB_blend_func_extended", + "GL_KHR_robustness", "GL_KHR_debug", ]) .write_bindings(GlobalGenerator, &mut file) diff --git a/alacritty/src/display/mod.rs b/alacritty/src/display/mod.rs index 6c685a2a..9f11ccf6 100644 --- a/alacritty/src/display/mod.rs +++ b/alacritty/src/display/mod.rs @@ -8,7 +8,10 @@ use std::num::NonZeroU32; use std::ops::{Deref, DerefMut}; use std::time::{Duration, Instant}; +use glutin::config::GetGlConfig; use glutin::context::{NotCurrentContext, PossiblyCurrentContext}; +use glutin::display::GetGlDisplay; +use glutin::error::ErrorKind; use glutin::prelude::*; use glutin::surface::{Surface, SwapInterval, WindowSurface}; @@ -33,6 +36,7 @@ use alacritty_terminal::term::{ }; use alacritty_terminal::vte::ansi::{CursorShape, NamedColor}; +use crate::config::debug::RendererPreference; use crate::config::font::Font; use crate::config::window::Dimensions; #[cfg(not(windows))] @@ -49,7 +53,7 @@ use crate::display::window::Window; use crate::event::{Event, EventType, Mouse, SearchState}; use crate::message_bar::{MessageBuffer, MessageType}; use crate::renderer::rects::{RenderLine, RenderLines, RenderRect}; -use crate::renderer::{self, GlyphCache, Renderer}; +use crate::renderer::{self, platform, GlyphCache, Renderer}; use crate::scheduler::{Scheduler, TimerId, Topic}; use crate::string::{ShortenDirection, StrShortener}; @@ -385,6 +389,7 @@ pub struct Display { hint_mouse_point: Option, renderer: ManuallyDrop, + renderer_preference: Option, surface: ManuallyDrop>, @@ -421,7 +426,7 @@ impl Display { } // Create the GL surface to draw into. - let surface = renderer::platform::create_gl_surface( + let surface = platform::create_gl_surface( &gl_context, window.inner_size(), window.raw_window_handle(), @@ -513,6 +518,7 @@ impl Display { context: ManuallyDrop::new(Replaceable::new(context)), visual_bell: VisualBell::from(&config.bell), renderer: ManuallyDrop::new(renderer), + renderer_preference: config.debug.renderer, surface: ManuallyDrop::new(surface), colors: List::from(&config.colors), frame_timer: FrameTimer::new(), @@ -552,10 +558,55 @@ impl Display { } } - pub fn make_current(&self) { - if !self.context.get().is_current() { - self.context.make_current(&self.surface).expect("failed to make context current") + pub fn make_current(&mut self) { + let is_current = self.context.get().is_current(); + + // Attempt to make the context current if it's not. + let context_loss = if is_current { + self.renderer.was_context_reset() + } else { + match self.context.make_current(&self.surface) { + Err(err) if err.error_kind() == ErrorKind::ContextLost => { + info!("Context lost for window {:?}", self.window.id()); + true + }, + _ => false, + } + }; + + if !context_loss { + return; } + + let gl_display = self.context.display(); + let gl_config = self.context.config(); + let raw_window_handle = Some(self.window.raw_window_handle()); + let context = platform::create_gl_context(&gl_display, &gl_config, raw_window_handle) + .expect("failed to recreate context."); + + // Drop the old context and renderer. + unsafe { + ManuallyDrop::drop(&mut self.renderer); + ManuallyDrop::drop(&mut self.context); + } + + // Activate new context. + let context = context.treat_as_possibly_current(); + self.context = ManuallyDrop::new(Replaceable::new(context)); + self.context.make_current(&self.surface).expect("failed to reativate context after reset."); + + // Recreate renderer. + let renderer = Renderer::new(&self.context, self.renderer_preference) + .expect("failed to recreate renderer after reset"); + self.renderer = ManuallyDrop::new(renderer); + + // Resize the renderer. + self.renderer.resize(&self.size_info); + + self.reset_glyph_cache(); + self.damage_tracker.frame().mark_fully_damaged(); + + debug!("Recovered window {:?} from gpu reset", self.window.id()); } fn swap_buffers(&self) { diff --git a/alacritty/src/renderer/mod.rs b/alacritty/src/renderer/mod.rs index 3362db42..92998e58 100644 --- a/alacritty/src/renderer/mod.rs +++ b/alacritty/src/renderer/mod.rs @@ -9,7 +9,7 @@ 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 log::{debug, info, LevelFilter}; use unicode_width::UnicodeWidthChar; use alacritty_terminal::index::Point; @@ -97,6 +97,7 @@ enum TextRendererProvider { pub struct Renderer { text_renderer: TextRendererProvider, rect_renderer: RectRenderer, + robustness: bool, } /// Wrapper around gl::GetString with error checking and reporting. @@ -144,6 +145,9 @@ impl Renderer { info!("Running on {renderer}"); info!("OpenGL version {gl_version}, shader_version {shader_version}"); + // Check if robustness is supported. + let robustness = Self::supports_robustness(); + let is_gles_context = matches!(context.context_api(), ContextApi::Gles(_)); // Use the config option to enforce a particular renderer configuration. @@ -175,7 +179,7 @@ impl Renderer { } } - Ok(Self { text_renderer, rect_renderer }) + Ok(Self { text_renderer, rect_renderer, robustness }) } pub fn draw_cells>( @@ -281,6 +285,49 @@ impl Renderer { } } + /// Get the context reset status. + pub fn was_context_reset(&self) -> bool { + // If robustness is not supported, don't use its functions. + 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(); @@ -349,7 +396,7 @@ impl GlExtensions { extern "system" fn gl_debug_log( _: gl::types::GLenum, - kind: gl::types::GLenum, + _: gl::types::GLenum, _: gl::types::GLuint, _: gl::types::GLenum, _: gl::types::GLsizei, @@ -357,11 +404,5 @@ extern "system" fn gl_debug_log( _: *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), - } + debug!("[gl_render] {}", msg); } diff --git a/alacritty/src/renderer/platform.rs b/alacritty/src/renderer/platform.rs index 87ed29c2..3b2e2fce 100644 --- a/alacritty/src/renderer/platform.rs +++ b/alacritty/src/renderer/platform.rs @@ -4,9 +4,9 @@ use std::num::NonZeroU32; use glutin::config::{ColorBufferType, Config, ConfigTemplateBuilder, GetGlConfig}; use glutin::context::{ - ContextApi, ContextAttributesBuilder, GlProfile, NotCurrentContext, Version, + ContextApi, ContextAttributesBuilder, GlProfile, NotCurrentContext, Robustness, Version, }; -use glutin::display::{Display, DisplayApiPreference, GetGlDisplay}; +use glutin::display::{Display, DisplayApiPreference, DisplayFeatures, GetGlDisplay}; use glutin::error::Result as GlutinResult; use glutin::prelude::*; use glutin::surface::{Surface, SurfaceAttributesBuilder, WindowSurface}; @@ -110,18 +110,24 @@ pub fn create_gl_context( raw_window_handle: Option, ) -> GlutinResult { let debug = log::max_level() >= LevelFilter::Debug; + let mut builder = ContextAttributesBuilder::new().with_debug(debug); + + // Try to enable robustness. + if gl_display.supported_features().contains(DisplayFeatures::CONTEXT_ROBUSTNESS) { + builder = builder.with_robustness(Robustness::RobustLoseContextOnReset); + } + let mut profiles = [ - ContextAttributesBuilder::new() - .with_debug(debug) + builder + .clone() .with_context_api(ContextApi::OpenGl(Some(Version::new(3, 3)))) .build(raw_window_handle), // Try gles before OpenGL 2.1 as it tends to be more stable. - ContextAttributesBuilder::new() - .with_debug(debug) + builder + .clone() .with_context_api(ContextApi::Gles(Some(Version::new(2, 0)))) .build(raw_window_handle), - ContextAttributesBuilder::new() - .with_debug(debug) + builder .with_profile(GlProfile::Compatibility) .with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1)))) .build(raw_window_handle),