Rewrite GpuFuture to avoid blocking and to use less space

Remove odd patching matching

Replace std::sync::Mutex with spin::Mutex in GpuFuture

Reduce GpuFuture usage to one explicit allocation instead of two

Fix examples to poll the device in the background when using map_read or map_write

Remove device.poll from GpuFuture::poll and document future invariants

Massively simplify examples

Use Arc::clone(...) instead of arc.clone()

Switch println to log::info
This commit is contained in:
Lachlan Sneff
2020-03-23 10:12:53 -04:00
parent 5f24202f7e
commit ea6bcd03e2
5 changed files with 138 additions and 87 deletions

View File

@@ -1,22 +1,31 @@
use std::future::Future;
use std::pin::Pin;
use std::sync::{Arc, Mutex};
use std::sync::Arc;
use std::task::{Context, Poll, Waker};
use parking_lot::Mutex;
use crate::BufferAddress;
struct GpuFutureInner<T> {
id: wgc::id::DeviceId,
result: Option<T>,
waker: Option<Waker>,
enum WakerOrResult<T> {
Waker(Waker),
Result(T),
}
/// A Future that can poll the wgpu::Device
pub struct GpuFuture<T> {
inner: Arc<Mutex<GpuFutureInner<T>>>,
data: Arc<Data<T>>,
}
pub enum OpaqueData {}
struct Data<T> {
buffer_id: wgc::id::BufferId,
size: BufferAddress,
waker_or_result: Mutex<Option<WakerOrResult<T>>>,
}
/// A completion handle to set the result on a GpuFuture
pub struct GpuFutureCompletion<T> {
inner: Arc<Mutex<GpuFutureInner<T>>>,
data: Arc<Data<T>>,
}
impl<T> Future for GpuFuture<T>
@@ -24,49 +33,62 @@ impl<T> Future for GpuFuture<T>
type Output = T;
fn poll(self: Pin<&mut Self>, context: &mut Context) -> Poll<Self::Output> {
// grab a clone of the Arc
let arc = Arc::clone(&self.get_mut().inner);
let mut waker_or_result = self.into_ref().get_ref().data.waker_or_result.lock();
// grab the device id and set the waker, but release the lock, so that the native callback can write to it
let device_id = {
let mut inner = arc.lock().unwrap();
inner.waker.replace(context.waker().clone());
inner.id
};
// polling the device should trigger the callback
wgn::wgpu_device_poll(device_id, true);
// now take the lock again, and check whether the future is complete
let mut inner = arc.lock().unwrap();
match inner.result.take() {
Some(value) => Poll::Ready(value),
_ => Poll::Pending,
match waker_or_result.take() {
Some(WakerOrResult::Result(res)) => Poll::Ready(res),
_ => {
*waker_or_result = Some(WakerOrResult::Waker(context.waker().clone()));
Poll::Pending
}
}
}
}
impl<T> GpuFutureCompletion<T> {
pub fn complete(self, value: T) {
let mut inner = self.inner.lock().unwrap();
inner.result.replace(value);
if let Some(waker) = &inner.waker {
waker.wake_by_ref();
let mut waker_or_result = self.data.waker_or_result.lock();
match waker_or_result.replace(WakerOrResult::Result(value)) {
Some(WakerOrResult::Waker(waker)) => waker.wake(),
None => {}
Some(WakerOrResult::Result(_)) => {
// Drop before panicking. Not sure if this is necessary, but it makes me feel better.
drop(waker_or_result);
unreachable!()
},
};
}
pub(crate) fn to_raw(self) -> *mut OpaqueData {
Arc::into_raw(self.data) as _
}
pub(crate) unsafe fn from_raw(this: *mut OpaqueData) -> Self {
Self {
data: Arc::from_raw(this as _)
}
}
pub(crate) fn get_buffer_info(&self) -> (wgc::id::BufferId, BufferAddress) {
(self.data.buffer_id, self.data.size)
}
}
pub(crate) fn new_gpu_future<T>(id: wgc::id::DeviceId) -> (GpuFuture<T>, GpuFutureCompletion<T>) {
let inner = Arc::new(Mutex::new(GpuFutureInner {
id,
result: None,
waker: None,
}));
pub(crate) fn new_gpu_future<T>(
buffer_id: wgc::id::BufferId,
size: BufferAddress,
) -> (GpuFuture<T>, GpuFutureCompletion<T>) {
let data = Arc::new(Data {
buffer_id,
size,
waker_or_result: Mutex::new(None),
});
(
GpuFuture {
inner: inner.clone(),
data: Arc::clone(&data),
},
GpuFutureCompletion { inner },
GpuFutureCompletion { data },
)
}

View File

@@ -12,6 +12,7 @@ use smallvec::SmallVec;
use std::{
ffi::CString,
ops::Range,
future::Future,
ptr,
slice,
thread,
@@ -55,6 +56,14 @@ pub struct Device {
temp: Temp,
}
/// This is passed to `Device::poll` to control whether
/// it should block or not.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Maintain {
Wait,
Poll,
}
/// A handle to a GPU-accessible buffer.
#[derive(Debug, PartialEq)]
pub struct Buffer {
@@ -536,8 +545,11 @@ impl Adapter {
impl Device {
/// Check for resource cleanups and mapping callbacks.
pub fn poll(&self, force_wait: bool) {
wgn::wgpu_device_poll(self.id, force_wait);
pub fn poll(&self, maintain: Maintain) {
wgn::wgpu_device_poll(self.id, match maintain {
Maintain::Poll => false,
Maintain::Wait => true,
});
}
/// Creates a shader module from SPIR-V source code.
@@ -889,25 +901,23 @@ impl Drop for BufferWriteMapping {
}
}
struct BufferMapReadFutureUserData
{
size: BufferAddress,
completion: native_gpu_future::GpuFutureCompletion<Result<BufferReadMapping, BufferAsyncErr>>,
buffer_id: wgc::id::BufferId,
}
struct BufferMapWriteFutureUserData
{
size: BufferAddress,
completion: native_gpu_future::GpuFutureCompletion<Result<BufferWriteMapping, BufferAsyncErr>>,
buffer_id: wgc::id::BufferId,
}
impl Buffer {
/// Map the buffer for reading. The result is returned in a future.
pub async fn map_read(&self, start: BufferAddress, size: BufferAddress) -> Result<BufferReadMapping, BufferAsyncErr>
///
/// For the future to complete, `device.poll(...)` must be called elsewhere in the runtime, possibly integrated
/// into an event loop, run on a separate thread, or continually polled in the same task runtime that this
/// future will be run on.
///
/// It's expected that wgpu will eventually supply its own event loop infrastructure that will be easy to integrate
/// into other event loops, like winit's.
pub fn map_read(&self, start: BufferAddress, size: BufferAddress) -> impl Future<Output = Result<BufferReadMapping, BufferAsyncErr>>
{
let (future, completion) = native_gpu_future::new_gpu_future(self.device_id);
let (future, completion) = native_gpu_future::new_gpu_future(
self.id,
size,
);
extern "C" fn buffer_map_read_future_wrapper(
status: wgc::resource::BufferMapAsyncStatus,
@@ -915,39 +925,43 @@ impl Buffer {
user_data: *mut u8,
)
{
let user_data =
unsafe { Box::from_raw(user_data as *mut BufferMapReadFutureUserData) };
let completion = unsafe {
native_gpu_future::GpuFutureCompletion::from_raw(user_data as _)
};
let (buffer_id, size) = completion.get_buffer_info();
if let wgc::resource::BufferMapAsyncStatus::Success = status {
user_data.completion.complete(Ok(BufferReadMapping {
completion.complete(Ok(BufferReadMapping {
data,
size: user_data.size as usize,
buffer_id: user_data.buffer_id,
size: size as usize,
buffer_id,
}));
} else {
user_data.completion.complete(Err(BufferAsyncErr));
completion.complete(Err(BufferAsyncErr));
}
}
let user_data = Box::new(BufferMapReadFutureUserData {
size,
completion,
buffer_id: self.id,
});
wgn::wgpu_buffer_map_read_async(
self.id,
start,
size,
buffer_map_read_future_wrapper,
Box::into_raw(user_data) as *mut u8,
completion.to_raw() as _,
);
future.await
future
}
/// Map the buffer for writing. The result is returned in a future.
pub async fn map_write(&self, start: BufferAddress, size: BufferAddress) -> Result<BufferWriteMapping, BufferAsyncErr>
///
/// See the documentation of (map_read)[#method.map_read] for more information about
/// how to run this future.
pub fn map_write(&self, start: BufferAddress, size: BufferAddress) -> impl Future<Output = Result<BufferWriteMapping, BufferAsyncErr>>
{
let (future, completion) = native_gpu_future::new_gpu_future(self.device_id);
let (future, completion) = native_gpu_future::new_gpu_future(
self.id,
size,
);
extern "C" fn buffer_map_write_future_wrapper(
status: wgc::resource::BufferMapAsyncStatus,
@@ -955,33 +969,31 @@ impl Buffer {
user_data: *mut u8,
)
{
let user_data =
unsafe { Box::from_raw(user_data as *mut BufferMapWriteFutureUserData) };
let completion = unsafe {
native_gpu_future::GpuFutureCompletion::from_raw(user_data as _)
};
let (buffer_id, size) = completion.get_buffer_info();
if let wgc::resource::BufferMapAsyncStatus::Success = status {
user_data.completion.complete(Ok(BufferWriteMapping {
completion.complete(Ok(BufferWriteMapping {
data,
size: user_data.size as usize,
buffer_id: user_data.buffer_id,
size: size as usize,
buffer_id,
}));
} else {
user_data.completion.complete(Err(BufferAsyncErr));
completion.complete(Err(BufferAsyncErr));
}
}
let user_data = Box::new(BufferMapWriteFutureUserData {
size,
completion,
buffer_id: self.id,
});
wgn::wgpu_buffer_map_write_async(
self.id,
start,
size,
buffer_map_write_future_wrapper,
Box::into_raw(user_data) as *mut u8,
completion.to_raw() as _,
);
future.await
future
}
/// Flushes any pending write operations and unmaps the buffer from host memory.