diff --git a/CHANGELOG.md b/CHANGELOG.md index b3c5ebdda7e..cb6b32ca300 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -42,6 +42,14 @@ Bottom level categories: ### New Features +#### General + +- It is now possible to create a dummy `wgpu` device even when no GPU is available. This may be useful for testing of code which manages graphics resources. Currently, it supports reading and writing buffers, and other resource types can be created but do nothing. + + To use it, enable the `noop` feature of `wgpu`, and add `NoopBackendOptions { enable: true }` to the backend options; this is an additional safeguard beyond the `Backends` bits. + + By @kpreid in [#7063](https://github.com/gfx-rs/wgpu/pull/7063). + #### Naga - Support @must_use attribute on function declarations. By @turbocrime in [#6801](https://github.com/gfx-rs/wgpu/pull/6801). diff --git a/Cargo.toml b/Cargo.toml index 2b0a1d350a4..6d98d9904e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -66,6 +66,7 @@ wgpu = { version = "24.0.0", path = "./wgpu", default-features = false, features "metal", "static-dxc", "webgl", + "noop", # This should be removed if we ever have non-test crates that depend on wgpu ] } wgpu-core = { version = "24.0.0", path = "./wgpu-core" } wgpu-hal = { version = "24.0.0", path = "./wgpu-hal" } diff --git a/deno_webgpu/lib.rs b/deno_webgpu/lib.rs index d4c201bb091..2527c25c680 100644 --- a/deno_webgpu/lib.rs +++ b/deno_webgpu/lib.rs @@ -394,6 +394,7 @@ pub fn op_webgpu_request_adapter( shader_compiler: wgpu_types::Dx12Compiler::Fxc, }, gl: wgpu_types::GlBackendOptions::default(), + noop: wgpu_types::NoopBackendOptions::default(), }, }, ))); diff --git a/player/tests/test.rs b/player/tests/test.rs index 7276e6c2920..4382f2f5146 100644 --- a/player/tests/test.rs +++ b/player/tests/test.rs @@ -3,7 +3,7 @@ //! and run the tests through them. //! //! Test requirements: -//! - all IDs have the backend `Empty` +//! - all IDs have the backend `Noop` //! - all expected buffers have `MAP_READ` usage //! - last action is `Submit` //! - no swapchain use @@ -74,9 +74,7 @@ impl Test<'_> { wgt::Backend::Gl => "Gl", _ => unreachable!(), }; - let string = read_to_string(&path) - .unwrap() - .replace("Empty", backend_name); + let string = read_to_string(&path).unwrap().replace("Noop", backend_name); ron::de::from_str(&string) .unwrap_or_else(|e| panic!("{path:?}:{} {}", e.position.line, e.code)) } diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 10a0555aa5f..5ee691ba05e 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -22,11 +22,16 @@ name = "wgpu-compile-test" path = "compile_tests/root.rs" harness = true +[[test]] +name = "wgpu-validation-test" +path = "validation_tests/root.rs" +harness = true + [features] webgl = ["wgpu/webgl"] [dependencies] -wgpu.workspace = true +wgpu = { workspace = true, features = ["noop"] } wgpu-macros.workspace = true anyhow.workspace = true diff --git a/tests/src/init.rs b/tests/src/init.rs index 60639f67a6a..28c7f334a6e 100644 --- a/tests/src/init.rs +++ b/tests/src/init.rs @@ -43,6 +43,8 @@ pub fn initialize_instance(backends: wgpu::Backends, force_fxc: bool) -> Instanc shader_compiler: dx12_shader_compiler, }, gl: wgpu::GlBackendOptions::from_env_or_default(), + // TODO(https://github.com/gfx-rs/wgpu/issues/7119): Enable noop backend? + noop: wgpu::NoopBackendOptions::default(), }, }) } diff --git a/tests/validation_tests/noop.rs b/tests/validation_tests/noop.rs new file mode 100644 index 00000000000..65c1681da04 --- /dev/null +++ b/tests/validation_tests/noop.rs @@ -0,0 +1,61 @@ +//! Tests of [`wgpu::Backend::Noop`]. + +use std::sync::atomic::{AtomicBool, Ordering::Relaxed}; +use std::sync::Arc; + +#[test] +fn device_is_not_available_by_default() { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::NOOP, + ..Default::default() + }); + + assert_eq!( + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())), + None, + "noop backend adapter present when it should not be" + ); +} + +#[test] +fn device_and_buffers() { + let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor { + backends: wgpu::Backends::NOOP, + backend_options: wgpu::BackendOptions { + noop: wgpu::NoopBackendOptions { enable: true }, + ..Default::default() + }, + ..Default::default() + }); + let adapter = + pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions::default())) + .expect("adapter"); + let (device, queue) = + pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor::default(), None)) + .expect("device"); + + assert_eq!(adapter.get_info().backend, wgpu::Backend::Noop); + + // Demonstrate that creating and *writing* to a buffer succeeds. + // This also involves creation of a staging buffer. + let buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("hello world"), + size: 8, + usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::COPY_SRC, + mapped_at_creation: false, + }); + assert_eq!(buffer.size(), 8); + queue.write_buffer(&buffer, 0, &[1, 2, 3, 4]); + queue.write_buffer(&buffer, 4, &[5, 6, 7, 8]); + + // Demonstrate that we can read back data from the buffer. + // This also involves copy_buffer_to_buffer(). + let done: Arc = Arc::default(); + let done2 = done.clone(); + wgpu::util::DownloadBuffer::read_buffer(&device, &queue, &buffer.slice(..), move |result| { + assert_eq!(*result.unwrap(), [1, 2, 3, 4, 5, 6, 7, 8],); + done.store(true, Relaxed); + }); + device.poll(wgpu::Maintain::Wait); + assert!(done2.load(Relaxed)); +} diff --git a/tests/validation_tests/root.rs b/tests/validation_tests/root.rs new file mode 100644 index 00000000000..e2e090c5b34 --- /dev/null +++ b/tests/validation_tests/root.rs @@ -0,0 +1,3 @@ +//! Tests of the [`wgpu`] library API that are not run against a particular GPU. + +mod noop; diff --git a/wgpu-core/Cargo.toml b/wgpu-core/Cargo.toml index 41481e3cdab..29f1fd8df2a 100644 --- a/wgpu-core/Cargo.toml +++ b/wgpu-core/Cargo.toml @@ -113,6 +113,10 @@ gles = ["wgpu-hal/gles"] ## Enable the `dx12` backend. dx12 = ["wgpu-hal/dx12"] +## Enable the `noop` backend. +# TODO(https://github.com/gfx-rs/wgpu/issues/7120): there should be a hal feature +noop = [] + [dependencies] naga.workspace = true wgpu-hal.workspace = true diff --git a/wgpu-core/src/hal_api.rs b/wgpu-core/src/hal_api.rs index b41847b8d53..e8d40d8eb2f 100644 --- a/wgpu-core/src/hal_api.rs +++ b/wgpu-core/src/hal_api.rs @@ -4,8 +4,8 @@ pub trait HalApi: hal::Api + 'static + WasmNotSendSync { const VARIANT: Backend; } -impl HalApi for hal::api::Empty { - const VARIANT: Backend = Backend::Empty; +impl HalApi for hal::api::Noop { + const VARIANT: Backend = Backend::Noop; } #[cfg(vulkan)] diff --git a/wgpu-core/src/id.rs b/wgpu-core/src/id.rs index 2fdfde9116b..dd68ba9456f 100644 --- a/wgpu-core/src/id.rs +++ b/wgpu-core/src/id.rs @@ -65,11 +65,11 @@ impl RawId { /// any backend, but the corresponding resource types ([`Texture`], for /// example) are always parameterized by a specific backend `A`. /// -/// So the `T` in `Id` is usually a resource type like `Texture`, -/// where [`Empty`] is the `wgpu_hal` dummy back end. These empty types are +/// So the `T` in `Id` is usually a resource type like `Texture`, +/// where [`Noop`] is the `wgpu_hal` dummy back end. These empty types are /// never actually used, beyond just making sure you access each `Storage` with /// the right kind of identifier. The members of [`Hub`] pair up each -/// `X` type with the resource type `X`, for some specific backend +/// `X` type with the resource type `X`, for some specific backend /// `A`. /// /// [`Global`]: crate::global::Global @@ -77,7 +77,7 @@ impl RawId { /// [`Hub`]: crate::hub::Hub /// [`Texture`]: crate::resource::Texture /// [`Registry`]: crate::hub::Registry -/// [`Empty`]: hal::api::Empty +/// [`Noop`]: hal::api::Noop #[repr(transparent)] #[cfg_attr(any(feature = "serde", feature = "trace"), derive(serde::Serialize))] #[cfg_attr(any(feature = "serde", feature = "replay"), derive(serde::Deserialize))] diff --git a/wgpu-core/src/instance.rs b/wgpu-core/src/instance.rs index c19a51d58e6..bf413b04e9c 100644 --- a/wgpu-core/src/instance.rs +++ b/wgpu-core/src/instance.rs @@ -107,6 +107,8 @@ impl Instance { init(hal::api::Dx12, instance_desc, &mut instance_per_backend); #[cfg(gles)] init(hal::api::Gles, instance_desc, &mut instance_per_backend); + #[cfg(feature = "noop")] + init(hal::api::Noop, instance_desc, &mut instance_per_backend); Self { name: name.to_string(), diff --git a/wgpu-hal/examples/halmark/main.rs b/wgpu-hal/examples/halmark/main.rs index 778cad0e0ea..731d9f0f026 100644 --- a/wgpu-hal/examples/halmark/main.rs +++ b/wgpu-hal/examples/halmark/main.rs @@ -815,7 +815,7 @@ cfg_if::cfg_if! { } // Fallback else { - type Api = hal::api::Empty; + type Api = hal::api::Noop; } } diff --git a/wgpu-hal/examples/ray-traced-triangle/main.rs b/wgpu-hal/examples/ray-traced-triangle/main.rs index a15aef84122..730f6b111cd 100644 --- a/wgpu-hal/examples/ray-traced-triangle/main.rs +++ b/wgpu-hal/examples/ray-traced-triangle/main.rs @@ -1129,7 +1129,7 @@ cfg_if::cfg_if! { } // Fallback else { - type Api = hal::api::Empty; + type Api = hal::api::Noop; } } diff --git a/wgpu-hal/src/lib.rs b/wgpu-hal/src/lib.rs index bf7cb364298..dd2ecb9dec1 100644 --- a/wgpu-hal/src/lib.rs +++ b/wgpu-hal/src/lib.rs @@ -239,14 +239,15 @@ extern crate wgpu_types as wgt; /// DirectX12 API internals. #[cfg(dx12)] pub mod dx12; -/// A dummy API implementation. -pub mod empty; /// GLES API internals. #[cfg(gles)] pub mod gles; /// Metal API internals. #[cfg(metal)] pub mod metal; +/// A dummy API implementation. +// TODO(https://github.com/gfx-rs/wgpu/issues/7120): this should have a cfg +pub mod noop; /// Vulkan API internals. #[cfg(vulkan)] pub mod vulkan; @@ -255,11 +256,11 @@ pub mod auxil; pub mod api { #[cfg(dx12)] pub use super::dx12::Api as Dx12; - pub use super::empty::Api as Empty; #[cfg(gles)] pub use super::gles::Api as Gles; #[cfg(metal)] pub use super::metal::Api as Metal; + pub use super::noop::Api as Noop; #[cfg(vulkan)] pub use super::vulkan::Api as Vulkan; } @@ -936,6 +937,8 @@ pub trait Device: WasmNotSendSync { /// Calling `wait` with a lower [`FenceValue`] than `fence`'s current value /// returns immediately. /// + /// Returns `Ok(true)` on success and `Ok(false)` on timeout. + /// /// [`Fence`]: Api::Fence /// [`FencePool`]: vulkan/enum.Fence.html#variant.FencePool unsafe fn wait( diff --git a/wgpu-hal/src/noop/buffer.rs b/wgpu-hal/src/noop/buffer.rs new file mode 100644 index 00000000000..ab3b1a5e62c --- /dev/null +++ b/wgpu-hal/src/noop/buffer.rs @@ -0,0 +1,82 @@ +use core::cell::UnsafeCell; +use core::ops::Range; +use core::ptr; +use std::sync::Arc; + +#[derive(Clone, Debug)] +pub struct Buffer { + /// This data is potentially accessed mutably in arbitrary non-overlapping slices, + /// so we must store it in `UnsafeCell` to avoid making any too-strong no-aliasing claims. + storage: Arc>, + + /// Size of the allocation. + /// + /// This is redundant with `storage.get().len()`, but that method is not + /// available until our MSRV is 1.79 or greater. + size: usize, +} + +/// SAFETY: +/// This shared mutable data will not be accessed in a way which causes data races; +/// the obligation to do so is on the caller of the HAL API. +/// For safe code, `wgpu-core` validation manages appropriate access. +unsafe impl Send for Buffer {} +unsafe impl Sync for Buffer {} + +impl Buffer { + pub(super) fn new(desc: &crate::BufferDescriptor) -> Result { + let &crate::BufferDescriptor { + label: _, + size, + usage: _, + memory_flags: _, + } = desc; + + let size = usize::try_from(size).map_err(|_| crate::DeviceError::OutOfMemory)?; + + let mut vector: Vec = Vec::new(); + vector + .try_reserve_exact(size) + .map_err(|_| crate::DeviceError::OutOfMemory)?; + vector.resize(size, 0); + let storage: Arc<[u8]> = Arc::from(vector); + debug_assert_eq!(storage.len(), size); + + // SAFETY: `UnsafeCell<[u8]>` and `[u8]` have the same layout. + // This is just adding a wrapper type without changing any layout, + // because there is not currently a safe language/`std` way to accomplish this. + let storage: Arc> = + unsafe { Arc::from_raw(Arc::into_raw(storage) as *mut UnsafeCell<[u8]>) }; + + Ok(Buffer { storage, size }) + } + + /// Returns a pointer to the memory owned by this buffer within the given `range`. + /// + /// This may be used to create any number of simultaneous pointers; + /// aliasing is only a concern when actually reading, writing, or converting the pointer + /// to a reference. + pub(super) fn get_slice_ptr(&self, range: crate::MemoryRange) -> *mut [u8] { + let base_ptr = self.storage.get(); + let range = range_to_usize(range, self.size); + + // We must obtain a slice pointer without ever creating a slice reference + // that could alias with another slice. + ptr::slice_from_raw_parts_mut( + // SAFETY: `range_to_usize` bounds checks this addition. + unsafe { base_ptr.cast::().add(range.start) }, + range.len(), + ) + } +} + +/// Convert a [`crate::MemoryRange`] to `Range` and bounds check it. +fn range_to_usize(range: crate::MemoryRange, upper_bound: usize) -> Range { + // Note: these assertions should be impossible to trigger from safe code. + // We're doing them anyway since this entire backend is for testing + // (except for when it is an unused placeholder) + let start = usize::try_from(range.start).expect("range too large"); + let end = usize::try_from(range.end).expect("range too large"); + assert!(start <= end && end <= upper_bound, "range out of bounds"); + start..end +} diff --git a/wgpu-hal/src/noop/command.rs b/wgpu-hal/src/noop/command.rs new file mode 100644 index 00000000000..9163e49a47e --- /dev/null +++ b/wgpu-hal/src/noop/command.rs @@ -0,0 +1,309 @@ +use core::mem; +use core::ops::Range; + +use super::{Api, Buffer, DeviceResult, Resource}; + +/// Command buffer type, which performs double duty as the command encoder type too. +#[derive(Debug)] +pub struct CommandBuffer { + commands: Vec, +} + +#[derive(Debug)] +enum Command { + ClearBuffer { + buffer: Buffer, + range: crate::MemoryRange, + }, + CopyBufferToBuffer { + src: Buffer, + dst: Buffer, + regions: Vec, + }, +} + +impl CommandBuffer { + /// # Safety + /// + /// Must be called with appropriate synchronization for the resources affected by the command, + /// such as ensuring that buffers are not accessed by a command while aliasing references exist. + pub(crate) unsafe fn execute(&self) { + for command in &self.commands { + unsafe { command.execute() }; + } + } + + pub(crate) fn new() -> Self { + Self { + commands: Vec::new(), + } + } +} + +impl crate::CommandEncoder for CommandBuffer { + type A = Api; + + unsafe fn begin_encoding(&mut self, label: crate::Label) -> DeviceResult<()> { + assert!(self.commands.is_empty()); + Ok(()) + } + unsafe fn discard_encoding(&mut self) { + self.commands.clear(); + } + unsafe fn end_encoding(&mut self) -> DeviceResult { + Ok(CommandBuffer { + commands: mem::take(&mut self.commands), + }) + } + unsafe fn reset_all(&mut self, command_buffers: I) {} + + unsafe fn transition_buffers<'a, T>(&mut self, barriers: T) + where + T: Iterator>, + { + } + + unsafe fn transition_textures<'a, T>(&mut self, barriers: T) + where + T: Iterator>, + { + } + + unsafe fn clear_buffer(&mut self, buffer: &Buffer, range: crate::MemoryRange) { + self.commands.push(Command::ClearBuffer { + buffer: buffer.clone(), + range, + }) + } + + unsafe fn copy_buffer_to_buffer(&mut self, src: &Buffer, dst: &Buffer, regions: T) + where + T: Iterator, + { + self.commands.push(Command::CopyBufferToBuffer { + src: src.clone(), + dst: dst.clone(), + regions: regions.collect(), + }); + } + + #[cfg(webgl)] + unsafe fn copy_external_image_to_texture( + &mut self, + src: &wgt::CopyExternalImageSourceInfo, + dst: &Resource, + dst_premultiplication: bool, + regions: T, + ) where + T: Iterator, + { + } + + unsafe fn copy_texture_to_texture( + &mut self, + src: &Resource, + src_usage: wgt::TextureUses, + dst: &Resource, + regions: T, + ) { + // TODO: consider implementing this and other texture manipulation + } + + unsafe fn copy_buffer_to_texture(&mut self, src: &Buffer, dst: &Resource, regions: T) { + // TODO: consider implementing this and other texture manipulation + } + + unsafe fn copy_texture_to_buffer( + &mut self, + src: &Resource, + src_usage: wgt::TextureUses, + dst: &Buffer, + regions: T, + ) { + // TODO: consider implementing this and other texture manipulation + } + + unsafe fn begin_query(&mut self, set: &Resource, index: u32) {} + unsafe fn end_query(&mut self, set: &Resource, index: u32) {} + unsafe fn write_timestamp(&mut self, set: &Resource, index: u32) {} + unsafe fn read_acceleration_structure_compact_size( + &mut self, + acceleration_structure: &Resource, + buf: &Buffer, + ) { + } + unsafe fn reset_queries(&mut self, set: &Resource, range: Range) {} + unsafe fn copy_query_results( + &mut self, + set: &Resource, + range: Range, + buffer: &Buffer, + offset: wgt::BufferAddress, + stride: wgt::BufferSize, + ) { + } + + // render + + unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { + } + unsafe fn end_render_pass(&mut self) {} + + unsafe fn set_bind_group( + &mut self, + layout: &Resource, + index: u32, + group: &Resource, + dynamic_offsets: &[wgt::DynamicOffset], + ) { + } + unsafe fn set_push_constants( + &mut self, + layout: &Resource, + stages: wgt::ShaderStages, + offset_bytes: u32, + data: &[u32], + ) { + } + + unsafe fn insert_debug_marker(&mut self, label: &str) {} + unsafe fn begin_debug_marker(&mut self, group_label: &str) {} + unsafe fn end_debug_marker(&mut self) {} + + unsafe fn set_render_pipeline(&mut self, pipeline: &Resource) {} + + unsafe fn set_index_buffer<'a>( + &mut self, + binding: crate::BufferBinding<'a, Buffer>, + format: wgt::IndexFormat, + ) { + } + unsafe fn set_vertex_buffer<'a>( + &mut self, + index: u32, + binding: crate::BufferBinding<'a, Buffer>, + ) { + } + unsafe fn set_viewport(&mut self, rect: &crate::Rect, depth_range: Range) {} + unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect) {} + unsafe fn set_stencil_reference(&mut self, value: u32) {} + unsafe fn set_blend_constants(&mut self, color: &[f32; 4]) {} + + unsafe fn draw( + &mut self, + first_vertex: u32, + vertex_count: u32, + first_instance: u32, + instance_count: u32, + ) { + } + unsafe fn draw_indexed( + &mut self, + first_index: u32, + index_count: u32, + base_vertex: i32, + first_instance: u32, + instance_count: u32, + ) { + } + unsafe fn draw_indirect( + &mut self, + buffer: &Buffer, + offset: wgt::BufferAddress, + draw_count: u32, + ) { + } + unsafe fn draw_indexed_indirect( + &mut self, + buffer: &Buffer, + offset: wgt::BufferAddress, + draw_count: u32, + ) { + } + unsafe fn draw_indirect_count( + &mut self, + buffer: &Buffer, + offset: wgt::BufferAddress, + count_buffer: &Buffer, + count_offset: wgt::BufferAddress, + max_count: u32, + ) { + } + unsafe fn draw_indexed_indirect_count( + &mut self, + buffer: &Buffer, + offset: wgt::BufferAddress, + count_buffer: &Buffer, + count_offset: wgt::BufferAddress, + max_count: u32, + ) { + } + + // compute + + unsafe fn begin_compute_pass(&mut self, desc: &crate::ComputePassDescriptor) {} + unsafe fn end_compute_pass(&mut self) {} + + unsafe fn set_compute_pipeline(&mut self, pipeline: &Resource) {} + + unsafe fn dispatch(&mut self, count: [u32; 3]) {} + unsafe fn dispatch_indirect(&mut self, buffer: &Buffer, offset: wgt::BufferAddress) {} + + unsafe fn build_acceleration_structures<'a, T>( + &mut self, + _descriptor_count: u32, + descriptors: T, + ) where + Api: 'a, + T: IntoIterator>, + { + } + + unsafe fn place_acceleration_structure_barrier( + &mut self, + _barriers: crate::AccelerationStructureBarrier, + ) { + } + + unsafe fn copy_acceleration_structure_to_acceleration_structure( + &mut self, + src: &Resource, + dst: &Resource, + copy: wgt::AccelerationStructureCopy, + ) { + } +} + +impl Command { + /// # Safety + /// + /// Must be called with appropriate synchronization for the resources affected by the command, + /// such as ensuring that buffers are not accessed by a command while aliasing references exist. + unsafe fn execute(&self) { + match self { + Command::ClearBuffer { ref buffer, range } => { + // SAFETY: + // Caller is responsible for ensuring this does not alias. + let buffer_slice: &mut [u8] = unsafe { &mut *buffer.get_slice_ptr(range.clone()) }; + buffer_slice.fill(0); + } + + Command::CopyBufferToBuffer { src, dst, regions } => { + for &crate::BufferCopy { + src_offset, + dst_offset, + size, + } in regions + { + // SAFETY: + // Caller is responsible for ensuring this does not alias. + let src_region: &[u8] = + unsafe { &*src.get_slice_ptr(src_offset..src_offset + size.get()) }; + let dst_region: &mut [u8] = + unsafe { &mut *dst.get_slice_ptr(dst_offset..dst_offset + size.get()) }; + dst_region.copy_from_slice(src_region); + } + } + } + } +} diff --git a/wgpu-hal/src/empty.rs b/wgpu-hal/src/noop/mod.rs similarity index 52% rename from wgpu-hal/src/empty.rs rename to wgpu-hal/src/noop/mod.rs index 27bd440ff73..9c0b5bdead5 100644 --- a/wgpu-hal/src/empty.rs +++ b/wgpu-hal/src/noop/mod.rs @@ -1,7 +1,13 @@ #![allow(unused_variables)] use crate::TlasInstance; -use std::ops::Range; +use core::ptr; +use core::sync::atomic::{AtomicU64, Ordering}; + +mod buffer; +pub use buffer::Buffer; +mod command; +pub use command::CommandBuffer; #[derive(Clone, Debug)] pub struct Api; @@ -11,6 +17,11 @@ pub struct Encoder; #[derive(Debug)] pub struct Resource; +#[derive(Debug)] +pub struct Fence { + value: AtomicU64, +} + type DeviceResult = Result; impl crate::Api for Api { @@ -20,16 +31,16 @@ impl crate::Api for Api { type Device = Context; type Queue = Context; - type CommandEncoder = Encoder; - type CommandBuffer = Resource; + type CommandEncoder = CommandBuffer; + type CommandBuffer = CommandBuffer; - type Buffer = Resource; + type Buffer = Buffer; type Texture = Resource; type SurfaceTexture = Resource; type TextureView = Resource; type Sampler = Resource; type QuerySet = Resource; - type Fence = Resource; + type Fence = Fence; type AccelerationStructure = Resource; type PipelineCache = Resource; @@ -41,15 +52,15 @@ impl crate::Api for Api { type ComputePipeline = Resource; } -crate::impl_dyn_resource!(Context, Encoder, Resource); +crate::impl_dyn_resource!(Buffer, CommandBuffer, Context, Fence, Resource); impl crate::DynAccelerationStructure for Resource {} impl crate::DynBindGroup for Resource {} impl crate::DynBindGroupLayout for Resource {} -impl crate::DynBuffer for Resource {} -impl crate::DynCommandBuffer for Resource {} +impl crate::DynBuffer for Buffer {} +impl crate::DynCommandBuffer for CommandBuffer {} impl crate::DynComputePipeline for Resource {} -impl crate::DynFence for Resource {} +impl crate::DynFence for Fence {} impl crate::DynPipelineCache for Resource {} impl crate::DynPipelineLayout for Resource {} impl crate::DynQuerySet for Resource {} @@ -70,7 +81,22 @@ impl crate::Instance for Context { type A = Api; unsafe fn init(desc: &crate::InstanceDescriptor) -> Result { - Ok(Context) + let crate::InstanceDescriptor { + backend_options: + wgt::BackendOptions { + noop: wgt::NoopBackendOptions { enable }, + .. + }, + name: _, + flags: _, + } = *desc; + if enable { + Ok(Context) + } else { + Err(crate::InstanceError::new(String::from( + "noop backend disabled because NoopBackendOptions::enable is false", + ))) + } } unsafe fn create_surface( &self, @@ -83,10 +109,82 @@ impl crate::Instance for Context { &self, _surface_hint: Option<&Context>, ) -> Vec> { - Vec::new() + vec![crate::ExposedAdapter { + adapter: Context, + info: wgt::AdapterInfo { + name: String::from("noop wgpu backend"), + vendor: 0, + device: 0, + device_type: wgt::DeviceType::Cpu, + driver: String::from("wgpu"), + driver_info: String::new(), + backend: wgt::Backend::Noop, + }, + features: wgt::Features::all(), + capabilities: CAPABILITIES, + }] } } +const CAPABILITIES: crate::Capabilities = { + /// Guaranteed to be no bigger than isize::MAX which is the maximum size of an allocation, + /// except on 16-bit platforms which we certainly don’t fit in. + const ALLOC_MAX_U32: u32 = i32::MAX as u32; + + crate::Capabilities { + limits: wgt::Limits { + // All maximally permissive + max_texture_dimension_1d: ALLOC_MAX_U32, + max_texture_dimension_2d: ALLOC_MAX_U32, + max_texture_dimension_3d: ALLOC_MAX_U32, + max_texture_array_layers: ALLOC_MAX_U32, + max_bind_groups: ALLOC_MAX_U32, + max_bindings_per_bind_group: ALLOC_MAX_U32, + max_dynamic_uniform_buffers_per_pipeline_layout: ALLOC_MAX_U32, + max_dynamic_storage_buffers_per_pipeline_layout: ALLOC_MAX_U32, + max_sampled_textures_per_shader_stage: ALLOC_MAX_U32, + max_samplers_per_shader_stage: ALLOC_MAX_U32, + max_storage_buffers_per_shader_stage: ALLOC_MAX_U32, + max_storage_textures_per_shader_stage: ALLOC_MAX_U32, + max_uniform_buffers_per_shader_stage: ALLOC_MAX_U32, + max_uniform_buffer_binding_size: ALLOC_MAX_U32, + max_storage_buffer_binding_size: ALLOC_MAX_U32, + max_vertex_buffers: ALLOC_MAX_U32, + max_buffer_size: ALLOC_MAX_U32 as u64, + max_vertex_attributes: ALLOC_MAX_U32, + max_vertex_buffer_array_stride: ALLOC_MAX_U32, + min_uniform_buffer_offset_alignment: 1, + min_storage_buffer_offset_alignment: 1, + max_inter_stage_shader_components: ALLOC_MAX_U32, + max_color_attachments: ALLOC_MAX_U32, + max_color_attachment_bytes_per_sample: ALLOC_MAX_U32, + max_compute_workgroup_storage_size: ALLOC_MAX_U32, + max_compute_invocations_per_workgroup: ALLOC_MAX_U32, + max_compute_workgroup_size_x: ALLOC_MAX_U32, + max_compute_workgroup_size_y: ALLOC_MAX_U32, + max_compute_workgroup_size_z: ALLOC_MAX_U32, + max_compute_workgroups_per_dimension: ALLOC_MAX_U32, + min_subgroup_size: 1, + max_subgroup_size: ALLOC_MAX_U32, + max_push_constant_size: ALLOC_MAX_U32, + max_non_sampler_bindings: ALLOC_MAX_U32, + }, + alignments: crate::Alignments { + // All maximally permissive + buffer_copy_offset: wgt::BufferSize::MIN, + buffer_copy_pitch: wgt::BufferSize::MIN, + uniform_bounds_check_alignment: wgt::BufferSize::MIN, + raw_tlas_instance_size: 0, + ray_tracing_scratch_buffer_alignment: 1, + }, + downlevel: wgt::DownlevelCapabilities { + flags: wgt::DownlevelFlags::all(), + limits: wgt::DownlevelLimits {}, + shader_model: wgt::ShaderModel::Sm5, + }, + } +}; + impl crate::Surface for Context { type A = Api; @@ -103,7 +201,7 @@ impl crate::Surface for Context { unsafe fn acquire_texture( &self, timeout: Option, - fence: &Resource, + fence: &Fence, ) -> Result>, crate::SurfaceError> { Ok(None) } @@ -119,7 +217,10 @@ impl crate::Adapter for Context { _limits: &wgt::Limits, _memory_hints: &wgt::MemoryHints, ) -> DeviceResult> { - Err(crate::DeviceError::Lost) + Ok(crate::OpenDevice { + device: Context, + queue: Context, + }) } unsafe fn texture_format_capabilities( &self, @@ -142,10 +243,19 @@ impl crate::Queue for Context { unsafe fn submit( &self, - command_buffers: &[&Resource], + command_buffers: &[&CommandBuffer], surface_textures: &[&Resource], - signal_fence: (&mut Resource, crate::FenceValue), + (fence, fence_value): (&mut Fence, crate::FenceValue), ) -> DeviceResult<()> { + // All commands are executed synchronously. + for cb in command_buffers { + // SAFETY: Caller is responsible for ensuring synchronization between commands and + // other mutations. + unsafe { + cb.execute(); + } + } + fence.value.store(fence_value, Ordering::Release); Ok(()) } unsafe fn present( @@ -164,22 +274,29 @@ impl crate::Queue for Context { impl crate::Device for Context { type A = Api; - unsafe fn create_buffer(&self, desc: &crate::BufferDescriptor) -> DeviceResult { - Ok(Resource) + unsafe fn create_buffer(&self, desc: &crate::BufferDescriptor) -> DeviceResult { + Buffer::new(desc) } - unsafe fn destroy_buffer(&self, buffer: Resource) {} - unsafe fn add_raw_buffer(&self, _buffer: &Resource) {} + + unsafe fn destroy_buffer(&self, buffer: Buffer) {} + unsafe fn add_raw_buffer(&self, _buffer: &Buffer) {} unsafe fn map_buffer( &self, - buffer: &Resource, + buffer: &Buffer, range: crate::MemoryRange, ) -> DeviceResult { - Err(crate::DeviceError::Lost) - } - unsafe fn unmap_buffer(&self, buffer: &Resource) {} - unsafe fn flush_mapped_ranges(&self, buffer: &Resource, ranges: I) {} - unsafe fn invalidate_mapped_ranges(&self, buffer: &Resource, ranges: I) {} + // Safety: the `wgpu-core` validation layer will prevent any user-accessible aliasing + // mappings from being created, so we don’t need to perform any checks here, except for + // bounds checks on the range which are built into `get_slice_ptr()`. + Ok(crate::BufferMapping { + ptr: ptr::NonNull::new(buffer.get_slice_ptr(range).cast::()).unwrap(), + is_coherent: true, + }) + } + unsafe fn unmap_buffer(&self, buffer: &Buffer) {} + unsafe fn flush_mapped_ranges(&self, buffer: &Buffer, ranges: I) {} + unsafe fn invalidate_mapped_ranges(&self, buffer: &Buffer, ranges: I) {} unsafe fn create_texture(&self, desc: &crate::TextureDescriptor) -> DeviceResult { Ok(Resource) @@ -203,8 +320,8 @@ impl crate::Device for Context { unsafe fn create_command_encoder( &self, desc: &crate::CommandEncoderDescriptor, - ) -> DeviceResult { - Ok(Encoder) + ) -> DeviceResult { + Ok(CommandBuffer::new()) } unsafe fn create_bind_group_layout( @@ -223,7 +340,7 @@ impl crate::Device for Context { unsafe fn destroy_pipeline_layout(&self, pipeline_layout: Resource) {} unsafe fn create_bind_group( &self, - desc: &crate::BindGroupDescriptor, + desc: &crate::BindGroupDescriptor, ) -> DeviceResult { Ok(Resource) } @@ -266,19 +383,28 @@ impl crate::Device for Context { Ok(Resource) } unsafe fn destroy_query_set(&self, set: Resource) {} - unsafe fn create_fence(&self) -> DeviceResult { - Ok(Resource) + unsafe fn create_fence(&self) -> DeviceResult { + Ok(Fence { + value: AtomicU64::new(0), + }) } - unsafe fn destroy_fence(&self, fence: Resource) {} - unsafe fn get_fence_value(&self, fence: &Resource) -> DeviceResult { - Ok(0) + unsafe fn destroy_fence(&self, fence: Fence) {} + unsafe fn get_fence_value(&self, fence: &Fence) -> DeviceResult { + Ok(fence.value.load(Ordering::Acquire)) } unsafe fn wait( &self, - fence: &Resource, + fence: &Fence, value: crate::FenceValue, timeout_ms: u32, ) -> DeviceResult { + // The relevant commands must have already been submitted, and noop-backend commands are + // executed synchronously, so there is no waiting — either it is already done, + // or this method was called incorrectly. + assert!( + fence.value.load(Ordering::Acquire) >= value, + "submission must have already been done" + ); Ok(true) } @@ -294,7 +420,7 @@ impl crate::Device for Context { } unsafe fn get_acceleration_structure_build_sizes<'a>( &self, - _desc: &crate::GetAccelerationStructureBuildSizesDescriptor<'a, Resource>, + _desc: &crate::GetAccelerationStructureBuildSizesDescriptor<'a, Buffer>, ) -> crate::AccelerationStructureBuildSizes { Default::default() } @@ -314,214 +440,3 @@ impl crate::Device for Context { Default::default() } } - -impl crate::CommandEncoder for Encoder { - type A = Api; - - unsafe fn begin_encoding(&mut self, label: crate::Label) -> DeviceResult<()> { - Ok(()) - } - unsafe fn discard_encoding(&mut self) {} - unsafe fn end_encoding(&mut self) -> DeviceResult { - Ok(Resource) - } - unsafe fn reset_all(&mut self, command_buffers: I) {} - - unsafe fn transition_buffers<'a, T>(&mut self, barriers: T) - where - T: Iterator>, - { - } - - unsafe fn transition_textures<'a, T>(&mut self, barriers: T) - where - T: Iterator>, - { - } - - unsafe fn clear_buffer(&mut self, buffer: &Resource, range: crate::MemoryRange) {} - - unsafe fn copy_buffer_to_buffer(&mut self, src: &Resource, dst: &Resource, regions: T) {} - - #[cfg(webgl)] - unsafe fn copy_external_image_to_texture( - &mut self, - src: &wgt::CopyExternalImageSourceInfo, - dst: &Resource, - dst_premultiplication: bool, - regions: T, - ) where - T: Iterator, - { - } - - unsafe fn copy_texture_to_texture( - &mut self, - src: &Resource, - src_usage: wgt::TextureUses, - dst: &Resource, - regions: T, - ) { - } - - unsafe fn copy_buffer_to_texture(&mut self, src: &Resource, dst: &Resource, regions: T) {} - - unsafe fn copy_texture_to_buffer( - &mut self, - src: &Resource, - src_usage: wgt::TextureUses, - dst: &Resource, - regions: T, - ) { - } - - unsafe fn begin_query(&mut self, set: &Resource, index: u32) {} - unsafe fn end_query(&mut self, set: &Resource, index: u32) {} - unsafe fn write_timestamp(&mut self, set: &Resource, index: u32) {} - unsafe fn read_acceleration_structure_compact_size( - &mut self, - acceleration_structure: &Resource, - buf: &Resource, - ) { - } - unsafe fn reset_queries(&mut self, set: &Resource, range: Range) {} - unsafe fn copy_query_results( - &mut self, - set: &Resource, - range: Range, - buffer: &Resource, - offset: wgt::BufferAddress, - stride: wgt::BufferSize, - ) { - } - - // render - - unsafe fn begin_render_pass(&mut self, desc: &crate::RenderPassDescriptor) { - } - unsafe fn end_render_pass(&mut self) {} - - unsafe fn set_bind_group( - &mut self, - layout: &Resource, - index: u32, - group: &Resource, - dynamic_offsets: &[wgt::DynamicOffset], - ) { - } - unsafe fn set_push_constants( - &mut self, - layout: &Resource, - stages: wgt::ShaderStages, - offset_bytes: u32, - data: &[u32], - ) { - } - - unsafe fn insert_debug_marker(&mut self, label: &str) {} - unsafe fn begin_debug_marker(&mut self, group_label: &str) {} - unsafe fn end_debug_marker(&mut self) {} - - unsafe fn set_render_pipeline(&mut self, pipeline: &Resource) {} - - unsafe fn set_index_buffer<'a>( - &mut self, - binding: crate::BufferBinding<'a, Resource>, - format: wgt::IndexFormat, - ) { - } - unsafe fn set_vertex_buffer<'a>( - &mut self, - index: u32, - binding: crate::BufferBinding<'a, Resource>, - ) { - } - unsafe fn set_viewport(&mut self, rect: &crate::Rect, depth_range: Range) {} - unsafe fn set_scissor_rect(&mut self, rect: &crate::Rect) {} - unsafe fn set_stencil_reference(&mut self, value: u32) {} - unsafe fn set_blend_constants(&mut self, color: &[f32; 4]) {} - - unsafe fn draw( - &mut self, - first_vertex: u32, - vertex_count: u32, - first_instance: u32, - instance_count: u32, - ) { - } - unsafe fn draw_indexed( - &mut self, - first_index: u32, - index_count: u32, - base_vertex: i32, - first_instance: u32, - instance_count: u32, - ) { - } - unsafe fn draw_indirect( - &mut self, - buffer: &Resource, - offset: wgt::BufferAddress, - draw_count: u32, - ) { - } - unsafe fn draw_indexed_indirect( - &mut self, - buffer: &Resource, - offset: wgt::BufferAddress, - draw_count: u32, - ) { - } - unsafe fn draw_indirect_count( - &mut self, - buffer: &Resource, - offset: wgt::BufferAddress, - count_buffer: &Resource, - count_offset: wgt::BufferAddress, - max_count: u32, - ) { - } - unsafe fn draw_indexed_indirect_count( - &mut self, - buffer: &Resource, - offset: wgt::BufferAddress, - count_buffer: &Resource, - count_offset: wgt::BufferAddress, - max_count: u32, - ) { - } - - // compute - - unsafe fn begin_compute_pass(&mut self, desc: &crate::ComputePassDescriptor) {} - unsafe fn end_compute_pass(&mut self) {} - - unsafe fn set_compute_pipeline(&mut self, pipeline: &Resource) {} - - unsafe fn dispatch(&mut self, count: [u32; 3]) {} - unsafe fn dispatch_indirect(&mut self, buffer: &Resource, offset: wgt::BufferAddress) {} - - unsafe fn build_acceleration_structures<'a, T>( - &mut self, - _descriptor_count: u32, - descriptors: T, - ) where - Api: 'a, - T: IntoIterator>, - { - } - - unsafe fn place_acceleration_structure_barrier( - &mut self, - _barriers: crate::AccelerationStructureBarrier, - ) { - } - - unsafe fn copy_acceleration_structure_to_acceleration_structure( - &mut self, - src: &Resource, - dst: &Resource, - copy: wgt::AccelerationStructureCopy, - ) { - } -} diff --git a/wgpu-types/src/instance.rs b/wgpu-types/src/instance.rs index 7e417a723b9..68848ccaa00 100644 --- a/wgpu-types/src/instance.rs +++ b/wgpu-types/src/instance.rs @@ -4,6 +4,9 @@ use alloc::string::String; use crate::Backends; +#[cfg(doc)] +use crate::Backend; + /// Options for creating an instance. #[derive(Clone, Debug)] pub struct InstanceDescriptor { @@ -181,12 +184,16 @@ impl InstanceFlags { } /// Options that are passed to a given backend. +/// +/// Part of [`InstanceDescriptor`]. #[derive(Clone, Debug, Default)] pub struct BackendOptions { - /// Options for the OpenGL/OpenGLES backend. + /// Options for the OpenGL/OpenGLES backend, [`Backend::Gl`]. pub gl: GlBackendOptions, - /// Options for the DX12 backend. + /// Options for the DX12 backend, [`Backend::Dx12`]. pub dx12: Dx12BackendOptions, + /// Options for the noop backend, [`Backend::Noop`]. + pub noop: NoopBackendOptions, } impl BackendOptions { @@ -195,9 +202,11 @@ impl BackendOptions { /// See those methods for more information. #[must_use] pub fn from_env_or_default() -> Self { - let gl = GlBackendOptions::from_env_or_default(); - let dx12 = Dx12BackendOptions::from_env_or_default(); - Self { gl, dx12 } + Self { + gl: GlBackendOptions::from_env_or_default(), + dx12: Dx12BackendOptions::from_env_or_default(), + noop: NoopBackendOptions::from_env_or_default(), + } } /// Takes the given options, modifies them based on the environment variables, and returns the result. @@ -205,13 +214,17 @@ impl BackendOptions { /// This is equivalent to calling `with_env` on every field. #[must_use] pub fn with_env(self) -> Self { - let gl = self.gl.with_env(); - let dx12 = self.dx12.with_env(); - Self { gl, dx12 } + Self { + gl: self.gl.with_env(), + dx12: self.dx12.with_env(), + noop: self.noop.with_env(), + } } } /// Configuration for the OpenGL/OpenGLES backend. +/// +/// Part of [`BackendOptions`]. #[derive(Clone, Debug, Default)] pub struct GlBackendOptions { /// Which OpenGL ES 3 minor version to request, if using OpenGL ES. @@ -248,6 +261,8 @@ impl GlBackendOptions { } /// Configuration for the DX12 backend. +/// +/// Part of [`BackendOptions`]. #[derive(Clone, Debug, Default)] pub struct Dx12BackendOptions { /// Which DX12 shader compiler to use. @@ -276,6 +291,52 @@ impl Dx12BackendOptions { } } +/// Configuration for the noop backend. +/// +/// Part of [`BackendOptions`]. +#[derive(Clone, Debug, Default)] +pub struct NoopBackendOptions { + /// Whether to allow the noop backend to be used. + /// + /// The noop backend stubs out all operations except for buffer creation and mapping, so + /// it must not be used when not expected. Therefore, it will not be used unless explicitly + /// enabled. + pub enable: bool, +} + +impl NoopBackendOptions { + /// Choose whether the noop backend is enabled from the environment. + /// + /// It will be enabled if the environment variable `WGPU_NOOP_BACKEND` has the value `1` + /// and not otherwise. Future versions may assign other meanings to other values. + #[must_use] + pub fn from_env_or_default() -> Self { + Self { + enable: Self::enable_from_env().unwrap_or(false), + } + } + + /// Takes the given options, modifies them based on the environment variables, and returns the + /// result. + /// + /// See [`from_env_or_default()`](Self::from_env_or_default) for the interpretation. + #[must_use] + pub fn with_env(self) -> Self { + Self { + enable: Self::enable_from_env().unwrap_or(self.enable), + } + } + + fn enable_from_env() -> Option { + let value = crate::env::var("WGPU_NOOP_BACKEND")?; + match value.as_str() { + "1" => Some(true), + "0" => Some(false), + _ => None, + } + } +} + /// Selects which DX12 shader compiler to use. /// /// If the `DynamicDxc` option is selected, but `dxcompiler.dll` and `dxil.dll` files aren't found, diff --git a/wgpu-types/src/lib.rs b/wgpu-types/src/lib.rs index e71c89c5c05..18e5ce5b836 100644 --- a/wgpu-types/src/lib.rs +++ b/wgpu-types/src/lib.rs @@ -71,12 +71,33 @@ pub const QUERY_SET_MAX_QUERIES: u32 = 4096; pub const QUERY_SIZE: u32 = 8; /// Backends supported by wgpu. +/// +/// See also [`Backends`]. #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Backend { - /// Dummy backend, used for testing. - Empty = 0, + /// Dummy backend, which may be used for testing. + /// + /// It performs no rendering or computation, but allows creation of stub GPU resource types, + /// so that code which manages GPU resources can be tested without an available GPU. + /// Specifically, the following operations are implemented: + /// + /// * Enumerating adapters will always return one noop adapter, which can be used to create + /// devices. + /// * Buffers may be created, written, mapped, and copied to other buffers. + /// * Command encoders may be created, but only buffer operations are useful. + /// + /// Other resources can be created but are nonfunctional; notably, + /// + /// * Render passes and compute passes are not executed. + /// * Textures may be created, but do not store any texels. + /// * There are no compatible surfaces. + /// + /// An adapter using the noop backend can only be obtained if [`NoopBackendOptions`] + /// enables it, in addition to the ordinary requirement of [`Backends::NOOP`] being set. + /// This ensures that applications not desiring a non-functional backend will not receive it. + Noop = 0, /// Vulkan API (Windows, Linux, Android, MacOS via `vulkan-portability`/MoltenVK) Vulkan = 1, /// Metal API (Apple platforms) @@ -94,7 +115,7 @@ impl Backend { #[must_use] pub const fn to_str(self) -> &'static str { match self { - Backend::Empty => "empty", + Backend::Noop => "noop", Backend::Vulkan => "vulkan", Backend::Metal => "metal", Backend::Dx12 => "dx12", @@ -148,22 +169,35 @@ bitflags::bitflags! { #[cfg_attr(feature = "serde", serde(transparent))] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct Backends: u32 { + /// [`Backend::Noop`]. + const NOOP = 1 << Backend::Noop as u32; + + /// [`Backend::Vulkan`]. /// Supported on Windows, Linux/Android, and macOS/iOS via Vulkan Portability (with the Vulkan feature enabled) const VULKAN = 1 << Backend::Vulkan as u32; + + /// [`Backend::Gl`]. /// Supported on Linux/Android, the web through webassembly via WebGL, and Windows and /// macOS/iOS via ANGLE const GL = 1 << Backend::Gl as u32; - /// Supported on macOS/iOS + + /// [`Backend::Metal`]. + /// Supported on macOS and iOS. const METAL = 1 << Backend::Metal as u32; + + /// [`Backend::Dx12`]. /// Supported on Windows 10 and later const DX12 = 1 << Backend::Dx12 as u32; - /// Supported when targeting the web through webassembly with the `webgpu` feature enabled. + + /// [`Backend::BrowserWebGpu`]. + /// Supported when targeting the web through WebAssembly with the `webgpu` feature enabled. /// /// The WebGPU backend is special in several ways: /// It is not not implemented by `wgpu_core` and instead by the higher level `wgpu` crate. /// Whether WebGPU is targeted is decided upon the creation of the `wgpu::Instance`, /// *not* upon adapter creation. See `wgpu::Instance::new`. const BROWSER_WEBGPU = 1 << Backend::BrowserWebGpu as u32; + /// All the apis that wgpu offers first tier of support for. /// /// * [`Backends::VULKAN`] @@ -174,6 +208,7 @@ bitflags::bitflags! { | Self::METAL.bits() | Self::DX12.bits() | Self::BROWSER_WEBGPU.bits(); + /// All the apis that wgpu offers second tier of support for. These may /// be unsupported/still experimental. /// @@ -233,6 +268,7 @@ impl Backends { "metal" | "mtl" => Self::METAL, "opengl" | "gles" | "gl" => Self::GL, "webgpu" => Self::BROWSER_WEBGPU, + "noop" => Self::NOOP, b => { log::warn!("unknown backend string '{}'", b); continue; diff --git a/wgpu/Cargo.toml b/wgpu/Cargo.toml index a69809c645c..5574fd5eec9 100644 --- a/wgpu/Cargo.toml +++ b/wgpu/Cargo.toml @@ -57,6 +57,14 @@ vulkan-portability = ["wgpu-core?/vulkan"] ## * ⚠️ WIP: Currently will also enable GLES dependencies on any other targets. webgl = ["dep:wgpu-hal", "wgpu-core/gles"] +## Enables the noop backend for testing. +## +## This backend allows creating resources such as buffers and textures, +## but performs no computation. +## Because it lacks basic functionality, it is only actually used if explicitly enabled +## through `NoopBackendOptions`. +noop = ["wgpu-core/noop"] + #! **Note:** In the documentation, if you see that an item depends on a backend, #! it means that the item is only available when that backend is enabled _and_ the backend #! is supported on the current platform. diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index cdfa2bbb1d5..ed0393c8a93 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -67,7 +67,7 @@ pub use wgt::{ Dx12BackendOptions, Dx12Compiler, DynamicOffset, Extent3d, Face, Features, FeaturesWGPU, FeaturesWebGPU, FilterMode, FrontFace, GlBackendOptions, Gles3MinorVersion, HalCounters, ImageSubresourceRange, IndexFormat, InstanceDescriptor, InstanceFlags, InternalCounters, - Limits, MaintainResult, MemoryHints, MultisampleState, Origin2d, Origin3d, + Limits, MaintainResult, MemoryHints, MultisampleState, NoopBackendOptions, Origin2d, Origin3d, PipelineStatisticsTypes, PolygonMode, PowerPreference, PredefinedColorSpace, PresentMode, PresentationTimestamp, PrimitiveState, PrimitiveTopology, PushConstantRange, QueryType, RenderBundleDepthStencil, SamplerBindingType, SamplerBorderColor, ShaderLocation, ShaderModel,