webgpu: Use safe callbacks & try_recv_timeout (#32008)

* Use safe callback in SwapChainPresent and remove present_buffer_maps

* Use rust closure in BufferMapAsync

* Remove buffer_maps and dead code elimination

* scope id passthrough

* Inline callbacks

* try_recv timeout and halve DEVICE_POLL_INTERVAL
This commit is contained in:
Samson 2024-04-30 13:47:57 +02:00 committed by GitHub
parent 1e186e9251
commit b6748db69d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 95 additions and 174 deletions

View file

@ -382,15 +382,5 @@ impl AsyncWGPUListener for GPUBuffer {
None => unreachable!("Failed to get a response for BufferMapAsync"),
}
*self.map_promise.borrow_mut() = None;
if let Err(e) = self
.channel
.0
.send((None, WebGPURequest::BufferMapComplete(self.buffer.0)))
{
warn!(
"Failed to send BufferMapComplete({:?}) ({})",
self.buffer.0, e
);
}
}
}

View file

@ -13,7 +13,6 @@ use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::num::NonZeroU64;
use std::rc::Rc;
use std::slice;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
@ -45,13 +44,13 @@ use wgpu::id;
use wgpu::instance::RequestAdapterOptions;
use wgpu::pipeline::{ComputePipelineDescriptor, RenderPipelineDescriptor, ShaderModuleDescriptor};
use wgpu::resource::{
BufferDescriptor, BufferMapAsyncStatus, BufferMapCallback, BufferMapCallbackC,
BufferMapOperation, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor,
BufferDescriptor, BufferMapCallback, BufferMapOperation, SamplerDescriptor, TextureDescriptor,
TextureViewDescriptor,
};
use wgt::InstanceDescriptor;
pub type ErrorScopeId = NonZeroU64;
const DEVICE_POLL_INTERVAL: u64 = 100;
const DEVICE_POLL_INTERVAL: Duration = Duration::from_millis(50);
pub const PRESENTATION_BUFFER_COUNT: usize = 10;
#[derive(Debug, Deserialize, Serialize)]
@ -84,7 +83,6 @@ pub enum WebGPURequest {
host_map: HostMap,
map_range: std::ops::Range<u64>,
},
BufferMapComplete(id::BufferId),
CommandEncoderFinish {
command_encoder_id: id::CommandEncoderId,
device_id: id::DeviceId,
@ -257,11 +255,6 @@ pub enum WebGPURequest {
offset: u64,
size: u64,
},
UpdateWebRenderData {
buffer_id: id::BufferId,
external_id: u64,
buffer_size: usize,
},
WriteBuffer {
queue_id: id::QueueId,
buffer_id: id::BufferId,
@ -281,14 +274,6 @@ pub enum WebGPURequest {
},
}
struct BufferMapInfo<'a, T> {
buffer_id: id::BufferId,
sender: IpcSender<T>,
global: &'a wgpu::global::Global,
size: usize,
external_id: Option<u64>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct WebGPU(pub IpcSender<(Option<ErrorScopeId>, WebGPURequest)>);
@ -353,35 +338,26 @@ impl WebGPU {
}
}
type WebGPUBufferMaps<'a> =
HashMap<id::BufferId, Rc<BufferMapInfo<'a, Option<WebGPUResponseResult>>>>;
type WebGPUPresentBufferMaps<'a> =
HashMap<id::BufferId, Rc<BufferMapInfo<'a, (Option<ErrorScopeId>, WebGPURequest)>>>;
#[allow(clippy::upper_case_acronyms)] // Name of the library
struct WGPU<'a> {
struct WGPU {
receiver: IpcReceiver<(Option<ErrorScopeId>, WebGPURequest)>,
sender: IpcSender<(Option<ErrorScopeId>, WebGPURequest)>,
script_sender: IpcSender<WebGPUMsg>,
global: wgpu::global::Global,
global: Arc<wgpu::global::Global>,
adapters: Vec<WebGPUAdapter>,
devices: HashMap<WebGPUDevice, PipelineId>,
// Track invalid adapters https://gpuweb.github.io/gpuweb/#invalid
_invalid_adapters: Vec<WebGPUAdapter>,
// Buffers with pending mapping
buffer_maps: WebGPUBufferMaps<'a>,
// Presentation Buffers with pending mapping
present_buffer_maps: WebGPUPresentBufferMaps<'a>,
//TODO: Remove this (https://github.com/gfx-rs/wgpu/issues/867)
error_command_encoders: RefCell<HashMap<id::CommandEncoderId, String>>,
webrender_api: RenderApi,
webrender_api: Arc<Mutex<RenderApi>>,
webrender_document: DocumentId,
external_images: Arc<Mutex<WebrenderExternalImageRegistry>>,
wgpu_image_map: Arc<Mutex<HashMap<u64, PresentationData>>>,
last_poll: Instant,
}
impl<'a> WGPU<'a> {
impl WGPU {
fn new(
receiver: IpcReceiver<(Option<ErrorScopeId>, WebGPURequest)>,
sender: IpcSender<(Option<ErrorScopeId>, WebGPURequest)>,
@ -395,20 +371,18 @@ impl<'a> WGPU<'a> {
receiver,
sender,
script_sender,
global: wgpu::global::Global::new(
global: Arc::new(wgpu::global::Global::new(
"wgpu-core",
InstanceDescriptor {
backends: wgt::Backends::PRIMARY,
..Default::default()
},
),
)),
adapters: Vec::new(),
devices: HashMap::new(),
_invalid_adapters: Vec::new(),
buffer_maps: HashMap::new(),
present_buffer_maps: HashMap::new(),
error_command_encoders: RefCell::new(HashMap::new()),
webrender_api: webrender_api_sender.create_api(),
webrender_api: Arc::new(Mutex::new(webrender_api_sender.create_api())),
webrender_document,
external_images,
wgpu_image_map,
@ -416,13 +390,17 @@ impl<'a> WGPU<'a> {
}
}
fn run(&'a mut self) {
fn run(&mut self) {
loop {
if self.last_poll.elapsed() >= Duration::from_millis(DEVICE_POLL_INTERVAL) {
let diff = DEVICE_POLL_INTERVAL.checked_sub(self.last_poll.elapsed());
if diff.is_none() {
let _ = self.global.poll_all_devices(false);
self.last_poll = Instant::now();
}
if let Ok((scope_id, msg)) = self.receiver.try_recv() {
if let Ok((scope_id, msg)) = self
.receiver
.try_recv_timeout(diff.unwrap_or(DEVICE_POLL_INTERVAL))
{
match msg {
WebGPURequest::BufferMapAsync {
sender,
@ -431,54 +409,42 @@ impl<'a> WGPU<'a> {
host_map,
map_range,
} => {
let map_info = BufferMapInfo {
buffer_id,
sender: sender.clone(),
global: &self.global,
size: (map_range.end - map_range.start) as usize,
external_id: None,
};
self.buffer_maps.insert(buffer_id, Rc::new(map_info));
// TODO(sagudev): replace with safe callback
unsafe extern "C" fn callback(
status: BufferMapAsyncStatus,
userdata: *mut u8,
) {
let info = Rc::from_raw(
userdata as *const BufferMapInfo<Option<WebGPUResponseResult>>,
);
let msg = match status {
BufferMapAsyncStatus::Success => {
let global = &info.global;
let (slice_pointer, range_size) = gfx_select!(info.buffer_id =>
global.buffer_get_mapped_range(info.buffer_id, 0, None))
let glob = Arc::clone(&self.global);
let resp_sender = sender.clone();
let callback = BufferMapCallback::from_rust(Box::from(move |result| {
match result {
Ok(()) => {
let global = &glob;
let (slice_pointer, range_size) = gfx_select!(buffer_id =>
global.buffer_get_mapped_range(buffer_id, 0, None))
.unwrap();
let data =
slice::from_raw_parts(slice_pointer, range_size as usize);
Ok(WebGPUResponse::BufferMapAsync(IpcSharedMemory::from_bytes(
data,
)))
// SAFETY: guarantee to be safe from wgpu
let data = unsafe {
slice::from_raw_parts(slice_pointer, range_size as usize)
};
if let Err(e) =
resp_sender.send(Some(Ok(WebGPUResponse::BufferMapAsync(
IpcSharedMemory::from_bytes(data),
))))
{
warn!("Could not send BufferMapAsync Response ({})", e);
}
},
_ => {
warn!("Could not map buffer({:?})", info.buffer_id);
Err(String::from("Failed to map Buffer"))
Err(_) => {
warn!("Could not map buffer({:?})", buffer_id);
if let Err(e) = resp_sender
.send(Some(Err(String::from("Failed to map Buffer"))))
{
warn!("Could not send BufferMapAsync Response ({})", e);
}
},
};
if let Err(e) = info.sender.send(Some(msg)) {
warn!("Could not send BufferMapAsync Response ({})", e);
}
}
}));
let operation = BufferMapOperation {
host: host_map,
callback: unsafe {
Some(BufferMapCallback::from_c(BufferMapCallbackC {
callback,
user_data: convert_to_pointer(
self.buffer_maps.get(&buffer_id).unwrap().clone(),
),
}))
},
callback: Some(callback),
};
let global = &self.global;
let result = gfx_select!(buffer_id => global.buffer_map_async(buffer_id, map_range, operation));
@ -489,9 +455,6 @@ impl<'a> WGPU<'a> {
}
self.send_result(device_id, scope_id, result);
},
WebGPURequest::BufferMapComplete(buffer_id) => {
self.buffer_maps.remove(&buffer_id);
},
WebGPURequest::CommandEncoderFinish {
command_encoder_id,
device_id,
@ -754,7 +717,8 @@ impl<'a> WGPU<'a> {
let width = image_desc.size.width;
let buffer_stride =
((width * 4) as u32 | (wgt::COPY_BYTES_PER_ROW_ALIGNMENT - 1)) + 1;
let image_key = self.webrender_api.generate_image_key();
let mut wr = self.webrender_api.lock().unwrap();
let image_key = wr.generate_image_key();
if let Err(e) = sender.send(image_key) {
warn!("Failed to send ImageKey ({})", e);
}
@ -783,8 +747,7 @@ impl<'a> WGPU<'a> {
let mut txn = Transaction::new();
txn.add_image(image_key, image_desc, image_data, None);
self.webrender_api
.send_transaction(self.webrender_document, txn);
wr.send_transaction(self.webrender_document, txn);
},
WebGPURequest::CreateTexture {
device_id,
@ -855,6 +818,8 @@ impl<'a> WGPU<'a> {
let mut txn = Transaction::new();
txn.delete_image(image_key);
self.webrender_api
.lock()
.unwrap()
.send_transaction(self.webrender_document, txn);
},
WebGPURequest::DestroyTexture {
@ -1146,50 +1111,55 @@ impl<'a> WGPU<'a> {
&[encoder_id.transmute()]
));
let map_info = BufferMapInfo {
buffer_id,
sender: self.sender.clone(),
global: &self.global,
size: buffer_size as usize,
external_id: Some(external_id),
};
self.present_buffer_maps
.insert(buffer_id, Rc::new(map_info));
// TODO(sagudev): replace with safe callback
unsafe extern "C" fn callback(
status: BufferMapAsyncStatus,
userdata: *mut u8,
) {
let info = Rc::from_raw(
userdata
as *const BufferMapInfo<(Option<ErrorScopeId>, WebGPURequest)>,
);
match status {
BufferMapAsyncStatus::Success => {
if let Err(e) = info.sender.send((
None,
WebGPURequest::UpdateWebRenderData {
buffer_id: info.buffer_id,
buffer_size: info.size,
external_id: info.external_id.unwrap(),
},
)) {
warn!("Could not send UpdateWebRenderData ({})", e);
let glob = Arc::clone(&self.global);
let wgpu_image_map = Arc::clone(&self.wgpu_image_map);
let webrender_api = Arc::clone(&self.webrender_api);
let webrender_document = self.webrender_document;
let callback = BufferMapCallback::from_rust(Box::from(move |result| {
match result {
Ok(()) => {
let global = &glob;
let (slice_pointer, range_size) = gfx_select!(buffer_id =>
global.buffer_get_mapped_range(buffer_id, 0, Some(buffer_size as u64)))
.unwrap();
let data = unsafe {
slice::from_raw_parts(slice_pointer, range_size as usize)
}
.to_vec();
if let Some(present_data) =
wgpu_image_map.lock().unwrap().get_mut(&external_id)
{
present_data.data = data;
let mut txn = Transaction::new();
txn.update_image(
present_data.image_key,
present_data.image_desc,
present_data.image_data.clone(),
&DirtyRect::All,
);
webrender_api
.lock()
.unwrap()
.send_transaction(webrender_document, txn);
present_data
.queued_buffer_ids
.retain(|b_id| *b_id != buffer_id);
present_data.available_buffer_ids.push(buffer_id);
} else {
warn!(
"Data not found for ExternalImageId({:?})",
external_id
);
}
let _ =
gfx_select!(buffer_id => global.buffer_unmap(buffer_id));
},
_ => error!("Could not map buffer({:?})", info.buffer_id),
_ => error!("Could not map buffer({:?})", buffer_id),
}
}
}));
let map_op = BufferMapOperation {
host: HostMap::Read,
callback: unsafe {
Some(BufferMapCallback::from_c(BufferMapCallbackC {
callback,
user_data: convert_to_pointer(
self.present_buffer_maps.get(&buffer_id).unwrap().clone(),
),
}))
},
callback: Some(callback),
};
let _ = gfx_select!(buffer_id => global.buffer_map_async(buffer_id, 0..buffer_size, map_op));
},
@ -1218,41 +1188,6 @@ impl<'a> WGPU<'a> {
let result = gfx_select!(buffer_id => global.buffer_unmap(buffer_id));
self.send_result(device_id, scope_id, result);
},
WebGPURequest::UpdateWebRenderData {
buffer_id,
buffer_size,
external_id,
} => {
let global = &self.global;
let (slice_pointer, range_size) = gfx_select!(buffer_id =>
global.buffer_get_mapped_range(buffer_id, 0, Some(buffer_size as u64)))
.unwrap();
let data =
unsafe { slice::from_raw_parts(slice_pointer, range_size as usize) }
.to_vec();
if let Some(present_data) =
self.wgpu_image_map.lock().unwrap().get_mut(&external_id)
{
present_data.data = data;
let mut txn = Transaction::new();
txn.update_image(
present_data.image_key,
present_data.image_desc,
present_data.image_data.clone(),
&DirtyRect::All,
);
self.webrender_api
.send_transaction(self.webrender_document, txn);
present_data
.queued_buffer_ids
.retain(|b_id| *b_id != buffer_id);
present_data.available_buffer_ids.push(buffer_id);
} else {
warn!("Data not found for ExternalImageId({:?})", external_id);
}
let _ = gfx_select!(buffer_id => global.buffer_unmap(buffer_id));
self.present_buffer_maps.remove(&buffer_id);
},
WebGPURequest::WriteBuffer {
queue_id,
buffer_id,
@ -1440,10 +1375,6 @@ impl<'a> WGPU<'a> {
}
}
fn convert_to_pointer<T: Sized>(obj: Rc<T>) -> *mut u8 {
Rc::into_raw(obj) as *mut u8
}
fn tuple_to_result<T, E>(res: (T, Option<E>)) -> Result<T, E> {
if let Some(err) = res.1 {
Err(err)