Skip to content

Commit

Permalink
Allow forcing WebGPU/WebGL on the web player, new command line argume…
Browse files Browse the repository at this point in the history
…nt to force graphics backend (#4981)

### What

* Fixes #4898

Bunch of related things that go in here:
* url parameter to force webgl/webgpu: `&renderer=webgpu` or
`&renderer=webgl`
* command line argument with similar syntax, can be used both for web
player and native e.g. `--renderer gl`, `--web-viewer --renderer webgpu`
* show webgpu/webgl url on crash
* ui action for reloading webpage with webgpu or webl
* updated trouble shooting docs on


Parsing of backend is actually fairly fuzzy but in docs I stick with
wgpu names. Front facing we talk usually about "renderer" but internally
we're more correct and call it "wgpu backend" which requires knowing a
bit more about how things work.



https://github.com/rerun-io/rerun/assets/1220815/ae83d405-2541-4cc5-8a8f-59478a2437e9

Screen on crash:

![image](https://github.com/rerun-io/rerun/assets/1220815/bf373567-02cf-4834-9b32-07d4d2e514e8)

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using newly built examples:
[app.rerun.io](https://app.rerun.io/pr/4981/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/4981/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[app.rerun.io](https://app.rerun.io/pr/4981/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG

- [PR Build Summary](https://build.rerun.io/pr/4981)
- [Docs
preview](https://rerun.io/preview/7d50a7f256f9144a5340c896b56d4e64c7beeb74/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/7d50a7f256f9144a5340c896b56d4e64c7beeb74/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
  • Loading branch information
Wumpf committed Feb 1, 2024
1 parent a1a4df2 commit df849d7
Show file tree
Hide file tree
Showing 17 changed files with 303 additions and 60 deletions.
3 changes: 3 additions & 0 deletions .cargo/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ rerun-release = "run --package rerun-cli --no-default-features --features native
# `cargo rerun-web` is short a convenient shorthand for building & starting the web viewer.
rerun-web = "run --package rerun-cli --no-default-features --features web_viewer -- --web-viewer"

# `cargo rerun-release-web` is short a convenient shorthand for building & starting the web viewer in release mode.
rerun-release-web = "run --package rerun-cli --no-default-features --features web_viewer --release -- --web-viewer"

# Run the codegen. Optionally pass `--profile` to it.
# NOTE: there are several CI jobs with `command: codegen` with the cargo action
codegen = "run --package re_types_builder --"
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions crates/re_renderer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,72 @@ pub fn supported_backends() -> wgpu::Backends {
wgpu::Backends::GL | wgpu::Backends::BROWSER_WEBGPU
}
}

/// Generous parsing of a graphics backend string.
pub fn parse_graphics_backend(backend: &str) -> Option<wgpu::Backend> {
match backend.to_lowercase().as_str() {
// "vulcan" is a common typo that we just swallow. We know what you mean ;)
"vulcan" | "vulkan" | "vk" => Some(wgpu::Backend::Vulkan),

"metal" | "apple" | "mtl" => Some(wgpu::Backend::Metal),

"dx12" | "dx" | "d3d" | "d3d12" | "directx" => Some(wgpu::Backend::Dx12),

// We don't want to lie - e.g. `webgl1` should not work!
// This means that `gles`/`gles3` stretches it a bit, but it's still close enough.
// Similarly, we accept both `webgl` & `opengl` on each desktop & web.
// This is a bit dubious but also too much hassle to forbid.
"webgl2" | "webgl" | "opengl" | "gles" | "gles3" | "gl" => Some(wgpu::Backend::Gl),

"browserwebgpu" | "webgpu" => Some(wgpu::Backend::BrowserWebGpu),

_ => None,
}
}

/// Validates that the given backend is applicable for the current build.
///
/// This is meant as a sanity check of first resort.
/// There are still many other reasons why a backend may not work on a given platform/build combination.
pub fn validate_graphics_backend_applicability(backend: wgpu::Backend) -> Result<(), &'static str> {
match backend {
wgpu::Backend::Empty => {
// This should never happen.
return Err("Cannot run with empty backend.");
}
wgpu::Backend::Vulkan => {
// Through emulation and build configs Vulkan may work everywhere except the web.
if cfg!(target_arch = "wasm32") {
return Err("Can only run with WebGL or WebGPU on the web.");
}
}
wgpu::Backend::Metal => {
if cfg!(target_arch = "wasm32") {
return Err("Can only run with WebGL or WebGPU on the web.");
}
if cfg!(target_os = "linux") || cfg!(target_os = "windows") {
return Err("Cannot run with DX12 backend on Linux & Windows.");
}
}
wgpu::Backend::Dx12 => {
// We don't have DX12 enabled right now, but someone could.
// TODO(wgpu#5166): But if we get this wrong we might crash.
// TODO(wgpu#5167): And we also can't query the config.
return Err("DX12 backend is currently not supported.");
}
wgpu::Backend::Gl => {
// Using Angle Mac might actually run GL, but we don't enable this.
// TODO(wgpu#5166): But if we get this wrong we might crash.
// TODO(wgpu#5167): And we also can't query the config.
if cfg!(target_os = "macos") {
return Err("Cannot run with GL backend on Mac.");
}
}
wgpu::Backend::BrowserWebGpu => {
if !cfg!(target_arch = "wasm32") {
return Err("Cannot run with WebGPU backend on native application.");
}
}
}
Ok(())
}
6 changes: 5 additions & 1 deletion crates/re_sdk/src/web_viewer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,14 +79,18 @@ impl WebViewerSink {
pub async fn host_web_viewer(
bind_ip: String,
web_port: WebViewerServerPort,
force_wgpu_backend: Option<String>,
open_browser: bool,
source_url: String,
) -> anyhow::Result<()> {
let web_server = re_web_viewer_server::WebViewerServer::new(&bind_ip, web_port)?;
let http_web_viewer_url = web_server.server_url();
let web_server_handle = web_server.serve();

let viewer_url = format!("{http_web_viewer_url}?url={source_url}");
let mut viewer_url = format!("{http_web_viewer_url}?url={source_url}");
if let Some(force_graphics) = force_wgpu_backend {
viewer_url = format!("{viewer_url}&renderer={force_graphics}");
}

re_log::info!("Hosting a web-viewer at {viewer_url}");
if open_browser {
Expand Down
24 changes: 23 additions & 1 deletion crates/re_ui/src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ pub enum UICommand {

#[cfg(target_arch = "wasm32")]
CopyDirectLink,

// Graphics options:
#[cfg(target_arch = "wasm32")]
RestartWithWebGl,
#[cfg(target_arch = "wasm32")]
RestartWithWebGpu,
}

impl UICommand {
Expand Down Expand Up @@ -208,7 +214,18 @@ impl UICommand {
Self::CopyDirectLink => (
"Copy direct link",
"Copy a link to the viewer with the URL parameter set to the current .rrd data source."
)
),

#[cfg(target_arch = "wasm32")]
Self::RestartWithWebGl => (
"Restart with WebGL",
"Reloads the webpage and force WebGL for rendering. All data will be lost."
),
#[cfg(target_arch = "wasm32")]
Self::RestartWithWebGpu => (
"Restart with WebGPU",
"Reloads the webpage and force WebGPU for rendering. All data will be lost."
),
}
}

Expand Down Expand Up @@ -294,6 +311,11 @@ impl UICommand {

#[cfg(target_arch = "wasm32")]
Self::CopyDirectLink => None,

#[cfg(target_arch = "wasm32")]
Self::RestartWithWebGl => None,
#[cfg(target_arch = "wasm32")]
Self::RestartWithWebGpu => None,
}
}

Expand Down
8 changes: 8 additions & 0 deletions crates/re_viewer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,15 @@ wgpu.workspace = true

# web dependencies:
[target.'cfg(target_arch = "wasm32")'.dependencies]
wasm-bindgen.workspace = true
wasm-bindgen-futures.workspace = true
web-sys = { workspace = true, features = [
'Location',
'Url',
'UrlSearchParams',
'Window',
] }


[build-dependencies]
re_build_tools.workspace = true
18 changes: 18 additions & 0 deletions crates/re_viewer/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ pub struct StartupOptions {
pub resolution_in_points: Option<[f32; 2]>,

pub skip_welcome_screen: bool,

/// Forces wgpu backend to use the specified graphics API.
pub force_wgpu_backend: Option<String>,
}

impl Default for StartupOptions {
Expand All @@ -75,6 +78,7 @@ impl Default for StartupOptions {
resolution_in_points: None,

skip_welcome_screen: false,
force_wgpu_backend: None,
}
}
}
Expand Down Expand Up @@ -657,6 +661,20 @@ impl App {
UICommand::CopyDirectLink => {
self.run_copy_direct_link_command(store_context);
}

#[cfg(target_arch = "wasm32")]
UICommand::RestartWithWebGl => {
if crate::web_tools::set_url_parameter_and_refresh("renderer", "webgl").is_err() {
re_log::error!("Failed to set URL parameter `renderer=webgl` & refresh page.");
}
}

#[cfg(target_arch = "wasm32")]
UICommand::RestartWithWebGpu => {
if crate::web_tools::set_url_parameter_and_refresh("renderer", "webgpu").is_err() {
re_log::error!("Failed to set URL parameter `renderer=webgpu` & refresh page.");
}
}
}
}

Expand Down
28 changes: 26 additions & 2 deletions crates/re_viewer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub use native::{run_native_app, run_native_viewer_with_messages};
#[cfg(target_arch = "wasm32")]
mod web;

#[cfg(target_arch = "wasm32")]
mod web_tools;

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

/// Information about this version of the crate.
Expand Down Expand Up @@ -129,8 +132,29 @@ impl AppEnvironment {

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

pub(crate) fn wgpu_options() -> egui_wgpu::WgpuConfiguration {
fn supported_graphics_backends(force_wgpu_backend: Option<String>) -> wgpu::Backends {
if let Some(force_wgpu_backend) = force_wgpu_backend {
if let Some(backend) = re_renderer::config::parse_graphics_backend(&force_wgpu_backend) {
if let Err(err) = re_renderer::config::validate_graphics_backend_applicability(backend)
{
re_log::error!("Failed to force rendering backend parsed from {force_wgpu_backend:?}: {err}\nUsing default backend instead.");
re_renderer::config::supported_backends()
} else {
re_log::info!("Forcing graphics backend to {backend:?}.");
backend.into()
}
} else {
re_log::error!("Failed to parse rendering backend string {force_wgpu_backend:?}. Using default backend instead.");
re_renderer::config::supported_backends()
}
} else {
re_renderer::config::supported_backends()
}
}

pub(crate) fn wgpu_options(force_wgpu_backend: Option<String>) -> egui_wgpu::WgpuConfiguration {
re_tracing::profile_function!();

egui_wgpu::WgpuConfiguration {
// When running wgpu on native debug builds, we want some extra control over how
// and when a poisoned surface gets recreated.
Expand All @@ -150,7 +174,7 @@ pub(crate) fn wgpu_options() -> egui_wgpu::WgpuConfiguration {
egui_wgpu::SurfaceErrorAction::SkipFrame
}
}),
supported_backends: re_renderer::config::supported_backends(),
supported_backends: supported_graphics_backends(force_wgpu_backend),
device_descriptor: std::sync::Arc::new(|adapter| re_renderer::config::DeviceCaps::from_adapter(adapter).device_descriptor()),
..Default::default()
}
Expand Down
26 changes: 17 additions & 9 deletions crates/re_viewer/src/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ type AppCreator =
Box<dyn FnOnce(&eframe::CreationContext<'_>, re_ui::ReUi) -> Box<dyn eframe::App>>;

// NOTE: the name of this function is hard-coded in `crates/rerun/src/crash_handler.rs`!
pub fn run_native_app(app_creator: AppCreator) -> eframe::Result<()> {
let native_options = eframe_options();
pub fn run_native_app(
app_creator: AppCreator,
force_wgpu_backend: Option<String>,
) -> eframe::Result<()> {
let native_options = eframe_options(force_wgpu_backend);

let window_title = "Rerun Viewer";
eframe::run_native(
Expand All @@ -21,7 +24,7 @@ pub fn run_native_app(app_creator: AppCreator) -> eframe::Result<()> {
)
}

pub fn eframe_options() -> eframe::NativeOptions {
pub fn eframe_options(force_wgpu_backend: Option<String>) -> eframe::NativeOptions {
re_tracing::profile_function!();
eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
Expand All @@ -40,7 +43,7 @@ pub fn eframe_options() -> eframe::NativeOptions {
default_theme: eframe::Theme::Dark,

renderer: eframe::Renderer::Wgpu,
wgpu_options: crate::wgpu_options(),
wgpu_options: crate::wgpu_options(force_wgpu_backend),
depth_buffer: 0,
multisampling: 0, // the 3D views do their own MSAA

Expand Down Expand Up @@ -92,9 +95,14 @@ pub fn run_native_viewer_with_messages(
for log_msg in log_messages {
tx.send(log_msg).ok();
}
run_native_app(Box::new(move |cc, re_ui| {
let mut app = crate::App::new(build_info, &app_env, startup_options, re_ui, cc.storage);
app.add_receiver(rx);
Box::new(app)
}))

let force_wgpu_backend = startup_options.force_wgpu_backend.clone();
run_native_app(
Box::new(move |cc, re_ui| {
let mut app = crate::App::new(build_info, &app_env, startup_options, re_ui, cc.storage);
app.add_receiver(rx);
Box::new(app)
}),
force_wgpu_backend,
)
}
24 changes: 22 additions & 2 deletions crates/re_viewer/src/ui/rerun_menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ impl App {
&self.command_sender,
&self.re_ui,
ui,
frame,
&mut self.state.app_options,
);
});
Expand Down Expand Up @@ -236,7 +237,7 @@ fn render_state_ui(ui: &mut egui::Ui, render_state: &egui_wgpu::RenderState) {

egui::Grid::new("adapter_info").show(ui, |ui| {
ui.label("Backend");
ui.label(format!("{backend:?}"));
ui.label(backend.to_str()); // TODO(wgpu#5170): Use std::fmt::Display for backend.
ui.end_row();

ui.label("Device Type");
Expand Down Expand Up @@ -274,7 +275,8 @@ fn render_state_ui(ui: &mut egui::Ui, render_state: &egui_wgpu::RenderState) {

let wgpu_adapter_ui = |ui: &mut egui::Ui, adapter: &eframe::wgpu::Adapter| {
let info = &adapter.get_info();
ui.label(format!("{:?}", info.backend)).on_hover_ui(|ui| {
// TODO(wgpu#5170): Use std::fmt::Display for backend.
ui.label(info.backend.to_str()).on_hover_ui(|ui| {
wgpu_adapter_details_ui(ui, adapter);
});
};
Expand Down Expand Up @@ -303,6 +305,7 @@ fn options_menu_ui(
command_sender: &re_viewer_context::CommandSender,
re_ui: &ReUi,
ui: &mut egui::Ui,
frame: &eframe::Frame,
app_options: &mut re_viewer_context::AppOptions,
) {
re_ui
Expand Down Expand Up @@ -338,6 +341,23 @@ fn options_menu_ui(
ui.label("Experimental features:");
experimental_feature_ui(command_sender, re_ui, ui, app_options);
}

if let Some(_backend) = frame
.wgpu_render_state()
.map(|state| state.adapter.get_info().backend)
{
// Adapter switching only implemented for web so far.
// For native it's less well defined since the application may be embedded in another application that reads arguments differently.
#[cfg(target_arch = "wasm32")]
{
ui.add_space(SPACING);
if _backend == wgpu::Backend::BrowserWebGpu {
UICommand::RestartWithWebGl.menu_button_ui(ui, command_sender);
} else {
UICommand::RestartWithWebGpu.menu_button_ui(ui, command_sender);
}
}
}
}

fn experimental_feature_ui(
Expand Down

0 comments on commit df849d7

Please sign in to comment.