I'm trying to develop a simple application to create simple objects (like a cube) with OpenGL.
So far I've created a wrapper to OpenGL using the "gl" module to initialize vbos, vaos, programs, shaders etc...
Initially I handled the events I was interested on in the main function, using the "glutin" module. The code looked something like this:
main.rs
fn main() {
let event_loop = EventLoop::new();
let window = WindowBuilder::new().with_title("Rust OpenGL");
let gl_context = ContextBuilder::new()
.with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
.build_windowed(window, &event_loop)
.expect("Cannot create windowed context");
let gl_context = unsafe {
gl_context
.make_current()
.expect("Failed to make context current")
};
gl::load_with(|ptr| gl_context.get_proc_address(ptr) as *const _);
let v1 = vec![
Vertex((-0.5, -0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
Vertex((0.5, -0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
Vertex((0.5, 0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
Vertex((-0.5, 0.5, 0.5).into(), (1.0, 0.0, 0.0).into()),
];
let front_face = Square::new(&v1);
let v2 = vec![
Vertex((-0.5, -0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
Vertex((0.5, -0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
Vertex((0.5, 0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
Vertex((-0.5, 0.5, -0.5).into(), (0.0, 1.0, 0.0).into()),
];
let back_face = Square::new(&v2);
let cube = Cube::from(&[front_face, back_face]);
let mut rotation_angle: Rad<f32> = Deg(0.0).into();
event_loop.run(move |event, target, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::LoopDestroyed => (),
Event::WindowEvent { event, .. } => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(physical_size) => gl_context.resize(physical_size),
WindowEvent::KeyboardInput { input, .. } => {
if let Some(VirtualKeyCode::A) = input.virtual_keycode {
match input.state {
ElementState::Pressed => {
rotation_angle -= Deg(0.5).into();
gl_context.window().request_redraw();
},
_ => ()
}
}
if let Some(VirtualKeyCode::D) = input.virtual_keycode {
match input.state {
ElementState::Pressed => {
rotation_angle += Deg(0.5).into();
gl_context.window().request_redraw();
},
_ => ()
}
}
}
_ => (),
},
Event::RedrawRequested(_) => {
println!("{:?}", rotation_angle);
let rotation_matrix_y = Matrix4::from_axis_angle(Vector3::unit_y(), rotation_angle);
let rotation_matrix_x = Matrix4::from_axis_angle(Vector3::unit_x(), Deg(30.0));
let transformation_matrix = rotation_matrix_y * rotation_matrix_x;
let transform_location = cube.gl_wrapper().program.get_uniform_location("Transform").unwrap();
unsafe {
gl::UniformMatrix4fv(transform_location as GLint, 1, gl::FALSE, transformation_matrix.as_ptr());
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
Renderer::draw(&cube);
}
gl_context.swap_buffers().unwrap();
}
_ => (),
}
});
}
In this demo, the Renderer struct was responsible for drawing simple shapes, and by pressing A and D keys I could rotate the cube respectively to the left and to the right; I then modified the code a little, but I'd like to focus on the event handling part.
As you might see, the code is a lot, especially to be all in the same function, the main function. I so thought it would be good to refactor this mess a little, and I came up with this:
main.rs
fn main() {
let event_loop = EventLoop::new();
let mut app = Application::new(&event_loop);
event_loop.run(move |event, _, control_flow| {
// Commenting these out as i'm not sure it works
// app.set_control_flow_reference(control_flow);
// app.set_control_flow(ControlFlow::Poll);
*control_flow = ControlFlow::Poll;
let mut generic_dispatcher = GenericDispatcher::new(&mut app);
generic_dispatcher.handle(event);
});
}
application.rs
#[derive(Debug)]
pub struct Application {
control_flow: ControlFlow,
gl_context: ContextWrapper<PossiblyCurrent, Window>
}
impl Application {
pub fn new(event_loop: &EventLoop<()>) -> Self {
let window_builder = WindowBuilder::new().with_title("Rust OpenGL");
let context = ContextBuilder::new()
.with_gl(GlRequest::Specific(Api::OpenGl, (3, 3)))
.build_windowed(window_builder, event_loop)
.expect("Cannot create windowed context");
let context = unsafe {
context
.make_current()
.expect("Failed to make context current")
};
gl::load_with(|ptr| context.get_proc_address(ptr) as *const _);
unsafe {
info::log_error("loading gl");
}
Self {
control_flow: ControlFlow::default(),
gl_context: context,
}
}
pub fn set_control_flow_reference(&mut self, new_control_flow_reference: &mut ControlFlow) {
let mut control_flow_reference = &mut self.control_flow;
control_flow_reference = new_control_flow_reference;
}
pub fn set_control_flow(&mut self, new_control_flow: ControlFlow) {
let control_flow_reference = &mut self.control_flow;
*control_flow_reference = new_control_flow;
}
pub fn gl_context(&self) -> &ContextWrapper<PossiblyCurrent, Window> {
&self.gl_context
}
}
handler.rs
pub trait Handler<E> {
fn handle(&mut self, item: E);
}
pub trait Dispatcher<'a> {
fn new(application: &'a mut Application) -> Self;
fn application(&'a self) -> &'a Application;
}
generic.rs
pub struct GenericDispatcher<'a> {
application: &'a mut Application,
angle_x: Deg<f32>,
angle_y: Deg<f32>,
cube: Cube
}
// snip the impl for the new associated function
impl<'a> Handler<Event<'_, ()>> for GenericDispatcher<'a> {
fn handle(&mut self, item: Event<'_, ()>) {
match item {
Event::NewEvents(_) => {}
Event::WindowEvent { window_id, event } => {
let mut window_dispatcher = WindowDispatcher::new(self.application);
window_dispatcher.handle(event);
}
Event::DeviceEvent { device_id, event } => {
let mut device_dispatcher = DeviceDispatcher::new(self.application);
device_dispatcher.handle(event);
}
Event::UserEvent(_) => {}
Event::Suspended => {}
Event::Resumed => {}
Event::MainEventsCleared => {
self.application.gl_context().window().request_redraw();
}
Event::RedrawRequested(_) => {
println!("angle_y: {:?}", self.angle_y);
println!("angle_x: {:?}", self.angle_x);
let rotation_matrix_y = Matrix4::from_axis_angle(Vector3::unit_y(), self.angle_y);
let rotation_matrix_x = Matrix4::from_axis_angle(Vector3::unit_x(), self.angle_x);
let model_matrix = rotation_matrix_y * rotation_matrix_x;
let model_location = self.cube.gl_wrapper().program.get_uniform_location("Model").unwrap();
unsafe {
gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_matrix.as_ptr());
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LESS);
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Clear(gl::DEPTH_BUFFER_BIT);
Renderer::draw(&self.cube);
}
self.application.gl_context().swap_buffers().unwrap();
}
Event::RedrawEventsCleared => {}
Event::LoopDestroyed => {}
}
}
}
window.rs
pub struct WindowDispatcher<'a> {
application: &'a mut Application
}
impl<'a> Handler<WindowEvent<'_>> for WindowDispatcher<'a> {
fn handle(&mut self, item: WindowEvent<'_>) {
let mut keyboard_dispatcher = KeyboardDispatcher::new(self.application);
match item {
WindowEvent::Resized(physical_size) => {
self.application.gl_context().resize(physical_size);
}
WindowEvent::Moved(_) => {}
WindowEvent::CloseRequested => {
self.application.set_control_flow(ControlFlow::Exit);
}
WindowEvent::Destroyed => {}
WindowEvent::DroppedFile(_) => {}
WindowEvent::HoveredFile(_) => {}
WindowEvent::HoveredFileCancelled => {}
WindowEvent::ReceivedCharacter(_) => {}
WindowEvent::Focused(_) => {}
WindowEvent::KeyboardInput { device_id, input, is_synthetic } => {
keyboard_dispatcher.handle(input);
}
WindowEvent::ModifiersChanged(_) => {}
WindowEvent::Ime(_) => {}
WindowEvent::CursorMoved { device_id, position, modifiers } => {}
WindowEvent::CursorEntered { device_id } => {}
WindowEvent::CursorLeft { device_id } => {}
WindowEvent::MouseWheel { device_id, delta, phase, modifiers } => {}
WindowEvent::MouseInput { device_id, state, button, modifiers } => {}
WindowEvent::TouchpadPressure { device_id, pressure, stage } => {}
WindowEvent::AxisMotion { device_id, axis, value } => {}
WindowEvent::Touch(_) => {}
WindowEvent::ScaleFactorChanged { scale_factor, new_inner_size } => {}
WindowEvent::ThemeChanged(_) => {}
WindowEvent::Occluded(_) => {}
}
}
}
keyboard.rs
pub struct KeyboardDispatcher<'a> {
application: &'a mut Application
}
impl<'a> Handler<KeyboardInput> for KeyboardDispatcher<'a> {
fn handle(&mut self, item: KeyboardInput) {
match item {
KeyboardInput { scancode, state, virtual_keycode, modifiers } => {
match state {
ElementState::Pressed => {
println!("{:?} pressed!", virtual_keycode.unwrap());
}
ElementState::Released => {
println!("{:?} released!", virtual_keycode.unwrap());
}
}
}
}
}
}
When executing the program I only get a white window, nothing gets drawn and no event seems to be catched. I can't even move the window around nor close it, to stop the program I have to force the execution to stop.
I've then tried to comment out the creation of the WindowDispatcher and DeviceDispatcher and the call Renderer::draw() inside the handle method of the GenericDispatcher to see if that was the problem:
impl<'a> Handler<Event<'_, ()>> for GenericDispatcher<'a> {
fn handle(&mut self, item: Event<'_, ()>) {
// snip
Event::WindowEvent { window_id, event } => {
// let mut window_dispatcher = WindowDispatcher::new(self.application);
// window_dispatcher.handle(event);
}
Event::DeviceEvent { device_id, event } => {
// let mut device_dispatcher = DeviceDispatcher::new(self.application);
// device_dispatcher.handle(event);
}
// snip
Event::RedrawRequested(_) => {
// snip
unsafe {
gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_matrix.as_ptr());
gl::Enable(gl::DEPTH_TEST);
gl::DepthFunc(gl::LESS);
gl::ClearColor(0.0, 0.0, 0.0, 1.0);
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::Clear(gl::DEPTH_BUFFER_BIT);
// Renderer::draw(&self.cube);
}
// snip
}
}
}
But i'm still facing the same issue. Sometimes, without changing the code at all, I get a black window and I get printed the angles, as expected, but then everything freezes once again and I have to stop the execution by force.
How can I fix this?
Yes, it "does", but it's because you told it to.
You don't need to use separate threads. You just need to tell glutin not to wait for the next event.
You assign
ControlFlow::Waittocontrol_flow, which quite literally means "wait until there are new events". So if you aren't pressing any buttons or moving the mouse, then "no" new events are emitted. Instead what you want isControlFlow::Poll, which causes your event handler to be continuously called, regardless of any pending events.So change this:
Into this:
Now, next you need to tell glutin to emit a new
Event::RedrawRequestedas well. You do this by callingwindow.request_redraw(). Note that this must be done inEvent::MainEventsCleared.So you need to add the following match arm to your
match event:As an aside, you only mentioned being new to Rust, not new to OpenGL. However, OpenGL and multiple threads don't really mix well. So unless you're experienced with this, I would stay clear of introducing threads into the mix.
Yes, you can of course use threads for non-graphics related stuff. However, the threads should probably also stay somewhat clear from the event loop. However, I don't recall enough about glutin to give a definite answer on that.