Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

configurable wgpu backend #2207

Merged
merged 7 commits into from
Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ NOTE: [`egui-winit`](../egui-winit/CHANGELOG.md), [`egui_glium`](../egui_glium/C
* 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)).
* Web: Add `WebInfo::user_agent` ([#2202](https://github.com/emilk/egui/pull/2202)).
* * Wgpu device/adapter/surface creation has now various configuration options exposed via `NativeOptions/WebOptions::wgpu_options` ([#2207](https://github.com/emilk/egui/pull/2207)).


## 0.19.0 - 2022-08-20
Expand Down
31 changes: 31 additions & 0 deletions crates/eframe/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::any::Any;

#[cfg(not(target_arch = "wasm32"))]
pub use crate::native::run::RequestRepaintEvent;

#[cfg(not(target_arch = "wasm32"))]
pub use winit::event_loop::EventLoopBuilder;

Expand Down Expand Up @@ -364,6 +365,10 @@ pub struct NativeOptions {
///
/// Wayland desktop currently not supported.
pub centered: bool,

/// Configures wgpu instance/device/adapter/surface creation and renderloop.
#[cfg(feature = "wgpu")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -372,6 +377,8 @@ impl Clone for NativeOptions {
Self {
icon_data: self.icon_data.clone(),
event_loop_builder: None, // Skip any builder callbacks if cloning
#[cfg(feature = "wgpu")]
wgpu_options: self.wgpu_options.clone(),
..*self
}
}
Expand Down Expand Up @@ -409,6 +416,8 @@ impl Default for NativeOptions {
#[cfg(feature = "glow")]
shader_version: None,
centered: false,
#[cfg(feature = "wgpu")]
wgpu_options: egui_wgpu::WgpuConfiguration::default(),
}
}
}
Expand Down Expand Up @@ -459,6 +468,10 @@ pub struct WebOptions {
/// Default: [`WebGlContextOption::BestFirst`].
#[cfg(feature = "glow")]
pub webgl_context_option: WebGlContextOption,

/// Configures wgpu instance/device/adapter/surface creation and renderloop.
#[cfg(feature = "wgpu")]
pub wgpu_options: egui_wgpu::WgpuConfiguration,
}

#[cfg(target_arch = "wasm32")]
Expand All @@ -467,8 +480,26 @@ impl Default for WebOptions {
Self {
follow_system_theme: true,
default_theme: Theme::Dark,

#[cfg(feature = "glow")]
webgl_context_option: WebGlContextOption::BestFirst,

#[cfg(feature = "wgpu")]
wgpu_options: egui_wgpu::WgpuConfiguration {
// WebGPU is not stable enough yet, use WebGL emulation
backends: wgpu::Backends::GL,
device_descriptor: wgpu::DeviceDescriptor {
label: Some("egui wgpu device"),
features: wgpu::Features::default(),
limits: wgpu::Limits {
// When using a depth buffer, we have to be able to create a texture
// large enough for the entire surface, and we want to support 4k+ displays.
max_texture_dimension_2d: 8192,
..wgpu::Limits::downlevel_webgl2_defaults()
},
},
..Default::default()
},
}
}
}
Expand Down
17 changes: 2 additions & 15 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ mod wgpu_integration {
/// a Resumed event. On Android this ensures that any graphics state is only
/// initialized once the application has an associated `SurfaceView`.
struct WgpuWinitRunning {
painter: egui_wgpu::winit::Painter<'static>,
painter: egui_wgpu::winit::Painter,
integration: epi_integration::EpiIntegration,
app: Box<dyn epi::App>,
}
Expand Down Expand Up @@ -723,23 +723,10 @@ mod wgpu_integration {
storage: Option<Box<dyn epi::Storage>>,
window: winit::window::Window,
) {
let mut limits = wgpu::Limits::downlevel_webgl2_defaults();
if self.native_options.depth_buffer > 0 {
// When using a depth buffer, we have to be able to create a texture large enough for the entire surface.
limits.max_texture_dimension_2d = 8192;
}

#[allow(unsafe_code, unused_mut, unused_unsafe)]
let painter = unsafe {
let mut painter = egui_wgpu::winit::Painter::new(
wgpu::Backends::PRIMARY | wgpu::Backends::GL,
wgpu::PowerPreference::HighPerformance,
wgpu::DeviceDescriptor {
label: None,
features: wgpu::Features::default(),
limits,
},
wgpu::PresentMode::Fifo,
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
self.native_options.depth_buffer,
);
Expand Down
93 changes: 51 additions & 42 deletions crates/eframe/src/web/web_painter_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;

use egui::{mutex::RwLock, Rgba};
use egui_wgpu::{renderer::ScreenDescriptor, RenderState};
use egui_wgpu::{renderer::ScreenDescriptor, RenderState, SurfaceErrorAction};

use crate::WebOptions;

Expand All @@ -14,9 +14,10 @@ pub(crate) struct WebPainterWgpu {
canvas: HtmlCanvasElement,
canvas_id: String,
surface: wgpu::Surface,
surface_size: [u32; 2],
surface_configuration: wgpu::SurfaceConfiguration,
limits: wgpu::Limits,
render_state: Option<RenderState>,
on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
}

impl WebPainterWgpu {
Expand All @@ -26,30 +27,27 @@ impl WebPainterWgpu {
}

#[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…");
pub async fn new(canvas_id: &str, options: &WebOptions) -> Result<Self, String> {
tracing::debug!("Creating wgpu painter");

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 instance = wgpu::Instance::new(options.wgpu_options.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 adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: options.wgpu_options.power_preference,
force_fallback_adapter: false,
compatible_surface: None,
})
.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 (?)
&options.wgpu_options.device_descriptor,
None, // Capture doesn't work in the browser environment.
)
.await
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;
Expand All @@ -65,15 +63,25 @@ impl WebPainterWgpu {
renderer: Arc::new(RwLock::new(renderer)),
};

let surface_configuration = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: target_format,
width: 0,
height: 0,
present_mode: options.wgpu_options.present_mode,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
};

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,
surface_configuration,
limits: options.wgpu_options.device_descriptor.limits.clone(),
on_surface_error: options.wgpu_options.on_surface_error.clone(),
})
}
}
Expand Down Expand Up @@ -103,28 +111,30 @@ impl WebPainter for WebPainterWgpu {
};

