eframe support for wgpu on the web (#2107)
* basic working wgpu @ webgl on websys * fix glow compile error * introduced WebPainter trait, provide wgpu renderstate * WebPainterWgpu destroy implemented * make custom3d demo work on wgpu backend * changelog entry for wgpu support eframe wasm * remove temporary logging hack * stop using pollster for web we're actually not allowed to block - this only worked because wgpu on webgl doesn't actually cause anything blocking. However, when trying webgpu this became an issue * revert cargo update * compile error if neither glow nor wgpu features are enabled * code cleanup * Error handling * Update changelog with link * Make sure --all-features work * Select best framebuffer format from the available ones * update to wasm-bindgen 0.2.83 * Fix typo * Clean up Cargo.toml * Log about using the wgpu painter * fixup wgpu labels * fix custom3d_wgpu_shader ub padding * remove duplicated uniforms struct in wgsl shader for custom3d * Update docs: add async/await to the web 'start' function Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
parent
c441f33ecf
commit
c2a37f4bd8
20 changed files with 432 additions and 211 deletions
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
|
@ -108,7 +108,7 @@ jobs:
|
||||||
- name: wasm-bindgen
|
- name: wasm-bindgen
|
||||||
uses: jetli/wasm-bindgen-action@v0.1.0
|
uses: jetli/wasm-bindgen-action@v0.1.0
|
||||||
with:
|
with:
|
||||||
version: "0.2.82"
|
version: "0.2.83"
|
||||||
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
- run: ./sh/wasm_bindgen_check.sh --skip-setup
|
||||||
|
|
||||||
cargo-deny:
|
cargo-deny:
|
||||||
|
|
21
Cargo.lock
generated
21
Cargo.lock
generated
|
@ -1223,6 +1223,7 @@ dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-wasm",
|
"tracing-wasm",
|
||||||
|
"wasm-bindgen-futures",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3979,9 +3980,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.82"
|
version = "0.2.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
|
@ -3989,9 +3990,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.82"
|
version = "0.2.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
|
@ -4016,9 +4017,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.82"
|
version = "0.2.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
|
@ -4026,9 +4027,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.82"
|
version = "0.2.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
@ -4039,9 +4040,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.82"
|
version = "0.2.83"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wayland-client"
|
name = "wayland-client"
|
||||||
|
|
|
@ -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)).
|
* 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)).
|
* 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 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
|
## 0.19.0 - 2022-08-20
|
||||||
|
|
|
@ -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)).
|
## 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.
|
## This overrides the `glow` feature.
|
||||||
wgpu = ["dep:wgpu", "egui-wgpu"]
|
wgpu = ["dep:wgpu", "dep:egui-wgpu"]
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -72,23 +72,23 @@ tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
document-features = { version = "0.2", optional = true }
|
document-features = { version = "0.2", optional = true }
|
||||||
|
|
||||||
egui_glow = { version = "0.19.0", path = "../egui_glow", optional = true, default-features = false }
|
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 }
|
glow = { version = "0.11", optional = true }
|
||||||
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
ron = { version = "0.8", optional = true, features = ["integer128"] }
|
||||||
serde = { version = "1", optional = true, features = ["derive"] }
|
serde = { version = "1", optional = true, features = ["derive"] }
|
||||||
wgpu = { version = "0.13", optional = true }
|
|
||||||
|
|
||||||
# -------------------------------------------
|
# -------------------------------------------
|
||||||
# native:
|
# native:
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[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"] }
|
egui-winit = { version = "0.19.0", path = "../egui-winit", default-features = false, features = ["clipboard", "links"] }
|
||||||
glutin = { version = "0.29.0" }
|
glutin = { version = "0.29.0" }
|
||||||
winit = "0.27.2"
|
winit = "0.27.2"
|
||||||
|
|
||||||
# optional native:
|
# optional native:
|
||||||
puffin = { version = "0.13", optional = true }
|
dark-light = { version = "0.2.1", optional = true }
|
||||||
directories-next = { version = "2", 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:
|
# web:
|
||||||
|
@ -142,6 +142,7 @@ web-sys = { version = "0.3.58", features = [
|
||||||
"Window",
|
"Window",
|
||||||
] }
|
] }
|
||||||
|
|
||||||
# optional
|
# optional web:
|
||||||
# feature screen_reader
|
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
|
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"] }
|
||||||
|
|
|
@ -455,6 +455,7 @@ pub struct WebOptions {
|
||||||
/// Which version of WebGl context to select
|
/// Which version of WebGl context to select
|
||||||
///
|
///
|
||||||
/// Default: [`WebGlContextOption::BestFirst`].
|
/// Default: [`WebGlContextOption::BestFirst`].
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
pub webgl_context_option: WebGlContextOption,
|
pub webgl_context_option: WebGlContextOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,6 +465,7 @@ impl Default for WebOptions {
|
||||||
Self {
|
Self {
|
||||||
follow_system_theme: true,
|
follow_system_theme: true,
|
||||||
default_theme: Theme::Dark,
|
default_theme: Theme::Dark,
|
||||||
|
#[cfg(feature = "glow")]
|
||||||
webgl_context_option: WebGlContextOption::BestFirst,
|
webgl_context_option: WebGlContextOption::BestFirst,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,9 +48,9 @@
|
||||||
//! /// Call this once from the HTML.
|
//! /// Call this once from the HTML.
|
||||||
//! #[cfg(target_arch = "wasm32")]
|
//! #[cfg(target_arch = "wasm32")]
|
||||||
//! #[wasm_bindgen]
|
//! #[wasm_bindgen]
|
||||||
//! pub fn start(canvas_id: &str) -> Result<AppRunnerRef, eframe::wasm_bindgen::JsValue> {
|
//! pub async fn start(canvas_id: &str) -> Result<AppRunnerRef, eframe::wasm_bindgen::JsValue> {
|
||||||
//! let web_options = eframe::WebOptions::default();
|
//! let web_options = eframe::WebOptions::default();
|
||||||
//! eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
|
//! eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
@ -103,18 +103,18 @@ pub use web_sys;
|
||||||
/// /// You can add more callbacks like this if you want to call in to your code.
|
/// /// You can add more callbacks like this if you want to call in to your code.
|
||||||
/// #[cfg(target_arch = "wasm32")]
|
/// #[cfg(target_arch = "wasm32")]
|
||||||
/// #[wasm_bindgen]
|
/// #[wasm_bindgen]
|
||||||
/// pub fn start(canvas_id: &str) -> Result<AppRunnerRef>, eframe::wasm_bindgen::JsValue> {
|
/// pub async fn start(canvas_id: &str) -> Result<AppRunnerRef>, eframe::wasm_bindgen::JsValue> {
|
||||||
/// let web_options = eframe::WebOptions::default();
|
/// let web_options = eframe::WebOptions::default();
|
||||||
/// eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc))))
|
/// eframe::start_web(canvas_id, web_options, Box::new(|cc| Box::new(MyEguiApp::new(cc)))).await
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
pub fn start_web(
|
pub async fn start_web(
|
||||||
canvas_id: &str,
|
canvas_id: &str,
|
||||||
web_options: WebOptions,
|
web_options: WebOptions,
|
||||||
app_creator: AppCreator,
|
app_creator: AppCreator,
|
||||||
) -> Result<AppRunnerRef, wasm_bindgen::JsValue> {
|
) -> 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)
|
Ok(handle)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use super::{WebPainter, *};
|
use super::{web_painter::WebPainter, *};
|
||||||
|
|
||||||
use crate::epi;
|
use crate::epi;
|
||||||
|
|
||||||
use egui::{
|
use egui::{
|
||||||
|
@ -162,7 +161,7 @@ fn test_parse_query() {
|
||||||
pub struct AppRunner {
|
pub struct AppRunner {
|
||||||
pub(crate) frame: epi::Frame,
|
pub(crate) frame: epi::Frame,
|
||||||
egui_ctx: egui::Context,
|
egui_ctx: egui::Context,
|
||||||
painter: WebPainter,
|
painter: ActiveWebPainter,
|
||||||
pub(crate) input: WebInput,
|
pub(crate) input: WebInput,
|
||||||
app: Box<dyn epi::App>,
|
app: Box<dyn epi::App>,
|
||||||
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
|
||||||
|
@ -182,13 +181,14 @@ impl Drop for AppRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppRunner {
|
impl AppRunner {
|
||||||
pub fn new(
|
pub async fn new(
|
||||||
canvas_id: &str,
|
canvas_id: &str,
|
||||||
web_options: crate::WebOptions,
|
web_options: crate::WebOptions,
|
||||||
app_creator: epi::AppCreator,
|
app_creator: epi::AppCreator,
|
||||||
) -> Result<Self, JsValue> {
|
) -> Result<Self, JsValue> {
|
||||||
let painter =
|
let painter = ActiveWebPainter::new(canvas_id, &web_options)
|
||||||
WebPainter::new(canvas_id, web_options.webgl_context_option).map_err(JsValue::from)?; // fail early
|
.await
|
||||||
|
.map_err(JsValue::from)?;
|
||||||
|
|
||||||
let system_theme = if web_options.follow_system_theme {
|
let system_theme = if web_options.follow_system_theme {
|
||||||
super::system_theme()
|
super::system_theme()
|
||||||
|
@ -216,9 +216,13 @@ impl AppRunner {
|
||||||
egui_ctx: egui_ctx.clone(),
|
egui_ctx: egui_ctx.clone(),
|
||||||
integration_info: info.clone(),
|
integration_info: info.clone(),
|
||||||
storage: Some(&storage),
|
storage: Some(&storage),
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl: Some(painter.painter.gl().clone()),
|
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,
|
wgpu_render_state: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -226,9 +230,13 @@ impl AppRunner {
|
||||||
info,
|
info,
|
||||||
output: Default::default(),
|
output: Default::default(),
|
||||||
storage: Some(Box::new(storage)),
|
storage: Some(Box::new(storage)),
|
||||||
|
|
||||||
#[cfg(feature = "glow")]
|
#[cfg(feature = "glow")]
|
||||||
gl: Some(painter.gl().clone()),
|
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,
|
wgpu_render_state: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -357,16 +365,12 @@ impl AppRunner {
|
||||||
Ok((repaint_after, clipped_primitives))
|
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`].
|
/// Paint the results of the last call to [`Self::logic`].
|
||||||
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
pub fn paint(&mut self, clipped_primitives: &[egui::ClippedPrimitive]) -> Result<(), JsValue> {
|
||||||
let textures_delta = std::mem::take(&mut self.textures_delta);
|
let textures_delta = std::mem::take(&mut self.textures_delta);
|
||||||
|
|
||||||
self.painter.paint_and_update_textures(
|
self.painter.paint_and_update_textures(
|
||||||
|
self.app.clear_color(&self.egui_ctx.style().visuals),
|
||||||
clipped_primitives,
|
clipped_primitives,
|
||||||
self.egui_ctx.pixels_per_point(),
|
self.egui_ctx.pixels_per_point(),
|
||||||
&textures_delta,
|
&textures_delta,
|
||||||
|
@ -512,12 +516,12 @@ impl AppRunnerContainer {
|
||||||
|
|
||||||
/// Install event listeners to register different input events
|
/// Install event listeners to register different input events
|
||||||
/// and start running the given app.
|
/// and start running the given app.
|
||||||
pub fn start(
|
pub async fn start(
|
||||||
canvas_id: &str,
|
canvas_id: &str,
|
||||||
web_options: crate::WebOptions,
|
web_options: crate::WebOptions,
|
||||||
app_creator: epi::AppCreator,
|
app_creator: epi::AppCreator,
|
||||||
) -> Result<AppRunnerRef, JsValue> {
|
) -> 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()?;
|
runner.warm_up()?;
|
||||||
start_runner(runner)
|
start_runner(runner)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,6 @@ pub fn paint_and_schedule(
|
||||||
|
|
||||||
if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
|
if !is_destroyed && runner_lock.needs_repaint.when_to_repaint() <= now_sec() {
|
||||||
runner_lock.needs_repaint.clear();
|
runner_lock.needs_repaint.clear();
|
||||||
runner_lock.clear_color_buffer();
|
|
||||||
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
|
let (repaint_after, clipped_primitives) = runner_lock.logic()?;
|
||||||
runner_lock.paint(&clipped_primitives)?;
|
runner_lock.paint(&clipped_primitives)?;
|
||||||
runner_lock
|
runner_lock
|
||||||
|
|
|
@ -8,12 +8,25 @@ mod input;
|
||||||
pub mod screen_reader;
|
pub mod screen_reader;
|
||||||
pub mod storage;
|
pub mod storage;
|
||||||
mod text_agent;
|
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 backend::*;
|
||||||
pub use events::*;
|
pub use events::*;
|
||||||
pub use storage::*;
|
pub use storage::*;
|
||||||
pub(crate) use web_glow_painter::WebPainter;
|
|
||||||
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
|
@ -244,47 +257,3 @@ pub fn percent_decode(s: &str) -> String {
|
||||||
.decode_utf8_lossy()
|
.decode_utf8_lossy()
|
||||||
.to_string()
|
.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
crates/eframe/src/web/web_painter.rs
Normal file
30
crates/eframe/src/web/web_painter.rs
Normal file
|
@ -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);
|
||||||
|
}
|
|
@ -1,25 +1,30 @@
|
||||||
use wasm_bindgen::JsCast;
|
use wasm_bindgen::JsCast;
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::HtmlCanvasElement;
|
use web_sys::HtmlCanvasElement;
|
||||||
#[cfg(not(target_arch = "wasm32"))]
|
|
||||||
use web_sys::{WebGl2RenderingContext, WebGlRenderingContext};
|
|
||||||
|
|
||||||
use egui::{ClippedPrimitive, Rgba};
|
use egui::Rgba;
|
||||||
use egui_glow::glow;
|
use egui_glow::glow;
|
||||||
|
|
||||||
use crate::WebGlContextOption;
|
use crate::{WebGlContextOption, WebOptions};
|
||||||
|
|
||||||
pub(crate) struct WebPainter {
|
use super::web_painter::WebPainter;
|
||||||
pub(crate) canvas: HtmlCanvasElement,
|
|
||||||
pub(crate) canvas_id: String,
|
pub(crate) struct WebPainterGlow {
|
||||||
pub(crate) painter: egui_glow::Painter,
|
canvas: HtmlCanvasElement,
|
||||||
|
canvas_id: String,
|
||||||
|
painter: egui_glow::Painter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebPainter {
|
impl WebPainterGlow {
|
||||||
pub fn new(canvas_id: &str, options: WebGlContextOption) -> Result<Self, String> {
|
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
|
||||||
|
self.painter.gl()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
|
||||||
let canvas = super::canvas_element_or_die(canvas_id);
|
let canvas = super::canvas_element_or_die(canvas_id);
|
||||||
|
|
||||||
let (gl, shader_prefix) = init_glow_context_from_canvas(&canvas, options)?;
|
let (gl, shader_prefix) =
|
||||||
|
init_glow_context_from_canvas(&canvas, options.webgl_context_option)?;
|
||||||
let gl = std::sync::Arc::new(gl);
|
let gl = std::sync::Arc::new(gl);
|
||||||
|
|
||||||
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
|
let painter = egui_glow::Painter::new(gl, shader_prefix, None)
|
||||||
|
@ -33,63 +38,40 @@ impl WebPainter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebPainter {
|
impl WebPainter for WebPainterGlow {
|
||||||
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
|
fn max_texture_side(&self) -> usize {
|
||||||
self.painter.gl()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_texture_side(&self) -> usize {
|
|
||||||
self.painter.max_texture_side()
|
self.painter.max_texture_side()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn canvas_id(&self) -> &str {
|
fn canvas_id(&self) -> &str {
|
||||||
&self.canvas_id
|
&self.canvas_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
fn paint_and_update_textures(
|
||||||
self.painter.set_texture(tex_id, delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn free_texture(&mut self, tex_id: egui::TextureId) {
|
|
||||||
self.painter.free_texture(tex_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&self, clear_color: Rgba) {
|
|
||||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
|
||||||
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paint_primitives(
|
|
||||||
&mut self,
|
|
||||||
clipped_primitives: &[ClippedPrimitive],
|
|
||||||
pixels_per_point: f32,
|
|
||||||
) -> Result<(), JsValue> {
|
|
||||||
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
|
||||||
self.painter
|
|
||||||
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paint_and_update_textures(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
clear_color: Rgba,
|
||||||
clipped_primitives: &[egui::ClippedPrimitive],
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
pixels_per_point: f32,
|
pixels_per_point: f32,
|
||||||
textures_delta: &egui::TexturesDelta,
|
textures_delta: &egui::TexturesDelta,
|
||||||
) -> Result<(), JsValue> {
|
) -> Result<(), JsValue> {
|
||||||
|
let canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||||
|
|
||||||
for (id, image_delta) in &textures_delta.set {
|
for (id, image_delta) in &textures_delta.set {
|
||||||
self.set_texture(*id, image_delta);
|
self.painter.set_texture(*id, image_delta);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.paint_primitives(clipped_primitives, pixels_per_point)?;
|
egui_glow::painter::clear(self.painter.gl(), canvas_dimension, clear_color);
|
||||||
|
self.painter
|
||||||
|
.paint_primitives(canvas_dimension, pixels_per_point, clipped_primitives);
|
||||||
|
|
||||||
for &id in &textures_delta.free {
|
for &id in &textures_delta.free {
|
||||||
self.free_texture(id);
|
self.painter.free_texture(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destroy(&mut self) {
|
fn destroy(&mut self) {
|
||||||
self.painter.destroy()
|
self.painter.destroy()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +113,7 @@ fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st
|
||||||
.dyn_into::<web_sys::WebGlRenderingContext>()
|
.dyn_into::<web_sys::WebGlRenderingContext>()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let shader_prefix = if super::webgl1_requires_brightening(&gl1_ctx) {
|
let shader_prefix = if webgl1_requires_brightening(&gl1_ctx) {
|
||||||
tracing::debug!("Enabling webkitGTK brightening workaround.");
|
tracing::debug!("Enabling webkitGTK brightening workaround.");
|
||||||
"#define APPLY_BRIGHTENING_GAMMA"
|
"#define APPLY_BRIGHTENING_GAMMA"
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,3 +141,45 @@ fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st
|
||||||
|
|
||||||
Some((gl, shader_prefix))
|
Some((gl, shader_prefix))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
196
crates/eframe/src/web/web_painter_wgpu.rs
Normal file
196
crates/eframe/src/web/web_painter_wgpu.rs
Normal file
|
@ -0,0 +1,196 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
use web_sys::HtmlCanvasElement;
|
||||||
|
|
||||||
|
use egui::{mutex::RwLock, Rgba};
|
||||||
|
use egui_wgpu::{renderer::ScreenDescriptor, RenderState};
|
||||||
|
|
||||||
|
use crate::WebOptions;
|
||||||
|
|
||||||
|
use super::web_painter::WebPainter;
|
||||||
|
|
||||||
|
pub(crate) struct WebPainterWgpu {
|
||||||
|
canvas: HtmlCanvasElement,
|
||||||
|
canvas_id: String,
|
||||||
|
surface: wgpu::Surface,
|
||||||
|
surface_size: [u32; 2],
|
||||||
|
limits: wgpu::Limits,
|
||||||
|
render_state: Option<RenderState>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebPainterWgpu {
|
||||||
|
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||||
|
pub fn render_state(&self) -> Option<RenderState> {
|
||||||
|
self.render_state.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)] // only used if `wgpu` is the only active feature.
|
||||||
|
pub async fn new(canvas_id: &str, _options: &WebOptions) -> Result<Self, String> {
|
||||||
|
tracing::debug!("Creating wgpu painter with WebGL backend…");
|
||||||
|
|
||||||
|
let canvas = super::canvas_element_or_die(canvas_id);
|
||||||
|
let limits = wgpu::Limits::downlevel_webgl2_defaults(); // TODO(Wumpf): Expose to eframe user
|
||||||
|
|
||||||
|
// TODO(Wumpf): Should be able to switch between WebGL & WebGPU (only)
|
||||||
|
let backends = wgpu::Backends::GL; //wgpu::util::backend_bits_from_env().unwrap_or_else(wgpu::Backends::all);
|
||||||
|
let instance = wgpu::Instance::new(backends);
|
||||||
|
let surface = instance.create_surface_from_canvas(&canvas);
|
||||||
|
|
||||||
|
let adapter =
|
||||||
|
wgpu::util::initialize_adapter_from_env_or_default(&instance, backends, Some(&surface))
|
||||||
|
.await
|
||||||
|
.ok_or_else(|| "No suitable GPU adapters found on the system".to_owned())?;
|
||||||
|
|
||||||
|
let (device, queue) = adapter
|
||||||
|
.request_device(
|
||||||
|
&wgpu::DeviceDescriptor {
|
||||||
|
label: Some("egui_webpainter"),
|
||||||
|
features: wgpu::Features::empty(),
|
||||||
|
limits: limits.clone(),
|
||||||
|
},
|
||||||
|
None, // No capture exposed so far - unclear how we can expose this in a browser environment (?)
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
|
||||||
|
|
||||||
|
// TODO(Wumpf): MSAA & depth
|
||||||
|
|
||||||
|
let target_format =
|
||||||
|
egui_wgpu::preferred_framebuffer_format(&surface.get_supported_formats(&adapter));
|
||||||
|
|
||||||
|
let renderer = egui_wgpu::Renderer::new(&device, target_format, 1, 0);
|
||||||
|
let render_state = RenderState {
|
||||||
|
device: Arc::new(device),
|
||||||
|
queue: Arc::new(queue),
|
||||||
|
target_format,
|
||||||
|
renderer: Arc::new(RwLock::new(renderer)),
|
||||||
|
};
|
||||||
|
|
||||||
|
tracing::debug!("wgpu painter initialized.");
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
canvas,
|
||||||
|
canvas_id: canvas_id.to_owned(),
|
||||||
|
render_state: Some(render_state),
|
||||||
|
surface,
|
||||||
|
surface_size: [0, 0],
|
||||||
|
limits,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebPainter for WebPainterWgpu {
|
||||||
|
fn canvas_id(&self) -> &str {
|
||||||
|
&self.canvas_id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn max_texture_side(&self) -> usize {
|
||||||
|
self.limits.max_texture_dimension_2d as _
|
||||||
|
}
|
||||||
|
|
||||||
|
fn paint_and_update_textures(
|
||||||
|
&mut self,
|
||||||
|
clear_color: Rgba,
|
||||||
|
clipped_primitives: &[egui::ClippedPrimitive],
|
||||||
|
pixels_per_point: f32,
|
||||||
|
textures_delta: &egui::TexturesDelta,
|
||||||
|
) -> Result<(), JsValue> {
|
||||||
|
let render_state = if let Some(render_state) = &self.render_state {
|
||||||
|
render_state
|
||||||
|
} else {
|
||||||
|
return Err(JsValue::from_str(
|
||||||
|
"Can't paint, wgpu renderer was already disposed",
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resize surface if needed
|
||||||
|
let canvas_size = [self.canvas.width(), self.canvas.height()];
|
||||||
|
if canvas_size != self.surface_size {
|
||||||
|
self.surface.configure(
|
||||||
|
&render_state.device,
|
||||||
|
&wgpu::SurfaceConfiguration {
|
||||||
|
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||||
|
format: render_state.target_format,
|
||||||
|
width: canvas_size[0],
|
||||||
|
height: canvas_size[1],
|
||||||
|
present_mode: wgpu::PresentMode::Fifo,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.surface_size = canvas_size.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
let frame = self.surface.get_current_texture().map_err(|err| {
|
||||||
|
JsValue::from_str(&format!(
|
||||||
|
"Failed to acquire next swap chain texture: {}",
|
||||||
|
err
|
||||||
|
))
|
||||||
|
})?;
|
||||||
|
let view = frame
|
||||||
|
.texture
|
||||||
|
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||||
|
|
||||||
|
let mut encoder =
|
||||||
|
render_state
|
||||||
|
.device
|
||||||
|
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
|
label: Some("egui_webpainter_paint_and_update_textures"),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Upload all resources for the GPU.
|
||||||
|
let screen_descriptor = ScreenDescriptor {
|
||||||
|
size_in_pixels: canvas_size,
|
||||||
|
pixels_per_point,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut renderer = render_state.renderer.write();
|
||||||
|
for (id, image_delta) in &textures_delta.set {
|
||||||
|
renderer.update_texture(
|
||||||
|
&render_state.device,
|
||||||
|
&render_state.queue,
|
||||||
|
*id,
|
||||||
|
image_delta,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.update_buffers(
|
||||||
|
&render_state.device,
|
||||||
|
&render_state.queue,
|
||||||
|
clipped_primitives,
|
||||||
|
&screen_descriptor,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record all render passes.
|
||||||
|
render_state.renderer.read().render(
|
||||||
|
&mut encoder,
|
||||||
|
&view,
|
||||||
|
clipped_primitives,
|
||||||
|
&screen_descriptor,
|
||||||
|
Some(wgpu::Color {
|
||||||
|
r: clear_color.r() as f64,
|
||||||
|
g: clear_color.g() as f64,
|
||||||
|
b: clear_color.b() as f64,
|
||||||
|
a: clear_color.a() as f64,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut renderer = render_state.renderer.write();
|
||||||
|
for id in &textures_delta.free {
|
||||||
|
renderer.free_texture(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Submit the commands.
|
||||||
|
render_state.queue.submit(std::iter::once(encoder.finish()));
|
||||||
|
frame.present();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn destroy(&mut self) {
|
||||||
|
self.render_state = None;
|
||||||
|
}
|
||||||
|
}
|
|
@ -17,5 +17,28 @@ pub use renderer::Renderer;
|
||||||
#[cfg(feature = "winit")]
|
#[cfg(feature = "winit")]
|
||||||
pub mod winit;
|
pub mod winit;
|
||||||
|
|
||||||
#[cfg(feature = "winit")]
|
use egui::mutex::RwLock;
|
||||||
pub use crate::winit::RenderState;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// Access to the render state for egui, which can be useful in combination with
|
||||||
|
/// [`egui::PaintCallback`]s for custom rendering using WGPU.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct RenderState {
|
||||||
|
pub device: Arc<wgpu::Device>,
|
||||||
|
pub queue: Arc<wgpu::Queue>,
|
||||||
|
pub target_format: wgpu::TextureFormat,
|
||||||
|
pub renderer: Arc<RwLock<Renderer>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the framebuffer format that egui prefers
|
||||||
|
pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
|
||||||
|
for &format in formats {
|
||||||
|
if matches!(
|
||||||
|
format,
|
||||||
|
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
|
||||||
|
) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
formats[0] // take the first
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#![allow(unsafe_code)]
|
#![allow(unsafe_code)]
|
||||||
|
|
||||||
|
use std::num::NonZeroU64;
|
||||||
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
|
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
|
||||||
|
|
||||||
use egui::{epaint::Primitive, PaintCallbackInfo};
|
use egui::{epaint::Primitive, PaintCallbackInfo};
|
||||||
|
@ -26,7 +27,7 @@ use wgpu::util::DeviceExt as _;
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// See the [`custom3d_glow`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
/// See the [`custom3d_wgpu`](https://github.com/emilk/egui/blob/master/crates/egui_demo_app/src/apps/custom3d_wgpu.rs) demo source for a detailed usage example.
|
||||||
pub struct CallbackFn {
|
pub struct CallbackFn {
|
||||||
prepare: Box<PrepareCallback>,
|
prepare: Box<PrepareCallback>,
|
||||||
paint: Box<PaintCallback>,
|
paint: Box<PaintCallback>,
|
||||||
|
@ -149,7 +150,7 @@ impl Renderer {
|
||||||
depth_bits: u8,
|
depth_bits: u8,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let shader = wgpu::ShaderModuleDescriptor {
|
let shader = wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("egui_shader"),
|
label: Some("egui"),
|
||||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
||||||
};
|
};
|
||||||
let module = device.create_shader_module(shader);
|
let module = device.create_shader_module(shader);
|
||||||
|
@ -175,7 +176,7 @@ impl Renderer {
|
||||||
visibility: wgpu::ShaderStages::VERTEX,
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: None,
|
min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
|
@ -306,8 +307,9 @@ impl Renderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_depth_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
pub fn update_depth_texture(&mut self, device: &wgpu::Device, width: u32, height: u32) {
|
||||||
|
// TODO(wumpf) don't recreate texture if size hasn't changed
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: None,
|
label: Some("egui_depth_texture"),
|
||||||
size: wgpu::Extent3d {
|
size: wgpu::Extent3d {
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
|
@ -361,7 +363,7 @@ impl Renderer {
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
depth_stencil_attachment,
|
depth_stencil_attachment,
|
||||||
label: Some("egui_render_pass"),
|
label: Some("egui_render"),
|
||||||
});
|
});
|
||||||
|
|
||||||
self.render_onto_renderpass(&mut render_pass, paint_jobs, screen_descriptor);
|
self.render_onto_renderpass(&mut render_pass, paint_jobs, screen_descriptor);
|
||||||
|
@ -559,9 +561,13 @@ impl Renderer {
|
||||||
origin,
|
origin,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
// TODO(Wumpf): Create only a new texture if we need to
|
||||||
// allocate a new texture
|
// allocate a new texture
|
||||||
|
// Use same label for all resources associated with this texture id (no point in retyping the type)
|
||||||
|
let label_str = format!("egui_texid_{:?}", id);
|
||||||
|
let label = Some(label_str.as_str());
|
||||||
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
let texture = device.create_texture(&wgpu::TextureDescriptor {
|
||||||
label: None,
|
label,
|
||||||
size,
|
size,
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: 1,
|
||||||
|
@ -573,14 +579,15 @@ impl Renderer {
|
||||||
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||||
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
||||||
};
|
};
|
||||||
|
// TODO(Wumpf): Reuse this sampler.
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
label: None,
|
label,
|
||||||
mag_filter: filter,
|
mag_filter: filter,
|
||||||
min_filter: filter,
|
min_filter: filter,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: None,
|
label,
|
||||||
layout: &self.texture_bind_group_layout,
|
layout: &self.texture_bind_group_layout,
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
|
@ -633,13 +640,7 @@ impl Renderer {
|
||||||
device,
|
device,
|
||||||
texture,
|
texture,
|
||||||
wgpu::SamplerDescriptor {
|
wgpu::SamplerDescriptor {
|
||||||
label: Some(
|
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||||
format!(
|
|
||||||
"egui_user_image_{}_texture_sampler",
|
|
||||||
self.next_user_texture_id
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
),
|
|
||||||
mag_filter: texture_filter,
|
mag_filter: texture_filter,
|
||||||
min_filter: texture_filter,
|
min_filter: texture_filter,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -661,13 +662,7 @@ impl Renderer {
|
||||||
device,
|
device,
|
||||||
texture,
|
texture,
|
||||||
wgpu::SamplerDescriptor {
|
wgpu::SamplerDescriptor {
|
||||||
label: Some(
|
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||||
format!(
|
|
||||||
"egui_user_image_{}_texture_sampler",
|
|
||||||
self.next_user_texture_id
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
),
|
|
||||||
mag_filter: texture_filter,
|
mag_filter: texture_filter,
|
||||||
min_filter: texture_filter,
|
min_filter: texture_filter,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
|
@ -698,13 +693,7 @@ impl Renderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some(
|
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||||
format!(
|
|
||||||
"egui_user_image_{}_texture_bind_group",
|
|
||||||
self.next_user_texture_id
|
|
||||||
)
|
|
||||||
.as_str(),
|
|
||||||
),
|
|
||||||
layout: &self.texture_bind_group_layout,
|
layout: &self.texture_bind_group_layout,
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
|
@ -748,9 +737,7 @@ impl Renderer {
|
||||||
});
|
});
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: Some(
|
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||||
format!("egui_user_{}_texture_bind_group", self.next_user_texture_id).as_str(),
|
|
||||||
),
|
|
||||||
layout: &self.texture_bind_group_layout,
|
layout: &self.texture_bind_group_layout,
|
||||||
entries: &[
|
entries: &[
|
||||||
wgpu::BindGroupEntry {
|
wgpu::BindGroupEntry {
|
||||||
|
|
|
@ -4,17 +4,7 @@ use egui::mutex::RwLock;
|
||||||
use tracing::error;
|
use tracing::error;
|
||||||
use wgpu::{Adapter, Instance, Surface};
|
use wgpu::{Adapter, Instance, Surface};
|
||||||
|
|
||||||
use crate::{renderer, Renderer};
|
use crate::{renderer, RenderState, Renderer};
|
||||||
|
|
||||||
/// Access to the render state for egui, which can be useful in combination with
|
|
||||||
/// [`egui::PaintCallback`]s for custom rendering using WGPU.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct RenderState {
|
|
||||||
pub device: Arc<wgpu::Device>,
|
|
||||||
pub queue: Arc<wgpu::Queue>,
|
|
||||||
pub target_format: wgpu::TextureFormat,
|
|
||||||
pub renderer: Arc<RwLock<Renderer>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct SurfaceState {
|
struct SurfaceState {
|
||||||
surface: Surface,
|
surface: Surface,
|
||||||
|
@ -119,7 +109,7 @@ impl<'a> Painter<'a> {
|
||||||
let adapter = self.adapter.as_ref().unwrap();
|
let adapter = self.adapter.as_ref().unwrap();
|
||||||
|
|
||||||
let swapchain_format =
|
let swapchain_format =
|
||||||
select_framebuffer_format(&surface.get_supported_formats(adapter));
|
crate::preferred_framebuffer_format(&surface.get_supported_formats(adapter));
|
||||||
|
|
||||||
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
|
let rs = pollster::block_on(self.init_render_state(adapter, swapchain_format));
|
||||||
self.render_state = Some(rs);
|
self.render_state = Some(rs);
|
||||||
|
@ -324,15 +314,3 @@ impl<'a> Painter<'a> {
|
||||||
// TODO(emilk): something here?
|
// TODO(emilk): something here?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
|
|
||||||
for &format in formats {
|
|
||||||
if matches!(
|
|
||||||
format,
|
|
||||||
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
|
|
||||||
) {
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
formats[0] // take the first
|
|
||||||
}
|
|
||||||
|
|
|
@ -69,3 +69,4 @@ tracing-subscriber = "0.3"
|
||||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
console_error_panic_hook = "0.1.6"
|
console_error_panic_hook = "0.1.6"
|
||||||
tracing-wasm = "0.2"
|
tracing-wasm = "0.2"
|
||||||
|
wasm-bindgen-futures = "0.4"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::sync::Arc;
|
use std::{num::NonZeroU64, sync::Arc};
|
||||||
|
|
||||||
use eframe::{
|
use eframe::{
|
||||||
egui_wgpu::{self, wgpu},
|
egui_wgpu::{self, wgpu},
|
||||||
|
@ -18,32 +18,32 @@ impl Custom3d {
|
||||||
let device = &wgpu_render_state.device;
|
let device = &wgpu_render_state.device;
|
||||||
|
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||||
});
|
});
|
||||||
|
|
||||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
entries: &[wgpu::BindGroupLayoutEntry {
|
entries: &[wgpu::BindGroupLayoutEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
visibility: wgpu::ShaderStages::VERTEX,
|
visibility: wgpu::ShaderStages::VERTEX,
|
||||||
ty: wgpu::BindingType::Buffer {
|
ty: wgpu::BindingType::Buffer {
|
||||||
ty: wgpu::BufferBindingType::Uniform,
|
ty: wgpu::BufferBindingType::Uniform,
|
||||||
has_dynamic_offset: false,
|
has_dynamic_offset: false,
|
||||||
min_binding_size: None,
|
min_binding_size: NonZeroU64::new(16),
|
||||||
},
|
},
|
||||||
count: None,
|
count: None,
|
||||||
}],
|
}],
|
||||||
});
|
});
|
||||||
|
|
||||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
bind_group_layouts: &[&bind_group_layout],
|
bind_group_layouts: &[&bind_group_layout],
|
||||||
push_constant_ranges: &[],
|
push_constant_ranges: &[],
|
||||||
});
|
});
|
||||||
|
|
||||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
layout: Some(&pipeline_layout),
|
layout: Some(&pipeline_layout),
|
||||||
vertex: wgpu::VertexState {
|
vertex: wgpu::VertexState {
|
||||||
module: &shader,
|
module: &shader,
|
||||||
|
@ -62,15 +62,15 @@ impl Custom3d {
|
||||||
});
|
});
|
||||||
|
|
||||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
contents: bytemuck::cast_slice(&[0.0]),
|
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||||
usage: wgpu::BufferUsages::COPY_DST
|
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||||
| wgpu::BufferUsages::MAP_WRITE
|
// (this *happens* to workaround this bug )
|
||||||
| wgpu::BufferUsages::UNIFORM,
|
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||||
});
|
});
|
||||||
|
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
label: None,
|
label: Some("custom3d"),
|
||||||
layout: &bind_group_layout,
|
layout: &bind_group_layout,
|
||||||
entries: &[wgpu::BindGroupEntry {
|
entries: &[wgpu::BindGroupEntry {
|
||||||
binding: 0,
|
binding: 0,
|
||||||
|
@ -165,7 +165,11 @@ struct TriangleRenderResources {
|
||||||
impl TriangleRenderResources {
|
impl TriangleRenderResources {
|
||||||
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||||
// Update our uniform buffer with the angle from the UI
|
// Update our uniform buffer with the angle from the UI
|
||||||
queue.write_buffer(&self.uniform_buffer, 0, bytemuck::cast_slice(&[angle]));
|
queue.write_buffer(
|
||||||
|
&self.uniform_buffer,
|
||||||
|
0,
|
||||||
|
bytemuck::cast_slice(&[angle, 0.0, 0.0, 0.0]),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
fn paint<'rp>(&'rp self, render_pass: &mut wgpu::RenderPass<'rp>) {
|
||||||
|
|
|
@ -4,7 +4,7 @@ struct VertexOut {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Uniforms {
|
struct Uniforms {
|
||||||
angle: f32,
|
@size(16) angle: f32, // pad to 16 bytes
|
||||||
};
|
};
|
||||||
|
|
||||||
@group(0) @binding(0)
|
@group(0) @binding(0)
|
||||||
|
|
|
@ -62,13 +62,14 @@ pub fn init_wasm_hooks() {
|
||||||
|
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
pub async fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||||
let web_options = eframe::WebOptions::default();
|
let web_options = eframe::WebOptions::default();
|
||||||
let handle = eframe::start_web(
|
let handle = eframe::start_web(
|
||||||
canvas_id,
|
canvas_id,
|
||||||
web_options,
|
web_options,
|
||||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||||
)
|
)
|
||||||
|
.await
|
||||||
.map(|handle| WebHandle { handle });
|
.map(|handle| WebHandle { handle });
|
||||||
|
|
||||||
handle
|
handle
|
||||||
|
@ -80,7 +81,7 @@ pub fn start_separate(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValu
|
||||||
/// You can add more callbacks like this if you want to call in to your code.
|
/// You can add more callbacks like this if you want to call in to your code.
|
||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[wasm_bindgen]
|
#[wasm_bindgen]
|
||||||
pub fn start(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
pub async fn start(canvas_id: &str) -> Result<WebHandle, wasm_bindgen::JsValue> {
|
||||||
init_wasm_hooks();
|
init_wasm_hooks();
|
||||||
start_separate(canvas_id)
|
start_separate(canvas_id).await
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,4 +5,4 @@ cd "$script_path/.."
|
||||||
|
|
||||||
# Pre-requisites:
|
# Pre-requisites:
|
||||||
rustup target add wasm32-unknown-unknown
|
rustup target add wasm32-unknown-unknown
|
||||||
cargo install wasm-bindgen-cli --version 0.2.82
|
cargo install wasm-bindgen-cli --version 0.2.83
|
||||||
|
|
Loading…
Reference in a new issue