mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 12:50:04 -04:00
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:
@@ -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
|
||||
}
|
||||
|
||||
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::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
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user