bevy_vulkano/
lib.rs

1#![allow(
2    clippy::needless_question_mark,
3    clippy::too_many_arguments,
4    clippy::type_complexity,
5    clippy::module_inception,
6    clippy::single_match,
7    clippy::match_like_matches_macro
8)]
9
10mod config;
11mod converters;
12mod system;
13mod vulkano_windows;
14
15use bevy::{
16    app::{App, AppExit, Plugin},
17    ecs::{
18        event::{Events, ManualEventReader},
19        system::{SystemParam, SystemState},
20    },
21    input::{
22        keyboard::KeyboardInput,
23        mouse::{MouseButtonInput, MouseMotion, MouseScrollUnit, MouseWheel},
24        touch::TouchInput,
25    },
26    math::{ivec2, DVec2, Vec2},
27    prelude::*,
28    utils::Instant,
29    window::{
30        exit_on_all_closed, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop,
31        ReceivedCharacter, RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested,
32        WindowCreated, WindowFocused, WindowMoved, WindowResized, WindowScaleFactorChanged,
33    },
34};
35pub use config::*;
36#[cfg(feature = "gui")]
37pub use egui_winit_vulkano;
38use vulkano_util::context::{VulkanoConfig, VulkanoContext};
39pub use vulkano_windows::*;
40
41/// Wrapper around [`VulkanoContext`] to allow using them as resources
42#[derive(Resource)]
43pub struct BevyVulkanoContext {
44    pub context: VulkanoContext,
45}
46
47#[cfg(target_os = "android")]
48pub use winit::platform::android::activity::AndroidApp;
49use winit::{
50    event::{self, DeviceEvent, Event, StartCause, WindowEvent},
51    event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget},
52};
53
54use crate::system::{changed_window, create_window, despawn_window, CachedWindow};
55
56#[cfg(target_os = "android")]
57pub static ANDROID_APP: once_cell::sync::OnceCell<AndroidApp> = once_cell::sync::OnceCell::new();
58
59/// A [`Plugin`] that utilizes [`winit`] for window creation and event loop management.
60/// In addition, windows include custom render functionality with Vulkano.
61/// This is intended to replace `bevy_winit`.
62#[derive(Default)]
63pub struct VulkanoWinitPlugin;
64
65impl Plugin for VulkanoWinitPlugin {
66    fn build(&self, app: &mut App) {
67        let mut event_loop_builder = EventLoopBuilder::<()>::with_user_event();
68
69        #[cfg(target_os = "android")]
70        {
71            use winit::platform::android::EventLoopBuilderExtAndroid;
72            event_loop_builder.with_android_app(
73                ANDROID_APP
74                    .get()
75                    .expect("Bevy must be setup with the #[bevy_main] macro on Android")
76                    .clone(),
77            );
78        }
79
80        let event_loop = event_loop_builder.build();
81        app.insert_non_send_resource(event_loop);
82
83        // Retrieve config, or use default.
84        let config = if app
85            .world
86            .get_non_send_resource::<BevyVulkanoSettings>()
87            .is_none()
88        {
89            BevyVulkanoSettings::default()
90        } else {
91            app.world
92                .remove_non_send_resource::<BevyVulkanoSettings>()
93                .unwrap()
94        };
95
96        // Create vulkano context using the vulkano config from settings
97        let BevyVulkanoSettings {
98            vulkano_config, ..
99        } = config;
100        let vulkano_context = BevyVulkanoContext {
101            context: VulkanoContext::new(vulkano_config),
102        };
103        // Place config back as resource..
104        let new_config = BevyVulkanoSettings {
105            vulkano_config: VulkanoConfig::default(),
106            ..config
107        };
108
109        app.init_non_send_resource::<BevyVulkanoWindows>()
110            .insert_resource(vulkano_context)
111            .insert_non_send_resource(new_config)
112            .set_runner(winit_runner)
113            // exit_on_all_closed only uses the query to determine if the query is empty,
114            // and so doesn't care about ordering relative to changed_window
115            .add_systems(
116                Last,
117                (
118                    changed_window.ambiguous_with(exit_on_all_closed),
119                    // Update the state of the window before attempting to despawn to ensure consistent event ordering
120                    despawn_window.after(changed_window),
121                ),
122            );
123
124        #[cfg(feature = "gui")]
125        {
126            app.add_systems(PreUpdate, begin_egui_frame_system);
127        }
128
129        let mut create_window_system_state: SystemState<(
130            Commands,
131            NonSendMut<EventLoop<()>>,
132            Query<(Entity, &mut Window)>,
133            EventWriter<WindowCreated>,
134            NonSendMut<BevyVulkanoWindows>,
135            Res<BevyVulkanoContext>,
136            NonSend<BevyVulkanoSettings>,
137        )> = SystemState::from_world(&mut app.world);
138
139        // And for ios and macos, we should not create window early, all ui related code should be executed inside
140        // UIApplicationMain/NSApplicationMain.
141        #[cfg(not(any(target_os = "android", target_os = "ios", target_os = "macos")))]
142        {
143            let (
144                commands,
145                event_loop,
146                mut new_windows,
147                event_writer,
148                vulkano_windows,
149                context,
150                settings,
151            ) = create_window_system_state.get_mut(&mut app.world);
152
153            // Here we need to create a winit-window and give it a WindowHandle which the renderer can use.
154            // It needs to be spawned before the start of the startup schedule, so we cannot use a regular system.
155            // Instead we need to create the window and spawn it using direct world access
156            create_window(
157                commands,
158                &event_loop,
159                new_windows.iter_mut(),
160                event_writer,
161                vulkano_windows,
162                context,
163                settings,
164            );
165        }
166
167        create_window_system_state.apply(&mut app.world);
168    }
169}
170
171fn run<F>(event_loop: EventLoop<()>, event_handler: F) -> !
172where
173    F: 'static + FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
174{
175    event_loop.run(event_handler)
176}
177
178#[cfg(any(
179    target_os = "windows",
180    target_os = "macos",
181    target_os = "linux",
182    target_os = "dragonfly",
183    target_os = "freebsd",
184    target_os = "netbsd",
185    target_os = "openbsd"
186))]
187fn run_return<F>(event_loop: &mut EventLoop<()>, event_handler: F)
188where
189    F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
190{
191    use winit::platform::run_return::EventLoopExtRunReturn;
192    event_loop.run_return(event_handler);
193}
194
195#[cfg(not(any(
196    target_os = "windows",
197    target_os = "macos",
198    target_os = "linux",
199    target_os = "dragonfly",
200    target_os = "freebsd",
201    target_os = "netbsd",
202    target_os = "openbsd"
203)))]
204fn run_return<F>(_event_loop: &mut EventLoop<()>, _event_handler: F)
205where
206    F: FnMut(Event<'_, ()>, &EventLoopWindowTarget<()>, &mut ControlFlow),
207{
208    panic!("Run return is not supported on this platform!")
209}
210
211#[derive(SystemParam)]
212struct WindowEvents<'w> {
213    window_resized: EventWriter<'w, WindowResized>,
214    window_close_requested: EventWriter<'w, WindowCloseRequested>,
215    window_scale_factor_changed: EventWriter<'w, WindowScaleFactorChanged>,
216    window_backend_scale_factor_changed: EventWriter<'w, WindowBackendScaleFactorChanged>,
217    window_focused: EventWriter<'w, WindowFocused>,
218    window_moved: EventWriter<'w, WindowMoved>,
219}
220
221#[derive(SystemParam)]
222struct InputEvents<'w> {
223    keyboard_input: EventWriter<'w, KeyboardInput>,
224    character_input: EventWriter<'w, ReceivedCharacter>,
225    mouse_button_input: EventWriter<'w, MouseButtonInput>,
226    mouse_wheel_input: EventWriter<'w, MouseWheel>,
227    touch_input: EventWriter<'w, TouchInput>,
228    ime_input: EventWriter<'w, Ime>,
229}
230
231#[derive(SystemParam)]
232struct CursorEvents<'w> {
233    cursor_moved: EventWriter<'w, CursorMoved>,
234    cursor_entered: EventWriter<'w, CursorEntered>,
235    cursor_left: EventWriter<'w, CursorLeft>,
236}
237
238/// Stores state that must persist between frames.
239struct WinitPersistentState {
240    /// Tracks whether or not the application is active or suspended.
241    active: bool,
242    /// Tracks whether or not an event has occurred this frame that would trigger an update in low
243    /// power mode. Should be reset at the end of every frame.
244    low_power_event: bool,
245    /// Tracks whether the event loop was started this frame because of a redraw request.
246    redraw_request_sent: bool,
247    /// Tracks if the event loop was started this frame because of a `WaitUntil` timeout.
248    timeout_reached: bool,
249    last_update: Instant,
250}
251
252impl Default for WinitPersistentState {
253    fn default() -> Self {
254        Self {
255            active: false,
256            low_power_event: false,
257            redraw_request_sent: false,
258            timeout_reached: false,
259            last_update: Instant::now(),
260        }
261    }
262}
263
264pub fn winit_runner(mut app: App) {
265    let mut event_loop = app
266        .world
267        .remove_non_send_resource::<EventLoop<()>>()
268        .unwrap();
269
270    let mut app_exit_event_reader = ManualEventReader::<AppExit>::default();
271    let mut redraw_event_reader = ManualEventReader::<RequestRedraw>::default();
272    let mut winit_state = WinitPersistentState::default();
273    app.world
274        .insert_non_send_resource(event_loop.create_proxy());
275
276    let return_from_run = app
277        .world
278        .non_send_resource::<BevyVulkanoSettings>()
279        .return_from_run;
280
281    trace!("Entering winit event loop");
282
283    let mut focused_window_state: SystemState<(NonSend<BevyVulkanoSettings>, Query<&Window>)> =
284        SystemState::from_world(&mut app.world);
285
286    let mut create_window_system_state: SystemState<(
287        Commands,
288        Query<(Entity, &mut Window), Added<Window>>,
289        EventWriter<WindowCreated>,
290        NonSendMut<BevyVulkanoWindows>,
291        Res<BevyVulkanoContext>,
292        NonSend<BevyVulkanoSettings>,
293    )> = SystemState::from_world(&mut app.world);
294
295    let event_handler = move |event: Event<()>,
296                              event_loop: &EventLoopWindowTarget<()>,
297                              control_flow: &mut ControlFlow| {
298        #[cfg(feature = "trace")]
299        let _span = bevy_utils::tracing::info_span!("winit event_handler").entered();
300
301        if let Some(app_exit_events) = app.world.get_resource::<Events<AppExit>>() {
302            if app_exit_event_reader.read(app_exit_events).last().is_some() {
303                *control_flow = ControlFlow::Exit;
304                return;
305            }
306        }
307
308        match event {
309            event::Event::NewEvents(start) => {
310                let (config, window_focused_query) = focused_window_state.get(&app.world);
311
312                let app_focused = window_focused_query.iter().any(|window| window.focused);
313
314                // Check if either the `WaitUntil` timeout was triggered by winit, or that same
315                // amount of time has elapsed since the last app update. This manual check is needed
316                // because we don't know if the criteria for an app update were met until the end of
317                // the frame.
318                let auto_timeout_reached = matches!(start, StartCause::ResumeTimeReached { .. });
319                let now = Instant::now();
320                let manual_timeout_reached = match config.update_mode(app_focused) {
321                    UpdateMode::Continuous => false,
322                    UpdateMode::Reactive {
323                        max_wait,
324                    }
325                    | UpdateMode::ReactiveLowPower {
326                        max_wait,
327                    } => now.duration_since(winit_state.last_update) >= *max_wait,
328                };
329                // The low_power_event state and timeout must be reset at the start of every frame.
330                winit_state.low_power_event = false;
331                winit_state.timeout_reached = auto_timeout_reached || manual_timeout_reached;
332            }
333            #[allow(unused_mut)]
334            event::Event::WindowEvent {
335                event,
336                window_id: winit_window_id,
337                ..
338            } => {
339                // Fetch and prepare details from the world
340                let mut system_state: SystemState<(
341                    NonSendMut<BevyVulkanoWindows>,
342                    Query<(&mut Window, &mut CachedWindow)>,
343                    WindowEvents,
344                    InputEvents,
345                    CursorEvents,
346                    EventWriter<FileDragAndDrop>,
347                )> = SystemState::new(&mut app.world);
348                let (
349                    mut vulkano_windows,
350                    mut window_query,
351                    mut window_events,
352                    mut input_events,
353                    mut cursor_events,
354                    mut file_drag_and_drop_events,
355                ) = system_state.get_mut(&mut app.world);
356
357                // Entity of this window
358                let window_entity =
359                    if let Some(entity) = vulkano_windows.get_window_entity(winit_window_id) {
360                        entity
361                    } else {
362                        warn!(
363                            "Skipped event {:?} for unknown winit Window Id {:?}",
364                            event, winit_window_id
365                        );
366                        return;
367                    };
368
369                let (mut window, mut cache) =
370                    if let Ok((window, info)) = window_query.get_mut(window_entity) {
371                        (window, info)
372                    } else {
373                        warn!(
374                            "Window {:?} is missing `Window` component, skipping event {:?}",
375                            window_entity, event
376                        );
377                        return;
378                    };
379
380                // Skip event if egui wants it
381                #[cfg(feature = "gui")]
382                {
383                    if let Some(vulkano_window) =
384                        vulkano_windows.get_vulkano_window_mut(window_entity)
385                    {
386                        // Update egui with the window event. If false, we should skip the event in bevy
387                        if vulkano_window.gui.update(&event) {
388                            return;
389                        }
390                    }
391                }
392
393                winit_state.low_power_event = true;
394
395                match event {
396                    WindowEvent::Resized(size) => {
397                        window
398                            .resolution
399                            .set_physical_resolution(size.width, size.height);
400
401                        window_events.window_resized.send(WindowResized {
402                            window: window_entity,
403                            width: window.width(),
404                            height: window.height(),
405                        });
406                    }
407                    WindowEvent::CloseRequested => {
408                        window_events
409                            .window_close_requested
410                            .send(WindowCloseRequested {
411                                window: window_entity,
412                            });
413                    }
414                    WindowEvent::KeyboardInput {
415                        ref input, ..
416                    } => {
417                        input_events
418                            .keyboard_input
419                            .send(converters::convert_keyboard_input(input, window_entity));
420                    }
421                    WindowEvent::CursorMoved {
422                        position, ..
423                    } => {
424                        let physical_position = DVec2::new(position.x, position.y);
425
426                        window.set_physical_cursor_position(Some(physical_position));
427
428                        cursor_events.cursor_moved.send(CursorMoved {
429                            window: window_entity,
430                            position: (physical_position / window.resolution.scale_factor())
431                                .as_vec2(),
432                        });
433                    }
434                    WindowEvent::CursorEntered {
435                        ..
436                    } => {
437                        cursor_events.cursor_entered.send(CursorEntered {
438                            window: window_entity,
439                        });
440                    }
441                    WindowEvent::CursorLeft {
442                        ..
443                    } => {
444                        window.set_physical_cursor_position(None);
445
446                        cursor_events.cursor_left.send(CursorLeft {
447                            window: window_entity,
448                        });
449                    }
450                    WindowEvent::MouseInput {
451                        state,
452                        button,
453                        ..
454                    } => {
455                        input_events.mouse_button_input.send(MouseButtonInput {
456                            button: converters::convert_mouse_button(button),
457                            state: converters::convert_element_state(state),
458                            window: window_entity,
459                        });
460                    }
461                    WindowEvent::MouseWheel {
462                        delta, ..
463                    } => match delta {
464                        event::MouseScrollDelta::LineDelta(x, y) => {
465                            input_events.mouse_wheel_input.send(MouseWheel {
466                                unit: MouseScrollUnit::Line,
467                                x,
468                                y,
469                                window: window_entity,
470                            });
471                        }
472                        event::MouseScrollDelta::PixelDelta(p) => {
473                            input_events.mouse_wheel_input.send(MouseWheel {
474                                unit: MouseScrollUnit::Pixel,
475                                x: p.x as f32,
476                                y: p.y as f32,
477                                window: window_entity,
478                            });
479                        }
480                    },
481                    WindowEvent::Touch(touch) => {
482                        let location = touch.location.to_logical(window.resolution.scale_factor());
483
484                        // Event
485                        input_events
486                            .touch_input
487                            .send(converters::convert_touch_input(touch, location));
488                    }
489                    WindowEvent::ReceivedCharacter(c) => {
490                        input_events.character_input.send(ReceivedCharacter {
491                            window: window_entity,
492                            char: c,
493                        });
494                    }
495                    WindowEvent::ScaleFactorChanged {
496                        scale_factor,
497                        new_inner_size,
498                    } => {
499                        window_events.window_backend_scale_factor_changed.send(
500                            WindowBackendScaleFactorChanged {
501                                window: window_entity,
502                                scale_factor,
503                            },
504                        );
505
506                        let prior_factor = window.resolution.scale_factor();
507                        window.resolution.set_scale_factor(scale_factor);
508                        let new_factor = window.resolution.scale_factor();
509
510                        if let Some(forced_factor) = window.resolution.scale_factor_override() {
511                            // If there is a scale factor override, then force that to be used
512                            // Otherwise, use the OS suggested size
513                            // We have already told the OS about our resize constraints, so
514                            // the new_inner_size should take those into account
515                            *new_inner_size =
516                                winit::dpi::LogicalSize::new(window.width(), window.height())
517                                    .to_physical::<u32>(forced_factor);
518                            // TODO: Should this not trigger a WindowsScaleFactorChanged?
519                        } else if approx::relative_ne!(new_factor, prior_factor) {
520                            // Trigger a change event if they are approximately different
521                            window_events.window_scale_factor_changed.send(
522                                WindowScaleFactorChanged {
523                                    window: window_entity,
524                                    scale_factor,
525                                },
526                            );
527                        }
528
529                        let new_logical_width = (new_inner_size.width as f64 / new_factor) as f32;
530                        let new_logical_height = (new_inner_size.height as f64 / new_factor) as f32;
531                        if approx::relative_ne!(window.width(), new_logical_width)
532                            || approx::relative_ne!(window.height(), new_logical_height)
533                        {
534                            window_events.window_resized.send(WindowResized {
535                                window: window_entity,
536                                width: new_logical_width,
537                                height: new_logical_height,
538                            });
539                        }
540                        window
541                            .resolution
542                            .set_physical_resolution(new_inner_size.width, new_inner_size.height);
543                    }
544                    WindowEvent::Focused(focused) => {
545                        // Component
546                        window.focused = focused;
547
548                        window_events.window_focused.send(WindowFocused {
549                            window: window_entity,
550                            focused,
551                        });
552                    }
553                    WindowEvent::DroppedFile(path_buf) => {
554                        file_drag_and_drop_events.send(FileDragAndDrop::DroppedFile {
555                            window: window_entity,
556                            path_buf,
557                        });
558                    }
559                    WindowEvent::HoveredFile(path_buf) => {
560                        file_drag_and_drop_events.send(FileDragAndDrop::HoveredFile {
561                            window: window_entity,
562                            path_buf,
563                        });
564                    }
565                    WindowEvent::HoveredFileCancelled => {
566                        file_drag_and_drop_events.send(FileDragAndDrop::HoveredFileCanceled {
567                            window: window_entity,
568                        });
569                    }
570                    WindowEvent::Moved(position) => {
571                        let position = ivec2(position.x, position.y);
572
573                        window.position.set(position);
574
575                        window_events.window_moved.send(WindowMoved {
576                            entity: window_entity,
577                            position,
578                        });
579                    }
580                    WindowEvent::Ime(event) => match event {
581                        event::Ime::Preedit(value, cursor) => {
582                            input_events.ime_input.send(Ime::Preedit {
583                                window: window_entity,
584                                value,
585                                cursor,
586                            });
587                        }
588                        event::Ime::Commit(value) => input_events.ime_input.send(Ime::Commit {
589                            window: window_entity,
590                            value,
591                        }),
592                        event::Ime::Enabled => input_events.ime_input.send(Ime::Enabled {
593                            window: window_entity,
594                        }),
595                        event::Ime::Disabled => input_events.ime_input.send(Ime::Disabled {
596                            window: window_entity,
597                        }),
598                    },
599                    _ => {}
600                }
601
602                if window.is_changed() {
603                    cache.window = window.clone();
604                }
605            }
606            event::Event::DeviceEvent {
607                event:
608                    DeviceEvent::MouseMotion {
609                        delta: (x, y),
610                    },
611                ..
612            } => {
613                let mut system_state: SystemState<EventWriter<MouseMotion>> =
614                    SystemState::new(&mut app.world);
615                let mut mouse_motion = system_state.get_mut(&mut app.world);
616
617                mouse_motion.send(MouseMotion {
618                    delta: Vec2::new(x as f32, y as f32),
619                });
620            }
621            event::Event::Suspended => {
622                winit_state.active = false;
623                #[cfg(target_os = "android")]
624                {
625                    // Bevy doesn't support suspend/resume so we just exit
626                    // and Android will restart the application on resume
627                    // TODO: Save save some state and load on resume
628                    *control_flow = ControlFlow::Exit;
629                }
630            }
631            event::Event::Resumed => {
632                winit_state.active = true;
633            }
634            event::Event::MainEventsCleared => {
635                let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
636
637                let update = if winit_state.active {
638                    // True if _any_ windows are currently being focused
639                    let app_focused = window_focused_query.iter().any(|window| window.focused);
640                    match winit_config.update_mode(app_focused) {
641                        UpdateMode::Continuous
642                        | UpdateMode::Reactive {
643                            ..
644                        } => true,
645                        UpdateMode::ReactiveLowPower {
646                            ..
647                        } => {
648                            winit_state.low_power_event
649                                || winit_state.redraw_request_sent
650                                || winit_state.timeout_reached
651                        }
652                    }
653                } else {
654                    false
655                };
656
657                if update {
658                    winit_state.last_update = Instant::now();
659                    app.update();
660                }
661            }
662            Event::RedrawEventsCleared => {
663                {
664                    // Fetch from world
665                    let (winit_config, window_focused_query) = focused_window_state.get(&app.world);
666
667                    // True if _any_ windows are currently being focused
668                    let app_focused = window_focused_query.iter().any(|window| window.focused);
669
670                    let now = Instant::now();
671                    use UpdateMode::*;
672                    *control_flow = match winit_config.update_mode(app_focused) {
673                        Continuous => ControlFlow::Poll,
674                        Reactive {
675                            max_wait,
676                        }
677                        | ReactiveLowPower {
678                            max_wait,
679                        } => {
680                            if let Some(instant) = now.checked_add(*max_wait) {
681                                ControlFlow::WaitUntil(instant)
682                            } else {
683                                ControlFlow::Wait
684                            }
685                        }
686                    };
687                }
688
689                // This block needs to run after `app.update()` in `MainEventsCleared`. Otherwise,
690                // we won't be able to see redraw requests until the next event, defeating the
691                // purpose of a redraw request!
692                let mut redraw = false;
693                if let Some(app_redraw_events) = app.world.get_resource::<Events<RequestRedraw>>() {
694                    if redraw_event_reader.read(app_redraw_events).last().is_some() {
695                        *control_flow = ControlFlow::Poll;
696                        redraw = true;
697                    }
698                }
699
700                winit_state.redraw_request_sent = redraw;
701            }
702
703            _ => (),
704        }
705
706        if winit_state.active {
707            let (
708                commands,
709                mut new_windows,
710                created_window_writer,
711                vulkano_windows,
712                context,
713                settings,
714            ) = create_window_system_state.get_mut(&mut app.world);
715
716            // Responsible for creating new windows
717            create_window(
718                commands,
719                event_loop,
720                new_windows.iter_mut(),
721                created_window_writer,
722                vulkano_windows,
723                context,
724                settings,
725            );
726
727            create_window_system_state.apply(&mut app.world);
728        }
729    };
730
731    // If true, returns control from Winit back to the main Bevy loop
732    if return_from_run {
733        run_return(&mut event_loop, event_handler);
734    } else {
735        run(event_loop, event_handler);
736    }
737}
738
739#[cfg(feature = "gui")]
740pub fn begin_egui_frame_system(mut vulkano_windows: NonSendMut<BevyVulkanoWindows>) {
741    for (_, w) in vulkano_windows.windows.iter_mut() {
742        w.gui.begin_frame();
743    }
744}