Skip to content

Commit

Permalink
examples: clean up examples to not recreate display
Browse files Browse the repository at this point in the history
It was incorrectly ported that the display was recreated, though, given
that passing the same arguments to it will yield the same results it was
still working as expected.
  • Loading branch information
kchibisov committed Sep 2, 2024
1 parent 7ec3bb9 commit 3901559
Showing 1 changed file with 136 additions and 90 deletions.
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

0 comments on commit 3901559

Please sign in to comment.