Skip to content

fix: avoid wasm panic without GL context (WebGL2 only) #12690

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion crates/bevy_app/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ pub struct App {
/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
/// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html
pub(crate) runner: RunnerFn,

run_sub_apps: bool,
}

impl Debug for App {
Expand Down Expand Up @@ -125,6 +127,7 @@ impl App {
main: SubApp::new(),
sub_apps: HashMap::new(),
},
run_sub_apps: true,
runner: Box::new(run_once),
}
}
Expand All @@ -135,7 +138,9 @@ impl App {
panic!("App::update() was called while a plugin was building.");
}

self.sub_apps.update();
if self.run_sub_apps {
self.sub_apps.update();
}
}

/// Runs the [`App`] by calling its [runner](Self::set_runner).
Expand Down Expand Up @@ -663,6 +668,16 @@ impl App {
self.sub_apps.sub_apps.remove(&label.intern())
}

/// Resumes sub apps
pub fn resume_sub_apps(&mut self) {
self.run_sub_apps = true;
}

/// Pauses sub apps from running.
pub fn pause_sub_apps(&mut self) {
self.run_sub_apps = false;
}

/// Inserts a new `schedule` under the provided `label`, overwriting any existing
/// schedule with the same label.
pub fn add_schedule(&mut self, schedule: Schedule) -> &mut Self {
Expand Down
2 changes: 2 additions & 0 deletions crates/bevy_internal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ webgl = [
"bevy_render?/webgl",
"bevy_gizmos?/webgl",
"bevy_sprite?/webgl",
"bevy_winit?/webgl",
]

webgpu = [
Expand All @@ -123,6 +124,7 @@ webgpu = [
"bevy_render?/webgpu",
"bevy_gizmos?/webgpu",
"bevy_sprite?/webgpu",
"bevy_winit?/webgpu",
]

# enable systems that allow for automated testing on CI
Expand Down
7 changes: 1 addition & 6 deletions crates/bevy_time/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub mod prelude {
use bevy_app::{prelude::*, RunFixedMainLoop};
use bevy_ecs::event::{signal_event_update_system, EventRegistry, ShouldUpdateEvents};
use bevy_ecs::prelude::*;
use bevy_utils::{tracing::warn, Duration, Instant};
use bevy_utils::{Duration, Instant};
pub use crossbeam_channel::TrySendError;
use crossbeam_channel::{Receiver, Sender};

Expand Down Expand Up @@ -116,17 +116,12 @@ pub fn time_system(
mut time: ResMut<Time>,
update_strategy: Res<TimeUpdateStrategy>,
time_recv: Option<Res<TimeReceiver>>,
mut has_received_time: Local<bool>,
) {
let new_time = if let Some(time_recv) = time_recv {
// TODO: Figure out how to handle this when using pipelined rendering.
if let Ok(new_time) = time_recv.0.try_recv() {
*has_received_time = true;
new_time
} else {
if *has_received_time {
warn!("time_system did not receive the time from the render world! Calculations depending on the time may be incorrect.");
}
Instant::now()
}
} else {
Expand Down
13 changes: 13 additions & 0 deletions crates/bevy_window/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -413,3 +413,16 @@ impl AppLifecycle {
}
}
}

/// A window event that is sent whenever a window's GL context is lost.
#[derive(Event, Debug, Clone, PartialEq, Reflect)]
#[reflect(Debug, PartialEq)]
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
reflect(Serialize, Deserialize)
)]
pub struct WindowGlContextLost {
/// Window that lose context.
pub window: Entity,
}
1 change: 1 addition & 0 deletions crates/bevy_window/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl Plugin for WindowPlugin {
.add_event::<FileDragAndDrop>()
.add_event::<WindowMoved>()
.add_event::<WindowThemeChanged>()
.add_event::<WindowGlContextLost>()
.add_event::<AppLifecycle>();

if let Some(primary_window) = &self.primary_window {
Expand Down
6 changes: 4 additions & 2 deletions crates/bevy_winit/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]

[features]
accesskit_unix = ["accesskit_winit/accesskit_unix", "accesskit_winit/async-io"]
serialize = ["serde"]
trace = []
wayland = ["winit/wayland", "winit/wayland-csd-adwaita"]
webgl = []
webgpu = []
x11 = ["winit/x11"]
accesskit_unix = ["accesskit_winit/accesskit_unix", "accesskit_winit/async-io"]
serialize = ["serde"]

[dependencies]
# bevy
Expand Down
44 changes: 44 additions & 0 deletions crates/bevy_winit/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,47 @@ impl<T: Event> ApplicationHandler<T> for WinitAppRunnerState<T> {
}
}

#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
{
use bevy_window::WindowGlContextLost;
use wasm_bindgen::JsCast;
use winit::platform::web::WindowExtWebSys;

fn get_gl_context(
window: &winit::window::Window,
) -> Option<web_sys::WebGl2RenderingContext> {
if let Some(canvas) = window.canvas() {
let context = canvas.get_context("webgl2").ok()??;

Some(context.dyn_into::<web_sys::WebGl2RenderingContext>().ok()?)
} else {
None
}
}

fn has_gl_context(window: &winit::window::Window) -> bool {
get_gl_context(window).map_or(false, |ctx| !ctx.is_context_lost())
}

let (_, windows) = focused_windows_state.get(&self.world());

if let Some((entity, _)) = windows.iter().next() {
let winit_windows = self.world().non_send_resource::<WinitWindows>();
let window = winit_windows.get_window(entity).expect("Window must exist");

if !has_gl_context(&window) {
self.winit_events
.send(WindowGlContextLost { window: entity });

// Pauses sub-apps to stop WGPU from crashing when there's no OpenGL context.
// Ensures that the rest of the systems in the main app keep running (i.e. physics).
self.app.pause_sub_apps();
} else {
self.app.resume_sub_apps();
}
}
}

// Notifies a lifecycle change
if self.lifecycle != self.previous_lifecycle {
self.previous_lifecycle = self.lifecycle;
Expand Down Expand Up @@ -719,6 +760,9 @@ impl<T: Event> WinitAppRunnerState<T> {
WinitEvent::KeyboardFocusLost(e) => {
world.send_event(e);
}
WinitEvent::WindowGlContextLost(e) => {
world.send_event(e);
}
}
}

Expand Down
12 changes: 10 additions & 2 deletions crates/bevy_winit/src/winit_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_window::{
AppLifecycle, CursorEntered, CursorLeft, CursorMoved, FileDragAndDrop, Ime, ReceivedCharacter,
RequestRedraw, WindowBackendScaleFactorChanged, WindowCloseRequested, WindowCreated,
WindowDestroyed, WindowFocused, WindowMoved, WindowOccluded, WindowResized,
WindowScaleFactorChanged, WindowThemeChanged,
WindowDestroyed, WindowFocused, WindowGlContextLost, WindowMoved, WindowOccluded,
WindowResized, WindowScaleFactorChanged, WindowThemeChanged,
};

/// Wraps all `bevy_window` events in a common enum.
Expand Down Expand Up @@ -65,6 +65,8 @@ pub enum WinitEvent {

KeyboardInput(KeyboardInput),
KeyboardFocusLost(KeyboardFocusLost),

WindowGlContextLost(WindowGlContextLost),
}

impl From<AppLifecycle> for WinitEvent {
Expand Down Expand Up @@ -202,8 +204,14 @@ impl From<KeyboardInput> for WinitEvent {
Self::KeyboardInput(e)
}
}

impl From<KeyboardFocusLost> for WinitEvent {
fn from(e: KeyboardFocusLost) -> Self {
Self::KeyboardFocusLost(e)
}
}
impl From<WindowGlContextLost> for WinitEvent {
fn from(e: WindowGlContextLost) -> Self {
Self::WindowGlContextLost(e)
}
}