diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 3a46b30d4..4d14f9d5c 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -196,12 +196,31 @@ pub struct SubmissionIndex(ObjectId, Arc); #[cfg(send_sync)] static_assertions::assert_impl_all!(SubmissionIndex: Send, Sync); -/// The main purpose of this struct is to resolve mapped ranges (convert sizes -/// to end points), and to ensure that the sub-ranges don't intersect. +/// The mapped portion of a buffer, if any, and its outstanding views. +/// +/// This ensures that views fall within the mapped range and don't overlap, and +/// also takes care of turning `Option` sizes into actual buffer +/// offsets. #[derive(Debug)] struct MapContext { + /// The overall size of the buffer. + /// + /// This is just a convenient copy of [`Buffer::size`]. total_size: BufferAddress, + + /// The range of the buffer that is mapped. + /// + /// This is `0..0` if the buffer is not mapped. This becomes non-empty when + /// the buffer is mapped at creation time, and when you call `map_async` on + /// some [`BufferSlice`] (so technically, it indicates the portion that is + /// *or has been requested to be* mapped.) + /// + /// All [`BufferView`]s and [`BufferViewMut`]s must fall within this range. initial_range: Range, + + /// The ranges covered by all outstanding [`BufferView`]s and + /// [`BufferViewMut`]s. These are non-overlapping, and are all contained + /// within `initial_range`. sub_ranges: Vec>, } @@ -214,6 +233,7 @@ impl MapContext { } } + /// Record that the buffer is no longer mapped. fn reset(&mut self) { self.initial_range = 0..0; @@ -223,12 +243,22 @@ impl MapContext { ); } + /// Record that the `size` bytes of the buffer at `offset` are now viewed. + /// + /// Return the byte offset within the buffer of the end of the viewed range. + /// + /// # Panics + /// + /// This panics if the given range overlaps with any existing range. fn add(&mut self, offset: BufferAddress, size: Option) -> BufferAddress { let end = match size { Some(s) => offset + s.get(), None => self.initial_range.end, }; assert!(self.initial_range.start <= offset && end <= self.initial_range.end); + // This check is essential for avoiding undefined behavior: it is the + // only thing that ensures that `&mut` references to the buffer's + // contents don't alias anything else. for sub in self.sub_ranges.iter() { assert!( end <= sub.start || offset >= sub.end, @@ -239,6 +269,14 @@ impl MapContext { end } + /// Record that the `size` bytes of the buffer at `offset` are no longer viewed. + /// + /// # Panics + /// + /// This panics if the given range does not exactly match one previously + /// passed to [`add`]. + /// + /// [`add]`: MapContext::add fn remove(&mut self, offset: BufferAddress, size: Option) { let end = match size { Some(s) => offset + s.get(), @@ -260,6 +298,112 @@ impl MapContext { /// [`DeviceExt::create_buffer_init`](util::DeviceExt::create_buffer_init). /// /// Corresponds to [WebGPU `GPUBuffer`](https://gpuweb.github.io/gpuweb/#buffer-interface). +/// +/// # Mapping buffers +/// +/// If a `Buffer` is created with the appropriate [`usage`], it can be *mapped*: +/// you can make its contents accessible to the CPU as an ordinary `&[u8]` or +/// `&mut [u8]` slice of bytes. Buffers created with the +/// [`mapped_at_creation`][mac] flag set are also mapped initially. +/// +/// Depending on the hardware, the buffer could be memory shared between CPU and +/// GPU, so that the CPU has direct access to the same bytes the GPU will +/// consult; or it may be ordinary CPU memory, whose contents the system must +/// copy to/from the GPU as needed. This crate's API is designed to work the +/// same way in either case: at any given time, a buffer is either mapped and +/// available to the CPU, or unmapped and ready for use by the GPU, but never +/// both. This makes it impossible for either side to observe changes by the +/// other immediately, and any necessary transfers can be carried out when the +/// buffer transitions from one state to the other. +/// +/// There are two ways to map a buffer: +/// +/// - If [`BufferDescriptor::mapped_at_creation`] is `true`, then the entire +/// buffer is mapped when it is created. This is the easiest way to initialize +/// a new buffer. You can set `mapped_at_creation` on any kind of buffer, +/// regardless of its [`usage`] flags. +/// +/// - If the buffer's [`usage`] includes the [`MAP_READ`] or [`MAP_WRITE`] +/// flags, then you can call `buffer.slice(range).map_async(mode, callback)` +/// to map the portion of `buffer` given by `range`. This waits for the GPU to +/// finish using the buffer, and invokes `callback` as soon as the buffer is +/// safe for the CPU to access. +/// +/// Once a buffer is mapped: +/// +/// - You can call `buffer.slice(range).get_mapped_range()` to obtain a +/// [`BufferView`], which dereferences to a `&[u8]` that you can use to read +/// the buffer's contents. +/// +/// - Or, you can call `buffer.slice(range).get_mapped_range_mut()` to obtain a +/// [`BufferViewMut`], which dereferences to a `&mut [u8]` that you can use to +/// read and write the buffer's contents. +/// +/// The given `range` must fall within the mapped portion of the buffer. If you +/// attempt to access overlapping ranges, even for shared access only, these +/// methods panic. +/// +/// For example: +/// +/// ```no_run +/// # let buffer: wgpu::Buffer = todo!(); +/// let slice = buffer.slice(10..20); +/// slice.map_async(wgpu::MapMode::Read, |result| { +/// match result { +/// Ok(()) => { +/// let view = slice.get_mapped_range(); +/// // read data from `view`, which dereferences to `&[u8]` +/// } +/// Err(e) => { +/// // handle mapping error +/// } +/// } +/// }); +/// ``` +/// +/// This example calls `Buffer::slice` to obtain a [`BufferSlice`] referring to +/// the second ten bytes of `buffer`. (To obtain access to the entire buffer, +/// you could call `buffer.slice(..)`.) The code then calls `map_async` to wait +/// for the buffer to be available, and finally calls `get_mapped_range` on the +/// slice to actually get at the bytes. +/// +/// If using `map_async` directly is awkward, you may find it more convenient to +/// use [`Queue::write_buffer`] and [`util::DownloadBuffer::read_buffer`]. +/// However, those each have their own tradeoffs; the asynchronous nature of GPU +/// execution makes it hard to avoid friction altogether. +/// +/// While a buffer is mapped, you must not submit any commands to the GPU that +/// access it. You may record command buffers that use the buffer, but you must +/// not submit such command buffers. +/// +/// When you are done using the buffer on the CPU, you must call +/// [`Buffer::unmap`] to make it available for use by the GPU again. All +/// [`BufferView`] and [`BufferViewMut`] views referring to the buffer must be +/// dropped before you unmap it; otherwise, [`Buffer::unmap`] will panic. +/// +/// ## Mapping buffers on the web +/// +/// When compiled to WebAssembly and running in a browser content process, +/// `wgpu` implements its API in terms of the browser's WebGPU implementation. +/// In this context, `wgpu` is further isolated from the GPU: +/// +/// - Depending on the browser's WebGPU implementation, mapping and unmapping +/// buffers probably entails copies between WebAssembly linear memory and the +/// graphics driver's buffers. +/// +/// - All modern web browsers isolate web content in its own sandboxed process, +/// which can only interact with the GPU via interprocess communication (IPC). +/// Although most browsers' IPC systems use shared memory for large data +/// transfers, there will still probably need to be copies into and out of the +/// shared memory buffers. +/// +/// All of these copies contribute to the cost of buffer mapping in this +/// configuration. +/// +/// [`usage`]: BufferDescriptor::usage +/// [mac]: BufferDescriptor::mapped_at_creation +/// [`MAP_READ`]: BufferUsages::MAP_READ +/// [`MAP_WRITE`]: BufferUsages::MAP_WRITE #[derive(Debug)] pub struct Buffer { context: Arc, @@ -273,14 +417,38 @@ pub struct Buffer { #[cfg(send_sync)] static_assertions::assert_impl_all!(Buffer: Send, Sync); -/// Slice into a [`Buffer`]. +/// A slice of a [`Buffer`], to be mapped, used for vertex or index data, or the like. /// -/// It can be created with [`Buffer::slice`]. To use the whole buffer, call with unbounded slice: +/// You can create a `BufferSlice` by calling [`Buffer::slice`]: /// -/// `buffer.slice(..)` +/// ```no_run +/// # let buffer: wgpu::Buffer = todo!(); +/// let slice = buffer.slice(10..20); +/// ``` /// -/// This type is unique to the Rust API of `wgpu`. In the WebGPU specification, -/// an offset and size are specified as arguments to each call working with the [`Buffer`], instead. +/// This returns a slice referring to the second ten bytes of `buffer`. To get a +/// slice of the entire `Buffer`: +/// +/// ```no_run +/// # let buffer: wgpu::Buffer = todo!(); +/// let whole_buffer_slice = buffer.slice(..); +/// ``` +/// +/// A [`BufferSlice`] is nothing more than a reference to the `Buffer` and a +/// starting and ending position. To access the slice's contents on the CPU, you +/// must first [map] the buffer, and then call [`BufferSlice::get_mapped_range`] +/// or [`BufferSlice::get_mapped_range_mut`] to obtain a view of the slice's +/// contents, which dereferences to a `&[u8]` or `&mut [u8]`. +/// +/// You can also pass buffer slices to methods like +/// [`RenderPass::set_vertex_buffer`] and [`RenderPass::set_index_buffer`] to +/// indicate which data a draw call should consume. +/// +/// The `BufferSlice` type is unique to the Rust API of `wgpu`. In the WebGPU +/// specification, an offset and size are specified as arguments to each call +/// working with the [`Buffer`], instead. +/// +/// [map]: Buffer#mapping-buffers #[derive(Copy, Clone, Debug)] pub struct BufferSlice<'a> { buffer: &'a Buffer, @@ -2932,6 +3100,18 @@ fn range_to_offset_size>( } /// Read only view into a mapped buffer. +/// +/// To get a `BufferView`, first [map] the buffer, and then +/// call `buffer.slice(range).get_mapped_range()`. +/// +/// `BufferView` dereferences to `&[u8]`, so you can use all the usual Rust +/// slice methods to access the buffer's contents. It also implements +/// `AsRef<[u8]>`, if that's more convenient. +/// +/// If you try to create overlapping views of a buffer, mutable or +/// otherwise, `get_mapped_range` will panic. +/// +/// [map]: Buffer#mapping-buffers #[derive(Debug)] pub struct BufferView<'a> { slice: BufferSlice<'a>, @@ -2940,8 +3120,20 @@ pub struct BufferView<'a> { /// Write only view into mapped buffer. /// +/// To get a `BufferViewMut`, first [map] the buffer, and then +/// call `buffer.slice(range).get_mapped_range_mut()`. +/// +/// `BufferViewMut` dereferences to `&mut [u8]`, so you can use all the usual +/// Rust slice methods to access the buffer's contents. It also implements +/// `AsMut<[u8]>`, if that's more convenient. +/// /// It is possible to read the buffer using this view, but doing so is not /// recommended, as it is likely to be slow. +/// +/// If you try to create overlapping views of a buffer, mutable or +/// otherwise, `get_mapped_range_mut` will panic. +/// +/// [map]: Buffer#mapping-buffers #[derive(Debug)] pub struct BufferViewMut<'a> { slice: BufferSlice<'a>,