// 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,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
},
);
self.surface_size = canvas_size;
let size_in_pixels = [self.canvas.width(), self.canvas.height()];
if size_in_pixels[0] != self.surface_configuration.width
|| size_in_pixels[1] != self.surface_configuration.height
{
self.surface_configuration.width = size_in_pixels[0];
self.surface_configuration.height = size_in_pixels[1];
self.surface
.configure(&render_state.device, &self.surface_configuration);
}

let frame = self.surface.get_current_texture().map_err(|err| {
JsValue::from_str(&format!(
"Failed to acquire next swap chain texture: {}",
err
))
})?;
let frame = match self.surface.get_current_texture() {
Ok(frame) => frame,
#[allow(clippy::single_match_else)]
Err(e) => match (*self.on_surface_error)(e) {
SurfaceErrorAction::RecreateSurface => {
self.surface
.configure(&render_state.device, &self.surface_configuration);
return Ok(());
}
SurfaceErrorAction::SkipFrame => {
return Ok(());
}
},
};

let mut encoder =
render_state
Expand All @@ -135,10 +145,9 @@ impl WebPainter for WebPainterWgpu {

// Upload all resources for the GPU.
let screen_descriptor = ScreenDescriptor {
size_in_pixels: canvas_size,
size_in_pixels,
pixels_per_point,
};

{
let mut renderer = render_state.renderer.write();
for (id, image_delta) in &textures_delta.set {
Expand Down
1 change: 1 addition & 0 deletions crates/egui-wgpu/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ All notable changes to the `egui-wgpu` integration will be noted in this file.
* `PrepareCallback` now passes `wgpu::CommandEncoder` ([#2136](https://github.com/emilk/egui/pull/2136))
* Only a single vertex & index buffer is now created and resized when necessary (previously, vertex/index buffers were allocated for every mesh) ([#2148](https://github.com/emilk/egui/pull/2148)).
* `Renderer::update_texture` no longer creates a new `wgpu::Sampler` with every new texture ([#2198](https://github.com/emilk/egui/pull/2198))
* `Painter`'s instance/device/adapter/surface creation is now configurable via `WgpuConfiguration` ([#2207](https://github.com/emilk/egui/pull/2207))

## 0.19.0 - 2022-08-20
* Enables deferred render + surface state initialization for Android ([#1634](https://github.com/emilk/egui/pull/1634)).
Expand Down
57 changes: 55 additions & 2 deletions crates/egui-wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ pub mod winit;
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.
/// Access to the render state for egui.
#[derive(Clone)]
pub struct RenderState {
pub device: Arc<wgpu::Device>,
Expand All @@ -30,6 +29,60 @@ pub struct RenderState {
pub renderer: Arc<RwLock<Renderer>>,
}

/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`]
pub enum SurfaceErrorAction {
/// Do nothing and skip the current frame.
SkipFrame,

/// Instructs egui to recreate the surface, then skip the current frame.
RecreateSurface,
}

/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
#[derive(Clone)]
pub struct WgpuConfiguration {
/// Configuration passed on device request.
pub device_descriptor: wgpu::DeviceDescriptor<'static>,

/// Backends that should be supported (wgpu will pick one of these)
pub backends: wgpu::Backends,

/// Present mode used for the primary surface.
pub present_mode: wgpu::PresentMode,

/// Power preference for the adapter.
pub power_preference: wgpu::PowerPreference,

/// Callback for surface errors.
pub on_surface_error: Arc<dyn Fn(wgpu::SurfaceError) -> SurfaceErrorAction>,
}

impl Default for WgpuConfiguration {
fn default() -> Self {
Self {
device_descriptor: wgpu::DeviceDescriptor {
label: Some("egui wgpu device"),
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
},
backends: wgpu::Backends::PRIMARY | wgpu::Backends::GL,
present_mode: wgpu::PresentMode::AutoVsync,
power_preference: wgpu::PowerPreference::HighPerformance,

on_surface_error: Arc::new(|err| {
if err == wgpu::SurfaceError::Outdated {
// This error occurs when the app is minimized on Windows.
// Silently return here to prevent spamming the console with:
// "The underlying surface has changed, and therefore the swap chain must be updated"
} else {
tracing::warn!("Dropped frame with error: {err}");
}
SurfaceErrorAction::SkipFrame
}),
}
}
}

/// Find the framebuffer format that egui prefers
pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
for &format in formats {
Expand Down
Loading