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

eframe support for wgpu on the web #2107

Merged
merged 23 commits into from
Oct 5, 2022
Merged
Show file tree
Hide file tree
Changes from 22 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: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
- name: wasm-bindgen
uses: jetli/wasm-bindgen-action@v0.1.0
with:
version: "0.2.82"
version: "0.2.83"
- run: ./sh/wasm_bindgen_check.sh --skip-setup

cargo-deny:
Expand Down
21 changes: 11 additions & 10 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
* Fix: app state is now saved when user presses Cmd-Q on Mac ([#2013](https://github.com/emilk/egui/pull/2013)).
* Added `center` to `NativeOptions` and `monitor_size` to `WindowInfo` on desktop ([#2035](https://github.com/emilk/egui/pull/2035)).
* Web: you can access your application from JS using `AppRunner::app_mut`. See `crates/egui_demo_app/src/lib.rs`.
* Web: You can now use WebGL on top of `wgpu` by enabling the `wgpu` feature (and disabling `glow` via disabling default features) ([#2107](https://github.com/emilk/egui/pull/2107)).


## 0.19.0 - 2022-08-20
Expand Down
15 changes: 8 additions & 7 deletions crates/eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ screen_reader = [

## Use [`wgpu`](https://docs.rs/wgpu) for painting (via [`egui-wgpu`](https://github.com/emilk/egui/tree/master/crates/egui-wgpu)).
## This overrides the `glow` feature.
wgpu = ["dep:wgpu", "egui-wgpu"]
wgpu = ["dep:wgpu", "dep:egui-wgpu"]


[dependencies]
Expand All @@ -72,23 +72,23 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
document-features = { version = "0.2", optional = true }

egui_glow = { version = "0.19.0", path = "../egui_glow", optional = true, default-features = false }
egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true, features = ["winit"] }
glow = { version = "0.11", optional = true }
ron = { version = "0.8", optional = true, features = ["integer128"] }
serde = { version = "1", optional = true, features = ["derive"] }
wgpu = { version = "0.13", optional = true }

# -------------------------------------------
# native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
dark-light = { version = "0.2.1", optional = true }
egui-winit = { version = "0.19.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
glutin = { version = "0.29.0" }
winit = "0.27.2"

# optional native:
puffin = { version = "0.13", optional = true }
dark-light = { version = "0.2.1", optional = true }
directories-next = { version = "2", optional = true }
egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true, features = ["winit"] } # if wgpu is used, use it with winit
puffin = { version = "0.13", optional = true }
wgpu = { version = "0.13", optional = true }

# -------------------------------------------
# web:
Expand Down Expand Up @@ -142,6 +142,7 @@ web-sys = { version = "0.3.58", features = [
"Window",
] }

# optional
# feature screen_reader
# optional web:
egui-wgpu = { version = "0.19.0", path = "../egui-wgpu", optional = true } # if wgpu is used, use it without (!) winit
tts = { version = "0.20", optional = true } # Can't use 0.21-0.24 due to compilation problems on linux
wgpu = { version = "0.13", optional = true, features = ["webgl"] }
2 changes: 2 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,7 @@ pub struct WebOptions {
/// Which version of WebGl context to select
///
/// Default: [`WebGlContextOption::BestFirst`].
#[cfg(feature = "glow")]
pub webgl_context_option: WebGlContextOption,
}

Expand All @@ -464,6 +465,7 @@ impl Default for WebOptions {
Self {
follow_system_theme: true,
default_theme: Theme::Dark,
#[cfg(feature = "glow")]
webgl_context_option: WebGlContextOption::BestFirst,
}
}
Expand Down
4 changes: 2 additions & 2 deletions crates/eframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,12 +109,12 @@ pub use web_sys;
/// }
/// ```
#[cfg(target_arch = "wasm32")]
pub fn start_web(
pub async fn start_web(
canvas_id: &str,
web_options: WebOptions,
app_creator: AppCreator,
) -> Result<AppRunnerRef, wasm_bindgen::JsValue> {
let handle = web::start(canvas_id, web_options, app_creator)?;
let handle = web::start(canvas_id, web_options, app_creator).await?;

Ok(handle)
}
Expand Down
36 changes: 20 additions & 16 deletions crates/eframe/src/web/backend.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use super::{WebPainter, *};

use super::{web_painter::WebPainter, *};
use crate::epi;

use egui::{
Expand Down Expand Up @@ -162,7 +161,7 @@ fn test_parse_query() {
pub struct AppRunner {
pub(crate) frame: epi::Frame,
egui_ctx: egui::Context,
painter: WebPainter,
painter: ActiveWebPainter,
pub(crate) input: WebInput,
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
Expand All @@ -182,13 +181,14 @@ impl Drop for AppRunner {
}

impl AppRunner {
pub fn new(
pub async fn new(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<Self, JsValue> {
let painter =
WebPainter::new(canvas_id, web_options.webgl_context_option).map_err(JsValue::from)?; // fail early
let painter = ActiveWebPainter::new(canvas_id, &web_options)
.await
.map_err(JsValue::from)?;

let system_theme = if web_options.follow_system_theme {
super::system_theme()
Expand Down Expand Up @@ -216,19 +216,27 @@ impl AppRunner {
egui_ctx: egui_ctx.clone(),
integration_info: info.clone(),
storage: Some(&storage),

#[cfg(feature = "glow")]
gl: Some(painter.painter.gl().clone()),
#[cfg(feature = "wgpu")]
gl: Some(painter.gl().clone()),

#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
});

let frame = epi::Frame {
info,
output: Default::default(),
storage: Some(Box::new(storage)),

#[cfg(feature = "glow")]
gl: Some(painter.gl().clone()),
#[cfg(feature = "wgpu")]

#[cfg(all(feature = "wgpu", not(feature = "glow")))]
wgpu_render_state: painter.render_state(),
#[cfg(all(feature = "wgpu", feature = "glow"))]
wgpu_render_state: None,
};

Expand Down Expand Up @@ -357,16 +365,12 @@ impl AppRunner {
Ok((repaint_after, clipped_primitives))
}

pub fn clear_color_buffer(&self) {
self.painter
.clear(self.app.clear_color(&self.egui_ctx.style().visuals));
}

/// Paint the results of the last call to [`Self::logic`].
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
let textures_delta = std::mem::take(&mut self.textures_delta);

self.painter.paint_and_update_textures(
self.app.clear_color(&self.egui_ctx.style().visuals),
clipped_primitives,
self.egui_ctx.pixels_per_point(),
&textures_delta,
Expand Down Expand Up @@ -512,12 +516,12 @@ impl AppRunnerContainer {

/// Install event listeners to register different input events
/// and start running the given app.
pub fn start(
pub async fn start(
canvas_id: &str,
web_options: crate::WebOptions,
app_creator: epi::AppCreator,
) -> Result<AppRunnerRef, JsValue> {
let mut runner = AppRunner::new(canvas_id, web_options, app_creator)?;
let mut runner = AppRunner::new(canvas_id, web_options, app_creator).await?;
runner.warm_up()?;
start_runner(runner)
}
Expand Down
1 change: 0 additions & 1 deletion crates/eframe/src/web/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ pub fn paint_and_schedule(

if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
runner_lock.needs_repaint.clear();
runner_lock.clear_color_buffer();
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
runner_lock.paint(&clipped_primitives)?;
runner_lock
Expand Down
61 changes: 15 additions & 46 deletions crates/eframe/src/web/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@ mod input;
pub mod screen_reader;
pub mod storage;
mod text_agent;
mod web_glow_painter;

#[cfg(not(any(feature = "glow", feature = "wgpu")))]
compile_error!("You must enable either the 'glow' or 'wgpu' feature");

mod web_painter;

#[cfg(feature = "glow")]
mod web_painter_glow;
#[cfg(feature = "glow")]
pub(crate) type ActiveWebPainter = web_painter_glow::WebPainterGlow;

#[cfg(feature = "wgpu")]
mod web_painter_wgpu;
#[cfg(all(feature = "wgpu", not(feature = "glow")))]
pub(crate) type ActiveWebPainter = web_painter_wgpu::WebPainterWgpu;

pub use backend::*;
pub use events::*;
pub use storage::*;
pub(crate) use web_glow_painter::WebPainter;

use std::collections::BTreeMap;
use std::sync::{
Expand Down Expand Up @@ -244,47 +257,3 @@ pub fn percent_decode(s: &str) -> String {
.decode_utf8_lossy()
.to_string()
}

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

pub(crate) fn webgl1_requires_brightening(gl: &web_sys::WebGlRenderingContext) -> bool {
// See https://github.com/emilk/egui/issues/794

// detect WebKitGTK

// WebKitGTK use WebKit default unmasked vendor and renderer
// but safari use same vendor and renderer
// so exclude "Mac OS X" user-agent.
let user_agent = web_sys::window().unwrap().navigator().user_agent().unwrap();
!user_agent.contains("Mac OS X") && is_safari_and_webkit_gtk(gl)
}

/// detecting Safari and `webkitGTK`.
///
/// Safari and `webkitGTK` use unmasked renderer :Apple GPU
///
/// If we detect safari or `webkitGTKs` returns true.
///
/// This function used to avoid displaying linear color with `sRGB` supported systems.
fn is_safari_and_webkit_gtk(gl: &web_sys::WebGlRenderingContext) -> bool {
// This call produces a warning in Firefox ("WEBGL_debug_renderer_info is deprecated in Firefox and will be removed.")
// but unless we call it we get errors in Chrome when we call `get_parameter` below.
// TODO(emilk): do something smart based on user agent?
if gl
.get_extension("WEBGL_debug_renderer_info")
.unwrap()
.is_some()
{
if let Ok(renderer) =
gl.get_parameter(web_sys::WebglDebugRendererInfo::UNMASKED_RENDERER_WEBGL)
{
if let Some(renderer) = renderer.as_string() {
if renderer.contains("Apple") {
return true;
}
}
}
}

false
}
30 changes: 30 additions & 0 deletions crates/eframe/src/web/web_painter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use egui::Rgba;
use wasm_bindgen::JsValue;

/// Renderer for a browser canvas.
/// As of writing we're not allowing to decide on the painter at runtime,
/// therefore this trait is merely there for specifying and documenting the interface.
pub(crate) trait WebPainter {
// Create a new web painter targeting a given canvas.
// fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String>
// where
// Self: Sized;

/// Id of the canvas in use.
fn canvas_id(&self) -> &str;

/// Maximum size of a texture in one direction.
fn max_texture_side(&self) -> usize;

/// Update all internal textures and paint gui.
fn paint_and_update_textures(
&mut self,
clear_color: Rgba,
clipped_primitives: &[egui::ClippedPrimitive],
pixels_per_point: f32,
textures_delta: &egui::TexturesDelta,
) -> Result<(), JsValue>;

/// Destroy all resources.
fn destroy(&mut self);
}
Loading