MVP no_std for wgpu (#7747)

Co-authored-by: Andreas Reich <1220815+Wumpf@users.noreply.github.com>
This commit is contained in:
Zachary Harrold 2025-06-10 16:40:07 +10:00 committed by GitHub
parent e10bceb1dc
commit e72c4d8326
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 146 additions and 19 deletions

View File

@ -300,11 +300,13 @@ jobs:
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features
# Check with all compatible features
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-types --no-default-features --features strict_asserts,fragile-send-sync-non-atomic-wasm,serde,counters
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p naga --no-default-features --features dot-out,compact
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu-hal --no-default-features --features fragile-send-sync-non-atomic-wasm
cargo clippy --target ${{ matrix.target }} ${{ matrix.extra-flags }} -p wgpu --no-default-features --features serde
# Building for native platforms with standard tests.
- name: Check native

1
Cargo.lock generated
View File

@ -4760,6 +4760,7 @@ dependencies = [
"arrayvec",
"bitflags 2.9.1",
"bytemuck",
"cfg-if",
"cfg_aliases 0.2.1",
"document-features",
"hashbrown",

View File

@ -29,7 +29,16 @@ ignored = ["cfg_aliases"]
[lib]
[features]
default = ["dx12", "metal", "gles", "vulkan", "wgsl", "webgpu"]
default = [
"std",
"parking_lot",
"dx12",
"metal",
"gles",
"vulkan",
"wgsl",
"webgpu",
]
#! ### Backends
# --------------------------------------------------------------------
@ -155,6 +164,19 @@ fragile-send-sync-non-atomic-wasm = [
## context around the WASM VM, e.g., when the WASM binary is used in a browser.
web = ["dep:wasm-bindgen", "dep:js-sys", "dep:web-sys", "wgpu-types/web"]
## Enables use of the standard library within `wgpu` and its dependencies.
##
## This can allow for better error reporting and for improved multithreading
## support.
std = ["raw-window-handle/std", "wgpu-types/std", "wgpu-core?/std"]
## Uses `parking_lot` as the implementation for locking primitives.
##
## This is a recommended feature for most users and should only be disabled when
## required, e.g., for `no_std` support.
## If disabled, either `std::sync::Mutex` or `core::cell::RefCell` will be used,
## based on whether `std` is enabled or not.
parking_lot = ["dep:parking_lot"]
#########################
# Standard Dependencies #
@ -168,12 +190,13 @@ wgpu-types.workspace = true
# Needed for both wgpu-core and webgpu
arrayvec.workspace = true
bitflags.workspace = true
cfg-if.workspace = true
document-features.workspace = true
hashbrown.workspace = true
log.workspace = true
parking_lot.workspace = true
parking_lot = { workspace = true, optional = true }
profiling.workspace = true
raw-window-handle = { workspace = true, features = ["std"] }
raw-window-handle = { workspace = true, features = ["alloc"] }
static_assertions.workspace = true
########################################

View File

@ -47,6 +47,16 @@ fn main() {
// ⚠️ Keep in sync with target.cfg() definition in wgpu-hal/Cargo.toml and cfg_alias in `wgpu-hal` crate ⚠️
static_dxc: { all(target_os = "windows", feature = "static-dxc", not(target_arch = "aarch64")) },
supports_64bit_atomics: { target_has_atomic = "64" },
custom: {any(feature = "custom")}
custom: {any(feature = "custom")},
std: { any(
feature = "std",
// TODO: Remove this when an alternative Mutex implementation is available for `no_std`.
// send_sync requires an appropriate Mutex implementation, which is only currently
// possible with `std` enabled.
send_sync,
// Unwinding panics necessitate access to `std` to determine if a thread is panicking
panic = "unwind"
) },
no_std: { not(std) }
}
}

View File

@ -4,8 +4,7 @@ use core::{
ops::{Bound, Deref, DerefMut, Range, RangeBounds},
};
use parking_lot::Mutex;
use crate::util::Mutex;
use crate::*;
/// Handle to a GPU-accessible buffer.

View File

@ -1,10 +1,9 @@
use alloc::{boxed::Box, string::String, sync::Arc};
use core::{error, fmt, future::Future};
use parking_lot::Mutex;
use crate::api::blas::{Blas, BlasGeometrySizeDescriptors, CreateBlasDescriptor};
use crate::api::tlas::{CreateTlasDescriptor, Tlas};
use crate::util::Mutex;
use crate::*;
/// Open connection to a graphics and/or compute device.

View File

@ -2,9 +2,7 @@
use alloc::vec::Vec;
use core::future::Future;
use parking_lot::Mutex;
use crate::{dispatch::InstanceInterface, *};
use crate::{dispatch::InstanceInterface, util::Mutex, *};
bitflags::bitflags! {
/// WGSL language extensions.

View File

@ -1,9 +1,9 @@
use alloc::{boxed::Box, string::String, vec, vec::Vec};
use core::{error, fmt};
use parking_lot::Mutex;
use raw_window_handle::{HasDisplayHandle, HasWindowHandle};
use crate::util::Mutex;
use crate::*;
/// Describes a [`Surface`].
@ -413,7 +413,10 @@ impl error::Error for CreateSurfaceError {
#[cfg(wgpu_core)]
CreateSurfaceErrorKind::Hal(e) => e.source(),
CreateSurfaceErrorKind::Web(_) => None,
#[cfg(feature = "std")]
CreateSurfaceErrorKind::RawHandle(e) => e.source(),
#[cfg(not(feature = "std"))]
CreateSurfaceErrorKind::RawHandle(_) => None,
}
}
}

View File

@ -1,5 +1,4 @@
use core::{error, fmt};
use std::thread;
use crate::*;
@ -48,7 +47,7 @@ impl SurfaceTexture {
impl Drop for SurfaceTexture {
fn drop(&mut self) {
if !self.presented && !thread::panicking() {
if !self.presented && !thread_panicking() {
self.detail.texture_discard();
}
}
@ -83,3 +82,22 @@ impl fmt::Display for SurfaceError {
}
impl error::Error for SurfaceError {}
fn thread_panicking() -> bool {
cfg_if::cfg_if! {
if #[cfg(std)] {
std::thread::panicking()
} else if #[cfg(panic = "abort")] {
// If `panic = "abort"` then a thread _cannot_ be observably panicking by definition.
false
} else {
// TODO: This is potentially overly pessimistic; it may be appropriate to instead allow a
// texture to not be discarded.
// Alternatively, this could _also_ be a `panic!`, since we only care if the thread is panicking
// when the surface has not been presented.
compile_error!(
"cannot determine if a thread is panicking without either `panic = \"abort\"` or `std`"
);
}
}
}

View File

@ -10,11 +10,11 @@ use alloc::{
use core::{error::Error, fmt, future::ready, ops::Range, pin::Pin, ptr::NonNull, slice};
use arrayvec::ArrayVec;
use parking_lot::Mutex;
use smallvec::SmallVec;
use wgc::{command::bundle_ffi::*, error::ContextErrorSource, pipeline::CreateShaderModuleError};
use wgt::WasmNotSendSync;
use crate::util::Mutex;
use crate::{
api,
dispatch::{self, BufferMappedRangeInterface},

View File

@ -31,6 +31,7 @@
#![cfg_attr(not(any(wgpu_core, webgpu)), allow(unused))]
extern crate alloc;
#[cfg(std)]
extern crate std;
#[cfg(wgpu_core)]
pub extern crate wgpu_core as wgc;

View File

@ -9,10 +9,16 @@ pub fn initialize_adapter_from_env(
instance: &Instance,
compatible_surface: Option<&Surface<'_>>,
) -> Result<Adapter, wgt::RequestAdapterError> {
let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
.as_deref()
.map(str::to_lowercase)
.map_err(|_| wgt::RequestAdapterError::EnvNotSet)?;
cfg_if::cfg_if! {
if #[cfg(std)] {
let desired_adapter_name = std::env::var("WGPU_ADAPTER_NAME")
.as_deref()
.map(str::to_lowercase)
.map_err(|_| wgt::RequestAdapterError::EnvNotSet)?;
} else {
return Err(wgt::RequestAdapterError::EnvNotSet);
}
}
let adapters = instance.enumerate_adapters(crate::Backends::all());

View File

@ -3,15 +3,20 @@
//! Nothing in this module is a part of the WebGPU API specification;
//! they are unique to the `wgpu` library.
// TODO: For [`belt::StagingBelt`] to be available in `no_std` its usage of [`std::sync::mpsc`]
// must be replaced with an appropriate alternative.
#[cfg(std)]
mod belt;
mod device;
mod encoder;
mod init;
mod mutex;
mod texture_blitter;
use alloc::{borrow::Cow, format, string::String, vec};
use core::ptr::copy_nonoverlapping;
#[cfg(std)]
pub use belt::StagingBelt;
pub use device::{BufferInitDescriptor, DeviceExt};
pub use encoder::RenderEncoder;
@ -22,6 +27,8 @@ pub use wgt::{
math::*, DispatchIndirectArgs, DrawIndexedIndirectArgs, DrawIndirectArgs, TextureDataOrder,
};
pub(crate) use mutex::Mutex;
use crate::dispatch;
/// Treat the given byte slice as a SPIR-V module.

60
wgpu/src/util/mutex.rs Normal file
View File

@ -0,0 +1,60 @@
//! Provides a [`Mutex`] for internal use based on what features are available.
cfg_if::cfg_if! {
if #[cfg(feature = "parking_lot")] {
use parking_lot::Mutex as MutexInner;
} else if #[cfg(std)] {
use std::sync::Mutex as MutexInner;
} else {
use core::cell::RefCell as MutexInner;
}
}
pub(crate) struct Mutex<T: ?Sized> {
inner: MutexInner<T>,
}
impl<T: ?Sized> core::fmt::Debug for Mutex<T>
where
MutexInner<T>: core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
<MutexInner<T> as core::fmt::Debug>::fmt(&self.inner, f)
}
}
impl<T: Default> Default for Mutex<T> {
fn default() -> Self {
Self::new(<T as Default>::default())
}
}
impl<T> Mutex<T> {
pub const fn new(value: T) -> Self {
Self {
inner: MutexInner::new(value),
}
}
}
impl<T: ?Sized> Mutex<T> {
pub fn lock(&self) -> impl core::ops::DerefMut<Target = T> + '_ {
cfg_if::cfg_if! {
if #[cfg(feature = "parking_lot")] {
self.inner.lock()
} else if #[cfg(std)] {
self.inner.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
} else {
loop {
let Ok(lock) = self.inner.try_borrow_mut() else {
// Without `std` all we can do is spin until the current lock is released
core::hint::spin_loop();
continue;
};
break lock;
}
}
}
}
}