Skip to content

Commit

Permalink
allow wgpu setup to configure the instance
Browse files Browse the repository at this point in the history
  • Loading branch information
Wumpf committed Jan 3, 2025
1 parent 34f5520 commit b4c2d05
Show file tree
Hide file tree
Showing 5 changed files with 136 additions and 130 deletions.
4 changes: 2 additions & 2 deletions crates/eframe/src/native/wgpu_integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ impl<'app> WgpuWinitApp<'app> {
) -> crate::Result<&mut WgpuWinitRunning<'app>> {
profiling::function_scope!();
#[allow(unsafe_code, unused_mut, unused_unsafe)]
let mut painter = egui_wgpu::winit::Painter::new(
let mut painter = pollster::block_on(egui_wgpu::winit::Painter::new(
egui_ctx.clone(),
self.native_options.wgpu_options.clone(),
self.native_options.multisampling.max(1) as _,
Expand All @@ -193,7 +193,7 @@ impl<'app> WgpuWinitApp<'app> {
),
self.native_options.viewport.transparent.unwrap_or(false),
self.native_options.dithering,
);
));

let window = Arc::new(window);

Expand Down
46 changes: 2 additions & 44 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 super::web_painter::WebPainter;
use crate::WebOptions;
use egui::{Event, UserData, ViewportId};
use egui_wgpu::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
use egui_wgpu::{RenderState, SurfaceErrorAction, WgpuSetup, WgpuSetupCreateNew};
use egui_wgpu::{RenderState, SurfaceErrorAction};
use wasm_bindgen::JsValue;
use web_sys::HtmlCanvasElement;

Expand Down Expand Up @@ -63,49 +63,7 @@ impl WebPainterWgpu {
) -> Result<Self, String> {
log::debug!("Creating wgpu painter");

let instance = match &options.wgpu_options.wgpu_setup {
WgpuSetup::CreateNew(WgpuSetupCreateNew {
supported_backends: backends,
power_preference,
..
}) => {
let mut backends = *backends;

// Don't try WebGPU if we're not in a secure context.
if backends.contains(wgpu::Backends::BROWSER_WEBGPU) {
let is_secure_context =
web_sys::window().map_or(false, |w| w.is_secure_context());
if !is_secure_context {
log::info!(
"WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost."
);

// Don't try WebGPU since we established now that it will fail.
backends.remove(wgpu::Backends::BROWSER_WEBGPU);

if backends.is_empty() {
return Err("No available supported graphics backends.".to_owned());
}
}
}

log::debug!("Creating wgpu instance with backends {:?}", backends);

let instance =
wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor {
backends,
..Default::default()
})
.await;

// On wasm, depending on feature flags, wgpu objects may or may not implement sync.
// It doesn't make sense to switch to Rc for that special usecase, so simply disable the lint.
#[allow(clippy::arc_with_non_send_sync)]
Arc::new(instance)
}
WgpuSetup::Existing { instance, .. } => instance.clone(),
};

