polish glutin upgrade with glutin-winit crate (#2526)
* use glutin-winit for glow context creation * added some tracing for easier debugging of glutin problems * fmt * add more debug logs * more tracing * fallback egl instead of prefer egl * update pure glow example to use glutin_winit * add more logging. ignore vsync option if not supported * cranky lint * add some logging for easier debugging * drop window after glutin surface * small changes based on pr review * build fix --------- Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
e8b9e706ca
commit
be9b5a3641
8 changed files with 353 additions and 178 deletions
14
Cargo.lock
generated
14
Cargo.lock
generated
|
@ -1278,6 +1278,7 @@ dependencies = [
|
|||
"egui_glow",
|
||||
"glow 0.11.2",
|
||||
"glutin",
|
||||
"glutin-winit",
|
||||
"image",
|
||||
"js-sys",
|
||||
"percent-encoding",
|
||||
|
@ -1404,6 +1405,7 @@ dependencies = [
|
|||
"egui-winit",
|
||||
"glow 0.11.2",
|
||||
"glutin",
|
||||
"glutin-winit",
|
||||
"memoffset",
|
||||
"puffin",
|
||||
"raw-window-handle",
|
||||
|
@ -1817,6 +1819,18 @@ dependencies = [
|
|||
"x11-dl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutin-winit"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "629a873fc04062830bfe8f97c03773bcd7b371e23bcc465d0a61448cd1588fa4"
|
||||
dependencies = [
|
||||
"cfg_aliases",
|
||||
"glutin",
|
||||
"raw-window-handle",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glutin_egl_sys"
|
||||
version = "0.3.1"
|
||||
|
|
|
@ -36,7 +36,7 @@ dark-light = ["dep:dark-light"]
|
|||
default_fonts = ["egui/default_fonts"]
|
||||
|
||||
## Use [`glow`](https://github.com/grovesNL/glow) for painting, via [`egui_glow`](https://github.com/emilk/egui/tree/master/crates/egui_glow).
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin"]
|
||||
glow = ["dep:glow", "dep:egui_glow", "dep:glutin", "dep:glutin-winit"]
|
||||
|
||||
## Enable saving app state to disk.
|
||||
persistence = [
|
||||
|
@ -104,14 +104,8 @@ pollster = { version = "0.2", optional = true } # needed for wgpu
|
|||
|
||||
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
|
||||
# this can be done at the same time we expose x11/wayland features of winit crate.
|
||||
glutin = { version = "0.30.0", optional = true, es = [
|
||||
"egl",
|
||||
"glx",
|
||||
"x11",
|
||||
"wayland",
|
||||
"wgl",
|
||||
] }
|
||||
|
||||
glutin = { version = "0.30", optional = true }
|
||||
glutin-winit = { version = "0.3.0", optional = true }
|
||||
image = { version = "0.24", optional = true, default-features = false, features = [
|
||||
"png",
|
||||
] }
|
||||
|
|
|
@ -221,8 +221,8 @@ pub enum Error {
|
|||
Glutin(#[from] glutin::error::Error),
|
||||
|
||||
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
|
||||
#[error("Found no glutin configs matching the template: {0:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate),
|
||||
#[error("Found no glutin configs matching the template: {0:?}. error: {1:?}")]
|
||||
NoGlutinConfigs(glutin::config::ConfigTemplate, Box<dyn std::error::Error>),
|
||||
|
||||
#[cfg(feature = "wgpu")]
|
||||
#[error("WGPU error: {0}")]
|
||||
|
|
|
@ -64,15 +64,13 @@ pub fn read_window_info(
|
|||
monitor_size,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn build_window<E>(
|
||||
pub fn window_builder<E>(
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: Option<WindowSettings>,
|
||||
) -> Result<winit::window::Window, winit::error::OsError> {
|
||||
) -> winit::window::WindowBuilder {
|
||||
let epi::NativeOptions {
|
||||
always_on_top,
|
||||
maximized,
|
||||
decorated,
|
||||
fullscreen,
|
||||
|
@ -159,11 +157,19 @@ pub fn build_window<E>(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
window_builder
|
||||
}
|
||||
pub fn build_window<E>(
|
||||
event_loop: &EventLoopWindowTarget<E>,
|
||||
title: &str,
|
||||
native_options: &epi::NativeOptions,
|
||||
window_settings: Option<WindowSettings>,
|
||||
) -> Result<winit::window::Window, winit::error::OsError> {
|
||||
let window_builder = window_builder(event_loop, title, native_options, window_settings);
|
||||
let window = window_builder.build(event_loop)?;
|
||||
|
||||
use winit::window::WindowLevel;
|
||||
window.set_window_level(if *always_on_top {
|
||||
window.set_window_level(if native_options.always_on_top {
|
||||
WindowLevel::AlwaysOnTop
|
||||
} else {
|
||||
WindowLevel::Normal
|
||||
|
|
|
@ -152,8 +152,8 @@ fn run_and_return(
|
|||
event => match winit_app.on_event(event_loop, event) {
|
||||
Ok(event_result) => event_result,
|
||||
Err(err) => {
|
||||
tracing::error!("Exiting because of error: {err:?} on event {event:?}");
|
||||
returned_result = Err(err);
|
||||
tracing::debug!("Exiting because of an error");
|
||||
EventResult::Exit
|
||||
}
|
||||
},
|
||||
|
@ -297,6 +297,12 @@ mod glow_integration {
|
|||
use std::sync::Arc;
|
||||
|
||||
use egui::NumExt as _;
|
||||
use glutin::{
|
||||
display::GetGlDisplay,
|
||||
prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext},
|
||||
surface::GlSurface,
|
||||
};
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
@ -321,132 +327,257 @@ mod glow_integration {
|
|||
painter: egui_glow::Painter,
|
||||
integration: epi_integration::EpiIntegration,
|
||||
app: Box<dyn epi::App>,
|
||||
|
||||
// Conceptually this will be split out eventually so that the rest of the state
|
||||
// can be persistent.
|
||||
gl_window: GlutinWindowContext,
|
||||
}
|
||||
|
||||
/// This struct will contain both persistent and temporary glutin state.
|
||||
///
|
||||
/// Platform Quirks:
|
||||
/// * Microsoft Windows: requires that we create a window before opengl context.
|
||||
/// * Android: window and surface should be destroyed when we receive a suspend event. recreate on resume event.
|
||||
///
|
||||
/// winit guarantees that we will get a Resumed event on startup on all platforms.
|
||||
/// * Before Resumed event: `gl_config`, `gl_context` can be created at any time. on windows, a window must be created to get `gl_context`.
|
||||
/// * Resumed: `gl_surface` will be created here. `window` will be re-created here for android.
|
||||
/// * Suspended: on android, we drop window + surface. on other platforms, we don't get Suspended event.
|
||||
///
|
||||
/// The setup is divided between the `new` fn and `on_resume` fn. we can just assume that `on_resume` is a continuation of
|
||||
/// `new` fn on all platforms. only on android, do we get multiple resumed events because app can be suspended.
|
||||
struct GlutinWindowContext {
|
||||
window: winit::window::Window,
|
||||
gl_context: glutin::context::PossiblyCurrentContext,
|
||||
gl_display: glutin::display::Display,
|
||||
gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
|
||||
builder: winit::window::WindowBuilder,
|
||||
swap_interval: glutin::surface::SwapInterval,
|
||||
gl_config: glutin::config::Config,
|
||||
current_gl_context: Option<glutin::context::PossiblyCurrentContext>,
|
||||
gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
|
||||
not_current_gl_context: Option<glutin::context::NotCurrentContext>,
|
||||
window: Option<winit::window::Window>,
|
||||
}
|
||||
|
||||
impl GlutinWindowContext {
|
||||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
/// There is a lot of complexity with opengl creation, so prefer extensivve logging to get all the help we can to debug issues.
|
||||
///
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(
|
||||
winit_window: winit::window::Window,
|
||||
winit_window_builder: winit::window::WindowBuilder,
|
||||
native_options: &epi::NativeOptions,
|
||||
event_loop: &EventLoopWindowTarget<UserEvent>,
|
||||
) -> Result<Self> {
|
||||
use glutin::prelude::*;
|
||||
use raw_window_handle::*;
|
||||
// convert native options to glutin options
|
||||
let hardware_acceleration = match native_options.hardware_acceleration {
|
||||
crate::HardwareAcceleration::Required => Some(true),
|
||||
crate::HardwareAcceleration::Preferred => None,
|
||||
crate::HardwareAcceleration::Off => Some(false),
|
||||
};
|
||||
|
||||
let raw_display_handle = winit_window.raw_display_handle();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
|
||||
// EGL is crossplatform and the official khronos way
|
||||
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||
// TODO: check whether we can expose these options as "features", so that users can select the relevant backend they want.
|
||||
|
||||
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||
#[cfg(target_os = "windows")]
|
||||
let preference =
|
||||
glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||
// try egl and fallback to x11 glx
|
||||
#[cfg(target_os = "linux")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||
winit::platform::x11::register_xlib_error_hook,
|
||||
));
|
||||
#[cfg(target_os = "macos")]
|
||||
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference)?;
|
||||
let swap_interval = if native_options.vsync {
|
||||
glutin::surface::SwapInterval::Wait(std::num::NonZeroU32::new(1).unwrap())
|
||||
} else {
|
||||
glutin::surface::SwapInterval::DontWait
|
||||
};
|
||||
|
||||
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||
/* opengl setup flow goes like this:
|
||||
1. we create a configuration for opengl "Display" / "Config" creation
|
||||
2. choose between special extensions like glx or egl or wgl and use them to create config/display
|
||||
3. opengl context configuration
|
||||
4. opengl context creation
|
||||
*/
|
||||
// start building config for gl display
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(hardware_acceleration)
|
||||
.with_depth_size(native_options.depth_buffer);
|
||||
.with_depth_size(native_options.depth_buffer)
|
||||
.with_stencil_size(native_options.stencil_buffer)
|
||||
.with_transparency(native_options.transparent);
|
||||
// we don't know if multi sampling option is set. so, check if its more than 0.
|
||||
let config_template = if native_options.multisampling > 0 {
|
||||
config_template.with_multisampling(
|
||||
let config_template_builder = if native_options.multisampling > 0 {
|
||||
config_template_builder.with_multisampling(
|
||||
native_options
|
||||
.multisampling
|
||||
.try_into()
|
||||
.expect("failed to fit multisamples into u8"),
|
||||
.expect("failed to fit multisamples option of native_options into u8"),
|
||||
)
|
||||
} else {
|
||||
config_template
|
||||
config_template_builder
|
||||
};
|
||||
let config_template = config_template
|
||||
.with_stencil_size(native_options.stencil_buffer)
|
||||
.with_transparency(native_options.transparent)
|
||||
.compatible_with_native_window(raw_window_handle)
|
||||
.build();
|
||||
// finds all valid configurations supported by this display that match the config_template
|
||||
// this is where we will try to get a "fallback" config if we are okay with ignoring some native
|
||||
// options required by user like multi sampling, srgb, transparency etc..
|
||||
// TODO: need to figure out a good fallback config template
|
||||
let config = gl_display
|
||||
.find_configs(config_template.clone())?
|
||||
.next()
|
||||
.ok_or(crate::Error::NoGlutinConfigs(config_template))?;
|
||||
|
||||
tracing::debug!(
|
||||
"trying to create glutin Display with config: {:?}",
|
||||
&config_template_builder
|
||||
);
|
||||
// create gl display. this may probably create a window too on most platforms. definitely on `MS windows`. never on android.
|
||||
let (window, gl_config) = glutin_winit::DisplayBuilder::new()
|
||||
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
|
||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(winit_window_builder.clone()))
|
||||
.build(
|
||||
event_loop,
|
||||
config_template_builder.clone(),
|
||||
|mut config_iterator| {
|
||||
let config = config_iterator.next().expect(
|
||||
"failed to find a matching configuration for creating glutin config",
|
||||
);
|
||||
tracing::debug!(
|
||||
"using the first config from config picker closure. config: {:?}",
|
||||
&config
|
||||
);
|
||||
config
|
||||
},
|
||||
)
|
||||
.map_err(|e| crate::Error::NoGlutinConfigs(config_template_builder.build(), e))?;
|
||||
|
||||
let gl_display = gl_config.display();
|
||||
tracing::debug!(
|
||||
"successfully created GL Display with version: {} and supported features: {:?}",
|
||||
gl_display.version_string(),
|
||||
gl_display.supported_features()
|
||||
);
|
||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
||||
tracing::debug!(
|
||||
"creating gl context using raw window handle: {:?}",
|
||||
raw_window_handle
|
||||
);
|
||||
|
||||
// create gl context. if core context cannot be created, try gl es context as fallback.
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||
// for surface creation.
|
||||
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||
.build(raw_window_handle);
|
||||
let gl_context = match gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &context_attributes)
|
||||
{
|
||||
Ok(it) => it,
|
||||
Err(err) => {
|
||||
tracing::warn!("failed to create context using default context attributes {context_attributes:?} due to error: {err}");
|
||||
tracing::debug!("retrying with fallback context attributes: {fallback_context_attributes:?}");
|
||||
gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &fallback_context_attributes)?
|
||||
}
|
||||
};
|
||||
let not_current_gl_context = Some(gl_context);
|
||||
// the fun part with opengl gl is that we never know whether there is an error. the context creation might have failed, but
|
||||
// it could keep working until we try to make surface current or swap buffers or something else. future glutin improvements might
|
||||
// help us start from scratch again if we fail context creation and go back to preferEgl or try with different config etc..
|
||||
// https://github.com/emilk/egui/pull/2541#issuecomment-1370767582
|
||||
Ok(GlutinWindowContext {
|
||||
builder: winit_window_builder,
|
||||
swap_interval,
|
||||
gl_config,
|
||||
current_gl_context: None,
|
||||
window,
|
||||
gl_surface: None,
|
||||
not_current_gl_context,
|
||||
})
|
||||
}
|
||||
/// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime.
|
||||
/// roughly,
|
||||
/// 1. check if window already exists. otherwise, create one now.
|
||||
/// 2. create attributes for surface creation.
|
||||
/// 3. create surface.
|
||||
/// 4. make surface and context current.
|
||||
///
|
||||
/// we presently assume that we will
|
||||
#[allow(unsafe_code)]
|
||||
fn on_resume(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
|
||||
if self.gl_surface.is_some() {
|
||||
tracing::warn!(
|
||||
"on_resume called even thought we already have a surface. early return"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
tracing::debug!("running on_resume fn.");
|
||||
// make sure we have a window or create one.
|
||||
let window = self.window.take().unwrap_or_else(|| {
|
||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
||||
glutin_winit::finalize_window(event_loop, self.builder.clone(), &self.gl_config)
|
||||
.expect("failed to finalize glutin window")
|
||||
});
|
||||
// surface attributes
|
||||
let (width, height): (u32, u32) = window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(raw_window_handle, width, height);
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display.create_context(&config, &context_attributes)?;
|
||||
|
||||
let gl_surface = gl_display.create_window_surface(&config, &surface_attributes)?;
|
||||
let gl_context = gl_context.make_current(&gl_surface)?;
|
||||
gl_surface.set_swap_interval(&gl_context, swap_interval)?;
|
||||
Ok(GlutinWindowContext {
|
||||
window: winit_window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
})
|
||||
.build(window.raw_window_handle(), width, height);
|
||||
tracing::debug!(
|
||||
"creating surface with attributes: {:?}",
|
||||
&surface_attributes
|
||||
);
|
||||
// create surface
|
||||
let gl_surface = unsafe {
|
||||
self.gl_config
|
||||
.display()
|
||||
.create_window_surface(&self.gl_config, &surface_attributes)?
|
||||
};
|
||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
||||
// make surface and context current.
|
||||
let not_current_gl_context = self
|
||||
.not_current_gl_context
|
||||
.take()
|
||||
.expect("failed to get not current context after resume event. impossible!");
|
||||
let current_gl_context = not_current_gl_context.make_current(&gl_surface)?;
|
||||
// try setting swap interval. but its not absolutely necessary, so don't panic on failure.
|
||||
tracing::debug!("made context current. setting swap interval for surface");
|
||||
if let Err(e) = gl_surface.set_swap_interval(¤t_gl_context, self.swap_interval) {
|
||||
tracing::error!("failed to set swap interval due to error: {e:?}");
|
||||
}
|
||||
// we will reach this point only once in most platforms except android.
|
||||
// create window/surface/make context current once and just use them forever.
|
||||
self.gl_surface = Some(gl_surface);
|
||||
self.current_gl_context = Some(current_gl_context);
|
||||
self.window = Some(window);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// only applies for android. but we basically drop surface + window and make context not current
|
||||
fn on_suspend(&mut self) -> Result<()> {
|
||||
tracing::debug!("received suspend event. dropping window and surface");
|
||||
self.gl_surface.take();
|
||||
self.window.take();
|
||||
if let Some(current) = self.current_gl_context.take() {
|
||||
tracing::debug!("context is current, so making it non-current");
|
||||
self.not_current_gl_context = Some(current.make_not_current()?);
|
||||
} else {
|
||||
tracing::debug!(
|
||||
"context is already not current??? could be duplicate suspend event"
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn window(&self) -> &winit::window::Window {
|
||||
&self.window
|
||||
self.window.as_ref().expect("winit window doesn't exist")
|
||||
}
|
||||
|
||||
fn resize(&self, physical_size: winit::dpi::PhysicalSize<u32>) {
|
||||
use glutin::surface::GlSurface;
|
||||
let width = std::num::NonZeroU32::new(physical_size.width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(physical_size.height.at_least(1)).unwrap();
|
||||
self.gl_surface.resize(&self.gl_context, width, height);
|
||||
self.gl_surface
|
||||
.as_ref()
|
||||
.expect("failed to get surface to resize")
|
||||
.resize(
|
||||
self.current_gl_context
|
||||
.as_ref()
|
||||
.expect("failed to get current context to resize surface"),
|
||||
width,
|
||||
height,
|
||||
);
|
||||
}
|
||||
|
||||
fn swap_buffers(&self) -> glutin::error::Result<()> {
|
||||
use glutin::surface::GlSurface;
|
||||
self.gl_surface.swap_buffers(&self.gl_context)
|
||||
self.gl_surface
|
||||
.as_ref()
|
||||
.expect("failed to get surface to swap buffers")
|
||||
.swap_buffers(
|
||||
self.current_gl_context
|
||||
.as_ref()
|
||||
.expect("failed to get current context to swap buffers"),
|
||||
)
|
||||
}
|
||||
|
||||
fn get_proc_address(&self, addr: &std::ffi::CStr) -> *const std::ffi::c_void {
|
||||
use glutin::display::GlDisplay;
|
||||
self.gl_display.get_proc_address(addr)
|
||||
self.gl_config.display().get_proc_address(addr)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -494,11 +625,12 @@ mod glow_integration {
|
|||
|
||||
let window_settings = epi_integration::load_window_settings(storage);
|
||||
|
||||
let winit_window =
|
||||
epi_integration::build_window(event_loop, title, native_options, window_settings)?;
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context =
|
||||
unsafe { GlutinWindowContext::new(winit_window, native_options)? };
|
||||
let winit_window_builder =
|
||||
epi_integration::window_builder(event_loop, title, native_options, window_settings);
|
||||
let mut glutin_window_context = unsafe {
|
||||
GlutinWindowContext::new(winit_window_builder, native_options, event_loop)?
|
||||
};
|
||||
glutin_window_context.on_resume(event_loop)?;
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
|
@ -727,26 +859,24 @@ mod glow_integration {
|
|||
) -> Result<EventResult> {
|
||||
Ok(match event {
|
||||
winit::event::Event::Resumed => {
|
||||
// first resume event.
|
||||
// we can actually move this outside of event loop.
|
||||
// and just run the on_resume fn of gl_window
|
||||
if self.running.is_none() {
|
||||
self.init_run_state(event_loop)?;
|
||||
} else {
|
||||
// not the first resume event. create whatever you need.
|
||||
self.running
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.gl_window
|
||||
.on_resume(event_loop)?;
|
||||
}
|
||||
EventResult::RepaintNow
|
||||
}
|
||||
winit::event::Event::Suspended => {
|
||||
#[cfg(target_os = "android")]
|
||||
{
|
||||
tracing::error!("Suspended app can't destroy Window surface state with current Egui Glow backend (undefined behaviour)");
|
||||
// Instead of destroying everything which we _know_ we can't re-create
|
||||
// we instead currently just try our luck with not destroying anything.
|
||||
//
|
||||
// When the application resumes then it will get a new `SurfaceView` but
|
||||
// we have no practical way currently of creating a new EGL surface
|
||||
// via the Glutin API while keeping the GL context and the rest of
|
||||
// our app state. This will likely result in a black screen or
|
||||
// frozen screen.
|
||||
//
|
||||
//self.running = None;
|
||||
}
|
||||
self.running.as_mut().unwrap().gl_window.on_suspend()?;
|
||||
|
||||
EventResult::Wait
|
||||
}
|
||||
|
||||
|
|
|
@ -69,8 +69,9 @@ wasm-bindgen = { version = "0.2" }
|
|||
|
||||
|
||||
[dev-dependencies]
|
||||
glutin = "0.30.2" # examples/pure_glow
|
||||
glutin = "0.30" # examples/pure_glow
|
||||
raw-window-handle = "0.5.0"
|
||||
glutin-winit = "0.3.0"
|
||||
|
||||
|
||||
[[example]]
|
||||
|
|
|
@ -17,66 +17,91 @@ impl GlutinWindowContext {
|
|||
// refactor this function to use `glutin-winit` crate eventually.
|
||||
// preferably add android support at the same time.
|
||||
#[allow(unsafe_code)]
|
||||
unsafe fn new(winit_window: winit::window::Window) -> Self {
|
||||
use glutin::prelude::*;
|
||||
use raw_window_handle::*;
|
||||
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<()>) -> Self {
|
||||
use egui::NumExt;
|
||||
use glutin::context::NotCurrentGlContextSurfaceAccessor;
|
||||
use glutin::display::GetGlDisplay;
|
||||
use glutin::display::GlDisplay;
|
||||
use glutin::prelude::GlSurface;
|
||||
use raw_window_handle::HasRawWindowHandle;
|
||||
let winit_window_builder = winit::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example") // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.with_visible(false);
|
||||
|
||||
let raw_display_handle = winit_window.raw_display_handle();
|
||||
let raw_window_handle = winit_window.raw_window_handle();
|
||||
|
||||
// EGL is crossplatform and the official khronos way
|
||||
// but sometimes platforms/drivers may not have it, so we use back up options where possible.
|
||||
|
||||
// try egl and fallback to windows wgl. Windows is the only platform that *requires* window handle to create display.
|
||||
#[cfg(target_os = "windows")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenWgl(Some(raw_window_handle));
|
||||
// try egl and fallback to x11 glx
|
||||
#[cfg(target_os = "linux")]
|
||||
let preference = glutin::display::DisplayApiPreference::EglThenGlx(Box::new(
|
||||
winit::platform::x11::register_xlib_error_hook,
|
||||
));
|
||||
#[cfg(target_os = "macos")]
|
||||
let preference = glutin::display::DisplayApiPreference::Cgl;
|
||||
#[cfg(target_os = "android")]
|
||||
let preference = glutin::display::DisplayApiPreference::Egl;
|
||||
|
||||
let gl_display = glutin::display::Display::new(raw_display_handle, preference).unwrap();
|
||||
|
||||
let config_template = glutin::config::ConfigTemplateBuilder::new()
|
||||
let config_template_builder = glutin::config::ConfigTemplateBuilder::new()
|
||||
.prefer_hardware_accelerated(None)
|
||||
.with_depth_size(0)
|
||||
.with_stencil_size(0)
|
||||
.with_transparency(false)
|
||||
.compatible_with_native_window(raw_window_handle)
|
||||
.build();
|
||||
.with_transparency(false);
|
||||
|
||||
let config = gl_display
|
||||
.find_configs(config_template)
|
||||
.unwrap()
|
||||
.next()
|
||||
.unwrap();
|
||||
tracing::debug!("trying to get gl_config");
|
||||
let (mut window, gl_config) =
|
||||
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
|
||||
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
|
||||
.with_window_builder(Some(winit_window_builder.clone()))
|
||||
.build(
|
||||
event_loop,
|
||||
config_template_builder,
|
||||
|mut config_iterator| {
|
||||
config_iterator.next().expect(
|
||||
"failed to find a matching configuration for creating glutin config",
|
||||
)
|
||||
},
|
||||
)
|
||||
.expect("failed to create gl_config");
|
||||
let gl_display = gl_config.display();
|
||||
tracing::debug!("found gl_config: {:?}", &gl_config);
|
||||
|
||||
let raw_window_handle = window.as_ref().map(|w| w.raw_window_handle());
|
||||
tracing::debug!("raw window handle: {:?}", raw_window_handle);
|
||||
let context_attributes =
|
||||
glutin::context::ContextAttributesBuilder::new().build(Some(raw_window_handle));
|
||||
// for surface creation.
|
||||
let (width, height): (u32, u32) = winit_window.inner_size().into();
|
||||
glutin::context::ContextAttributesBuilder::new().build(raw_window_handle);
|
||||
// by default, glutin will try to create a core opengl context. but, if it is not available, try to create a gl-es context using this fallback attributes
|
||||
let fallback_context_attributes = glutin::context::ContextAttributesBuilder::new()
|
||||
.with_context_api(glutin::context::ContextApi::Gles(None))
|
||||
.build(raw_window_handle);
|
||||
let not_current_gl_context = unsafe {
|
||||
gl_display
|
||||
.create_context(&gl_config, &context_attributes)
|
||||
.unwrap_or_else(|_| {
|
||||
tracing::debug!("failed to create gl_context with attributes: {:?}. retrying with fallback context attributes: {:?}",
|
||||
&context_attributes,
|
||||
&fallback_context_attributes);
|
||||
gl_config
|
||||
.display()
|
||||
.create_context(&gl_config, &fallback_context_attributes)
|
||||
.expect("failed to create context even with fallback attributes")
|
||||
})
|
||||
};
|
||||
|
||||
// this is where the window is created, if it has not been created while searching for suitable gl_config
|
||||
let window = window.take().unwrap_or_else(|| {
|
||||
tracing::debug!("window doesn't exist yet. creating one now with finalize_window");
|
||||
glutin_winit::finalize_window(event_loop, winit_window_builder.clone(), &gl_config)
|
||||
.expect("failed to finalize glutin window")
|
||||
});
|
||||
let (width, height): (u32, u32) = window.inner_size().into();
|
||||
let width = std::num::NonZeroU32::new(width.at_least(1)).unwrap();
|
||||
let height = std::num::NonZeroU32::new(height.at_least(1)).unwrap();
|
||||
let surface_attributes =
|
||||
glutin::surface::SurfaceAttributesBuilder::<glutin::surface::WindowSurface>::new()
|
||||
.build(
|
||||
raw_window_handle,
|
||||
std::num::NonZeroU32::new(width).unwrap(),
|
||||
std::num::NonZeroU32::new(height).unwrap(),
|
||||
);
|
||||
// start creating the gl objects
|
||||
let gl_context = gl_display
|
||||
.create_context(&config, &context_attributes)
|
||||
.unwrap();
|
||||
|
||||
let gl_surface = gl_display
|
||||
.create_window_surface(&config, &surface_attributes)
|
||||
.unwrap();
|
||||
|
||||
let gl_context = gl_context.make_current(&gl_surface).unwrap();
|
||||
.build(window.raw_window_handle(), width, height);
|
||||
tracing::debug!(
|
||||
"creating surface with attributes: {:?}",
|
||||
&surface_attributes
|
||||
);
|
||||
let gl_surface = unsafe {
|
||||
gl_display
|
||||
.create_window_surface(&gl_config, &surface_attributes)
|
||||
.unwrap()
|
||||
};
|
||||
tracing::debug!("surface created successfully: {gl_surface:?}.making context current");
|
||||
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
|
||||
|
||||
gl_surface
|
||||
.set_swap_interval(
|
||||
|
@ -86,7 +111,7 @@ impl GlutinWindowContext {
|
|||
.unwrap();
|
||||
|
||||
GlutinWindowContext {
|
||||
window: winit_window,
|
||||
window,
|
||||
gl_context,
|
||||
gl_display,
|
||||
gl_surface,
|
||||
|
@ -216,19 +241,7 @@ fn main() {
|
|||
fn create_display(
|
||||
event_loop: &winit::event_loop::EventLoopWindowTarget<()>,
|
||||
) -> (GlutinWindowContext, glow::Context) {
|
||||
let winit_window = winit::window::WindowBuilder::new()
|
||||
.with_resizable(true)
|
||||
.with_inner_size(winit::dpi::LogicalSize {
|
||||
width: 800.0,
|
||||
height: 600.0,
|
||||
})
|
||||
.with_title("egui_glow example")
|
||||
.with_visible(false) // Keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
|
||||
.build(event_loop)
|
||||
.unwrap();
|
||||
|
||||
// a lot of the code below has been lifted from glutin example in their repo.
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(winit_window) };
|
||||
let glutin_window_context = unsafe { GlutinWindowContext::new(event_loop) };
|
||||
let gl = unsafe {
|
||||
glow::Context::from_loader_function(|s| {
|
||||
let s = std::ffi::CString::new(s)
|
||||
|
|
|
@ -106,6 +106,23 @@ impl Painter {
|
|||
crate::profile_function!();
|
||||
crate::check_for_gl_error_even_in_release!(&gl, "before Painter::new");
|
||||
|
||||
// some useful debug info. all three of them are present in gl 1.1.
|
||||
unsafe {
|
||||
let version = gl.get_parameter_string(glow::VERSION);
|
||||
let renderer = gl.get_parameter_string(glow::RENDERER);
|
||||
let vendor = gl.get_parameter_string(glow::VENDOR);
|
||||
tracing::debug!(
|
||||
"\nopengl version: {version}\nopengl renderer: {renderer}\nopengl vendor: {vendor}"
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if gl.version().major < 2 {
|
||||
// this checks on desktop that we are not using opengl 1.1 microsoft sw rendering context.
|
||||
// ShaderVersion::get fn will segfault due to SHADING_LANGUAGE_VERSION (added in gl2.0)
|
||||
return Err("egui_glow requires opengl 2.0+. ".to_owned());
|
||||
}
|
||||
|
||||
let max_texture_side = unsafe { gl.get_parameter_i32(glow::MAX_TEXTURE_SIZE) } as usize;
|
||||
let shader_version = shader_version.unwrap_or_else(|| ShaderVersion::get(&gl));
|
||||
let is_webgl_1 = shader_version == ShaderVersion::Es100;
|
||||
|
|
Loading…
Reference in a new issue