Я пытаюсь разработать простое приложение для создания простых объектов (например, куба) с помощью OpenGL.
На данный момент я создал оболочку для OpenGL, используя модуль «gl» для инициализации vbos, vaos, программ, шейдеров и т. д.
Первоначально я обрабатывал интересующие меня события в основной функции, используя модуль «glutin». Код выглядел примерно так:
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();
}
_ => (),
}
});
}
В этой демонстрации структура Renderer отвечала за рисование простых фигур, и, нажимая клавиши A и D, я мог вращать куб соответственно влево и вправо; Затем я немного изменил код, но мне бы хотелось сосредоточиться на части обработки событий.
Как видите, кода очень много, особенно если все это находится в одной и той же функции — основной функции. Я так подумал, что было бы неплохо немного отрефакторить этот беспорядок, и придумал следующее:
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
}
}
обработчик.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 => {}
}
}
}
окно.рс
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(_) => {}
}
}
}
клавиатура.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());
}
}
}
}
}
}
При выполнении программы я получаю только белое окно, ничего не рисуется и, похоже, не фиксируется ни одно событие. Я не могу даже переместить окно или закрыть его: чтобы остановить программу, мне приходится принудительно остановить выполнение.
Затем я попытался закомментировать создание WindowDispatcher и DeviceDispatcher и вызов Renderer::draw() внутри метода handle метода GenericDispatcher, чтобы увидеть, была ли в этом проблема:
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
}
}
}
Но я все еще сталкиваюсь с той же проблемой. Иногда, вообще не меняя код, получаю черное окно и печатаю углы, как и положено, но потом все снова зависает и приходится принудительно останавливать выполнение.
Как я могу это исправить?
@vallentin Да, это так. Мне не удалось заставить работать более новую версию; [зависимости] cgmath = "0.18.0" gl = "0.14.0" Glutin = "0.29.1" thiserror = "1.0.56"

Это работает, но окно блокируется, и я думаю, это потому, что я использую только один поток.
Да, так и есть, но это потому, что вы так сказали.
Вам не нужно использовать отдельные потоки. Вам просто нужно сказать Глютину не ждать следующего события.
Вы назначаете ControlFlow::Wait для control_flow, что буквально означает «подождать, пока не появятся новые события». Поэтому, если вы не нажимаете какие-либо кнопки и не перемещаете мышь, новые события не создаются. Вместо этого вам нужен ControlFlow::Poll, который вызывает непрерывный вызов вашего обработчика событий, независимо от каких-либо ожидающих событий.
Итак, измените это:
*control_flow = ControlFlow::Wait;
В это:
*control_flow = ControlFlow::Poll;
Теперь вам нужно указать Glutin создать новое событие Event::RedrawRequested . Вы делаете это, вызывая window.request_redraw() . Обратите внимание, что это необходимо сделать в Event::MainEventsCleared.
Итак, вам нужно добавить следующую спичку к вашему match event:
match event {
...
Event::MainEventsCleared => {
gl_context.window().request_redraw();
}
_ => (),
}
Кстати, вы упомянули только о том, что вы новичок в Rust, а не в OpenGL. Однако OpenGL и многопоточность не очень хорошо сочетаются. Так что, если у вас нет опыта в этом, я бы воздержался от добавления тем в эту смесь.
Да, вы, конечно, можете использовать потоки для вещей, не связанных с графикой. Однако потоки, вероятно, также должны оставаться в стороне от цикла событий. Однако я недостаточно помню о глютине, чтобы дать однозначный ответ на этот вопрос.
Большое спасибо за ваш ответ. Да, я тоже новичок в OpenGL. Я попробовал ваше решение, но выполнение программы все равно почему-то заблокировано.
@Лука, пожалуйста! Не стесняйтесь обновлять код в вашем вопросе. Тогда я смогу взглянуть на него еще раз. Вы наверное просто что-то не туда добавили :)
@Лука Эй, извини за задержку. Итак, я еще раз проверил ваш код, я действительно его протестировал. С моей стороны все работает так, как ожидалось. Когда вы запускаете его, angle_x и angle_y не выводятся постоянно на консоль? Если нет, то у вас может быть что-то в WindowDispatcher, DeviceDispatcher или Renderer::draw(). Вы их не предоставили, поэтому я удалил их при тестировании. Делая это, и он постоянно рисует
Это странно. Я также закомментировал строки внутри метода handle структуры GenericDispatcher, где я создаю два диспетчера и вызов Renderer::draw(), но я все равно получаю замороженное окно, и на консоли ничего не печатается. При необходимости могу предоставить полный код.
Собственно, иногда, вообще не меняя код, я получаю как и ожидалось черное окно, углы печатаются один раз, но после этого из окна вообще нет ответа, и мне приходится принудительно останавливать выполнение.
@Лука, что ты делаешь в WindowDispatcher и DeviceDispatcher, там что-то похоже, может привести к блокировке
Кому интересно, мне удалось решить свою проблему.
Вот 5 ключевых шагов:
Оказывается, вам нужна более свежая версия ящиков gl и glutin, а также ящики winit, glutin-winit и raw-window-handle.
Используйте перечисление для каждого события, которое может понадобиться вашему приложению.
pub enum CustomEvent {
Log(String),
// others
}
Используйте структуру для хранения полезных данных (например, прокси-сервера, используемого для отправки данных в приложение):
use winit::event_loop::EventLoopProxy;
pub struct Dispatcher {
// Make sure to pass as generic the enum created in point 2.
proxy: EventLoopProxy<CustomEvent>,
// others
}
и реализовать методы для обработки событий:
pub fn handle_key_pressed(&mut self, physical_key: PhysicalKey, logical_key: Key) {
// Match physical and logical keys
match logical_key {
Key::Named(named) => match named {
// Implement logic
// Send event to the application
self.proxy.
send_event(CustomEvent::Log("hello".to_string())
.unwrap();
}
}
pub fn handle_mouse_pressed(&self) {
println!("Mouse pressed");
}
// Implement other methods
ApplicationHandler в свое приложение.Определите структуру Application (или как вы хотите ее назвать) и реализуйте эту черту из winit::application:
use winit::application::ApplicationHandler;
pub struct Application {
// your fields
}
В частности, обратите внимание на функцию user_event:
// Once again use as generic your CustomEvent enum
impl ApplicationHandler<CustomEvent> for Application {
// Implement every method
fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: CustomEvent) {
// Match events received from the Dispatcher event proxy
match event {
CustomEvent::Log(str) => {
println!("{str}");
}
// Implement your logic
}
}
}
// Initialize your app
let app = Application::new();
// Create the event loop (and for the 3rd time, make sure to use the your CustomEvent enum)
let event_loop = EventLoop::<CustomEvent>::with_user_event().build().unwrap();
// Create the event loop proxy
let proxy = event_loop.create_proxy();
// Create your dispatcher, which will need the proxy
let dispatcher = Dispatcher::new(proxy);
// Feel free to organize your code as you like (e.g. store the dispatcher inside the Application itself)
// Run the application
event_loop.run_app(app).unwrap();
Вот и все!
Спасибо всем за помощь.
Пожалуйста, знайте, что эта реализация может быть не самой лучшей или следовать идиоматическому Rust, я просто так это сделал.
Вы используете старую версию глютина? Я не могу разобраться в некоторых типах. Не могли бы вы включить список зависимостей?