Extend `WgpuSetup`, `egui_kittest` now prefers software rasterizers for testing (#5506)
This commit is contained in:
parent
7186f72cbe
commit
443df84a22
29
Cargo.lock
29
Cargo.lock
|
|
@ -1349,7 +1349,6 @@ dependencies = [
|
|||
"egui_kittest",
|
||||
"serde",
|
||||
"unicode_names2",
|
||||
"wgpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -1927,6 +1926,18 @@ dependencies = [
|
|||
"bitflags 2.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-allocator"
|
||||
version = "0.27.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c151a2a5ef800297b4e79efa4f4bec035c5f51d5ae587287c9b952bdf734cacd"
|
||||
dependencies = [
|
||||
"log",
|
||||
"presser",
|
||||
"thiserror",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gpu-descriptor"
|
||||
version = "0.3.0"
|
||||
|
|
@ -3110,6 +3121,12 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "presser"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.2.0"
|
||||
|
|
@ -3263,6 +3280,12 @@ dependencies = [
|
|||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "range-alloc"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
|
||||
|
||||
[[package]]
|
||||
name = "raw-window-handle"
|
||||
version = "0.5.2"
|
||||
|
|
@ -4513,6 +4536,7 @@ dependencies = [
|
|||
"android_system_properties",
|
||||
"arrayvec",
|
||||
"ash",
|
||||
"bit-set 0.8.0",
|
||||
"bitflags 2.6.0",
|
||||
"block",
|
||||
"bytemuck",
|
||||
|
|
@ -4521,6 +4545,7 @@ dependencies = [
|
|||
"glow 0.14.2",
|
||||
"glutin_wgl_sys",
|
||||
"gpu-alloc",
|
||||
"gpu-allocator",
|
||||
"gpu-descriptor",
|
||||
"js-sys",
|
||||
"khronos-egl",
|
||||
|
|
@ -4534,6 +4559,7 @@ dependencies = [
|
|||
"once_cell",
|
||||
"parking_lot",
|
||||
"profiling",
|
||||
"range-alloc",
|
||||
"raw-window-handle 0.6.2",
|
||||
"renderdoc-sys",
|
||||
"rustc-hash",
|
||||
|
|
@ -4543,6 +4569,7 @@ dependencies = [
|
|||
"web-sys",
|
||||
"wgpu-types",
|
||||
"windows",
|
||||
"windows-core 0.58.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
|
|
@ -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 _,
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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};
|
||||
use egui_wgpu::{RenderState, SurfaceErrorAction};
|
||||
use wasm_bindgen::JsValue;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
|
|
@ -63,49 +63,7 @@ impl WebPainterWgpu {
|
|||
) -> Result<Self, String> {
|
||||
log::debug!("Creating wgpu painter");
|
||||
|
||||
let instance = match &options.wgpu_options.wgpu_setup {
|
||||
WgpuSetup::CreateNew {
|
||||
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}"))?;
|
||||
|
|
|
|||
|
|
@ -23,8 +23,10 @@ pub use wgpu;
|
|||
/// Low-level painting of [`egui`](https://github.com/emilk/egui) on [`wgpu`].
|
||||
mod renderer;
|
||||
|
||||
mod setup;
|
||||
|
||||
pub use renderer::*;
|
||||
use wgpu::{Adapter, Device, Instance, Queue, TextureFormat};
|
||||
pub use setup::{NativeAdapterSelectorMethod, WgpuSetup, WgpuSetupCreateNew, WgpuSetupExisting};
|
||||
|
||||
/// Helpers for capturing screenshots of the UI.
|
||||
pub mod capture;
|
||||
|
|
@ -40,8 +42,8 @@ use epaint::mutex::RwLock;
|
|||
/// An error produced by egui-wgpu.
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum WgpuError {
|
||||
#[error("Failed to create wgpu adapter, no suitable adapter found.")]
|
||||
NoSuitableAdapterFound,
|
||||
#[error("Failed to create wgpu adapter, no suitable adapter found: {0}")]
|
||||
NoSuitableAdapterFound(String),
|
||||
|
||||
#[error("There was no valid format for the surface at all.")]
|
||||
NoSurfaceFormatsAvailable,
|
||||
|
|
@ -67,8 +69,9 @@ pub struct RenderState {
|
|||
///
|
||||
/// This is not available on web.
|
||||
/// On web, we always select WebGPU is available, then fall back to WebGL if not.
|
||||
// TODO(gfx-rs/wgpu#6665): Remove layer of `Arc` here once we update to wgpu 24
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub available_adapters: Arc<[wgpu::Adapter]>,
|
||||
pub available_adapters: Arc<[Arc<wgpu::Adapter>]>,
|
||||
|
||||
/// Wgpu device used for rendering, created from the adapter.
|
||||
pub device: Arc<wgpu::Device>,
|
||||
|
|
@ -83,6 +86,75 @@ pub struct RenderState {
|
|||
pub renderer: Arc<RwLock<Renderer>>,
|
||||
}
|
||||
|
||||
async fn request_adapter(
|
||||
instance: &wgpu::Instance,
|
||||
power_preference: wgpu::PowerPreference,
|
||||
compatible_surface: Option<&wgpu::Surface<'_>>,
|
||||
_available_adapters: &[Arc<wgpu::Adapter>],
|
||||
) -> Result<Arc<wgpu::Adapter>, WgpuError> {
|
||||
profiling::function_scope!();
|
||||
|
||||
let adapter = instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
power_preference,
|
||||
compatible_surface,
|
||||
// We don't expose this as an option right now since it's fairly rarely useful:
|
||||
// * only has an effect on native
|
||||
// * fails if there's no software rasterizer available
|
||||
// * can achieve the same with `native_adapter_selector`
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if _available_adapters.is_empty() {
|
||||
log::info!("No wgpu adapters found");
|
||||
} else if _available_adapters.len() == 1 {
|
||||
log::info!(
|
||||
"The only available wgpu adapter was not suitable: {}",
|
||||
adapter_info_summary(&_available_adapters[0].get_info())
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"No suitable wgpu adapter found out of the {} available ones: {}",
|
||||
_available_adapters.len(),
|
||||
describe_adapters(_available_adapters)
|
||||
);
|
||||
}
|
||||
|
||||
WgpuError::NoSuitableAdapterFound("`request_adapters` returned `None`".to_owned())
|
||||
})?;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
log::debug!(
|
||||
"Picked wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if _available_adapters.len() == 1 {
|
||||
log::debug!(
|
||||
"Picked the only available wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"There were {} available wgpu adapters: {}",
|
||||
_available_adapters.len(),
|
||||
describe_adapters(_available_adapters)
|
||||
);
|
||||
log::debug!(
|
||||
"Picked wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
}
|
||||
|
||||
// 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)]
|
||||
Ok(Arc::new(adapter))
|
||||
}
|
||||
|
||||
impl RenderState {
|
||||
/// Creates a new [`RenderState`], containing everything needed for drawing egui with wgpu.
|
||||
///
|
||||
|
|
@ -100,96 +172,73 @@ impl RenderState {
|
|||
|
||||
// This is always an empty list on web.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
let available_adapters = instance.enumerate_adapters(wgpu::Backends::all());
|
||||
let available_adapters = {
|
||||
let backends = if let WgpuSetup::CreateNew(create_new) = &config.wgpu_setup {
|
||||
create_new.instance_descriptor.backends
|
||||
} else {
|
||||
wgpu::Backends::all()
|
||||
};
|
||||
|
||||
instance
|
||||
.enumerate_adapters(backends)
|
||||
// TODO(gfx-rs/wgpu#6665): Remove layer of `Arc` here once we update to wgpu 24.
|
||||
.into_iter()
|
||||
.map(Arc::new)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let (adapter, device, queue) = match config.wgpu_setup.clone() {
|
||||
WgpuSetup::CreateNew {
|
||||
supported_backends: _,
|
||||
WgpuSetup::CreateNew(WgpuSetupCreateNew {
|
||||
instance_descriptor: _,
|
||||
power_preference,
|
||||
native_adapter_selector: _native_adapter_selector,
|
||||
device_descriptor,
|
||||
} => {
|
||||
trace_path,
|
||||
}) => {
|
||||
let adapter = {
|
||||
profiling::scope!("request_adapter");
|
||||
instance
|
||||
.request_adapter(&wgpu::RequestAdapterOptions {
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
request_adapter(instance, power_preference, compatible_surface, &[]).await
|
||||
}
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(native_adapter_selector) = _native_adapter_selector {
|
||||
native_adapter_selector(&available_adapters, compatible_surface)
|
||||
.map_err(WgpuError::NoSuitableAdapterFound)
|
||||
} else {
|
||||
request_adapter(
|
||||
instance,
|
||||
power_preference,
|
||||
compatible_surface,
|
||||
force_fallback_adapter: false,
|
||||
})
|
||||
&available_adapters,
|
||||
)
|
||||
.await
|
||||
.ok_or_else(|| {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if available_adapters.is_empty() {
|
||||
log::info!("No wgpu adapters found");
|
||||
} else if available_adapters.len() == 1 {
|
||||
log::info!(
|
||||
"The only available wgpu adapter was not suitable: {}",
|
||||
adapter_info_summary(&available_adapters[0].get_info())
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"No suitable wgpu adapter found out of the {} available ones: {}",
|
||||
available_adapters.len(),
|
||||
describe_adapters(&available_adapters)
|
||||
);
|
||||
}
|
||||
}
|
||||
}?;
|
||||
|
||||
WgpuError::NoSuitableAdapterFound
|
||||
})?
|
||||
};
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
log::debug!(
|
||||
"Picked wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if available_adapters.len() == 1 {
|
||||
log::debug!(
|
||||
"Picked the only available wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
} else {
|
||||
log::info!(
|
||||
"There were {} available wgpu adapters: {}",
|
||||
available_adapters.len(),
|
||||
describe_adapters(&available_adapters)
|
||||
);
|
||||
log::debug!(
|
||||
"Picked wgpu adapter: {}",
|
||||
adapter_info_summary(&adapter.get_info())
|
||||
);
|
||||
}
|
||||
|
||||
let trace_path = std::env::var("WGPU_TRACE");
|
||||
let (device, queue) = {
|
||||
profiling::scope!("request_device");
|
||||
adapter
|
||||
.request_device(
|
||||
&(*device_descriptor)(&adapter),
|
||||
trace_path.ok().as_ref().map(std::path::Path::new),
|
||||
)
|
||||
.request_device(&(*device_descriptor)(&adapter), trace_path.as_deref())
|
||||
.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(adapter), Arc::new(device), Arc::new(queue))
|
||||
(adapter, Arc::new(device), Arc::new(queue))
|
||||
}
|
||||
WgpuSetup::Existing {
|
||||
WgpuSetup::Existing(WgpuSetupExisting {
|
||||
instance: _,
|
||||
adapter,
|
||||
device,
|
||||
queue,
|
||||
} => (adapter, device, queue),
|
||||
}) => (adapter, device, queue),
|
||||
};
|
||||
|
||||
let surface_formats = {
|
||||
profiling::scope!("get_capabilities");
|
||||
compatible_surface.map_or_else(
|
||||
|| vec![TextureFormat::Rgba8Unorm],
|
||||
|| vec![wgpu::TextureFormat::Rgba8Unorm],
|
||||
|s| s.get_capabilities(&adapter).formats,
|
||||
)
|
||||
};
|
||||
|
|
@ -219,20 +268,17 @@ impl RenderState {
|
|||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn describe_adapters(adapters: &[wgpu::Adapter]) -> String {
|
||||
fn describe_adapters(adapters: &[Arc<wgpu::Adapter>]) -> String {
|
||||
if adapters.is_empty() {
|
||||
"(none)".to_owned()
|
||||
} else if adapters.len() == 1 {
|
||||
adapter_info_summary(&adapters[0].get_info())
|
||||
} else {
|
||||
let mut list_string = String::new();
|
||||
for adapter in adapters {
|
||||
if !list_string.is_empty() {
|
||||
list_string += ", ";
|
||||
}
|
||||
list_string += &format!("{{{}}}", adapter_info_summary(&adapter.get_info()));
|
||||
}
|
||||
list_string
|
||||
adapters
|
||||
.iter()
|
||||
.map(|a| format!("{{{}}}", adapter_info_summary(&a.get_info())))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,62 +291,6 @@ pub enum SurfaceErrorAction {
|
|||
RecreateSurface,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum WgpuSetup {
|
||||
/// Construct a wgpu setup using some predefined settings & heuristics.
|
||||
/// This is the default option. You can customize most behaviours overriding the
|
||||
/// supported backends, power preferences, and device description.
|
||||
///
|
||||
/// This can also be configured with the environment variables:
|
||||
/// * `WGPU_BACKEND`: `vulkan`, `dx11`, `dx12`, `metal`, `opengl`, `webgpu`
|
||||
/// * `WGPU_POWER_PREF`: `low`, `high` or `none`
|
||||
CreateNew {
|
||||
/// Backends that should be supported (wgpu will pick one of these).
|
||||
///
|
||||
/// For instance, 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`.
|
||||
supported_backends: wgpu::Backends,
|
||||
|
||||
/// Power preference for the adapter.
|
||||
power_preference: wgpu::PowerPreference,
|
||||
|
||||
/// Configuration passed on device request, given an adapter
|
||||
device_descriptor:
|
||||
Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
|
||||
},
|
||||
|
||||
/// Run on an existing wgpu setup.
|
||||
Existing {
|
||||
instance: Arc<Instance>,
|
||||
adapter: Arc<Adapter>,
|
||||
device: Arc<Device>,
|
||||
queue: Arc<Queue>,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WgpuSetup {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CreateNew {
|
||||
supported_backends,
|
||||
power_preference,
|
||||
device_descriptor: _,
|
||||
} => f
|
||||
.debug_struct("AdapterSelection::Standard")
|
||||
.field("supported_backends", &supported_backends)
|
||||
.field("power_preference", &power_preference)
|
||||
.finish(),
|
||||
Self::Existing { .. } => f
|
||||
.debug_struct("AdapterSelection::Existing")
|
||||
.finish_non_exhaustive(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for using wgpu with eframe or the egui-wgpu winit feature.
|
||||
#[derive(Clone)]
|
||||
pub struct WgpuConfiguration {
|
||||
|
|
@ -352,42 +342,8 @@ impl Default for WgpuConfiguration {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
present_mode: wgpu::PresentMode::AutoVsync,
|
||||
|
||||
desired_maximum_frame_latency: None,
|
||||
|
||||
// By default, create a new wgpu setup. This will create a new instance, adapter, device and queue.
|
||||
// This will create an instance for the supported backends (which can be configured by
|
||||
// `WGPU_BACKEND`), and will pick an adapter by iterating adapters based on their power preference. The power
|
||||
// preference can also be configured by `WGPU_POWER_PREF`.
|
||||
wgpu_setup: WgpuSetup::CreateNew {
|
||||
// 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),
|
||||
|
||||
power_preference: wgpu::util::power_preference_from_env()
|
||||
.unwrap_or(wgpu::PowerPreference::HighPerformance),
|
||||
device_descriptor: Arc::new(|adapter| {
|
||||
let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
wgpu::Limits::default()
|
||||
};
|
||||
|
||||
wgpu::DeviceDescriptor {
|
||||
label: Some("egui wgpu device"),
|
||||
required_features: wgpu::Features::default(),
|
||||
required_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,
|
||||
..base_limits
|
||||
},
|
||||
memory_hints: wgpu::MemoryHints::default(),
|
||||
}
|
||||
}),
|
||||
},
|
||||
|
||||
wgpu_setup: Default::default(),
|
||||
on_surface_error: Arc::new(|err| {
|
||||
if err == wgpu::SurfaceError::Outdated {
|
||||
// This error occurs when the app is minimized on Windows.
|
||||
|
|
@ -468,8 +424,14 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
|
|||
summary += &format!(", driver_info: {driver_info:?}");
|
||||
}
|
||||
if *vendor != 0 {
|
||||
// TODO(emilk): decode using https://github.com/gfx-rs/wgpu/blob/767ac03245ee937d3dc552edc13fe7ab0a860eec/wgpu-hal/src/auxil/mod.rs#L7
|
||||
summary += &format!(", vendor: 0x{vendor:04X}");
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
{
|
||||
summary += &format!(", vendor: {} (0x{vendor:04X})", parse_vendor_id(*vendor));
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
{
|
||||
summary += &format!(", vendor: 0x{vendor:04X}");
|
||||
}
|
||||
}
|
||||
if *device != 0 {
|
||||
summary += &format!(", device: 0x{device:02X}");
|
||||
|
|
@ -477,3 +439,20 @@ pub fn adapter_info_summary(info: &wgpu::AdapterInfo) -> String {
|
|||
|
||||
summary
|
||||
}
|
||||
|
||||
/// Tries to parse the adapter's vendor ID to a human-readable string.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
pub fn parse_vendor_id(vendor_id: u32) -> &'static str {
|
||||
match vendor_id {
|
||||
wgpu::hal::auxil::db::amd::VENDOR => "AMD",
|
||||
wgpu::hal::auxil::db::apple::VENDOR => "Apple",
|
||||
wgpu::hal::auxil::db::arm::VENDOR => "ARM",
|
||||
wgpu::hal::auxil::db::broadcom::VENDOR => "Broadcom",
|
||||
wgpu::hal::auxil::db::imgtec::VENDOR => "Imagination Technologies",
|
||||
wgpu::hal::auxil::db::intel::VENDOR => "Intel",
|
||||
wgpu::hal::auxil::db::mesa::VENDOR => "Mesa",
|
||||
wgpu::hal::auxil::db::nvidia::VENDOR => "NVIDIA",
|
||||
wgpu::hal::auxil::db::qualcomm::VENDOR => "Qualcomm",
|
||||
_ => "Unknown",
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,237 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum WgpuSetup {
|
||||
/// Construct a wgpu setup using some predefined settings & heuristics.
|
||||
/// 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 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.
|
||||
Existing(WgpuSetupExisting),
|
||||
}
|
||||
|
||||
impl Default for WgpuSetup {
|
||||
fn default() -> Self {
|
||||
Self::CreateNew(WgpuSetupCreateNew::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for WgpuSetup {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::CreateNew(create_new) => f
|
||||
.debug_tuple("WgpuSetup::CreateNew")
|
||||
.field(create_new)
|
||||
.finish(),
|
||||
Self::Existing { .. } => f.debug_tuple("WgpuSetup::Existing").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
#[allow(clippy::arc_with_non_send_sync)]
|
||||
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(existing) => existing.instance.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WgpuSetupCreateNew> for WgpuSetup {
|
||||
fn from(create_new: WgpuSetupCreateNew) -> Self {
|
||||
Self::CreateNew(create_new)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<WgpuSetupExisting> for WgpuSetup {
|
||||
fn from(existing: WgpuSetupExisting) -> Self {
|
||||
Self::Existing(existing)
|
||||
}
|
||||
}
|
||||
|
||||
/// Method for selecting an adapter on native.
|
||||
///
|
||||
/// This can be used for fully custom adapter selection.
|
||||
/// If available, `wgpu::Surface` is passed to allow checking for surface compatibility.
|
||||
// TODO(gfx-rs/wgpu#6665): Remove layer of `Arc` here.
|
||||
pub type NativeAdapterSelectorMethod = Arc<
|
||||
dyn Fn(&[Arc<wgpu::Adapter>], Option<&wgpu::Surface<'_>>) -> Result<Arc<wgpu::Adapter>, String>
|
||||
+ Send
|
||||
+ Sync,
|
||||
>;
|
||||
|
||||
/// Configuration for creating a new wgpu setup.
|
||||
///
|
||||
/// Used for [`WgpuSetup::CreateNew`].
|
||||
pub struct WgpuSetupCreateNew {
|
||||
/// Instance descriptor for creating a wgpu instance.
|
||||
///
|
||||
/// 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 instance_descriptor: wgpu::InstanceDescriptor,
|
||||
|
||||
/// 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::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>,
|
||||
|
||||
/// Configuration passed on device request, given an adapter
|
||||
pub device_descriptor:
|
||||
Arc<dyn Fn(&wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> + Send + Sync>,
|
||||
|
||||
/// Option path to output a wgpu trace file.
|
||||
///
|
||||
/// This only works if this feature is enabled in `wgpu-core`.
|
||||
/// Does not work when running with WebGPU.
|
||||
/// Defaults to the path set in the `WGPU_TRACE` environment variable.
|
||||
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("instance_descriptor", &self.instance_descriptor)
|
||||
.field("power_preference", &self.power_preference)
|
||||
.field(
|
||||
"native_adapter_selector",
|
||||
&self.native_adapter_selector.is_some(),
|
||||
)
|
||||
.field("trace_path", &self.trace_path)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for WgpuSetupCreateNew {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
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),
|
||||
|
||||
native_adapter_selector: None,
|
||||
|
||||
device_descriptor: Arc::new(|adapter| {
|
||||
let base_limits = if adapter.get_info().backend == wgpu::Backend::Gl {
|
||||
wgpu::Limits::downlevel_webgl2_defaults()
|
||||
} else {
|
||||
wgpu::Limits::default()
|
||||
};
|
||||
|
||||
wgpu::DeviceDescriptor {
|
||||
label: Some("egui wgpu device"),
|
||||
required_features: wgpu::Features::default(),
|
||||
required_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,
|
||||
..base_limits
|
||||
},
|
||||
memory_hints: wgpu::MemoryHints::default(),
|
||||
}
|
||||
}),
|
||||
|
||||
trace_path: std::env::var("WGPU_TRACE")
|
||||
.ok()
|
||||
.map(std::path::PathBuf::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration for using an existing wgpu setup.
|
||||
///
|
||||
/// Used for [`WgpuSetup::Existing`].
|
||||
#[derive(Clone)]
|
||||
pub struct WgpuSetupExisting {
|
||||
pub instance: Arc<wgpu::Instance>,
|
||||
pub adapter: Arc<wgpu::Adapter>,
|
||||
pub device: Arc<wgpu::Device>,
|
||||
pub queue: Arc<wgpu::Queue>,
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ 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,
|
||||
|
|
@ -59,17 +59,8 @@ impl Painter {
|
|||
support_transparent_backbuffer: bool,
|
||||
dithering: bool,
|
||||
) -> Self {
|
||||
let instance = match &configuration.wgpu_setup {
|
||||
crate::WgpuSetup::CreateNew {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ serde = { workspace = true, optional = true }
|
|||
[dev-dependencies]
|
||||
criterion.workspace = true
|
||||
egui_kittest = { workspace = true, features = ["wgpu", "snapshot"] }
|
||||
wgpu = { workspace = true, features = ["metal"] }
|
||||
egui = { workspace = true, features = ["default_fonts"] }
|
||||
|
||||
[[bench]]
|
||||
|
|
|
|||
|
|
@ -20,7 +20,13 @@ include = ["../LICENSE-APACHE", "../LICENSE-MIT", "**/*.rs", "Cargo.toml"]
|
|||
|
||||
[features]
|
||||
# Adds a wgpu-based test renderer.
|
||||
wgpu = ["dep:egui-wgpu", "dep:pollster", "dep:image", "eframe?/wgpu"]
|
||||
wgpu = [
|
||||
"dep:egui-wgpu",
|
||||
"dep:pollster",
|
||||
"dep:image",
|
||||
"dep:wgpu",
|
||||
"eframe?/wgpu",
|
||||
]
|
||||
|
||||
# Adds a dify-based image snapshot utility.
|
||||
snapshot = ["dep:dify", "dep:image", "image/png"]
|
||||
|
|
@ -38,6 +44,8 @@ eframe = { workspace = true, optional = true }
|
|||
egui-wgpu = { workspace = true, optional = true }
|
||||
pollster = { workspace = true, optional = true }
|
||||
image = { workspace = true, optional = true }
|
||||
# Enable DX12 because it always comes with a software rasterizer.
|
||||
wgpu = { workspace = true, features = ["metal", "dx12"], optional = true }
|
||||
|
||||
# snapshot dependencies
|
||||
dify = { workspace = true, optional = true }
|
||||
|
|
@ -48,7 +56,6 @@ document-features = { workspace = true, optional = true }
|
|||
[dev-dependencies]
|
||||
egui = { workspace = true, features = ["default_fonts"] }
|
||||
image = { workspace = true, features = ["png"] }
|
||||
wgpu = { workspace = true, features = ["metal"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
|
|
|||
|
|
@ -1,27 +1,56 @@
|
|||
use crate::texture_to_image::texture_to_image;
|
||||
use eframe::epaint::TextureId;
|
||||
use egui::TexturesDelta;
|
||||
use egui_wgpu::wgpu::{Backends, StoreOp, TextureFormat};
|
||||
use egui_wgpu::{wgpu, RenderState, ScreenDescriptor, WgpuSetup};
|
||||
use image::RgbaImage;
|
||||
use std::iter::once;
|
||||
use std::sync::Arc;
|
||||
use wgpu::Maintain;
|
||||
|
||||
// TODO(#5506): Replace this with the setup from https://github.com/emilk/egui/pull/5506
|
||||
use egui::TexturesDelta;
|
||||
use egui_wgpu::{wgpu, RenderState, ScreenDescriptor, WgpuSetup};
|
||||
use image::RgbaImage;
|
||||
|
||||
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 {
|
||||
supported_backends: Backends::all(),
|
||||
device_descriptor: Arc::new(|_| wgpu::DeviceDescriptor::default()),
|
||||
power_preference: wgpu::PowerPreference::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 { .. } => Default::default(),
|
||||
};
|
||||
let instance = pollster::block_on(setup.new_instance());
|
||||
|
||||
pollster::block_on(egui_wgpu::RenderState::create(
|
||||
&egui_wgpu::WgpuConfiguration {
|
||||
|
|
@ -72,7 +101,7 @@ impl WgpuTestRenderer {
|
|||
render_state
|
||||
.renderer
|
||||
.read()
|
||||
.texture(&TextureId::Managed(0))
|
||||
.texture(&egui::epaint::TextureId::Managed(0))
|
||||
.is_none(),
|
||||
"The RenderState passed in has been used before, pass in a fresh RenderState instead."
|
||||
);
|
||||
|
|
@ -143,7 +172,7 @@ impl crate::TestRenderer for WgpuTestRenderer {
|
|||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
format: self.render_state.target_format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_SRC,
|
||||
view_formats: &[],
|
||||
});
|
||||
|
|
@ -159,12 +188,10 @@ impl crate::TestRenderer for WgpuTestRenderer {
|
|||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
|
||||
store: StoreOp::Store,
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
occlusion_query_set: None,
|
||||
timestamp_writes: None,
|
||||
..Default::default()
|
||||
})
|
||||
.forget_lifetime();
|
||||
|
||||
|
|
@ -175,7 +202,7 @@ impl crate::TestRenderer for WgpuTestRenderer {
|
|||
.queue
|
||||
.submit(user_buffers.into_iter().chain(once(encoder.finish())));
|
||||
|
||||
self.render_state.device.poll(Maintain::Wait);
|
||||
self.render_state.device.poll(wgpu::Maintain::Wait);
|
||||
|
||||
Ok(texture_to_image(
|
||||
&self.render_state.device,
|
||||
|
|
|
|||
Loading…
Reference in New Issue