mirror of
https://github.com/wez/wezterm.git
synced 2024-11-22 22:42:48 +03:00
window: handle opengl context loss on windows.
With this commit, we now survive a reinstall or upgrade of the nvidia drivers on my Windows sytem without crashing. This commit allows notifying the application of the context loss so the application can either try to reinit opengl or open a new window as a replacement and init opengl there. I've not had success at reinitializing opengl after a driver upgrade; it seems to be persistently stuck in a state where it fails to allocate a vertex buffer. SO, the state we have now is that we try to reinit opengl on a new window, and if that fails, leave it set to the software renderer. This isn't a perfect UX, but it is better than terminating! refs: https://github.com/wez/wezterm/issues/156
This commit is contained in:
parent
3f04ce3bba
commit
8f1f1a65ea
@ -108,6 +108,7 @@ impl wezterm_term::Clipboard for ClipboardHelper {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct PrevCursorPos {
|
||||
pos: StableCursorPosition,
|
||||
when: Instant,
|
||||
@ -140,7 +141,7 @@ impl PrevCursorPos {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TabState {
|
||||
/// If is_some(), the top row of the visible screen.
|
||||
/// Otherwise, the viewport is at the bottom of the
|
||||
@ -562,11 +563,74 @@ impl WindowCallbacks for TermWindow {
|
||||
metrics::value!("gui.paint.software", start.elapsed());
|
||||
}
|
||||
|
||||
fn opengl_context_lost(&mut self, prior_window: &dyn WindowOps) -> anyhow::Result<()> {
|
||||
log::error!("context was lost, set up a new window");
|
||||
|
||||
let render_state = RenderState::Software(SoftwareRenderState::new(
|
||||
&self.fonts,
|
||||
&self.render_metrics,
|
||||
ATLAS_SIZE,
|
||||
)?);
|
||||
|
||||
let clipboard_contents = Arc::clone(&self.clipboard_contents);
|
||||
let dimensions = self.dimensions.clone();
|
||||
let mux_window_id = self.mux_window_id;
|
||||
|
||||
let window = Window::new_window(
|
||||
"org.wezfurlong.wezterm",
|
||||
"wezterm",
|
||||
dimensions.pixel_width,
|
||||
dimensions.pixel_height,
|
||||
Box::new(Self {
|
||||
window: None,
|
||||
focused: None,
|
||||
mux_window_id,
|
||||
fonts: Rc::clone(&self.fonts),
|
||||
render_metrics: self.render_metrics.clone(),
|
||||
dimensions,
|
||||
terminal_size: self.terminal_size.clone(),
|
||||
render_state,
|
||||
input_map: InputMap::new(),
|
||||
show_tab_bar: self.show_tab_bar,
|
||||
show_scroll_bar: self.show_scroll_bar,
|
||||
tab_bar: self.tab_bar.clone(),
|
||||
last_mouse_coords: self.last_mouse_coords.clone(),
|
||||
last_mouse_terminal_coords: self.last_mouse_terminal_coords.clone(),
|
||||
scroll_drag_start: self.scroll_drag_start.clone(),
|
||||
config_generation: self.config_generation,
|
||||
prev_cursor: self.prev_cursor.clone(),
|
||||
last_scroll_info: self.last_scroll_info.clone(),
|
||||
clipboard_contents: Arc::clone(&clipboard_contents),
|
||||
tab_state: RefCell::new(self.tab_state.borrow().clone()),
|
||||
current_mouse_button: self.current_mouse_button.clone(),
|
||||
last_mouse_click: self.last_mouse_click.clone(),
|
||||
current_highlight: self.current_highlight.clone(),
|
||||
shape_cache: RefCell::new(LruCache::new(65536)),
|
||||
last_blink_paint: Instant::now(),
|
||||
}),
|
||||
)?;
|
||||
|
||||
Self::apply_icon(&window)?;
|
||||
Self::start_periodic_maintenance(window.clone());
|
||||
Self::setup_clipboard(&window, mux_window_id, clipboard_contents);
|
||||
|
||||
prior_window.close();
|
||||
window.enable_opengl();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn opengl_initialize(
|
||||
&mut self,
|
||||
window: &dyn WindowOps,
|
||||
maybe_ctx: anyhow::Result<std::rc::Rc<glium::backend::Context>>,
|
||||
) -> anyhow::Result<()> {
|
||||
self.render_state = RenderState::Software(SoftwareRenderState::new(
|
||||
&self.fonts,
|
||||
&self.render_metrics,
|
||||
ATLAS_SIZE,
|
||||
)?);
|
||||
|
||||
match maybe_ctx {
|
||||
Ok(ctx) => {
|
||||
match OpenGLRenderState::new(
|
||||
@ -579,22 +643,29 @@ impl WindowCallbacks for TermWindow {
|
||||
) {
|
||||
Ok(gl) => {
|
||||
log::info!(
|
||||
"OpenGL initialized! {} {}",
|
||||
"OpenGL initialized! {} {} is_context_loss_possible={}",
|
||||
gl.context.get_opengl_renderer_string(),
|
||||
gl.context.get_opengl_version_string()
|
||||
gl.context.get_opengl_version_string(),
|
||||
gl.context.is_context_loss_possible(),
|
||||
);
|
||||
self.render_state = RenderState::GL(gl);
|
||||
}
|
||||
Err(err) => {
|
||||
log::error!("OpenGL init failed: {}", err);
|
||||
log::error!("failed to create OpenGLRenderState: {}", err);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(err) => log::error!("OpenGL init failed: {}", err),
|
||||
Err(err) => {
|
||||
log::error!("OpenGL init failed: {}", err);
|
||||
}
|
||||
};
|
||||
|
||||
window.show();
|
||||
Ok(())
|
||||
|
||||
match &self.render_state {
|
||||
RenderState::Software(_) => Err(anyhow::anyhow!("Falling back to software renderer")),
|
||||
RenderState::GL(_) => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn paint_opengl(&mut self, frame: &mut glium::Frame) {
|
||||
@ -729,6 +800,40 @@ impl TermWindow {
|
||||
}),
|
||||
)?;
|
||||
|
||||
Self::apply_icon(&window)?;
|
||||
Self::start_periodic_maintenance(window.clone());
|
||||
Self::setup_clipboard(&window, mux_window_id, clipboard_contents);
|
||||
|
||||
if super::is_opengl_enabled() {
|
||||
window.enable_opengl();
|
||||
} else {
|
||||
window.show();
|
||||
}
|
||||
|
||||
crate::update::start_update_checker();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn setup_clipboard(
|
||||
window: &Window,
|
||||
mux_window_id: MuxWindowId,
|
||||
clipboard_contents: Arc<Mutex<Option<String>>>,
|
||||
) {
|
||||
let clipboard: Arc<dyn wezterm_term::Clipboard> = Arc::new(ClipboardHelper {
|
||||
window: window.clone(),
|
||||
clipboard_contents,
|
||||
});
|
||||
let mux = Mux::get().unwrap();
|
||||
|
||||
let mut mux_window = mux.get_window_mut(mux_window_id).unwrap();
|
||||
|
||||
mux_window.set_clipboard(&clipboard);
|
||||
for tab in mux_window.iter() {
|
||||
tab.set_clipboard(&clipboard);
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_icon(window: &Window) -> anyhow::Result<()> {
|
||||
let icon_image =
|
||||
image::load_from_memory(include_bytes!("../../../assets/icon/terminal.png"))?;
|
||||
let image = icon_image.to_bgra();
|
||||
@ -738,122 +843,105 @@ impl TermWindow {
|
||||
height as usize,
|
||||
image.into_raw(),
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
let cloned_window = window.clone();
|
||||
|
||||
crate::update::start_update_checker();
|
||||
|
||||
fn start_periodic_maintenance(window: Window) {
|
||||
Connection::get().unwrap().schedule_timer(
|
||||
std::time::Duration::from_millis(35),
|
||||
move || {
|
||||
cloned_window.apply(move |myself, _| {
|
||||
window.apply(move |myself, window| {
|
||||
if let Some(myself) = myself.downcast_mut::<Self>() {
|
||||
let mux = Mux::get().unwrap();
|
||||
|
||||
if let Some(tab) = myself.get_active_tab_or_overlay() {
|
||||
let mut needs_invalidate = false;
|
||||
|
||||
// If the config was reloaded, ask the window to apply
|
||||
// and render any changes
|
||||
myself.check_for_config_reload();
|
||||
|
||||
let config = configuration();
|
||||
|
||||
let render = tab.renderer();
|
||||
|
||||
// If blinking is permitted, and the cursor shape is set
|
||||
// to a blinking variant, and it's been longer than the
|
||||
// blink rate interval, then invalidate and redraw
|
||||
// so that we will re-evaluate the cursor visibility.
|
||||
// This is pretty heavyweight: it would be nice to only invalidate
|
||||
// the line on which the cursor resides, and then only if the cursor
|
||||
// is within the viewport.
|
||||
if config.cursor_blink_rate != 0 && myself.focused.is_some() {
|
||||
let shape = config
|
||||
.default_cursor_style
|
||||
.effective_shape(render.get_cursor_position().shape);
|
||||
if shape.is_blinking() {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(myself.last_blink_paint)
|
||||
> Duration::from_millis(config.cursor_blink_rate)
|
||||
{
|
||||
needs_invalidate = true;
|
||||
myself.last_blink_paint = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the model is dirty, arrange to re-paint
|
||||
let dims = render.get_dimensions();
|
||||
let viewport = myself
|
||||
.get_viewport(tab.tab_id())
|
||||
.unwrap_or(dims.physical_top);
|
||||
let visible_range =
|
||||
viewport..viewport + dims.viewport_rows as StableRowIndex;
|
||||
let dirty = render.get_dirty_lines(visible_range);
|
||||
|
||||
if !dirty.is_empty() {
|
||||
if tab.downcast_ref::<SearchOverlay>().is_none()
|
||||
&& tab.downcast_ref::<CopyOverlay>().is_none()
|
||||
{
|
||||
// If any of the changed lines intersect with the
|
||||
// selection, then we need to clear the selection, but not
|
||||
// when the search overlay is active; the search overlay
|
||||
// marks lines as dirty to force invalidate them for
|
||||
// highlighting purpose but also manipulates the selection
|
||||
// and we want to allow it to retain the selection it made!
|
||||
|
||||
let clear_selection = if let Some(selection_range) =
|
||||
myself.selection(tab.tab_id()).range.as_ref()
|
||||
{
|
||||
let selection_rows = selection_range.rows();
|
||||
selection_rows.into_iter().any(|row| dirty.contains(row))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if clear_selection {
|
||||
myself.selection(tab.tab_id()).range.take();
|
||||
myself.selection(tab.tab_id()).start.take();
|
||||
}
|
||||
}
|
||||
|
||||
needs_invalidate = true;
|
||||
}
|
||||
|
||||
if let Some(mut mux_window) = mux.get_window_mut(mux_window_id) {
|
||||
if mux_window.check_and_reset_invalidated() {
|
||||
needs_invalidate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if needs_invalidate {
|
||||
myself.window.as_ref().unwrap().invalidate();
|
||||
}
|
||||
} else {
|
||||
myself.window.as_ref().unwrap().close();
|
||||
}
|
||||
myself.periodic_window_maintenance(window)?;
|
||||
}
|
||||
Ok(())
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let clipboard: Arc<dyn wezterm_term::Clipboard> = Arc::new(ClipboardHelper {
|
||||
window: window.clone(),
|
||||
clipboard_contents,
|
||||
});
|
||||
tab.set_clipboard(&clipboard);
|
||||
Mux::get()
|
||||
.unwrap()
|
||||
.get_window_mut(mux_window_id)
|
||||
.unwrap()
|
||||
.set_clipboard(&clipboard);
|
||||
fn periodic_window_maintenance(&mut self, _window: &dyn WindowOps) -> anyhow::Result<()> {
|
||||
let mux = Mux::get().unwrap();
|
||||
|
||||
if super::is_opengl_enabled() {
|
||||
window.enable_opengl();
|
||||
if let Some(tab) = self.get_active_tab_or_overlay() {
|
||||
let mut needs_invalidate = false;
|
||||
|
||||
// If the config was reloaded, ask the window to apply
|
||||
// and render any changes
|
||||
self.check_for_config_reload();
|
||||
|
||||
let config = configuration();
|
||||
|
||||
let render = tab.renderer();
|
||||
|
||||
// If blinking is permitted, and the cursor shape is set
|
||||
// to a blinking variant, and it's been longer than the
|
||||
// blink rate interval, then invalidate and redraw
|
||||
// so that we will re-evaluate the cursor visibility.
|
||||
// This is pretty heavyweight: it would be nice to only invalidate
|
||||
// the line on which the cursor resides, and then only if the cursor
|
||||
// is within the viewport.
|
||||
if config.cursor_blink_rate != 0 && self.focused.is_some() {
|
||||
let shape = config
|
||||
.default_cursor_style
|
||||
.effective_shape(render.get_cursor_position().shape);
|
||||
if shape.is_blinking() {
|
||||
let now = Instant::now();
|
||||
if now.duration_since(self.last_blink_paint)
|
||||
> Duration::from_millis(config.cursor_blink_rate)
|
||||
{
|
||||
needs_invalidate = true;
|
||||
self.last_blink_paint = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the model is dirty, arrange to re-paint
|
||||
let dims = render.get_dimensions();
|
||||
let viewport = self.get_viewport(tab.tab_id()).unwrap_or(dims.physical_top);
|
||||
let visible_range = viewport..viewport + dims.viewport_rows as StableRowIndex;
|
||||
let dirty = render.get_dirty_lines(visible_range);
|
||||
|
||||
if !dirty.is_empty() {
|
||||
if tab.downcast_ref::<SearchOverlay>().is_none()
|
||||
&& tab.downcast_ref::<CopyOverlay>().is_none()
|
||||
{
|
||||
// If any of the changed lines intersect with the
|
||||
// selection, then we need to clear the selection, but not
|
||||
// when the search overlay is active; the search overlay
|
||||
// marks lines as dirty to force invalidate them for
|
||||
// highlighting purpose but also manipulates the selection
|
||||
// and we want to allow it to retain the selection it made!
|
||||
|
||||
let clear_selection = if let Some(selection_range) =
|
||||
self.selection(tab.tab_id()).range.as_ref()
|
||||
{
|
||||
let selection_rows = selection_range.rows();
|
||||
selection_rows.into_iter().any(|row| dirty.contains(row))
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if clear_selection {
|
||||
self.selection(tab.tab_id()).range.take();
|
||||
self.selection(tab.tab_id()).start.take();
|
||||
}
|
||||
}
|
||||
|
||||
needs_invalidate = true;
|
||||
}
|
||||
|
||||
if let Some(mut mux_window) = mux.get_window_mut(self.mux_window_id) {
|
||||
if mux_window.check_and_reset_invalidated() {
|
||||
needs_invalidate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if needs_invalidate {
|
||||
self.window.as_ref().unwrap().invalidate();
|
||||
}
|
||||
} else {
|
||||
window.show();
|
||||
self.window.as_ref().unwrap().close();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -43,7 +43,7 @@ pub struct MouseEvent {
|
||||
/// within the `CLICK_INTERVAL`. The streak is reset to 1 each time
|
||||
/// the mouse button differs from the last click, or when the elapsed
|
||||
/// time exceeds `CLICK_INTERVAL`.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LastMouseClick {
|
||||
pub button: MouseButton,
|
||||
time: Instant,
|
||||
|
@ -47,7 +47,32 @@ fn main() {
|
||||
let reg = Registry::new(Api::Wgl, (1, 0), Profile::Core, Fallbacks::All, []);
|
||||
|
||||
reg.write_bindings(gl_generator::StructGenerator, &mut file)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
|
||||
let mut file = File::create(&dest.join("wgl_extra_bindings.rs")).unwrap();
|
||||
Registry::new(
|
||||
Api::Wgl,
|
||||
(1, 0),
|
||||
Profile::Core,
|
||||
Fallbacks::All,
|
||||
[
|
||||
"WGL_ARB_create_context",
|
||||
"WGL_ARB_create_context_profile",
|
||||
"WGL_ARB_create_context_robustness",
|
||||
"WGL_ARB_context_flush_control",
|
||||
"WGL_ARB_extensions_string",
|
||||
"WGL_ARB_framebuffer_sRGB",
|
||||
"WGL_ARB_multisample",
|
||||
"WGL_ARB_pixel_format",
|
||||
"WGL_ARB_pixel_format_float",
|
||||
"WGL_EXT_create_context_es2_profile",
|
||||
"WGL_EXT_extensions_string",
|
||||
"WGL_EXT_framebuffer_sRGB",
|
||||
"WGL_EXT_swap_control",
|
||||
],
|
||||
)
|
||||
.write_bindings(gl_generator::StructGenerator, &mut file)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -144,9 +144,8 @@ pub trait WindowCallbacks: Any {
|
||||
frame.clear_color_srgb(0.25, 0.125, 0.375, 1.0);
|
||||
}
|
||||
|
||||
/// Called when opengl is initialized by enable_opengl().
|
||||
/// (and perhaps also if/when opengl is reinitialized after the
|
||||
/// context is lost)
|
||||
/// Called when opengl is initialized by enable_opengl(),
|
||||
/// and also prior to calling opengl_context_lost with and Err value.
|
||||
#[cfg(feature = "opengl")]
|
||||
fn opengl_initialize(
|
||||
&mut self,
|
||||
@ -156,6 +155,12 @@ pub trait WindowCallbacks: Any {
|
||||
context.map(|_| ())
|
||||
}
|
||||
|
||||
/// Called if the opengl context is lost
|
||||
#[cfg(feature = "opengl")]
|
||||
fn opengl_context_lost(&mut self, _window: &dyn WindowOps) -> anyhow::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Called to handle a key event.
|
||||
/// If your window didn't handle the event, you must return false.
|
||||
/// This is particularly important for eg: ALT keys on windows,
|
||||
|
@ -1,25 +1,101 @@
|
||||
#![cfg(feature = "opengl")]
|
||||
|
||||
use super::*;
|
||||
use glium::backend::Backend;
|
||||
use std::ffi::CStr;
|
||||
use std::io::Error as IoError;
|
||||
use std::os::raw::c_void;
|
||||
use std::ptr::{null, null_mut};
|
||||
use winapi::shared::windef::*;
|
||||
use winapi::um::libloaderapi::GetModuleHandleW;
|
||||
use winapi::um::wingdi::*;
|
||||
use winapi::um::winuser::*;
|
||||
|
||||
pub mod ffi {
|
||||
include!(concat!(env!("OUT_DIR"), "/wgl_bindings.rs"));
|
||||
}
|
||||
pub mod ffiextra {
|
||||
include!(concat!(env!("OUT_DIR"), "/wgl_extra_bindings.rs"));
|
||||
}
|
||||
|
||||
pub struct WglWrapper {
|
||||
struct WglWrapper {
|
||||
lib: libloading::Library,
|
||||
wgl: ffi::Wgl,
|
||||
ext: Option<ffiextra::Wgl>,
|
||||
}
|
||||
|
||||
type GetProcAddressFunc =
|
||||
unsafe extern "system" fn(*const std::os::raw::c_char) -> *const std::os::raw::c_void;
|
||||
|
||||
impl Drop for WglWrapper {
|
||||
fn drop(&mut self) {
|
||||
log::trace!("dropping WglWrapper and libloading {:?}", self.lib);
|
||||
}
|
||||
}
|
||||
|
||||
impl WglWrapper {
|
||||
pub fn create() -> anyhow::Result<Self> {
|
||||
fn load() -> anyhow::Result<Self> {
|
||||
let class_name = wide_string("wezterm wgl extension probing window");
|
||||
let h_inst = unsafe { GetModuleHandleW(null()) };
|
||||
let class = WNDCLASSW {
|
||||
style: CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
|
||||
lpfnWndProc: Some(DefWindowProcW),
|
||||
cbClsExtra: 0,
|
||||
cbWndExtra: 0,
|
||||
hInstance: h_inst,
|
||||
hIcon: null_mut(),
|
||||
hCursor: null_mut(),
|
||||
hbrBackground: null_mut(),
|
||||
lpszMenuName: null(),
|
||||
lpszClassName: class_name.as_ptr(),
|
||||
};
|
||||
|
||||
if unsafe { RegisterClassW(&class) } == 0 {
|
||||
let err = IoError::last_os_error();
|
||||
match err.raw_os_error() {
|
||||
Some(code)
|
||||
if code == winapi::shared::winerror::ERROR_CLASS_ALREADY_EXISTS as i32 => {}
|
||||
_ => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
|
||||
let hwnd = unsafe {
|
||||
CreateWindowExW(
|
||||
0,
|
||||
class_name.as_ptr(),
|
||||
class_name.as_ptr(),
|
||||
WS_OVERLAPPEDWINDOW,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
1024,
|
||||
768,
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
null_mut(),
|
||||
)
|
||||
};
|
||||
if hwnd.is_null() {
|
||||
let err = IoError::last_os_error();
|
||||
anyhow::bail!("CreateWindowExW: {}", err);
|
||||
}
|
||||
|
||||
let mut state = GlState::create_basic(WglWrapper::create()?, hwnd)?;
|
||||
|
||||
unsafe {
|
||||
state.make_current();
|
||||
}
|
||||
|
||||
let _ = state.wgl.as_mut().unwrap().load_ext();
|
||||
|
||||
state.make_not_current();
|
||||
|
||||
Ok(state.into_wrapper())
|
||||
}
|
||||
|
||||
fn create() -> anyhow::Result<Self> {
|
||||
let lib = libloading::Library::new("opengl32.dll")?;
|
||||
log::trace!("loading opengl32.dll as {:?}", lib);
|
||||
|
||||
let get_proc_address: libloading::Symbol<GetProcAddressFunc> =
|
||||
unsafe { lib.get(b"wglGetProcAddress\0")? };
|
||||
@ -30,20 +106,198 @@ impl WglWrapper {
|
||||
}
|
||||
unsafe { get_proc_address(sym_name.as_ptr()) }
|
||||
});
|
||||
Ok(Self { lib, wgl })
|
||||
Ok(Self {
|
||||
lib,
|
||||
wgl,
|
||||
ext: None,
|
||||
})
|
||||
}
|
||||
|
||||
fn load_ext(&mut self) -> anyhow::Result<()> {
|
||||
let get_proc_address: libloading::Symbol<GetProcAddressFunc> =
|
||||
unsafe { self.lib.get(b"wglGetProcAddress\0")? };
|
||||
|
||||
self.ext
|
||||
.replace(ffiextra::Wgl::load_with(|s: &'static str| {
|
||||
let sym_name = std::ffi::CString::new(s).expect("symbol to be cstring compatible");
|
||||
if let Ok(sym) = unsafe { self.lib.get(sym_name.as_bytes_with_nul()) } {
|
||||
return *sym;
|
||||
}
|
||||
unsafe { get_proc_address(sym_name.as_ptr()) }
|
||||
}));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GlState {
|
||||
wgl: WglWrapper,
|
||||
wgl: Option<WglWrapper>,
|
||||
hdc: HDC,
|
||||
rc: ffi::types::HGLRC,
|
||||
}
|
||||
|
||||
impl GlState {
|
||||
pub fn create(window: HWND) -> anyhow::Result<Self> {
|
||||
let wgl = WglWrapper::create()?;
|
||||
fn has_extension(extensions: &str, wanted: &str) -> bool {
|
||||
extensions.split(' ').find(|&ext| ext == wanted).is_some()
|
||||
}
|
||||
|
||||
impl GlState {
|
||||
fn into_wrapper(mut self) -> WglWrapper {
|
||||
self.delete();
|
||||
self.wgl.take().unwrap()
|
||||
}
|
||||
|
||||
pub fn create(window: HWND) -> anyhow::Result<Self> {
|
||||
let wgl = WglWrapper::load()?;
|
||||
|
||||
if let Some(ext) = wgl.ext.as_ref() {
|
||||
let hdc = unsafe { GetDC(window) };
|
||||
|
||||
fn cstr(data: *const i8) -> String {
|
||||
let data = unsafe { CStr::from_ptr(data).to_bytes().to_vec() };
|
||||
String::from_utf8(data).unwrap()
|
||||
}
|
||||
|
||||
let extensions = if ext.GetExtensionsStringARB.is_loaded() {
|
||||
unsafe { cstr(ext.GetExtensionsStringARB(hdc as *const _)) }
|
||||
} else if ext.GetExtensionsStringEXT.is_loaded() {
|
||||
unsafe { cstr(ext.GetExtensionsStringEXT()) }
|
||||
} else {
|
||||
"".to_owned()
|
||||
};
|
||||
log::trace!("opengl extensions: {:?}", extensions);
|
||||
|
||||
if has_extension(&extensions, "WGL_ARB_pixel_format") {
|
||||
return Self::create_ext(wgl, extensions, hdc);
|
||||
}
|
||||
}
|
||||
|
||||
Self::create_basic(wgl, window)
|
||||
}
|
||||
|
||||
fn create_ext(wgl: WglWrapper, extensions: String, hdc: HDC) -> anyhow::Result<Self> {
|
||||
use ffiextra::*;
|
||||
|
||||
let mut attribs: Vec<i32> = vec![
|
||||
DRAW_TO_WINDOW_ARB as i32,
|
||||
1,
|
||||
SUPPORT_OPENGL_ARB as i32,
|
||||
1,
|
||||
DOUBLE_BUFFER_ARB as i32,
|
||||
1,
|
||||
PIXEL_TYPE_ARB as i32,
|
||||
TYPE_RGBA_ARB as i32,
|
||||
ACCELERATION_ARB as i32,
|
||||
FULL_ACCELERATION_ARB as i32,
|
||||
COLOR_BITS_ARB as i32,
|
||||
32,
|
||||
ALPHA_BITS_ARB as i32,
|
||||
8,
|
||||
DEPTH_BITS_ARB as i32,
|
||||
24,
|
||||
STENCIL_BITS_ARB as i32,
|
||||
8,
|
||||
SAMPLE_BUFFERS_ARB as i32,
|
||||
1,
|
||||
SAMPLES_ARB as i32,
|
||||
4,
|
||||
];
|
||||
|
||||
if has_extension(&extensions, "WGL_ARB_framebuffer_sRGB") {
|
||||
attribs.push(FRAMEBUFFER_SRGB_CAPABLE_ARB as i32);
|
||||
attribs.push(1);
|
||||
} else if has_extension(&extensions, "WGL_EXT_framebuffer_sRGB") {
|
||||
attribs.push(FRAMEBUFFER_SRGB_CAPABLE_EXT as i32);
|
||||
attribs.push(1);
|
||||
}
|
||||
|
||||
attribs.push(0);
|
||||
|
||||
let mut format_id = 0;
|
||||
let mut num_formats = 0;
|
||||
|
||||
let res = unsafe {
|
||||
wgl.ext.as_ref().unwrap().ChoosePixelFormatARB(
|
||||
hdc as _,
|
||||
attribs.as_ptr(),
|
||||
null(),
|
||||
1,
|
||||
&mut format_id,
|
||||
&mut num_formats,
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
anyhow::bail!("ChoosePixelFormatARB returned 0");
|
||||
}
|
||||
|
||||
if num_formats == 0 {
|
||||
anyhow::bail!("ChoosePixelFormatARB returned 0 formats");
|
||||
}
|
||||
|
||||
let mut pfd: PIXELFORMATDESCRIPTOR = unsafe { std::mem::zeroed() };
|
||||
|
||||
let res = unsafe {
|
||||
DescribePixelFormat(
|
||||
hdc,
|
||||
format_id,
|
||||
std::mem::size_of::<PIXELFORMATDESCRIPTOR>() as _,
|
||||
&mut pfd,
|
||||
)
|
||||
};
|
||||
if res == 0 {
|
||||
anyhow::bail!(
|
||||
"DescribePixelFormat function failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
let res = unsafe { SetPixelFormat(hdc, format_id, &pfd) };
|
||||
if res == 0 {
|
||||
anyhow::bail!(
|
||||
"SetPixelFormat function failed: {}",
|
||||
std::io::Error::last_os_error()
|
||||
);
|
||||
}
|
||||
|
||||
let mut attribs = vec![
|
||||
CONTEXT_MAJOR_VERSION_ARB as i32,
|
||||
4,
|
||||
CONTEXT_MINOR_VERSION_ARB as i32,
|
||||
5,
|
||||
CONTEXT_PROFILE_MASK_ARB as i32,
|
||||
CONTEXT_CORE_PROFILE_BIT_ARB as i32,
|
||||
];
|
||||
|
||||
if has_extension(&extensions, "WGL_ARB_create_context_robustness") {
|
||||
attribs.push(CONTEXT_RESET_NOTIFICATION_STRATEGY_ARB as i32);
|
||||
attribs.push(LOSE_CONTEXT_ON_RESET_ARB as i32);
|
||||
attribs.push(CONTEXT_FLAGS_ARB as i32);
|
||||
attribs.push(CONTEXT_ROBUST_ACCESS_BIT_ARB as i32);
|
||||
}
|
||||
attribs.push(0);
|
||||
|
||||
let rc = unsafe {
|
||||
wgl.ext
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.CreateContextAttribsARB(hdc as _, null(), attribs.as_ptr())
|
||||
};
|
||||
|
||||
if rc.is_null() {
|
||||
anyhow::bail!("failed to make context");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
wgl.wgl.MakeCurrent(hdc as *mut _, rc);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
wgl: Some(wgl),
|
||||
rc,
|
||||
hdc,
|
||||
})
|
||||
}
|
||||
|
||||
fn create_basic(wgl: WglWrapper, window: HWND) -> anyhow::Result<Self> {
|
||||
let hdc = unsafe { GetDC(window) };
|
||||
|
||||
let pfd = PIXELFORMATDESCRIPTOR {
|
||||
@ -84,7 +338,34 @@ impl GlState {
|
||||
wgl.wgl.MakeCurrent(hdc as *mut _, rc);
|
||||
}
|
||||
|
||||
Ok(Self { wgl, rc, hdc })
|
||||
Ok(Self {
|
||||
wgl: Some(wgl),
|
||||
rc,
|
||||
hdc,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_not_current(&self) {
|
||||
if let Some(wgl) = self.wgl.as_ref() {
|
||||
unsafe {
|
||||
wgl.wgl.MakeCurrent(self.hdc as *mut _, std::ptr::null());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn delete(&mut self) {
|
||||
self.make_not_current();
|
||||
if let Some(wgl) = self.wgl.as_ref() {
|
||||
unsafe {
|
||||
wgl.wgl.DeleteContext(self.rc);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GlState {
|
||||
fn drop(&mut self) {
|
||||
self.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,11 +379,22 @@ unsafe impl glium::backend::Backend for GlState {
|
||||
|
||||
unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
|
||||
let sym_name = std::ffi::CString::new(symbol).expect("symbol to be cstring compatible");
|
||||
if let Ok(sym) = self.wgl.lib.get(sym_name.as_bytes_with_nul()) {
|
||||
if let Ok(sym) = self
|
||||
.wgl
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.lib
|
||||
.get(sym_name.as_bytes_with_nul())
|
||||
{
|
||||
//eprintln!("{} -> {:?}", symbol, sym);
|
||||
return *sym;
|
||||
}
|
||||
let res = self.wgl.wgl.GetProcAddress(sym_name.as_ptr()) as *const c_void;
|
||||
let res = self
|
||||
.wgl
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.wgl
|
||||
.GetProcAddress(sym_name.as_ptr()) as *const c_void;
|
||||
// eprintln!("{} -> {:?}", symbol, res);
|
||||
res
|
||||
}
|
||||
@ -112,10 +404,14 @@ unsafe impl glium::backend::Backend for GlState {
|
||||
}
|
||||
|
||||
fn is_current(&self) -> bool {
|
||||
unsafe { self.wgl.wgl.GetCurrentContext() == self.rc }
|
||||
unsafe { self.wgl.as_ref().unwrap().wgl.GetCurrentContext() == self.rc }
|
||||
}
|
||||
|
||||
unsafe fn make_current(&self) {
|
||||
self.wgl.wgl.MakeCurrent(self.hdc as *mut _, self.rc);
|
||||
self.wgl
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.wgl
|
||||
.MakeCurrent(self.hdc as *mut _, self.rc);
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,40 @@ fn take_rc_from_pointer(lparam: LPVOID) -> Rc<RefCell<WindowInner>> {
|
||||
unsafe { Rc::from_raw(std::mem::transmute(lparam)) }
|
||||
}
|
||||
|
||||
impl WindowInner {
|
||||
#[cfg(feature = "opengl")]
|
||||
fn enable_opengl(&mut self) -> anyhow::Result<()> {
|
||||
let window = Window(self.hwnd);
|
||||
|
||||
let gl_state = super::wgl::GlState::create(self.hwnd.0)
|
||||
.map(Rc::new)
|
||||
.and_then(|state| unsafe {
|
||||
Ok(glium::backend::Context::new(
|
||||
Rc::clone(&state),
|
||||
true,
|
||||
if cfg!(debug_assertions) {
|
||||
glium::debug::DebugCallbackBehavior::DebugMessageOnError
|
||||
} else {
|
||||
glium::debug::DebugCallbackBehavior::Ignore
|
||||
},
|
||||
)?)
|
||||
});
|
||||
|
||||
self.gl_state = gl_state.as_ref().map(Rc::clone).ok();
|
||||
|
||||
if let Err(err) = self
|
||||
.callbacks
|
||||
.borrow_mut()
|
||||
.opengl_initialize(&window, gl_state)
|
||||
{
|
||||
self.gl_state.take();
|
||||
Err(err)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Window {
|
||||
fn from_hwnd(hwnd: HWND) -> Self {
|
||||
Self(HWindow(hwnd))
|
||||
@ -395,30 +429,7 @@ impl WindowOps for Window {
|
||||
|
||||
#[cfg(feature = "opengl")]
|
||||
fn enable_opengl(&self) -> promise::Future<()> {
|
||||
Connection::with_window_inner(self.0, move |inner| {
|
||||
let window = Window(inner.hwnd);
|
||||
|
||||
let gl_state = super::wgl::GlState::create(inner.hwnd.0)
|
||||
.map(Rc::new)
|
||||
.and_then(|state| unsafe {
|
||||
Ok(glium::backend::Context::new(
|
||||
Rc::clone(&state),
|
||||
true,
|
||||
if cfg!(debug_assertions) {
|
||||
glium::debug::DebugCallbackBehavior::DebugMessageOnError
|
||||
} else {
|
||||
glium::debug::DebugCallbackBehavior::Ignore
|
||||
},
|
||||
)?)
|
||||
});
|
||||
|
||||
inner.gl_state = gl_state.as_ref().map(Rc::clone).ok();
|
||||
|
||||
inner
|
||||
.callbacks
|
||||
.borrow_mut()
|
||||
.opengl_initialize(&window, gl_state)
|
||||
})
|
||||
Connection::with_window_inner(self.0, move |inner| inner.enable_opengl())
|
||||
}
|
||||
|
||||
fn get_clipboard(&self, _clipboard: Clipboard) -> Future<String> {
|
||||
@ -580,9 +591,9 @@ unsafe fn wm_kill_focus(
|
||||
None
|
||||
}
|
||||
|
||||
unsafe fn wm_paint(hwnd: HWND, _msg: UINT, _wparam: WPARAM, _lparam: LPARAM) -> Option<LRESULT> {
|
||||
unsafe fn wm_paint(hwnd: HWND, msg: UINT, wparam: WPARAM, lparam: LPARAM) -> Option<LRESULT> {
|
||||
if let Some(inner) = rc_from_hwnd(hwnd) {
|
||||
let inner = inner.borrow();
|
||||
let mut inner = inner.borrow_mut();
|
||||
|
||||
let mut ps = PAINTSTRUCT {
|
||||
fErase: 0,
|
||||
@ -612,6 +623,24 @@ unsafe fn wm_paint(hwnd: HWND, _msg: UINT, _wparam: WPARAM, _lparam: LPARAM) ->
|
||||
#[cfg(feature = "opengl")]
|
||||
{
|
||||
if let Some(gl_context) = inner.gl_state.as_ref() {
|
||||
if gl_context.is_context_lost() {
|
||||
log::error!("opengl context was lost; should reinit");
|
||||
|
||||
drop(inner.gl_state.take());
|
||||
let _ = inner.callbacks.borrow_mut().opengl_initialize(
|
||||
&Window(inner.hwnd),
|
||||
Err(anyhow::anyhow!("opengl context lost")),
|
||||
);
|
||||
|
||||
let _ = inner
|
||||
.callbacks
|
||||
.borrow_mut()
|
||||
.opengl_context_lost(&Window(inner.hwnd));
|
||||
inner.gl_state.take();
|
||||
drop(inner);
|
||||
return wm_paint(hwnd, msg, wparam, lparam);
|
||||
}
|
||||
|
||||
let mut frame =
|
||||
glium::Frame::new(Rc::clone(&gl_context), (width as u32, height as u32));
|
||||
|
||||
@ -1243,6 +1272,9 @@ unsafe extern "system" fn wnd_proc(
|
||||
.unwrap_or_else(|| DefWindowProcW(hwnd, msg, wparam, lparam))
|
||||
}) {
|
||||
Ok(result) => result,
|
||||
Err(_) => std::process::exit(1),
|
||||
Err(e) => {
|
||||
log::error!("caught {:?}", e);
|
||||
std::process::exit(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user