我对 Rust 有点陌生,我正在尝试开发一个简单的应用程序来使用 OpenGL 创建简单的对象(如立方体)。
到目前为止,我已经使用“gl”模块创建了 OpenGL 的包装器来初始化 vbos、vaos、程序、着色器等...
最初我使用“glutin”模块在主函数中处理我感兴趣的事件。 代码看起来像这样:
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();
}
_ => (),
}
});
}
在这个演示中,Renderer 结构体负责绘制简单的形状,通过按 A 和 D 键我可以旋转立方体;然后我稍微修改了代码,但我想重点关注事件处理部分。
正如您所见,代码很多,尤其是都在同一个函数(主函数)中。
我认为稍微重构一下这个混乱会很好,我想出了这个:
fn main() {
let event_loop = EventLoop::new();
let mut app = Application::new(&event_loop);
event_loop.run(move |event, _, control_flow| {
app.set_control_flow_reference(control_flow);
app.set_control_flow(ControlFlow::Wait);
let mut generic_dispatcher = GenericDispatcher::new(&mut app);
generic_dispatcher.handle(event);
});
}
#[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
}
}
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;
}
pub struct GenericDispatcher<'a> {
application: &'a mut Application,
angle_x: Deg<f32>,
angle_y: Deg<f32>,
cube: Cube
}
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 => {}
Event::RedrawRequested(_) => {
println!("angle_y: {:?}", self.angle_y);
println!("angle_x: {:?}", self.angle_x);
let size = self.application.gl_context().window().inner_size();
let width = size.width;
let height = size.height;
let aspect_ratio = width as f32 / height as f32;
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();
// let view_location = cube.gl_wrapper().program.get_uniform_location("View").unwrap();
// let projection_location = cube.gl_wrapper().program.get_uniform_location("Projection").unwrap();
unsafe {
gl::UniformMatrix4fv(model_location as GLint, 1, gl::FALSE, model_matrix.as_ptr());
// gl::UniformMatrix4fv(view_location as GLint, 1, gl::FALSE, view_matrix.as_ptr());
// gl::UniformMatrix4fv(projection_location as GLint, 1, gl::FALSE, projection_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 => {}
}
}
}
还有更多的调度程序结构体,但它们与通用调度程序相同,只是改变了handle方法。
这可行,但窗口会阻塞,我认为这是因为我只使用一个线程。
问题是我的应用程序结构包装器内的“glutin”模块中的 ContextWrapper 结构没有实现在线程之间共享数据的特征(同步和发送特征)。
我也尝试过这个:
struct EventQueue<T> {
data: Mutex<VecDeque<T>>,
}
impl<T> EventQueue<T> {
fn new() -> Self {
Self {
data: Mutex::new(VecDeque::new()),
}
}
// Add an event to the queue
fn push(&self, value: T) {
self.data.lock().unwrap().push_back(value);
}
// Remove and return an event from the queue
fn pop(&self) -> Option<T> {
self.data.lock().unwrap().pop_front()
}
}
fn main() {
let event_loop = EventLoop::new();
let mut app = Application::new(&event_loop);
event_loop.run(move |event, _, control_flow| {
app.set_control_flow_reference(control_flow);
app.set_control_flow(ControlFlow::Wait);
let event_queue = Arc::new(EventQueue::new());
// Clone the event data before the closure
let event_data = event.clone();
// Spawn worker threads
for _ in 0..4 {
let event_queue = Arc::clone(&event_queue);
thread::spawn(move || {
loop {
if let Some(event) = event_queue.pop() {
println!("Event: {:?}", event);
}
}
});
}
// Push the cloned event data into the queue
event_queue.push(event_data);
});
}
但是我收到错误,该事件正在逃避关闭。
我该如何解决这个问题?我应该找到处理事件的解决方法吗? 对于任何错误以及我糟糕的编写代码/ Rust 知识,提前表示歉意。
这可行,但窗口会阻塞,我认为这是因为我只使用一个线程。
是的,它“确实”,但那是因为你告诉它的。
您不需要使用单独的线程。你只需要告诉 glutin 不要等待下一个事件。
ControlFlow::Wait
分配给 control_flow
,字面意思是“等待,直到有新事件”。因此,如果您没有按任何按钮或移动鼠标,则“不会”发出新事件。相反,您想要的是 ControlFlow::Poll
,这会导致您的事件处理程序被连续调用,而不管是否有任何待处理的事件。
所以改变这个:
*control_flow = ControlFlow::Wait;
进入此:
*control_flow = ControlFlow::Poll;
Event::RedrawRequested
。您可以通过拨打 window.request_redraw()
来完成此操作。请注意,这必须在Event::MainEventsCleared
中完成。
因此您需要将以下匹配臂添加到您的
match event
:
match event {
...
Event::MainEventsCleared => {
gl_context.window().request_redraw();
}
_ => (),
}
顺便说一句,您只提到了 Rust 新手,而不是 OpenGL 新手。然而,OpenGL 和多线程并没有真正很好地混合在一起。因此,除非您对此有经验,否则我将避免在混合中引入线程。
是的,您当然可以将线程用于非图形相关的东西。然而,线程可能也应该与事件循环保持一定的距离。 但是,我对明胶的记忆不够,无法对此给出明确的答案。