mirror of
https://github.com/gfx-rs/wgpu.git
synced 2024-11-21 22:33:49 +00:00
[wgpu] Document buffer mapping.
This commit is contained in:
parent
cbace631ec
commit
40db4df670
206
wgpu/src/lib.rs
206
wgpu/src/lib.rs
@ -196,12 +196,31 @@ pub struct SubmissionIndex(ObjectId, Arc<crate::Data>);
|
||||
#[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<BufferSize>` 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<BufferAddress>,
|
||||
|
||||
/// 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<Range<BufferAddress>>,
|
||||
}
|
||||
|
||||
@ -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<BufferSize>) -> 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<BufferSize>) {
|
||||
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<C>,
|
||||
@ -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<S: RangeBounds<BufferAddress>>(
|
||||
}
|
||||
|
||||
/// 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>,
|
||||
|
Loading…
Reference in New Issue
Block a user