Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples: clean up examples to not recreate display #1692

Merged
merged 1 commit into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion glutin_examples/examples/egl_device.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ mod example {
}

let path = Path::new(IMG_PATH);
let file = OpenOptions::new().write(true).truncate(true).open(path).unwrap();
let file = OpenOptions::new().create(true).write(true).truncate(true).open(path).unwrap();

let mut encoder = png::Encoder::new(file, 1280, 720);
encoder.set_depth(png::BitDepth::Eight);
Expand Down
226 changes: 136 additions & 90 deletions glutin_examples/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ use gl::types::GLfloat;
use raw_window_handle::HasWindowHandle;
use winit::application::ApplicationHandler;
use winit::event::{KeyEvent, WindowEvent};
use winit::event_loop::ActiveEventLoop;
use winit::keyboard::{Key, NamedKey};
use winit::window::Window;
use winit::window::{Window, WindowAttributes};

use glutin::config::{Config, ConfigTemplateBuilder};
use glutin::config::{Config, ConfigTemplateBuilder, GetGlConfig};
use glutin::context::{
ContextApi, ContextAttributesBuilder, NotCurrentContext, PossiblyCurrentContext, Version,
};
Expand All @@ -28,10 +29,6 @@ pub mod gl {
}

pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box<dyn Error>> {
let window_attributes = Window::default_attributes()
.with_transparent(true)
.with_title("Glutin triangle gradient example (press Escape to exit)");

// The template will match only the configurations supporting rendering
// to windows.
//
Expand All @@ -43,7 +40,7 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box<dyn
let template =
ConfigTemplateBuilder::new().with_alpha_size(8).with_transparency(cfg!(cgl_backend));

let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes()));

let mut app = App::new(template, display_builder);
event_loop.run_app(&mut app)?;
Expand All @@ -52,112 +49,93 @@ pub fn main(event_loop: winit::event_loop::EventLoop<()>) -> Result<(), Box<dyn
}

impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {
let (mut window, gl_config) = match self.display_builder.clone().build(
event_loop,
self.template.clone(),
gl_config_picker,
) {
Ok(ok) => ok,
Err(e) => {
self.exit_state = Err(e);
event_loop.exit();
return;
},
};

println!("Picked a config with {} samples", gl_config.num_samples());

let raw_window_handle = window
.as_ref()
.and_then(|window| window.window_handle().ok())
.map(|handle| handle.as_raw());

// XXX The display could be obtained from any object created by it, so we can
// query it from the config.
let gl_display = gl_config.display();

// The context creation part.
let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);

// Since glutin by default tries to create OpenGL core context, which may not be
// present we should try gles.
let fallback_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(None))
.build(raw_window_handle);

// There are also some old devices that support neither modern OpenGL nor GLES.
// To support these we can try and create a 2.1 context.
let legacy_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1))))
.build(raw_window_handle);

// Reuse the uncurrented context from a suspended() call if it exists, otherwise
// this is the first time resumed() is called, where the context still
// has to be created.
let not_current_gl_context = self.not_current_gl_context.take().unwrap_or_else(|| unsafe {
gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| {
gl_display.create_context(&gl_config, &fallback_context_attributes).unwrap_or_else(
|_| {
gl_display
.create_context(&gl_config, &legacy_context_attributes)
.expect("failed to create context")
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let (window, gl_config) = match &self.gl_display {
// We just created the event loop, so initialize the display, pick the config, and
// create the context.
GlDisplayCreationState::Builder(display_builder) => {
let (window, gl_config) = match display_builder.clone().build(
event_loop,
self.template.clone(),
gl_config_picker,
) {
Ok((window, gl_config)) => (window.unwrap(), gl_config),
Err(err) => {
self.exit_state = Err(err);
event_loop.exit();
return;
},
)
})
});
};

#[cfg(android_platform)]
println!("Android window available");
println!("Picked a config with {} samples", gl_config.num_samples());

let window = window.take().unwrap_or_else(|| {
let window_attributes = Window::default_attributes()
.with_transparent(true)
.with_title("Glutin triangle gradient example (press Escape to exit)");
glutin_winit::finalize_window(event_loop, window_attributes, &gl_config).unwrap()
});
// Mark the display as initialized to not recreate it on resume, since the
// display is valid until we explicitly destroy it.
self.gl_display = GlDisplayCreationState::Init;

// Create gl context.
self.gl_context =
Some(create_gl_context(&window, &gl_config).treat_as_possibly_current());

(window, gl_config)
},
GlDisplayCreationState::Init => {
println!("Recreating window in `resumed`");
// Pick the config which we already use for the context.
let gl_config = self.gl_context.as_ref().unwrap().config();
match glutin_winit::finalize_window(event_loop, window_attributes(), &gl_config) {
Ok(window) => (window, gl_config),
Err(err) => {
self.exit_state = Err(err.into());
event_loop.exit();
return;
},
}
},
};

let attrs = window
.build_surface_attributes(Default::default())
.expect("Failed to build surface attributes");
let gl_surface =
unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };

// Make it current.
let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();

// The context needs to be current for the Renderer to set up shaders and
// buffers. It also performs function loading, which needs a current context on
// WGL.
self.renderer.get_or_insert_with(|| Renderer::new(&gl_display));
let gl_context = self.gl_context.as_ref().unwrap();
gl_context.make_current(&gl_surface).unwrap();

self.renderer.get_or_insert_with(|| Renderer::new(&gl_config.display()));