let instance = options.wgpu_options.wgpu_setup.new_instance().await;
let surface = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas.clone()))
.map_err(|err| format!("failed to create wgpu surface: {err}"))?;
Expand Down
110 changes: 90 additions & 20 deletions crates/egui-wgpu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,8 @@ impl RenderState {
// This is always an empty list on web.
#[cfg(not(target_arch = "wasm32"))]
let available_adapters = {
let backends = if let WgpuSetup::CreateNew(WgpuSetupCreateNew {
supported_backends,
..
}) = &config.wgpu_setup
{
*supported_backends
let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
create_new.instance_descriptor.backends
} else {
wgpu::Backends::all()
};
Expand All @@ -191,7 +187,7 @@ impl RenderState {

let (adapter, device, queue) = match config.wgpu_setup.clone() {
WgpuSetup::CreateNew(WgpuSetupCreateNew {
supported_backends: _,
instance_descriptor: _,
power_preference,
native_adapter_selector: _native_adapter_selector,
device_descriptor,
Expand Down Expand Up @@ -302,10 +298,16 @@ pub enum WgpuSetup {
/// This is the default option. You can customize most behaviours overriding the
/// supported backends, power preferences, and device description.
///
/// By default can also be configured with the environment variables:
/// By default can also be configured with various environment variables:
/// * `WGPU_BACKEND`: `vulkan`, `dx12`, `metal`, `opengl`, `webgpu`
/// * `WGPU_POWER_PREF`: `low`, `high` or `none`
/// * `WGPU_TRACE`: Path to a file to output a wgpu trace file.
///
/// Each instance flag also comes with an environment variable (for details see [`wgpu::InstanceFlags`]):
/// * `WGPU_VALIDATION`: Enables validation (enabled by default in debug builds).
/// * `WGPU_DEBUG`: Generate debug information in shaders and objects (enabled by default in debug builds).
/// * `WGPU_ALLOW_UNDERLYING_NONCOMPLIANT_ADAPTER`: Whether wgpu should expose adapters that run on top of non-compliant adapters.
/// * `WGPU_GPU_BASED_VALIDATION`: Enable GPU-based validation.
CreateNew(WgpuSetupCreateNew),

/// Run on an existing wgpu setup.
Expand Down Expand Up @@ -338,6 +340,50 @@ impl std::fmt::Debug for WgpuSetup {
}
}

impl WgpuSetup {
/// Creates a new [`wgpu::Instance`] or clones the existing one.
///
/// Does *not* store the wgpu instance, so calling this repeatedly may
/// create a new instance every time!
pub async fn new_instance(&self) -> Arc<wgpu::Instance> {
match self {
Self::CreateNew(create_new) => {
#[allow(unused_mut)]
let mut backends = create_new.instance_descriptor.backends;

// Don't try WebGPU if we're not in a secure context.
#[cfg(target_arch = "wasm32")]
if backends.contains(wgpu::Backends::BROWSER_WEBGPU) {
let is_secure_context =
wgpu::web_sys::window().map_or(false, |w| w.is_secure_context());
if !is_secure_context {
log::info!(
"WebGPU is only available in secure contexts, i.e. on HTTPS and on localhost."
);
backends.remove(wgpu::Backends::BROWSER_WEBGPU);
}
}

log::debug!("Creating wgpu instance with backends {:?}", backends);

Arc::new(
wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor {
backends: create_new.instance_descriptor.backends,
flags: create_new.instance_descriptor.flags,
dx12_shader_compiler: create_new
.instance_descriptor
.dx12_shader_compiler
.clone(),
gles_minor_version: create_new.instance_descriptor.gles_minor_version,
})
.await,
)
}
Self::Existing { instance, .. } => instance.clone(),
}
}
}

/// Method for selecting an adapter on native.
///
/// This can be used for fully custom adapter selection.
Expand All @@ -352,26 +398,27 @@ pub type NativeAdapterSelectorMethod = Arc<
/// Configuration for creating a new wgpu setup.
///
/// Used for [`WgpuSetup::CreateNew`].
#[derive(Clone)]
pub struct WgpuSetupCreateNew {
/// Backends that should be supported (wgpu will pick one of these).
/// Instance descriptor for creating a wgpu instance.
///
/// For instance, if you only want to support WebGL (and not WebGPU),
/// The most important field is [`wgpu::InstanceDescriptor::backends`], which
/// controls which backends are supported (wgpu will pick one of these).
/// If you only want to support WebGL (and not WebGPU),
/// you can set this to [`wgpu::Backends::GL`].
///
/// By default on web, WebGPU will be used if available.
/// WebGL will only be used as a fallback,
/// and only if you have enabled the `webgl` feature of crate `wgpu`.
pub supported_backends: wgpu::Backends,
pub instance_descriptor: wgpu::InstanceDescriptor,

/// Power preference for the adapter.
/// Power preference for the adapter if [`Self::native_adapter_selector`] is not set or targeting web.
pub power_preference: wgpu::PowerPreference,

/// Optional selector for native adapters.
///
/// This field has no effect when targeting web!
/// Otherwise, if set [`Self::power_preference`] is ignored and the adapter is instead selected by this method.
/// Note that [`Self::supported_backends`] is still used to filter the adapter enumeration in the first place.
/// Note that [`Self::instance_descriptor`]'s [`wgpu::InstanceDescriptor::backends`]
/// are still used to filter the adapter enumeration in the first place.
///
/// Defaults to `None`.
pub native_adapter_selector: Option<NativeAdapterSelectorMethod>,
Expand All @@ -388,10 +435,28 @@ pub struct WgpuSetupCreateNew {
pub trace_path: Option<std::path::PathBuf>,
}

impl Clone for WgpuSetupCreateNew {
fn clone(&self) -> Self {
Self {
// TODO(gfx-rs/wgpu/#6849): use .clone()
instance_descriptor: wgpu::InstanceDescriptor {
backends: self.instance_descriptor.backends,
flags: self.instance_descriptor.flags,
dx12_shader_compiler: self.instance_descriptor.dx12_shader_compiler.clone(),
gles_minor_version: self.instance_descriptor.gles_minor_version,
},
power_preference: self.power_preference,
native_adapter_selector: self.native_adapter_selector.clone(),
device_descriptor: self.device_descriptor.clone(),
trace_path: self.trace_path.clone(),
}
}
}

impl std::fmt::Debug for WgpuSetupCreateNew {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("WgpuSetupCreateNew")
.field("supported_backends", &self.supported_backends)
.field("instance_descriptor", &self.instance_descriptor)
.field("power_preference", &self.power_preference)
.field(
"native_adapter_selector",
Expand All @@ -405,10 +470,15 @@ impl std::fmt::Debug for WgpuSetupCreateNew {
impl Default for WgpuSetupCreateNew {
fn default() -> Self {
Self {
// Add GL backend, primarily because WebGPU is not stable enough yet.
// (note however, that the GL backend needs to be opted-in via the wgpu feature flag "webgl")
supported_backends: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL),
instance_descriptor: wgpu::InstanceDescriptor {
// Add GL backend, primarily because WebGPU is not stable enough yet.
// (note however, that the GL backend needs to be opted-in via the wgpu feature flag "webgl")
backends: wgpu::util::backend_bits_from_env()
.unwrap_or(wgpu::Backends::PRIMARY | wgpu::Backends::GL),
flags: wgpu::InstanceFlags::from_build_config().with_env(),
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
},

power_preference: wgpu::util::power_preference_from_env()
.unwrap_or(wgpu::PowerPreference::HighPerformance),
Expand Down
15 changes: 3 additions & 12 deletions crates/egui-wgpu/src/winit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#![allow(clippy::undocumented_unsafe_blocks)]

use crate::capture::{capture_channel, CaptureReceiver, CaptureSender, CaptureState};
use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration, WgpuSetupCreateNew};
use crate::{renderer, RenderState, SurfaceErrorAction, WgpuConfiguration};
use egui::{Context, Event, UserData, ViewportId, ViewportIdMap, ViewportIdSet};
use std::{num::NonZeroU32, sync::Arc};

Expand Down Expand Up @@ -51,25 +51,16 @@ impl Painter {
/// [`set_window()`](Self::set_window) once you have
/// a [`winit::window::Window`] with a valid `.raw_window_handle()`
/// associated.
pub fn new(
pub async fn new(
context: Context,
configuration: WgpuConfiguration,
msaa_samples: u32,
depth_format: Option<wgpu::TextureFormat>,
support_transparent_backbuffer: bool,
dithering: bool,
) -> Self {
let instance = match &configuration.wgpu_setup {
crate::WgpuSetup::CreateNew(WgpuSetupCreateNew {
supported_backends, ..
}) => Arc::new(wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: *supported_backends,
..Default::default()
})),
crate::WgpuSetup::Existing { instance, .. } => instance.clone(),
};

let (capture_tx, capture_rx) = capture_channel();
let instance = configuration.wgpu_setup.new_instance().await;

Self {
context,
Expand Down
91 changes: 39 additions & 52 deletions crates/egui_kittest/src/wgpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,61 +10,48 @@ use crate::texture_to_image::texture_to_image;

/// Default wgpu setup used for the wgpu renderer.
pub fn default_wgpu_setup() -> egui_wgpu::WgpuSetup {
egui_wgpu::WgpuSetup::CreateNew(egui_wgpu::WgpuSetupCreateNew {
// WebGPU not supported yet since we rely on blocking screenshots.
supported_backends: wgpu::util::backend_bits_from_env().unwrap_or(
wgpu::Backends::all().intersection(wgpu::Backends::BROWSER_WEBGPU.complement()),
),

native_adapter_selector: Some(Arc::new(|adapters, _surface| {
let mut adapters = adapters.iter().collect::<Vec<_>>();

// Adapters are already sorted by preferred backend by wgpu, but let's be explicit.
adapters.sort_by_key(|a| match a.get_info().backend {
wgpu::Backend::Metal => 0,
wgpu::Backend::Vulkan => 1,
wgpu::Backend::Dx12 => 2,
wgpu::Backend::Gl => 4,
wgpu::Backend::BrowserWebGpu => 6,
wgpu::Backend::Empty => 7,
});

// Prefer CPU adapters, otherwise if we can't, prefer discrete GPU over integrated GPU.
adapters.sort_by_key(|a| match a.get_info().device_type {
wgpu::DeviceType::Cpu => 0, // CPU is the best for our purposes!
wgpu::DeviceType::DiscreteGpu => 1,
wgpu::DeviceType::Other
| wgpu::DeviceType::IntegratedGpu
| wgpu::DeviceType::VirtualGpu => 2,
});

adapters
.first()
.map(|a| (*a).clone())
.ok_or("No adapter found".to_owned())
})),

device_descriptor: std::sync::Arc::new(|_| wgpu::DeviceDescriptor {
label: Some("egui-kittest"),
..Default::default()
}),

..Default::default()
})
let mut setup = egui_wgpu::WgpuSetupCreateNew::default();

// WebGPU not supported yet since we rely on blocking screenshots.
setup
.instance_descriptor
.backends
.remove(wgpu::Backends::BROWSER_WEBGPU);

// Prefer software rasterizers.
setup.native_adapter_selector = Some(Arc::new(|adapters, _surface| {
let mut adapters = adapters.iter().collect::<Vec<_>>();

// Adapters are already sorted by preferred backend by wgpu, but let's be explicit.
adapters.sort_by_key(|a| match a.get_info().backend {
wgpu::Backend::Metal => 0,
wgpu::Backend::Vulkan => 1,
wgpu::Backend::Dx12 => 2,
wgpu::Backend::Gl => 4,
wgpu::Backend::BrowserWebGpu => 6,
wgpu::Backend::Empty => 7,
});

// Prefer CPU adapters, otherwise if we can't, prefer discrete GPU over integrated GPU.
adapters.sort_by_key(|a| match a.get_info().device_type {
wgpu::DeviceType::Cpu => 0, // CPU is the best for our purposes!
wgpu::DeviceType::DiscreteGpu => 1,
wgpu::DeviceType::Other
| wgpu::DeviceType::IntegratedGpu
| wgpu::DeviceType::VirtualGpu => 2,
});

adapters
.first()
.map(|a| (*a).clone())
.ok_or("No adapter found".to_owned())
}));

egui_wgpu::WgpuSetup::CreateNew(setup)
}

pub fn create_render_state(setup: WgpuSetup) -> egui_wgpu::RenderState {
let instance = match &setup {
WgpuSetup::Existing { instance, .. } => instance.clone(),
WgpuSetup::CreateNew(egui_wgpu::WgpuSetupCreateNew {
supported_backends, ..
}) => Arc::new(wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: *supported_backends,
flags: wgpu::InstanceFlags::default(),
dx12_shader_compiler: wgpu::Dx12Compiler::Fxc,
gles_minor_version: wgpu::Gles3MinorVersion::Automatic,
})),
};
let instance = pollster::block_on(setup.new_instance());

pollster::block_on(egui_wgpu::RenderState::create(
&egui_wgpu::WgpuConfiguration {
Expand Down

0 comments on commit b4c2d05

Please sign in to comment.