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
|
@ -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:
|
||||
|
|
|
@ -1223,6 +1223,7 @@ dependencies = [
|
|||
"tracing",
|
||||
"tracing-subscriber",
|
||||
"tracing-wasm",
|
||||
"wasm-bindgen-futures",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3979,9 +3980,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -3989,9 +3990,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -4016,9 +4017,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -4026,9 +4027,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4039,9 +4040,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
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)).
|
||||
* 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
|
||||
|
|
|
@ -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]
|
||||
|
@ -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:
|
||||
|
@ -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"] }
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -48,9 +48,9 @@
|
|||
//! /// Call this once from the HTML.
|
||||
//! #[cfg(target_arch = "wasm32")]
|
||||
//! #[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();
|
||||
//! 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.
|
||||
/// #[cfg(target_arch = "wasm32")]
|
||||
/// #[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();
|
||||
/// 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")]
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
use super::{WebPainter, *};
|
||||
|
||||
use super::{web_painter::WebPainter, *};
|
||||
use crate::epi;
|
||||
|
||||
use egui::{
|
||||
|
@ -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>,
|
||||
|
@ -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()
|
||||
|
@ -216,9 +216,13 @@ 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,
|
||||
});
|
||||
|
||||
|
@ -226,9 +230,13 @@ impl AppRunner {
|
|||
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,
|
||||
};
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::{
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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::JsValue;
|
||||
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 crate::WebGlContextOption;
|
||||
use crate::{WebGlContextOption, WebOptions};
|
||||
|
||||
pub(crate) struct WebPainter {
|
||||
pub(crate) canvas: HtmlCanvasElement,
|
||||
pub(crate) canvas_id: String,
|
||||
pub(crate) painter: egui_glow::Painter,
|
||||
use super::web_painter::WebPainter;
|
||||
|
||||
pub(crate) struct WebPainterGlow {
|
||||
canvas: HtmlCanvasElement,
|
||||
canvas_id: String,
|
||||
painter: egui_glow::Painter,
|
||||
}
|
||||
|
||||
impl WebPainter {
|
||||
pub fn new(canvas_id: &str, options: WebGlContextOption) -> Result<Self, String> {
|
||||
impl WebPainterGlow {
|
||||
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 (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 painter = egui_glow::Painter::new(gl, shader_prefix, None)
|
||||
|
@ -33,63 +38,40 @@ impl WebPainter {
|
|||
}
|
||||
}
|
||||
|
||||
impl WebPainter {
|
||||
pub fn gl(&self) -> &std::sync::Arc<glow::Context> {
|
||||
self.painter.gl()
|
||||
}
|
||||
|
||||
pub fn max_texture_side(&self) -> usize {
|
||||
impl WebPainter for WebPainterGlow {
|
||||
fn max_texture_side(&self) -> usize {
|
||||
self.painter.max_texture_side()
|
||||
}
|
||||
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
fn canvas_id(&self) -> &str {
|
||||
&self.canvas_id
|
||||
}
|
||||
|
||||
pub fn set_texture(&mut self, tex_id: egui::TextureId, delta: &egui::epaint::ImageDelta) {
|
||||
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(
|
||||
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 canvas_dimension = [self.canvas.width(), self.canvas.height()];
|
||||
|
||||
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 {
|
||||
self.free_texture(id);
|
||||
self.painter.free_texture(id);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn destroy(&mut self) {
|
||||
fn destroy(&mut self) {
|
||||
self.painter.destroy()
|
||||
}
|
||||
}
|
||||
|
@ -131,7 +113,7 @@ fn init_webgl1(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st
|
|||
.dyn_into::<web_sys::WebGlRenderingContext>()
|
||||
.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.");
|
||||
"#define APPLY_BRIGHTENING_GAMMA"
|
||||
} else {
|
||||
|
@ -159,3 +141,45 @@ fn init_webgl2(canvas: &HtmlCanvasElement) -> Option<(glow::Context, &'static st
|
|||
|
||||
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
|
||||
}
|
|
@ -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")]
|
||||
pub mod winit;
|
||||
|
||||
#[cfg(feature = "winit")]
|
||||
pub use crate::winit::RenderState;
|
||||
use egui::mutex::RwLock;
|
||||
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)]
|
||||
|
||||
use std::num::NonZeroU64;
|
||||
use std::{borrow::Cow, collections::HashMap, num::NonZeroU32};
|
||||
|
||||
use egui::{epaint::Primitive, PaintCallbackInfo};
|
||||
|
@ -26,7 +27,7 @@ use wgpu::util::DeviceExt as _;
|
|||
///
|
||||
/// # 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 {
|
||||
prepare: Box<PrepareCallback>,
|
||||
paint: Box<PaintCallback>,
|
||||
|
@ -149,7 +150,7 @@ impl Renderer {
|
|||
depth_bits: u8,
|
||||
) -> Self {
|
||||
let shader = wgpu::ShaderModuleDescriptor {
|
||||
label: Some("egui_shader"),
|
||||
label: Some("egui"),
|
||||
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("egui.wgsl"))),
|
||||
};
|
||||
let module = device.create_shader_module(shader);
|
||||
|
@ -175,7 +176,7 @@ impl Renderer {
|
|||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
min_binding_size: NonZeroU64::new(std::mem::size_of::<UniformBuffer>() as _),
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
},
|
||||
count: None,
|
||||
|
@ -306,8 +307,9 @@ impl Renderer {
|
|||
}
|
||||
|
||||
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 {
|
||||
label: None,
|
||||
label: Some("egui_depth_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width,
|
||||
height,
|
||||
|
@ -361,7 +363,7 @@ impl Renderer {
|
|||
},
|
||||
})],
|
||||
depth_stencil_attachment,
|
||||
label: Some("egui_render_pass"),
|
||||
label: Some("egui_render"),
|
||||
});
|
||||
|
||||
self.render_onto_renderpass(&mut render_pass, paint_jobs, screen_descriptor);
|
||||
|
@ -559,9 +561,13 @@ impl Renderer {
|
|||
origin,
|
||||
);
|
||||
} else {
|
||||
// TODO(Wumpf): Create only a new texture if we need to
|
||||
// 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 {
|
||||
label: None,
|
||||
label,
|
||||
size,
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
|
@ -573,14 +579,15 @@ impl Renderer {
|
|||
egui::TextureFilter::Nearest => wgpu::FilterMode::Nearest,
|
||||
egui::TextureFilter::Linear => wgpu::FilterMode::Linear,
|
||||
};
|
||||
// TODO(Wumpf): Reuse this sampler.
|
||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||
label: None,
|
||||
label,
|
||||
mag_filter: filter,
|
||||
min_filter: filter,
|
||||
..Default::default()
|
||||
});
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
label,
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
|
@ -633,13 +640,7 @@ impl Renderer {
|
|||
device,
|
||||
texture,
|
||||
wgpu::SamplerDescriptor {
|
||||
label: Some(
|
||||
format!(
|
||||
"egui_user_image_{}_texture_sampler",
|
||||
self.next_user_texture_id
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
mag_filter: texture_filter,
|
||||
min_filter: texture_filter,
|
||||
..Default::default()
|
||||
|
@ -661,13 +662,7 @@ impl Renderer {
|
|||
device,
|
||||
texture,
|
||||
wgpu::SamplerDescriptor {
|
||||
label: Some(
|
||||
format!(
|
||||
"egui_user_image_{}_texture_sampler",
|
||||
self.next_user_texture_id
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
mag_filter: texture_filter,
|
||||
min_filter: texture_filter,
|
||||
..Default::default()
|
||||
|
@ -698,13 +693,7 @@ impl Renderer {
|
|||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(
|
||||
format!(
|
||||
"egui_user_image_{}_texture_bind_group",
|
||||
self.next_user_texture_id
|
||||
)
|
||||
.as_str(),
|
||||
),
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
|
@ -748,9 +737,7 @@ impl Renderer {
|
|||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: Some(
|
||||
format!("egui_user_{}_texture_bind_group", self.next_user_texture_id).as_str(),
|
||||
),
|
||||
label: Some(format!("egui_user_image_{}", self.next_user_texture_id).as_str()),
|
||||
layout: &self.texture_bind_group_layout,
|
||||
entries: &[
|
||||
wgpu::BindGroupEntry {
|
||||
|
|
|
@ -4,17 +4,7 @@ use egui::mutex::RwLock;
|
|||
use tracing::error;
|
||||
use wgpu::{Adapter, Instance, Surface};
|
||||
|
||||
use crate::{renderer, 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>>,
|
||||
}
|
||||
use crate::{renderer, RenderState, Renderer};
|
||||
|
||||
struct SurfaceState {
|
||||
surface: Surface,
|
||||
|
@ -119,7 +109,7 @@ impl<'a> Painter<'a> {
|
|||
let adapter = self.adapter.as_ref().unwrap();
|
||||
|
||||
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));
|
||||
self.render_state = Some(rs);
|
||||
|
@ -324,15 +314,3 @@ impl<'a> Painter<'a> {
|
|||
// 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]
|
||||
console_error_panic_hook = "0.1.6"
|
||||
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::{
|
||||
egui_wgpu::{self, wgpu},
|
||||
|
@ -18,32 +18,32 @@ impl Custom3d {
|
|||
let device = &wgpu_render_state.device;
|
||||
|
||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||
label: None,
|
||||
label: Some("custom3d"),
|
||||
source: wgpu::ShaderSource::Wgsl(include_str!("./custom3d_wgpu_shader.wgsl").into()),
|
||||
});
|
||||
|
||||
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||
label: None,
|
||||
label: Some("custom3d"),
|
||||
entries: &[wgpu::BindGroupLayoutEntry {
|
||||
binding: 0,
|
||||
visibility: wgpu::ShaderStages::VERTEX,
|
||||
ty: wgpu::BindingType::Buffer {
|
||||
ty: wgpu::BufferBindingType::Uniform,
|
||||
has_dynamic_offset: false,
|
||||
min_binding_size: None,
|
||||
min_binding_size: NonZeroU64::new(16),
|
||||
},
|
||||
count: None,
|
||||
}],
|
||||
});
|
||||
|
||||
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||
label: None,
|
||||
label: Some("custom3d"),
|
||||
bind_group_layouts: &[&bind_group_layout],
|
||||
push_constant_ranges: &[],
|
||||
});
|
||||
|
||||
let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
||||
label: None,
|
||||
label: Some("custom3d"),
|
||||
layout: Some(&pipeline_layout),
|
||||
vertex: wgpu::VertexState {
|
||||
module: &shader,
|
||||
|
@ -62,15 +62,15 @@ impl Custom3d {
|
|||
});
|
||||
|
||||
let uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
||||
label: None,
|
||||
contents: bytemuck::cast_slice(&[0.0]),
|
||||
usage: wgpu::BufferUsages::COPY_DST
|
||||
| wgpu::BufferUsages::MAP_WRITE
|
||||
| wgpu::BufferUsages::UNIFORM,
|
||||
label: Some("custom3d"),
|
||||
contents: bytemuck::cast_slice(&[0.0_f32; 4]), // 16 bytes aligned!
|
||||
// Mapping at creation (as done by the create_buffer_init utility) doesn't require us to to add the MAP_WRITE usage
|
||||
// (this *happens* to workaround this bug )
|
||||
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::UNIFORM,
|
||||
});
|
||||
|
||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||
label: None,
|
||||
label: Some("custom3d"),
|
||||
layout: &bind_group_layout,
|
||||
entries: &[wgpu::BindGroupEntry {
|
||||
binding: 0,
|
||||
|
@ -165,7 +165,11 @@ struct TriangleRenderResources {
|
|||
impl TriangleRenderResources {
|
||||
fn prepare(&self, _device: &wgpu::Device, queue: &wgpu::Queue, angle: f32) {
|
||||
// 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>) {
|
||||
|
|
|
@ -4,7 +4,7 @@ struct VertexOut {
|
|||
};
|
||||
|
||||
struct Uniforms {
|
||||
angle: f32,
|
||||
@size(16) angle: f32, // pad to 16 bytes
|
||||
};
|
||||
|
||||
@group(0) @binding(0)
|
||||
|
|
|
@ -62,13 +62,14 @@ pub fn init_wasm_hooks() {
|
|||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[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 handle = eframe::start_web(
|
||||
canvas_id,
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(WrapApp::new(cc))),
|
||||
)
|
||||
.await
|
||||
.map(|handle| WebHandle { 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.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[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();
|
||||
start_separate(canvas_id)
|
||||
start_separate(canvas_id).await
|
||||
}
|
||||
|
|
|
@ -5,4 +5,4 @@ cd "$script_path/.."
|
|||
|
||||
# Pre-requisites:
|
||||
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 New Issue