// Try setting vsync.
if let Err(res) = gl_surface
.set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
.set_swap_interval(gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
{
eprintln!("Error setting vsync: {res:?}");
}

assert!(self.state.replace(AppState { gl_context, gl_surface, window }).is_none());
assert!(self.state.replace(AppState { gl_surface, window }).is_none());
}

fn suspended(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
fn suspended(&mut self, _event_loop: &ActiveEventLoop) {
// This event is only raised on Android, where the backing NativeWindow for a GL
// Surface can appear and disappear at any moment.
println!("Android window removed");

// Destroy the GL Surface and un-current the GL Context before ndk-glue releases
// the window back to the system.
let gl_context = self.state.take().unwrap().gl_context;
assert!(self
.not_current_gl_context
.replace(gl_context.make_not_current().unwrap())
.is_none());
self.state = None;

// Make context not current.
self.gl_context = Some(
self.gl_context.take().unwrap().make_not_current().unwrap().treat_as_possibly_current(),
);
}

fn window_event(
&mut self,
event_loop: &winit::event_loop::ActiveEventLoop,
event_loop: &ActiveEventLoop,
_window_id: winit::window::WindowId,
event: WindowEvent,
) {
Expand All @@ -167,12 +145,14 @@ impl ApplicationHandler for App {
// Notable platforms here are Wayland and macOS, other don't require it
// and the function is no-op, but it's wise to resize it for portability
// reasons.
if let Some(AppState { gl_context, gl_surface, window: _ }) = self.state.as_ref() {
if let Some(AppState { gl_surface, window: _ }) = self.state.as_ref() {
let gl_context = self.gl_context.as_ref().unwrap();
gl_surface.resize(
gl_context,
NonZeroU32::new(size.width).unwrap(),
NonZeroU32::new(size.height).unwrap(),
);

let renderer = self.renderer.as_ref().unwrap();
renderer.resize(size.width as i32, size.height as i32);
}
Expand All @@ -186,8 +166,26 @@ impl ApplicationHandler for App {
}
}

fn about_to_wait(&mut self, _event_loop: &winit::event_loop::ActiveEventLoop) {
if let Some(AppState { gl_context, gl_surface, window }) = self.state.as_ref() {
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
// NOTE: The handling below is only needed due to nvidia on Wayland to not crash
// on exit due to nvidia driver touching the Wayland display from on
// `exit` hook.
let _gl_display = self.gl_context.take().unwrap().display();

// Clear the window.
self.state = None;
#[cfg(egl_backend)]
#[allow(irrefutable_let_patterns)]
if let glutin::display::Display::Egl(display) = _gl_display {
unsafe {
display.terminate();
}
}
}

fn about_to_wait(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(AppState { gl_surface, window }) = self.state.as_ref() {
let gl_context = self.gl_context.as_ref().unwrap();
let renderer = self.renderer.as_ref().unwrap();
renderer.draw();
window.request_redraw();
Expand All @@ -197,31 +195,79 @@ impl ApplicationHandler for App {
}
}

fn create_gl_context(window: &Window, gl_config: &Config) -> NotCurrentContext {
let raw_window_handle = window.window_handle().ok().map(|wh| wh.as_raw());

// The context creation part.
let context_attributes = ContextAttributesBuilder::new().build(raw_window_handle);

// Since glutin by default tries to create OpenGL core context, which may not be
// present we should try gles.
let fallback_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::Gles(None))
.build(raw_window_handle);

// There are also some old devices that support neither modern OpenGL nor GLES.
// To support these we can try and create a 2.1 context.
let legacy_context_attributes = ContextAttributesBuilder::new()
.with_context_api(ContextApi::OpenGl(Some(Version::new(2, 1))))
.build(raw_window_handle);

// Reuse the uncurrented context from a suspended() call if it exists, otherwise
// this is the first time resumed() is called, where the context still
// has to be created.
let gl_display = gl_config.display();

unsafe {
gl_display.create_context(gl_config, &context_attributes).unwrap_or_else(|_| {
gl_display.create_context(gl_config, &fallback_context_attributes).unwrap_or_else(
|_| {
gl_display
.create_context(gl_config, &legacy_context_attributes)
.expect("failed to create context")
},
)
})
}
}

fn window_attributes() -> WindowAttributes {
Window::default_attributes()
.with_transparent(true)
.with_title("Glutin triangle gradient example (press Escape to exit)")
}

enum GlDisplayCreationState {
/// The display was not build yet.
Builder(DisplayBuilder),
/// The display was already created for the application.
Init,
}

struct App {
template: ConfigTemplateBuilder,
display_builder: DisplayBuilder,
exit_state: Result<(), Box<dyn Error>>,
not_current_gl_context: Option<NotCurrentContext>,
renderer: Option<Renderer>,
// NOTE: `AppState` carries the `Window`, thus it should be dropped after everything else.
state: Option<AppState>,
gl_context: Option<PossiblyCurrentContext>,
gl_display: GlDisplayCreationState,
exit_state: Result<(), Box<dyn Error>>,
}

impl App {
fn new(template: ConfigTemplateBuilder, display_builder: DisplayBuilder) -> Self {
Self {
template,
display_builder,
gl_display: GlDisplayCreationState::Builder(display_builder),
exit_state: Ok(()),
not_current_gl_context: None,
gl_context: None,
state: None,
renderer: None,
}
}
}

struct AppState {
gl_context: PossiblyCurrentContext,
gl_surface: Surface<WindowSurface>,
// NOTE: Window should be dropped after all resources created using its
// raw-window-handle.
Expand Down
Loading