Three root causes, all fixed and verified on the Pixel 9 Pro: 1. Present phase (backend + renderer): VSYNC_LOCKED_RENDER defers the render to the vsync wake; RENDER_AHEAD presents the previous frame at the start of render() (phase = wake + ~1.1 ms) and holds the new frame one slot. Crash fix: a held wgpu frame must be PRESENTED, never merely dropped, before the next acquire (AlreadyAcquired panic at launch otherwise); discard_pending_frame presents too. 2. Full-res prefetch churn (src/lib.rs): the 8-slot full cache cycled through a ~20-cell viewport forever (137 decodes/20 s) and each eviction free()'d a 50 MB Vec on the UI thread (20-52 ms CPU spikes, ~7/s = the residual SLOWEV NonMain jank). Fix: 60 s tombstone cooldown on evicted paths (idle prefetch skips, urgent tap bypasses) + off-thread reaper for the freed blobs (insert: 21 ms -> 4 us). 3. (earlier) Skia final flush + Mailbox present mode (vendor, M3). Instrumentation (all behind --features perf): OTHR/INVK/INVK_TAG/FLCK lines for cross-thread closure timing and push-site tagging. Warm measured swipe, 8580-file grid: cadence median 8.33 ms (exact 120 Hz), p99 15.5 ms, 5/552 gaps >16.7 ms (0.9%, max 28.3 ms, zero >33 ms); PRES phase median ~994 us, zero late frames. Docs updated (docs/BUGS/scroll-jank-120hz.md, full scorecard + line reference). Also: vendored i-slint-core + wgpu-hal (vkshim interposition patch), vkshim profiling tooling, backend made a non-optional dep, debuggable flag for simpleperf (REMOVE before shipping).
548 lines
22 KiB
Rust
548 lines
22 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
|
|
|
#![warn(missing_docs)]
|
|
|
|
/*!
|
|
This module contains types that are public and re-exported in the slint-rs as well as the slint-interpreter crate as public API,
|
|
in particular the `BackendSelector` type, to configure the WGPU-based renderer(s).
|
|
*/
|
|
|
|
use alloc::boxed::Box;
|
|
|
|
pub use wgpu_30 as wgpu;
|
|
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
pub mod api {
|
|
/*!
|
|
This module contains types that are public and re-exported in the slint-rs as well as the slint-interpreter crate as public API.
|
|
*/
|
|
|
|
#[doc(no_inline)]
|
|
pub use super::wgpu;
|
|
|
|
/// This data structure provides settings for initializing WGPU renderers.
|
|
#[derive(Clone, Debug)]
|
|
#[non_exhaustive]
|
|
pub struct WGPUSettings {
|
|
/// The backends to use for the WGPU instance.
|
|
pub backends: wgpu_30::Backends,
|
|
/// The different options that are given to the selected backends.
|
|
pub backend_options: wgpu_30::BackendOptions,
|
|
/// The flags to fine-tune behavior of the WGPU instance.
|
|
pub instance_flags: wgpu_30::InstanceFlags,
|
|
/// Memory budget thresholds used by some backends.
|
|
pub instance_memory_budget_thresholds: wgpu_30::MemoryBudgetThresholds,
|
|
|
|
/// The power preference is used to influence the WGPU adapter selection.
|
|
pub power_preference: wgpu_30::PowerPreference,
|
|
|
|
/// The label for the device. This is used to identify the device in debugging tools.
|
|
pub device_label: Option<std::borrow::Cow<'static, str>>,
|
|
/// The required features for the device.
|
|
pub device_required_features: wgpu_30::Features,
|
|
/// The required limits for the device.
|
|
pub device_required_limits: wgpu_30::Limits,
|
|
/// The experimental features for the device.
|
|
pub device_experimental_features: wgpu_30::ExperimentalFeatures,
|
|
/// The memory hints for the device.
|
|
pub device_memory_hints: wgpu_30::MemoryHints,
|
|
}
|
|
|
|
impl Default for WGPUSettings {
|
|
fn default() -> Self {
|
|
let backends = wgpu_30::Backends::from_env().unwrap_or_default();
|
|
|
|
Self {
|
|
backends,
|
|
backend_options: wgpu_30::BackendOptions::from_env_or_default(),
|
|
instance_flags: wgpu_30::InstanceFlags::from_build_config().with_env(),
|
|
instance_memory_budget_thresholds: wgpu_30::MemoryBudgetThresholds::default(),
|
|
|
|
power_preference: wgpu_30::PowerPreference::from_env().unwrap_or_default(),
|
|
|
|
device_label: None,
|
|
device_required_features: wgpu_30::Features::empty(),
|
|
device_required_limits: wgpu_30::Limits::downlevel_webgl2_defaults(),
|
|
device_experimental_features: wgpu_30::ExperimentalFeatures::disabled(),
|
|
device_memory_hints: wgpu_30::MemoryHints::MemoryUsage,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// This enum describes the different ways to configure WGPU for rendering.
|
|
#[derive(Clone, Debug)]
|
|
#[non_exhaustive]
|
|
#[allow(clippy::large_enum_variant)]
|
|
pub enum WGPUConfiguration {
|
|
/// Use `Manual` if you've initialized WGPU and want to supply the instance, adapter,
|
|
/// device, and queue for use.
|
|
Manual {
|
|
/// The WGPU instance to use.
|
|
instance: wgpu_30::Instance,
|
|
/// The WGPU adapter to use.
|
|
adapter: wgpu_30::Adapter,
|
|
/// The WGPU device to use.
|
|
device: wgpu_30::Device,
|
|
/// The WGPU queue to use.
|
|
queue: wgpu_30::Queue,
|
|
},
|
|
/// Use `Automatic` if you want to let Slint select the WGPU instance, adapter, and
|
|
/// device, but fine-tune aspects such as memory limits or features.
|
|
Automatic(WGPUSettings),
|
|
}
|
|
|
|
impl Default for WGPUConfiguration {
|
|
fn default() -> Self {
|
|
Self::Automatic(WGPUSettings::default())
|
|
}
|
|
}
|
|
|
|
impl TryFrom<wgpu_30::Texture> for super::super::Image {
|
|
type Error = TextureImportError;
|
|
|
|
fn try_from(texture: wgpu_30::Texture) -> Result<Self, Self::Error> {
|
|
if texture.format() != wgpu_30::TextureFormat::Rgba8Unorm
|
|
&& texture.format() != wgpu_30::TextureFormat::Rgba8UnormSrgb
|
|
{
|
|
return Err(Self::Error::InvalidFormat);
|
|
}
|
|
let usages = texture.usage();
|
|
if !usages.contains(wgpu_30::TextureUsages::TEXTURE_BINDING)
|
|
|| !usages.contains(wgpu_30::TextureUsages::RENDER_ATTACHMENT)
|
|
{
|
|
return Err(Self::Error::InvalidUsage);
|
|
}
|
|
Ok(Self(super::super::ImageInner::WGPUTexture(
|
|
super::super::WGPUTexture::WGPU30Texture(texture),
|
|
)))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, derive_more::Error)]
|
|
#[non_exhaustive]
|
|
/// This enum describes the possible errors that can occur when importing a WGPU texture,
|
|
/// via [`Image::try_from()`](super::super::Image::try_from()).
|
|
pub enum TextureImportError {
|
|
/// The texture format is not supported. The only supported format is Rgba8Unorm and Rgba8UnormSrgb.
|
|
InvalidFormat,
|
|
/// The texture usage must include TEXTURE_BINDING as well as RENDER_ATTACHMENT.
|
|
InvalidUsage,
|
|
}
|
|
|
|
impl core::fmt::Display for TextureImportError {
|
|
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
|
match self {
|
|
TextureImportError::InvalidFormat => f.write_str(
|
|
"The texture format is not supported. The only supported format is Rgba8Unorm and Rgba8UnormSrgb",
|
|
),
|
|
TextureImportError::InvalidUsage => f.write_str(
|
|
"The texture usage must include TEXTURE_BINDING as well as RENDER_ATTACHMENT",
|
|
),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
use super::RequestedGraphicsAPI;
|
|
|
|
/// Backends the WGPU renderers do not use on this platform.
|
|
/// Subtracted at instance creation so unused driver stacks are not loaded.
|
|
#[doc(hidden)]
|
|
pub fn default_backends_to_avoid() -> wgpu::Backends {
|
|
let mut avoid = wgpu::Backends::GL;
|
|
#[cfg(not(target_vendor = "apple"))]
|
|
avoid.insert(wgpu::Backends::METAL);
|
|
#[cfg(not(target_family = "windows"))]
|
|
avoid.insert(wgpu::Backends::DX12);
|
|
avoid
|
|
}
|
|
|
|
/// Subtract `backends_to_avoid` from `requested`.
|
|
/// If that would leave no backends, keep `requested`.
|
|
/// That honors an explicit choice such as `WGPU_BACKEND=gl`.
|
|
#[doc(hidden)]
|
|
pub fn mask_backends(
|
|
requested: wgpu::Backends,
|
|
backends_to_avoid: wgpu::Backends,
|
|
) -> wgpu::Backends {
|
|
let masked = requested & !backends_to_avoid;
|
|
if masked.is_empty() { requested } else { masked }
|
|
}
|
|
|
|
/// Internal helper to see if there are any GPU adapters for hardware accelerated rendering.
|
|
/// This is used to determine if we should fall back to software rendering (instead of using WGPU
|
|
/// software rendering, such as DX12's Warp adapter).
|
|
///
|
|
/// `backends_to_avoid` is subtracted from the instance backends the same way as in
|
|
/// [`init_instance_adapter_device_queue_surface`].
|
|
/// The throwaway probe then does not load stacks the renderer will not use.
|
|
pub fn any_wgpu30_adapters_with_gpu(
|
|
requested_graphics_api: Option<RequestedGraphicsAPI>,
|
|
backends_to_avoid: wgpu::Backends,
|
|
) -> bool {
|
|
// On WASM the wgpu init path uses
|
|
// `wgpu::util::new_instance_with_webgpu_detection`, which probes
|
|
// `navigator.gpu.requestAdapter()` asynchronously and falls through
|
|
// to the WebGL backend (compiled in via the wgpu-30 `webgl` feature)
|
|
// when no WebGPU adapter is reachable. So a hardware-accelerated
|
|
// adapter is effectively always available; assume yes here and
|
|
// let the actual init surface a real error if both fail.
|
|
if cfg!(target_family = "wasm") {
|
|
return true;
|
|
}
|
|
let allow_cpu = std::env::var("SLINT_WGPU_CPU").is_ok();
|
|
if allow_cpu {
|
|
return true;
|
|
}
|
|
let (instance, backends) = match requested_graphics_api {
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
Some(RequestedGraphicsAPI::WGPU30(api::WGPUConfiguration::Manual { instance, .. })) => {
|
|
(instance, wgpu::Backends::all())
|
|
}
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
Some(RequestedGraphicsAPI::WGPU30(api::WGPUConfiguration::Automatic(wgpu30_settings))) => {
|
|
let backends = mask_backends(wgpu30_settings.backends, backends_to_avoid);
|
|
(
|
|
wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
backends,
|
|
flags: wgpu30_settings.instance_flags,
|
|
backend_options: wgpu30_settings.backend_options,
|
|
memory_budget_thresholds: wgpu30_settings.instance_memory_budget_thresholds,
|
|
display: None,
|
|
}),
|
|
backends,
|
|
)
|
|
}
|
|
None => {
|
|
let backends =
|
|
mask_backends(wgpu::Backends::from_env().unwrap_or_default(), backends_to_avoid);
|
|
|
|
(
|
|
wgpu::Instance::new(wgpu::InstanceDescriptor {
|
|
backends,
|
|
flags: wgpu::InstanceFlags::from_build_config().with_env(),
|
|
backend_options: wgpu::BackendOptions::from_env_or_default(),
|
|
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
|
|
display: None,
|
|
}),
|
|
backends,
|
|
)
|
|
}
|
|
Some(_) => return false,
|
|
};
|
|
if backends.is_empty() {
|
|
return false;
|
|
}
|
|
poll_once(instance.enumerate_adapters(backends))
|
|
.unwrap()
|
|
.into_iter()
|
|
.any(|adapter| adapter.get_info().device_type != wgpu::DeviceType::Cpu)
|
|
}
|
|
|
|
/// Enum to represent different surface targets for WGPU initialization.
|
|
pub enum SurfaceTarget {
|
|
/// Standard window handle for windowed rendering. Must also expose a display handle.
|
|
WindowHandle(Box<dyn wgpu::DisplayAndWindowHandle + 'static>),
|
|
/// DRM surface target for direct rendering on Linux KMS.
|
|
Drm(wgpu::SurfaceTargetUnsafe),
|
|
}
|
|
|
|
impl From<Box<dyn wgpu::DisplayAndWindowHandle + 'static>> for SurfaceTarget {
|
|
fn from(handle: Box<dyn wgpu::DisplayAndWindowHandle + 'static>) -> Self {
|
|
Self::WindowHandle(handle)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
fn device_descriptor_from_settings<'a>(
|
|
settings: &'a api::WGPUSettings,
|
|
adapter: &wgpu::Adapter,
|
|
) -> wgpu::DeviceDescriptor<'a> {
|
|
wgpu::DeviceDescriptor {
|
|
label: settings.device_label.as_deref(),
|
|
required_features: settings.device_required_features,
|
|
// support images the size of the swapchain
|
|
required_limits: settings.device_required_limits.clone().using_resolution(adapter.limits()),
|
|
experimental_features: settings.device_experimental_features,
|
|
memory_hints: settings.device_memory_hints.clone(),
|
|
trace: wgpu::Trace::default(),
|
|
}
|
|
}
|
|
|
|
fn default_device_descriptor(adapter: &wgpu::Adapter) -> wgpu::DeviceDescriptor<'static> {
|
|
wgpu::DeviceDescriptor {
|
|
label: None,
|
|
required_features: adapter.features() - wgpu::Features::all_experimental_mask(),
|
|
required_limits: adapter.limits(),
|
|
experimental_features: wgpu::ExperimentalFeatures::disabled(),
|
|
memory_hints: wgpu::MemoryHints::MemoryUsage,
|
|
trace: wgpu::Trace::default(),
|
|
}
|
|
}
|
|
|
|
/// Internal async helper function to initialize the wgpu instance/adapter/device/queue from either scratch or
|
|
/// developer-provided config. This is called by any renderer intending to support WGPU.
|
|
pub async fn async_init_instance_adapter_device_queue_surface(
|
|
surface_target: impl Into<SurfaceTarget>,
|
|
requested_graphics_api: Option<RequestedGraphicsAPI>,
|
|
backends_to_avoid: wgpu::Backends,
|
|
) -> Result<
|
|
(
|
|
wgpu_30::Instance,
|
|
wgpu_30::Adapter,
|
|
wgpu_30::Device,
|
|
wgpu_30::Queue,
|
|
wgpu_30::Surface<'static>,
|
|
),
|
|
Box<dyn std::error::Error + Send + Sync + 'static>,
|
|
> {
|
|
#![allow(unsafe_code)]
|
|
|
|
let surface_target = surface_target.into();
|
|
|
|
let create_surface = |instance: &wgpu::Instance| {
|
|
match surface_target {
|
|
SurfaceTarget::WindowHandle(window_handle) => instance.create_surface(window_handle),
|
|
// Safety: The caller ensures the DRM file descriptor in the surface target
|
|
// remains valid for the lifetime of the returned surface, by storing the
|
|
// DrmOutput in the renderer adapter.
|
|
SurfaceTarget::Drm(surface_target_unsafe) => unsafe {
|
|
instance.create_surface_unsafe(surface_target_unsafe)
|
|
},
|
|
}
|
|
.map_err(|e| {
|
|
crate::api::PlatformError::from(alloc::format!(
|
|
"Error creating wgpu window surface: {e}"
|
|
))
|
|
})
|
|
};
|
|
|
|
let (instance, adapter, device, queue, surface) = match requested_graphics_api {
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
Some(RequestedGraphicsAPI::WGPU30(api::WGPUConfiguration::Manual {
|
|
instance,
|
|
adapter,
|
|
device,
|
|
queue,
|
|
})) => {
|
|
let surface = create_surface(&instance)?;
|
|
(instance, adapter, device, queue, surface)
|
|
}
|
|
#[cfg(feature = "unstable-wgpu-30")]
|
|
Some(RequestedGraphicsAPI::WGPU30(api::WGPUConfiguration::Automatic(wgpu30_settings))) => {
|
|
let instance =
|
|
wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor {
|
|
backends: mask_backends(wgpu30_settings.backends, backends_to_avoid),
|
|
flags: wgpu30_settings.instance_flags,
|
|
backend_options: wgpu30_settings.backend_options.clone(),
|
|
memory_budget_thresholds: wgpu30_settings.instance_memory_budget_thresholds,
|
|
display: None,
|
|
})
|
|
.await;
|
|
|
|
let surface = create_surface(&instance)?;
|
|
|
|
let adapter = match wgpu::util::initialize_adapter_from_env(&instance, Some(&surface))
|
|
.await
|
|
{
|
|
Ok(adapter) => Ok(adapter),
|
|
Err(_) => {
|
|
instance
|
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
|
power_preference: wgpu30_settings.power_preference,
|
|
force_fallback_adapter: false,
|
|
compatible_surface: Some(&surface),
|
|
apply_limit_buckets: false,
|
|
})
|
|
.await
|
|
}
|
|
}
|
|
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync + 'static> {
|
|
alloc::format!("Failed to find an appropriate adapter: {e}").into()
|
|
})?;
|
|
|
|
let (device, queue) = adapter
|
|
.request_device(&device_descriptor_from_settings(&wgpu30_settings, &adapter))
|
|
.await
|
|
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync + 'static> {
|
|
alloc::format!("Failed to create device: {e}").into()
|
|
})?;
|
|
|
|
(instance, adapter, device, queue, surface)
|
|
}
|
|
maybe_native_api @ (None
|
|
| Some(
|
|
RequestedGraphicsAPI::Metal
|
|
| RequestedGraphicsAPI::Vulkan
|
|
| RequestedGraphicsAPI::Direct3D,
|
|
)) => {
|
|
let requested_backends = match &maybe_native_api {
|
|
Some(RequestedGraphicsAPI::Metal) => wgpu::Backends::METAL,
|
|
Some(RequestedGraphicsAPI::Vulkan) => wgpu::Backends::VULKAN,
|
|
Some(RequestedGraphicsAPI::Direct3D) => wgpu::Backends::DX12,
|
|
_ => wgpu::Backends::from_env().unwrap_or_default(),
|
|
};
|
|
let backends = if maybe_native_api.is_none() {
|
|
mask_backends(requested_backends, backends_to_avoid)
|
|
} else {
|
|
requested_backends & !backends_to_avoid
|
|
};
|
|
if backends.is_empty() {
|
|
return Err(alloc::format!(
|
|
"The requested graphics API ({maybe_native_api:?}) is not supported under wgpu on this platform"
|
|
)
|
|
.into());
|
|
}
|
|
|
|
let instance =
|
|
wgpu::util::new_instance_with_webgpu_detection(wgpu::InstanceDescriptor {
|
|
backends,
|
|
flags: wgpu::InstanceFlags::from_build_config().with_env(),
|
|
backend_options: wgpu::BackendOptions::from_env_or_default(),
|
|
memory_budget_thresholds: wgpu::MemoryBudgetThresholds::default(),
|
|
display: None,
|
|
})
|
|
.await;
|
|
|
|
let surface = create_surface(&instance)?;
|
|
|
|
let adapter =
|
|
wgpu::util::initialize_adapter_from_env_or_default(&instance, Some(&surface))
|
|
.await
|
|
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync + 'static> {
|
|
alloc::format!("Failed to find an appropriate adapter: {e}").into()
|
|
})?;
|
|
|
|
let (device, queue) = adapter
|
|
.request_device(&default_device_descriptor(&adapter))
|
|
.await
|
|
.map_err(|e| -> Box<dyn std::error::Error + Send + Sync + 'static> {
|
|
alloc::format!("Failed to create device: {e}").into()
|
|
})?;
|
|
(instance, adapter, device, queue, surface)
|
|
}
|
|
Some(other) => {
|
|
return Err(alloc::format!(
|
|
"The requested graphics API ({other:?}) is not supported under wgpu"
|
|
)
|
|
.into());
|
|
}
|
|
};
|
|
Ok((instance, adapter, device, queue, surface))
|
|
}
|
|
|
|
/// Blocking wrapper around [`async_init_instance_adapter_device_queue_surface`] that uses
|
|
/// `poll_once` to synchronously drive the future. This works on all platforms except WASM
|
|
/// where the wgpu futures don't resolve on first poll.
|
|
pub fn init_instance_adapter_device_queue_surface(
|
|
surface_target: impl Into<SurfaceTarget>,
|
|
requested_graphics_api: Option<RequestedGraphicsAPI>,
|
|
backends_to_avoid: wgpu::Backends,
|
|
) -> Result<
|
|
(
|
|
wgpu_30::Instance,
|
|
wgpu_30::Adapter,
|
|
wgpu_30::Device,
|
|
wgpu_30::Queue,
|
|
wgpu_30::Surface<'static>,
|
|
),
|
|
Box<dyn std::error::Error + Send + Sync + 'static>,
|
|
> {
|
|
poll_once(async_init_instance_adapter_device_queue_surface(
|
|
surface_target,
|
|
requested_graphics_api,
|
|
backends_to_avoid,
|
|
))
|
|
.expect("internal error: wgpu setup is not expected to be async")
|
|
}
|
|
|
|
/// Runs [`async_init_instance_adapter_device_queue_surface`] and passes the created
|
|
/// objects on to `finalize`. On most platforms the initialization future resolves on
|
|
/// the first poll, so this happens synchronously and errors (including `finalize`'s)
|
|
/// are returned to the caller. On WASM the initialization does real async work (the
|
|
/// WebGPU adapter probe is a JsFuture), so the future is spawned on the event loop
|
|
/// via `context`, `finalize` runs when it resolves, and errors can only be logged.
|
|
pub fn init_instance_adapter_device_queue_surface_then(
|
|
context: &crate::SlintContext,
|
|
surface_target: impl Into<SurfaceTarget> + 'static,
|
|
requested_graphics_api: Option<RequestedGraphicsAPI>,
|
|
backends_to_avoid: wgpu::Backends,
|
|
finalize: impl FnOnce(
|
|
wgpu_30::Instance,
|
|
wgpu_30::Adapter,
|
|
wgpu_30::Device,
|
|
wgpu_30::Queue,
|
|
wgpu_30::Surface<'static>,
|
|
) -> Result<(), crate::api::PlatformError>
|
|
+ 'static,
|
|
) -> Result<(), crate::api::PlatformError> {
|
|
let init_future = async move {
|
|
let (instance, adapter, device, queue, surface) =
|
|
async_init_instance_adapter_device_queue_surface(
|
|
surface_target,
|
|
requested_graphics_api,
|
|
backends_to_avoid,
|
|
)
|
|
.await
|
|
.map_err(|e| {
|
|
crate::api::PlatformError::from(alloc::format!("WGPU initialization failed: {e}"))
|
|
})?;
|
|
finalize(instance, adapter, device, queue, surface)
|
|
};
|
|
|
|
#[cfg(not(target_arch = "wasm32"))]
|
|
{
|
|
let _ = context;
|
|
poll_once(init_future).expect("internal error: wgpu setup is not expected to be async")
|
|
}
|
|
#[cfg(target_arch = "wasm32")]
|
|
{
|
|
context
|
|
.spawn_local(async move {
|
|
if let Err(e) = init_future.await {
|
|
crate::debug_log!("{e}");
|
|
}
|
|
})
|
|
.map_err(|e| {
|
|
crate::api::PlatformError::from(alloc::format!(
|
|
"Error spawning async wgpu initialization: {e}"
|
|
))
|
|
})?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
// Helper function to poll a future once. Remove once the suspension API uses async.
|
|
fn poll_once<F: std::future::Future>(future: F) -> Option<F::Output> {
|
|
let waker = std::task::Waker::noop();
|
|
let mut ctx = std::task::Context::from_waker(waker);
|
|
|
|
let future = std::pin::pin!(future);
|
|
|
|
match future.poll(&mut ctx) {
|
|
std::task::Poll::Ready(result) => Some(result),
|
|
std::task::Poll::Pending => None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn mask_backends_keeps_explicit_choice_when_empty() {
|
|
let avoid = wgpu::Backends::GL | wgpu::Backends::METAL | wgpu::Backends::DX12;
|
|
assert_eq!(mask_backends(wgpu::Backends::GL, avoid), wgpu::Backends::GL);
|
|
assert!(mask_backends(wgpu::Backends::all(), avoid).contains(wgpu::Backends::VULKAN));
|
|
assert!(!mask_backends(wgpu::Backends::all(), avoid).contains(wgpu::Backends::GL));
|
|
assert_eq!(mask_backends(wgpu::Backends::empty(), avoid), wgpu::Backends::empty());
|
|
assert_eq!(mask_backends(wgpu::Backends::VULKAN, avoid), wgpu::Backends::VULKAN);
|
|
}
|
|
|
|
#[test]
|
|
fn default_avoid_includes_gl() {
|
|
assert!(default_backends_to_avoid().contains(wgpu::Backends::GL));
|
|
}
|
|
}
|