Skip to content
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

Better crash reports on Web + WebGPU support detection #1975

Merged
merged 6 commits into from
May 2, 2023
Merged
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
2 changes: 0 additions & 2 deletions crates/re_viewer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ pub use misc::profiler::Profiler;

#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_arch = "wasm32")]
pub use web::start;

// ---------------------------------------------------------------------------

Expand Down
242 changes: 139 additions & 103 deletions crates/re_viewer/src/web.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use eframe::wasm_bindgen::{self, prelude::*};
use eframe::{
wasm_bindgen::{self, prelude::*},
web::AppRunnerRef,
};

use std::sync::Arc;

use re_memory::AccountingAllocator;
Expand All @@ -7,111 +11,143 @@ use re_memory::AccountingAllocator;
static GLOBAL: AccountingAllocator<std::alloc::System> =
AccountingAllocator::new(std::alloc::System);

/// This is the entry-point for all the Wasm.
///
/// This is called once from the HTML.
/// It loads the app, installs some callbacks, then returns.
/// The `url` is an optional URL to either an .rrd file over http, or a Rerun WebSocket server.
#[wasm_bindgen]
pub async fn start(
canvas_id: &str,
url: Option<String>,
) -> std::result::Result<(), eframe::wasm_bindgen::JsValue> {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();

re_log::setup_web_logging();

let web_options = eframe::WebOptions {
follow_system_theme: false,
default_theme: eframe::Theme::Dark,
wgpu_options: crate::wgpu_options(),
depth_buffer: 0,
};

eframe::start_web(
canvas_id,
web_options,
Box::new(move |cc| {
let build_info = re_build_info::build_info!();
let app_env = crate::AppEnvironment::Web;
let persist_state = get_persist_state(&cc.integration_info);
let startup_options = crate::StartupOptions {
memory_limit: re_memory::MemoryLimit {
// On wasm32 we only have 4GB of memory to play around with.
limit: Some(2_500_000_000),
},
persist_state,
};
let re_ui = crate::customize_eframe(cc);
let url = url.unwrap_or_else(|| get_url(&cc.integration_info));

match categorize_uri(url) {
EndpointCategory::HttpRrd(url) => {
// Download an .rrd file over http:
let (tx, rx) =
re_smart_channel::smart_channel(re_smart_channel::Source::RrdHttpStream {
url: url.clone(),
});
let egui_ctx = cc.egui_ctx.clone();
re_log_encoding::stream_rrd_from_http::stream_rrd_from_http(
url,
Arc::new(move |msg| {
egui_ctx.request_repaint(); // wake up ui thread
tx.send(msg).ok();
}),
);

Box::new(crate::App::from_receiver(
build_info,
&app_env,
startup_options,
re_ui,
cc.storage,
rx,
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
))
}
EndpointCategory::WebEventListener => {
// Process an rrd when it's posted via `window.postMessage`
let (tx, rx) = re_smart_channel::smart_channel(
re_smart_channel::Source::RrdWebEventListener,
);
let egui_ctx = cc.egui_ctx.clone();
re_log_encoding::stream_rrd_from_http::stream_rrd_from_event_listener(
Arc::new(move |msg| {
egui_ctx.request_repaint(); // wake up ui thread
tx.send(msg).ok();
}),
);

Box::new(crate::App::from_receiver(
build_info,
&app_env,
startup_options,
re_ui,
cc.storage,
rx,
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
))
}
EndpointCategory::WebSocket(url) => {
// Connect to a Rerun server over WebSockets.
Box::new(crate::RemoteViewerApp::new(
build_info,
app_env,
startup_options,
re_ui,
cc.storage,
url,
))
pub struct WebHandle {
runner: AppRunnerRef,
}

#[wasm_bindgen]
impl WebHandle {
/// This is the entry-point for all the Wasm.
///
/// This is called once from the HTML.
/// It loads the app, installs some callbacks, then returns.
/// The `url` is an optional URL to either an .rrd file over http, or a Rerun WebSocket server.
#[wasm_bindgen(constructor)]
pub async fn new(
canvas_id: &str,
url: Option<String>,
) -> Result<WebHandle, wasm_bindgen::JsValue> {
// Make sure panics are logged using `console.error`.
console_error_panic_hook::set_once();

re_log::setup_web_logging();

let web_options = eframe::WebOptions {
follow_system_theme: false,
default_theme: eframe::Theme::Dark,
wgpu_options: crate::wgpu_options(),
depth_buffer: 0,
};

let runner = eframe::start_web(
canvas_id,
web_options,
Box::new(move |cc| {
let build_info = re_build_info::build_info!();
let app_env = crate::AppEnvironment::Web;
let persist_state = get_persist_state(&cc.integration_info);
let startup_options = crate::StartupOptions {
memory_limit: re_memory::MemoryLimit {
// On wasm32 we only have 4GB of memory to play around with.
limit: Some(2_500_000_000),
},
persist_state,
};
let re_ui = crate::customize_eframe(cc);
let url = url.unwrap_or_else(|| get_url(&cc.integration_info));

match categorize_uri(url) {
EndpointCategory::HttpRrd(url) => {
// Download an .rrd file over http:
let (tx, rx) = re_smart_channel::smart_channel(
re_smart_channel::Source::RrdHttpStream { url: url.clone() },
);
let egui_ctx = cc.egui_ctx.clone();
re_log_encoding::stream_rrd_from_http::stream_rrd_from_http(
url,
Arc::new(move |msg| {
egui_ctx.request_repaint(); // wake up ui thread
tx.send(msg).ok();
}),
);

Box::new(crate::App::from_receiver(
build_info,
&app_env,
startup_options,
re_ui,
cc.storage,
rx,
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
))
}
EndpointCategory::WebEventListener => {
// Process an rrd when it's posted via `window.postMessage`
let (tx, rx) = re_smart_channel::smart_channel(
re_smart_channel::Source::RrdWebEventListener,
);
let egui_ctx = cc.egui_ctx.clone();
re_log_encoding::stream_rrd_from_http::stream_rrd_from_event_listener(
Arc::new(move |msg| {
egui_ctx.request_repaint(); // wake up ui thread
tx.send(msg).ok();
}),
);

Box::new(crate::App::from_receiver(
build_info,
&app_env,
startup_options,
re_ui,
cc.storage,
rx,
std::sync::Arc::new(std::sync::atomic::AtomicBool::new(false)),
))
}
EndpointCategory::WebSocket(url) => {
// Connect to a Rerun server over WebSockets.
Box::new(crate::RemoteViewerApp::new(
build_info,
app_env,
startup_options,
re_ui,
cc.storage,
url,
))
}
}
}
}),
)
.await?;
}),
)
.await?;

Ok(WebHandle { runner })
}

#[wasm_bindgen]
pub fn destroy(&self) {
self.runner.destroy();
}

#[wasm_bindgen]
pub fn has_panicked(&self) -> bool {
self.runner.panic_summary().is_some()
}

#[wasm_bindgen]
pub fn panic_message(&self) -> Option<String> {
self.runner.panic_summary().map(|s| s.message())
}

Ok(())
#[wasm_bindgen]
pub fn panic_callstack(&self) -> Option<String> {
self.runner.panic_summary().map(|s| s.callstack())
}
}

#[wasm_bindgen]
pub fn is_webgpu_build() -> bool {
!cfg!(feature = "webgl")
}

enum EndpointCategory {
Expand Down
66 changes: 61 additions & 5 deletions web_viewer/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,65 @@


function on_wasm_loaded() {
console.debug("wasm loaded. starting app…");
// WebGPU version is currently only supported on browsers with WebGPU support, there is no dynamic fallback to WebGL.
if (wasm_bindgen.is_webgpu_build() && typeof navigator.gpu === 'undefined') {
console.debug("`navigator.gpu` is undefined. This indicates lack of WebGPU support.");
document.getElementById("center_text").innerHTML = `
<p>
Missing WebGPU support.
</p>
<p style="font-size:18px">
This version of Rerun requires WebGPU support which is not available in your browser.
Either try a different browser or use the WebGL version of Rerun.
</p>`;
return;
}

console.debug("Wasm loaded. Starting app…");

// This call installs a bunch of callbacks and then returns:
wasm_bindgen.start("the_canvas_id", determine_url());
let handle = new wasm_bindgen.WebHandle("the_canvas_id", determine_url());
handle.then(on_app_started).catch(on_wasm_error);
}

function on_app_started(handle) {
// Call `handle.destroy()` to stop. Uncomment to quick result:
// setTimeout(() => { handle.destroy(); handle.free()) }, 2000)

console.debug("app started.");
document.getElementById("center_text").remove();
console.debug("App started.");
document.getElementById("center_text").innerHTML = '';

if (window.location !== window.parent.location) {
window.parent.postMessage("READY", "*");
}

function check_for_panic() {
if (handle.has_panicked()) {
console.error("Rerun has crashed");

// Rerun already logs the panic message and callstack, but you
// can access them like this if you want to show them in the html:
// console.error(`${handle.panic_message()}`);
// console.error(`${handle.panic_callstack()}`);

document.getElementById("the_canvas_id").remove();
document.getElementById("center_text").innerHTML = `
<p>
Rerun has crashed.
</p>
<p style="font-size:14px">
See the console for details.
</p>
<p style="font-size:14px">
Reload the page to try again.
</p>`;
} else {
let delay_ms = 1000;
setTimeout(check_for_panic, delay_ms);
}
}

check_for_panic();
}

function determine_url() {
Expand Down Expand Up @@ -215,6 +263,14 @@

function on_wasm_error(error) {
console.error("Failed to start: " + error);

let render_backend_name = "WebGPU/WebGL";
try {
render_backend_name = wasm_bindgen.is_webgpu_build() ? "WebGPU" : "WebGL";
} catch (e) {
// loading the wasm probably failed.
}

document.getElementById("center_text").innerHTML = `
<p>
An error occurred during loading:
Expand All @@ -223,7 +279,7 @@
${error}
</p>
<p style="font-size:14px">
Make sure you use a modern browser with WebGL and Wasm enabled.
Make sure you use a modern browser with ${render_backend_name} and Wasm enabled.
</p>`;
}
</script>
Expand Down
Loading