From 1a8615a0284220117d82f465602aa24d60ef1622 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Fri, 14 Mar 2025 21:42:56 -0400 Subject: [PATCH 1/2] web: Reload with canvas when webglcontextlost and 8+ instances exist --- web/Cargo.toml | 2 +- .../core/src/internal/player/inner.tsx | 16 ++++++++++++++ web/src/lib.rs | 22 ++++++++++++++++++- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/web/Cargo.toml b/web/Cargo.toml index c71899eea5588..e0920a73e54c9 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -74,7 +74,7 @@ features = [ "EventTarget", "GainNode", "Headers", "HtmlCanvasElement", "HtmlDocument", "HtmlElement", "HtmlFormElement", "HtmlInputElement", "HtmlTextAreaElement", "KeyboardEvent", "Location", "PointerEvent", "Request", "RequestInit", "Response", "Storage", "WheelEvent", "Window", "ReadableStream", "RequestCredentials", - "Url", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton" + "Url", "WebGlContextEvent", "Clipboard", "FocusEvent", "ShadowRoot", "Gamepad", "GamepadButton" ] [package.metadata.cargo-machete] diff --git a/web/packages/core/src/internal/player/inner.tsx b/web/packages/core/src/internal/player/inner.tsx index d8af904f19b85..1611bd5fecd84 100644 --- a/web/packages/core/src/internal/player/inner.tsx +++ b/web/packages/core/src/internal/player/inner.tsx @@ -5,6 +5,7 @@ import { DataLoadOptions, DEFAULT_CONFIG, NetworkingAccessMode, + RenderBackend, UnmuteOverlay, URLLoadOptions, WindowMode, @@ -793,6 +794,21 @@ export class InnerPlayer { } } + /** + * Reloads the player, as if you called {@link RufflePlayer.load} with the same config as the last time it was called, but setting the preferredRenderer to "canvas". + * + * If this player has never been loaded, this method will return an error. + */ + async reloadWithCanvasRenderer(): Promise { + if (this.loadedConfig) { + const combinedOptions = { ...this.loadedConfig, preferredRenderer: RenderBackend.Canvas}; + await this.load(combinedOptions); + } else { + throw new Error("Cannot reload if load wasn't first called"); + } + } + + /** * Loads a specified movie into this player. * diff --git a/web/src/lib.rs b/web/src/lib.rs index b33ba182320c6..babb293afef80 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -39,7 +39,7 @@ use wasm_bindgen::prelude::*; use web_sys::{ AddEventListenerOptions, ClipboardEvent, Element, Event, EventTarget, FocusEvent, Gamepad as WebGamepad, GamepadButton as WebGamepadButton, HtmlCanvasElement, HtmlElement, - KeyboardEvent, Node, PointerEvent, ShadowRoot, WheelEvent, Window, + KeyboardEvent, Node, PointerEvent, ShadowRoot, WebGlContextEvent, WheelEvent, Window, }; static RUFFLE_GLOBAL_PANIC: Once = Once::new(); @@ -137,6 +137,7 @@ struct RuffleInstance { focusin_callback: Option>, focusout_callback: Option>, focus_on_press_callback: Option>, + webglcontextlost_callback: Option>, has_focus: bool, trace_observer: Rc>, log_subscriber: Arc>, @@ -193,6 +194,9 @@ extern "C" { #[wasm_bindgen(method, js_name = "suppressContextMenu")] fn suppress_context_menu(this: &JavascriptPlayer); + + #[wasm_bindgen(method, js_name = "reloadWithCanvasRenderer")] + fn reload_with_canvas_renderer(this: &JavascriptPlayer); } #[derive(Debug, Clone)] @@ -505,6 +509,7 @@ impl RuffleHandle { focusin_callback: None, focusout_callback: None, focus_on_press_callback: None, + webglcontextlost_callback: None, timestamp: None, has_focus: false, trace_observer: player.trace_observer, @@ -771,6 +776,21 @@ impl RuffleHandle { }, )); + // Create webglcontextlost handler. + instance.webglcontextlost_callback = Some(JsCallback::register( + &player.canvas, + "webglcontextlost", + false, + move |_js_event: WebGlContextEvent| { + INSTANCES.with(|instances| { + if instances.borrow().len() >= 8 { + let _ = ruffle.remove_instance(); + js_player.reload_with_canvas_renderer(); + } + }); + }, + )); + instance.unload_callback = Some(JsCallback::register(&window, "unload", false, move |_| { let _ = ruffle.with_core_mut(|core| { From 5404abe2563fcc9bb292d6dcf3159527493689d9 Mon Sep 17 00:00:00 2001 From: Daniel Jacobs Date: Tue, 18 Mar 2025 12:41:26 -0400 Subject: [PATCH 2/2] web: Don't allow repeated reloads with the canvas renderer --- web/packages/core/src/internal/player/inner.tsx | 7 +++++-- web/packages/core/texts/en-US/messages.ftl | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/packages/core/src/internal/player/inner.tsx b/web/packages/core/src/internal/player/inner.tsx index 1611bd5fecd84..4e49bce28371d 100644 --- a/web/packages/core/src/internal/player/inner.tsx +++ b/web/packages/core/src/internal/player/inner.tsx @@ -798,11 +798,14 @@ export class InnerPlayer { * Reloads the player, as if you called {@link RufflePlayer.load} with the same config as the last time it was called, but setting the preferredRenderer to "canvas". * * If this player has never been loaded, this method will return an error. + * If this player was already trying to use the canvas render, this method will panic. */ - async reloadWithCanvasRenderer(): Promise { - if (this.loadedConfig) { + protected async reloadWithCanvasRenderer(): Promise { + if (this.loadedConfig && this.loadedConfig.preferredRenderer !== RenderBackend.Canvas) { const combinedOptions = { ...this.loadedConfig, preferredRenderer: RenderBackend.Canvas}; await this.load(combinedOptions); + } else if (this.loadedConfig) { + this.panic(new Error(text("error-canvas-reload"))); } else { throw new Error("Cannot reload if load wasn't first called"); } diff --git a/web/packages/core/texts/en-US/messages.ftl b/web/packages/core/texts/en-US/messages.ftl index 4534186eadaf6..2239200719ed9 100644 --- a/web/packages/core/texts/en-US/messages.ftl +++ b/web/packages/core/texts/en-US/messages.ftl @@ -24,6 +24,7 @@ clipboard-message-description = clipboard-message-copy = { " " } for copy clipboard-message-cut = { " " } for cut clipboard-message-paste = { " " } for paste +error-canvas-reload = Cannot reload with the canvas renderer when the canvas renderer is already in use. error-file-protocol = It appears you are running Ruffle on the "file:" protocol. This doesn't work as browsers block many features from working for security reasons.