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

Allow forcing WebGPU/WebGL on the web player, new command line argument to force graphics backend #4981

Merged
merged 13 commits into from
Feb 1, 2024
Merged
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 @@ -74,6 +74,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 @@ -201,7 +207,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 @@ -285,6 +302,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 @@ -639,6 +643,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