Skip to content

Commit

Permalink
[wgpu] Expose wgpu::Adapter via RenderState (emilk#2954)
Browse files Browse the repository at this point in the history
* [wgpu] Expose adapter, better errors, better docs, more code sharing, use stencil bits

* doc fix

* remove unnecessary unsafe

* handle missing framebuffer format

* better handling of renderstate creation in winit.rs

* remove unnecessary use directive
  • Loading branch information
Wumpf authored Apr 25, 2023
1 parent 20e291d commit f76eefb
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 152 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,6 @@ opt-level = 2 # fast and small wasm, basically same as `opt-level = 's'`
# Optimize all dependencies even in debug builds (does not affect workspace packages):
[profile.dev.package."*"]
opt-level = 2

[workspace.dependencies]
thiserror = "1.0.37"
2 changes: 1 addition & 1 deletion crates/eframe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ egui = { version = "0.21.0", path = "../egui", default-features = false, feature
"log",
] }
log = { version = "0.4", features = ["std"] }
thiserror = "1.0.37"
thiserror.workspace = true

#! ### Optional dependencies
## Enable this when generating docs.
Expand Down
11 changes: 7 additions & 4 deletions crates/eframe/src/epi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,10 +312,6 @@ pub struct NativeOptions {
/// Sets the number of bits in the depth buffer.
///
/// `egui` doesn't need the depth buffer, so the default value is 0.
///
/// On `wgpu` backends, due to limited depth texture format options, this
/// will be interpreted as a boolean (non-zero = true) for whether or not
/// specifically a `Depth32Float` buffer is used.
pub depth_buffer: u8,

/// Sets the number of bits in the stencil buffer.
Expand Down Expand Up @@ -475,6 +471,12 @@ pub struct WebOptions {
/// Default: `Theme::Dark`.
pub default_theme: Theme,

/// Sets the number of bits in the depth buffer.
///
/// `egui` doesn't need the depth buffer, so the default value is 0.
/// Unused by webgl context as of writing.
pub depth_buffer: u8,

/// Which version of WebGl context to select
///
/// Default: [`WebGlContextOption::BestFirst`].
Expand All @@ -492,6 +494,7 @@ impl Default for WebOptions {
Self {
follow_system_theme: true,
default_theme: Theme::Dark,
depth_buffer: 0,

#[cfg(feature = "glow")]
webgl_context_option: WebGlContextOption::BestFirst,
Expand Down
26 changes: 11 additions & 15 deletions crates/eframe/src/native/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1122,9 +1122,7 @@ mod wgpu_integration {
) -> std::result::Result<(), egui_wgpu::WgpuError> {
self.window = Some(window);
if let Some(running) = &mut self.running {
unsafe {
pollster::block_on(running.painter.set_window(self.window.as_ref()))?;
}
pollster::block_on(running.painter.set_window(self.window.as_ref()))?;
}
Ok(())
}
Expand All @@ -1134,9 +1132,7 @@ mod wgpu_integration {
fn drop_window(&mut self) -> std::result::Result<(), egui_wgpu::WgpuError> {
self.window = None;
if let Some(running) = &mut self.running {
unsafe {
pollster::block_on(running.painter.set_window(None))?;
}
pollster::block_on(running.painter.set_window(None))?;
}
Ok(())
}
Expand All @@ -1148,16 +1144,16 @@ mod wgpu_integration {
window: winit::window::Window,
) -> std::result::Result<(), egui_wgpu::WgpuError> {
#[allow(unsafe_code, unused_mut, unused_unsafe)]
let painter = unsafe {
let mut painter = egui_wgpu::winit::Painter::new(
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
let mut painter = egui_wgpu::winit::Painter::new(
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
egui_wgpu::depth_format_from_bits(
self.native_options.depth_buffer,
self.native_options.transparent,
);
pollster::block_on(painter.set_window(Some(&window)))?;
painter
};
self.native_options.stencil_buffer,
),
self.native_options.transparent,
);
pollster::block_on(painter.set_window(Some(&window)))?;

let wgpu_render_state = painter.render_state();

Expand Down
38 changes: 7 additions & 31 deletions crates/eframe/src/web/web_painter_wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::sync::Arc;
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;

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

use crate::WebOptions;
Expand Down Expand Up @@ -99,43 +98,20 @@ impl WebPainterWgpu {
}
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;

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(
&(*options.wgpu_options.device_descriptor)(&adapter),
None, // Capture doesn't work in the browser environment.
)
.await
.map_err(|err| format!("Failed to find wgpu device: {}", err))?;

let target_format =
egui_wgpu::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats);

let depth_format = options.wgpu_options.depth_format;
let renderer = egui_wgpu::Renderer::new(&device, target_format, depth_format, 1);
let render_state = RenderState {
device: Arc::new(device),
queue: Arc::new(queue),
target_format,
renderer: Arc::new(RwLock::new(renderer)),
};
let depth_format = egui_wgpu::depth_format_from_bits(options.depth_buffer, 0);
let render_state =
RenderState::create(&options.wgpu_options, &instance, &surface, depth_format, 1)
.await
.map_err(|err| err.to_string())?;

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

log::debug!("wgpu painter initialized.");
Expand Down
1 change: 1 addition & 0 deletions crates/egui-wgpu/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ epaint = { version = "0.21.0", path = "../epaint", default-features = false, fea

bytemuck = "1.7"
log = { version = "0.4", features = ["std"] }
thiserror.workspace = true
type-map = "0.5.0"
wgpu = "0.16.0"

Expand Down
120 changes: 86 additions & 34 deletions crates/egui-wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,80 @@ use std::sync::Arc;

use epaint::mutex::RwLock;

#[derive(thiserror::Error, Debug)]
pub enum WgpuError {
#[error("Failed to create wgpu adapter, no suitable adapter found.")]
NoSuitableAdapterFound,

#[error("There was no valid format for the surface at all.")]
NoSurfaceFormatsAvailable,

#[error(transparent)]
RequestDeviceError(#[from] wgpu::RequestDeviceError),

#[error(transparent)]
CreateSurfaceError(#[from] wgpu::CreateSurfaceError),
}

/// Access to the render state for egui.
#[derive(Clone)]
pub struct RenderState {
/// Wgpu adapter used for rendering.
pub adapter: Arc<wgpu::Adapter>,

/// Wgpu device used for rendering, created from the adapter.
pub device: Arc<wgpu::Device>,

/// Wgpu queue used for rendering, created from the adapter.
pub queue: Arc<wgpu::Queue>,

/// The target texture format used for presenting to the window.
pub target_format: wgpu::TextureFormat,

/// Egui renderer responsible for drawing the UI.
pub renderer: Arc<RwLock<Renderer>>,
}

impl RenderState {
/// Creates a new `RenderState`, containing everything needed for drawing egui with wgpu.
///
/// # Errors
/// Wgpu initialization may fail due to incompatible hardware or driver for a given config.
pub async fn create(
config: &WgpuConfiguration,
instance: &wgpu::Instance,
surface: &wgpu::Surface,
depth_format: Option<wgpu::TextureFormat>,
msaa_samples: u32,
) -> Result<Self, WgpuError> {
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: config.power_preference,
compatible_surface: Some(surface),
force_fallback_adapter: false,
})
.await
.ok_or(WgpuError::NoSuitableAdapterFound)?;

let target_format =
crate::preferred_framebuffer_format(&surface.get_capabilities(&adapter).formats)?;

let (device, queue) = adapter
.request_device(&(*config.device_descriptor)(&adapter), None)
.await?;

let renderer = Renderer::new(&device, target_format, depth_format, msaa_samples);

Ok(RenderState {
adapter: Arc::new(adapter),
device: Arc::new(device),
queue: Arc::new(queue),
target_format,
renderer: Arc::new(RwLock::new(renderer)),
})
}
}

/// Specifies which action should be taken as consequence of a [`wgpu::SurfaceError`]
pub enum SurfaceErrorAction {
/// Do nothing and skip the current frame.
Expand All @@ -56,8 +121,6 @@ pub struct WgpuConfiguration {

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

pub depth_format: Option<wgpu::TextureFormat>,
}

impl Default for WgpuConfiguration {
Expand Down Expand Up @@ -88,7 +151,6 @@ impl Default for WgpuConfiguration {
present_mode: wgpu::PresentMode::AutoVsync,
power_preference: wgpu::util::power_preference_from_env()
.unwrap_or(wgpu::PowerPreference::HighPerformance),
depth_format: None,

on_surface_error: Arc::new(|err| {
if err == wgpu::SurfaceError::Outdated {
Expand All @@ -105,50 +167,40 @@ impl Default for WgpuConfiguration {
}

/// Find the framebuffer format that egui prefers
pub fn preferred_framebuffer_format(formats: &[wgpu::TextureFormat]) -> wgpu::TextureFormat {
///
/// # Errors
/// Returns [`WgpuError::NoSurfaceFormatsAvailable`] if the given list of formats is empty.
pub fn preferred_framebuffer_format(
formats: &[wgpu::TextureFormat],
) -> Result<wgpu::TextureFormat, WgpuError> {
for &format in formats {
if matches!(
format,
wgpu::TextureFormat::Rgba8Unorm | wgpu::TextureFormat::Bgra8Unorm
) {
return format;
return Ok(format);
}
}
formats[0] // take the first
}
// maybe use this-error?
#[derive(Debug)]
pub enum WgpuError {
DeviceError(wgpu::RequestDeviceError),
SurfaceError(wgpu::CreateSurfaceError),
}

impl std::fmt::Display for WgpuError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}

impl std::error::Error for WgpuError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
WgpuError::DeviceError(e) => e.source(),
WgpuError::SurfaceError(e) => e.source(),
}
}
formats
.get(0)
.copied()
.ok_or(WgpuError::NoSurfaceFormatsAvailable)
}

impl From<wgpu::RequestDeviceError> for WgpuError {
fn from(e: wgpu::RequestDeviceError) -> Self {
Self::DeviceError(e)
/// Take's epi's depth/stencil bits and returns the corresponding wgpu format.
pub fn depth_format_from_bits(depth_buffer: u8, stencil_buffer: u8) -> Option<wgpu::TextureFormat> {
match (depth_buffer, stencil_buffer) {
(0, 8) => Some(wgpu::TextureFormat::Stencil8),
(16, 0) => Some(wgpu::TextureFormat::Depth16Unorm),
(24, 0) => Some(wgpu::TextureFormat::Depth24Plus),
(24, 8) => Some(wgpu::TextureFormat::Depth24PlusStencil8),
(32, 0) => Some(wgpu::TextureFormat::Depth32Float),
(32, 8) => Some(wgpu::TextureFormat::Depth32FloatStencil8),
_ => None,
}
}

impl From<wgpu::CreateSurfaceError> for WgpuError {
fn from(e: wgpu::CreateSurfaceError) -> Self {
Self::SurfaceError(e)
}
}
// ---------------------------------------------------------------------------

/// Profiling macro for feature "puffin"
Expand Down
Loading

0 comments on commit f76eefb

Please sign in to comment.