mirror of
https://github.com/denoland/deno.git
synced 2025-09-26 12:19:12 +00:00
refactor: object wrap WebGPU (#27665)
Fixes #25874 Fixes #26760 Fixes #24288 Fixes #24798 Fixes #25627 Fixes #25915 Fixes #26769
This commit is contained in:
parent
7a112643f5
commit
7253820764
39 changed files with 7138 additions and 11466 deletions
|
@ -1,209 +1,265 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
use deno_core::futures::channel::oneshot;
|
||||
use deno_core::op2;
|
||||
use deno_core::OpState;
|
||||
use deno_core::Resource;
|
||||
use deno_core::ResourceId;
|
||||
use deno_core::v8;
|
||||
use deno_core::webidl::WebIdlInterfaceConverter;
|
||||
use deno_core::GarbageCollected;
|
||||
use deno_core::WebIDL;
|
||||
use deno_error::JsErrorBox;
|
||||
use wgpu_core::device::HostMap as MapMode;
|
||||
|
||||
use super::error::WebGpuResult;
|
||||
use crate::Instance;
|
||||
|
||||
#[derive(WebIDL)]
|
||||
#[webidl(dictionary)]
|
||||
pub(crate) struct GPUBufferDescriptor {
|
||||
#[webidl(default = String::new())]
|
||||
pub label: String,
|
||||
|
||||
pub size: u64,
|
||||
#[options(enforce_range = true)]
|
||||
pub usage: u32,
|
||||
#[webidl(default = false)]
|
||||
pub mapped_at_creation: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error, deno_error::JsError)]
|
||||
pub enum BufferError {
|
||||
#[class(inherit)]
|
||||
#[class(generic)]
|
||||
#[error(transparent)]
|
||||
Resource(
|
||||
#[from]
|
||||
#[inherit]
|
||||
deno_core::error::ResourceError,
|
||||
),
|
||||
#[class(type)]
|
||||
#[error("usage is not valid")]
|
||||
InvalidUsage,
|
||||
Canceled(#[from] oneshot::Canceled),
|
||||
#[class("DOMExceptionOperationError")]
|
||||
#[error(transparent)]
|
||||
Access(wgpu_core::resource::BufferAccessError),
|
||||
Access(#[from] wgpu_core::resource::BufferAccessError),
|
||||
#[class("DOMExceptionOperationError")]
|
||||
#[error("{0}")]
|
||||
Operation(&'static str),
|
||||
#[class(inherit)]
|
||||
#[error(transparent)]
|
||||
Other(#[from] JsErrorBox),
|
||||
}
|
||||
|
||||
pub(crate) struct WebGpuBuffer(
|
||||
pub(crate) super::Instance,
|
||||
pub(crate) wgpu_core::id::BufferId,
|
||||
);
|
||||
impl Resource for WebGpuBuffer {
|
||||
fn name(&self) -> Cow<str> {
|
||||
"webGPUBuffer".into()
|
||||
}
|
||||
pub struct GPUBuffer {
|
||||
pub instance: Instance,
|
||||
pub error_handler: super::error::ErrorHandler,
|
||||
|
||||
fn close(self: Rc<Self>) {
|
||||
gfx_select!(self.1 => self.0.buffer_drop(self.1, true));
|
||||
pub id: wgpu_core::id::BufferId,
|
||||
pub device: wgpu_core::id::DeviceId,
|
||||
|
||||
pub label: String,
|
||||
|
||||
pub size: u64,
|
||||
pub usage: u32,
|
||||
|
||||
pub map_state: RefCell<&'static str>,
|
||||
pub map_mode: RefCell<Option<MapMode>>,
|
||||
|
||||
pub mapped_js_buffers: RefCell<Vec<v8::Global<v8::ArrayBuffer>>>,
|
||||
}
|
||||
|
||||
impl Drop for GPUBuffer {
|
||||
fn drop(&mut self) {
|
||||
self.instance.buffer_drop(self.id);
|
||||
}
|
||||
}
|
||||
|
||||
struct WebGpuBufferMapped(*mut u8, usize);
|
||||
impl Resource for WebGpuBufferMapped {
|
||||
fn name(&self) -> Cow<str> {
|
||||
"webGPUBufferMapped".into()
|
||||
}
|
||||
impl WebIdlInterfaceConverter for GPUBuffer {
|
||||
const NAME: &'static str = "GPUBuffer";
|
||||
}
|
||||
|
||||
impl GarbageCollected for GPUBuffer {}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_webgpu_create_buffer(
|
||||
state: &mut OpState,
|
||||
#[smi] device_rid: ResourceId,
|
||||
#[string] label: Cow<str>,
|
||||
#[number] size: u64,
|
||||
usage: u32,
|
||||
mapped_at_creation: bool,
|
||||
) -> Result<WebGpuResult, BufferError> {
|
||||
let instance = state.borrow::<super::Instance>();
|
||||
let device_resource = state
|
||||
.resource_table
|
||||
.get::<super::WebGpuDevice>(device_rid)?;
|
||||
let device = device_resource.1;
|
||||
|
||||
let descriptor = wgpu_core::resource::BufferDescriptor {
|
||||
label: Some(label),
|
||||
size,
|
||||
usage: wgpu_types::BufferUsages::from_bits(usage)
|
||||
.ok_or(BufferError::InvalidUsage)?,
|
||||
mapped_at_creation,
|
||||
};
|
||||
|
||||
gfx_put!(device => instance.device_create_buffer(
|
||||
device,
|
||||
&descriptor,
|
||||
None
|
||||
) => state, WebGpuBuffer)
|
||||
}
|
||||
|
||||
#[op2(async)]
|
||||
#[serde]
|
||||
pub async fn op_webgpu_buffer_get_map_async(
|
||||
state: Rc<RefCell<OpState>>,
|
||||
#[smi] buffer_rid: ResourceId,
|
||||
#[smi] device_rid: ResourceId,
|
||||
mode: u32,
|
||||
#[number] offset: u64,
|
||||
#[number] size: u64,
|
||||
) -> Result<WebGpuResult, BufferError> {
|
||||
let device;
|
||||
let done = Arc::new(Mutex::new(None));
|
||||
{
|
||||
let state_ = state.borrow();
|
||||
let instance = state_.borrow::<super::Instance>();
|
||||
let buffer_resource =
|
||||
state_.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
|
||||
let buffer = buffer_resource.1;
|
||||
let device_resource = state_
|
||||
.resource_table
|
||||
.get::<super::WebGpuDevice>(device_rid)?;
|
||||
device = device_resource.1;
|
||||
|
||||
let done_ = done.clone();
|
||||
let callback = Box::new(move |status| {
|
||||
*done_.lock().unwrap() = Some(status);
|
||||
});
|
||||
|
||||
let maybe_err = gfx_select!(buffer => instance.buffer_map_async(
|
||||
buffer,
|
||||
offset,
|
||||
Some(size),
|
||||
wgpu_core::resource::BufferMapOperation {
|
||||
host: match mode {
|
||||
1 => wgpu_core::device::HostMap::Read,
|
||||
2 => wgpu_core::device::HostMap::Write,
|
||||
_ => unreachable!(),
|
||||
},
|
||||
callback: Some(wgpu_core::resource::BufferMapCallback::from_rust(callback)),
|
||||
}
|
||||
))
|
||||
.err();
|
||||
|
||||
if maybe_err.is_some() {
|
||||
return Ok(WebGpuResult::maybe_err(maybe_err));
|
||||
}
|
||||
impl GPUBuffer {
|
||||
#[getter]
|
||||
#[string]
|
||||
fn label(&self) -> String {
|
||||
self.label.clone()
|
||||
}
|
||||
#[setter]
|
||||
#[string]
|
||||
fn label(&self, #[webidl] _label: String) {
|
||||
// TODO(@crowlKats): no-op, needs wpgu to implement changing the label
|
||||
}
|
||||
|
||||
loop {
|
||||
let result = done.lock().unwrap().take();
|
||||
match result {
|
||||
Some(Ok(())) => return Ok(WebGpuResult::empty()),
|
||||
Some(Err(e)) => return Err(BufferError::Access(e)),
|
||||
None => {
|
||||
#[getter]
|
||||
#[number]
|
||||
fn size(&self) -> u64 {
|
||||
self.size
|
||||
}
|
||||
#[getter]
|
||||
fn usage(&self) -> u32 {
|
||||
self.usage
|
||||
}
|
||||
|
||||
#[getter]
|
||||
#[string]
|
||||
fn map_state(&self) -> &'static str {
|
||||
*self.map_state.borrow()
|
||||
}
|
||||
|
||||
#[async_method]
|
||||
async fn map_async(
|
||||
&self,
|
||||
#[webidl(options(enforce_range = true))] mode: u32,
|
||||
#[webidl(default = 0)] offset: u64,
|
||||
#[webidl] size: Option<u64>,
|
||||
) -> Result<(), BufferError> {
|
||||
let read_mode = (mode & 0x0001) == 0x0001;
|
||||
let write_mode = (mode & 0x0002) == 0x0002;
|
||||
if (read_mode && write_mode) || (!read_mode && !write_mode) {
|
||||
return Err(BufferError::Operation(
|
||||
"exactly one of READ or WRITE map mode must be set",
|
||||
));
|
||||
}
|
||||
|
||||
let mode = if read_mode {
|
||||
MapMode::Read
|
||||
} else {
|
||||
assert!(write_mode);
|
||||
MapMode::Write
|
||||
};
|
||||
|
||||
{
|
||||
*self.map_state.borrow_mut() = "pending";
|
||||
}
|
||||
|
||||
let (sender, receiver) =
|
||||
oneshot::channel::<wgpu_core::resource::BufferAccessResult>();
|
||||
|
||||
{
|
||||
let callback = Box::new(move |status| {
|
||||
sender.send(status).unwrap();
|
||||
});
|
||||
|
||||
let err = self
|
||||
.instance
|
||||
.buffer_map_async(
|
||||
self.id,
|
||||
offset,
|
||||
size,
|
||||
wgpu_core::resource::BufferMapOperation {
|
||||
host: mode,
|
||||
callback: Some(callback),
|
||||
},
|
||||
)
|
||||
.err();
|
||||
|
||||
if err.is_some() {
|
||||
self.error_handler.push_error(err);
|
||||
return Err(BufferError::Operation("validation error occurred"));
|
||||
}
|
||||
}
|
||||
|
||||
let done = Rc::new(RefCell::new(false));
|
||||
let done_ = done.clone();
|
||||
let device_poll_fut = async move {
|
||||
while !*done.borrow() {
|
||||
{
|
||||
let state = state.borrow();
|
||||
let instance = state.borrow::<super::Instance>();
|
||||
gfx_select!(device => instance.device_poll(device, wgpu_types::Maintain::Poll)).unwrap();
|
||||
self
|
||||
.instance
|
||||
.device_poll(self.device, wgpu_types::Maintain::wait())
|
||||
.unwrap();
|
||||
}
|
||||
tokio::time::sleep(Duration::from_millis(10)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_webgpu_buffer_get_mapped_range(
|
||||
state: &mut OpState,
|
||||
#[smi] buffer_rid: ResourceId,
|
||||
#[number] offset: u64,
|
||||
#[number] size: Option<u64>,
|
||||
#[buffer] buf: &mut [u8],
|
||||
) -> Result<WebGpuResult, BufferError> {
|
||||
let instance = state.borrow::<super::Instance>();
|
||||
let buffer_resource = state.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
|
||||
let buffer = buffer_resource.1;
|
||||
|
||||
let (slice_pointer, range_size) =
|
||||
gfx_select!(buffer => instance.buffer_get_mapped_range(
|
||||
buffer,
|
||||
offset,
|
||||
size
|
||||
))
|
||||
.map_err(BufferError::Access)?;
|
||||
|
||||
// SAFETY: guarantee to be safe from wgpu
|
||||
let slice = unsafe {
|
||||
std::slice::from_raw_parts_mut(slice_pointer, range_size as usize)
|
||||
};
|
||||
buf.copy_from_slice(slice);
|
||||
|
||||
let rid = state
|
||||
.resource_table
|
||||
.add(WebGpuBufferMapped(slice_pointer, range_size as usize));
|
||||
|
||||
Ok(WebGpuResult::rid(rid))
|
||||
}
|
||||
|
||||
#[op2]
|
||||
#[serde]
|
||||
pub fn op_webgpu_buffer_unmap(
|
||||
state: &mut OpState,
|
||||
#[smi] buffer_rid: ResourceId,
|
||||
#[smi] mapped_rid: ResourceId,
|
||||
#[buffer] buf: Option<&[u8]>,
|
||||
) -> Result<WebGpuResult, BufferError> {
|
||||
let mapped_resource = state
|
||||
.resource_table
|
||||
.take::<WebGpuBufferMapped>(mapped_rid)?;
|
||||
let instance = state.borrow::<super::Instance>();
|
||||
let buffer_resource = state.resource_table.get::<WebGpuBuffer>(buffer_rid)?;
|
||||
let buffer = buffer_resource.1;
|
||||
|
||||
if let Some(buf) = buf {
|
||||
// SAFETY: guarantee to be safe from wgpu
|
||||
let slice = unsafe {
|
||||
std::slice::from_raw_parts_mut(mapped_resource.0, mapped_resource.1)
|
||||
Ok::<(), BufferError>(())
|
||||
};
|
||||
slice.copy_from_slice(buf);
|
||||
|
||||
let receiver_fut = async move {
|
||||
receiver.await??;
|
||||
let mut done = done_.borrow_mut();
|
||||
*done = true;
|
||||
Ok::<(), BufferError>(())
|
||||
};
|
||||
|
||||
tokio::try_join!(device_poll_fut, receiver_fut)?;
|
||||
|
||||
*self.map_state.borrow_mut() = "mapped";
|
||||
*self.map_mode.borrow_mut() = Some(mode);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
gfx_ok!(buffer => instance.buffer_unmap(buffer))
|
||||
fn get_mapped_range<'s>(
|
||||
&self,
|
||||
scope: &mut v8::HandleScope<'s>,
|
||||
#[webidl(default = 0)] offset: u64,
|
||||
#[webidl] size: Option<u64>,
|
||||
) -> Result<v8::Local<'s, v8::ArrayBuffer>, BufferError> {
|
||||
let (slice_pointer, range_size) = self
|
||||
.instance
|
||||
.buffer_get_mapped_range(self.id, offset, size)
|
||||
.map_err(BufferError::Access)?;
|
||||
|
||||
let mode = self.map_mode.borrow();
|
||||
let mode = mode.as_ref().unwrap();
|
||||
|
||||
let bs = if mode == &MapMode::Write {
|
||||
unsafe extern "C" fn noop_deleter_callback(
|
||||
_data: *mut std::ffi::c_void,
|
||||
_byte_length: usize,
|
||||
_deleter_data: *mut std::ffi::c_void,
|
||||
) {
|
||||
}
|
||||
|
||||
// SAFETY: creating a backing store from the pointer and length provided by wgpu
|
||||
unsafe {
|
||||
v8::ArrayBuffer::new_backing_store_from_ptr(
|
||||
slice_pointer.as_ptr() as _,
|
||||
range_size as usize,
|
||||
noop_deleter_callback,
|
||||
std::ptr::null_mut(),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// SAFETY: creating a vector from the pointer and length provided by wgpu
|
||||
let slice = unsafe {
|
||||
std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize)
|
||||
};
|
||||
v8::ArrayBuffer::new_backing_store_from_vec(slice.to_vec())
|
||||
};
|
||||
|
||||
let shared_bs = bs.make_shared();
|
||||
let ab = v8::ArrayBuffer::with_backing_store(scope, &shared_bs);
|
||||
|
||||
if mode == &MapMode::Write {
|
||||
self
|
||||
.mapped_js_buffers
|
||||
.borrow_mut()
|
||||
.push(v8::Global::new(scope, ab));
|
||||
}
|
||||
|
||||
Ok(ab)
|
||||
}
|
||||
|
||||
#[nofast]
|
||||
fn unmap(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> {
|
||||
for ab in self.mapped_js_buffers.replace(vec![]) {
|
||||
let ab = ab.open(scope);
|
||||
ab.detach(None);
|
||||
}
|
||||
|
||||
self
|
||||
.instance
|
||||
.buffer_unmap(self.id)
|
||||
.map_err(BufferError::Access)?;
|
||||
|
||||
*self.map_state.borrow_mut() = "unmapped";
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[fast]
|
||||
fn destroy(&self) -> Result<(), JsErrorBox> {
|
||||
self
|
||||
.instance
|
||||
.buffer_destroy(self.id)
|
||||
.map_err(|e| JsErrorBox::generic(e.to_string()))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue