deno_webgpu/
buffer.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3use std::cell::RefCell;
4use std::rc::Rc;
5use std::time::Duration;
6
7use deno_core::futures::channel::oneshot;
8use deno_core::op2;
9use deno_core::v8;
10use deno_core::webidl::WebIdlInterfaceConverter;
11use deno_core::GarbageCollected;
12use deno_core::WebIDL;
13use deno_error::JsErrorBox;
14use wgpu_core::device::HostMap as MapMode;
15
16use crate::Instance;
17
18#[derive(WebIDL)]
19#[webidl(dictionary)]
20pub(crate) struct GPUBufferDescriptor {
21  #[webidl(default = String::new())]
22  pub label: String,
23
24  pub size: u64,
25  #[options(enforce_range = true)]
26  pub usage: u32,
27  #[webidl(default = false)]
28  pub mapped_at_creation: bool,
29}
30
31#[derive(Debug, thiserror::Error, deno_error::JsError)]
32pub enum BufferError {
33  #[class(generic)]
34  #[error(transparent)]
35  Canceled(#[from] oneshot::Canceled),
36  #[class("DOMExceptionOperationError")]
37  #[error(transparent)]
38  Access(#[from] wgpu_core::resource::BufferAccessError),
39  #[class("DOMExceptionOperationError")]
40  #[error("{0}")]
41  Operation(&'static str),
42  #[class(inherit)]
43  #[error(transparent)]
44  Other(#[from] JsErrorBox),
45}
46
47pub struct GPUBuffer {
48  pub instance: Instance,
49  pub error_handler: super::error::ErrorHandler,
50
51  pub id: wgpu_core::id::BufferId,
52  pub device: wgpu_core::id::DeviceId,
53
54  pub label: String,
55
56  pub size: u64,
57  pub usage: u32,
58
59  pub map_state: RefCell<&'static str>,
60  pub map_mode: RefCell<Option<MapMode>>,
61
62  pub mapped_js_buffers: RefCell<Vec<v8::Global<v8::ArrayBuffer>>>,
63}
64
65impl Drop for GPUBuffer {
66  fn drop(&mut self) {
67    self.instance.buffer_drop(self.id);
68  }
69}
70
71impl WebIdlInterfaceConverter for GPUBuffer {
72  const NAME: &'static str = "GPUBuffer";
73}
74
75impl GarbageCollected for GPUBuffer {}
76
77#[op2]
78impl GPUBuffer {
79  #[getter]
80  #[string]
81  fn label(&self) -> String {
82    self.label.clone()
83  }
84  #[setter]
85  #[string]
86  fn label(&self, #[webidl] _label: String) {
87    // TODO(@crowlKats): no-op, needs wpgu to implement changing the label
88  }
89
90  #[getter]
91  #[number]
92  fn size(&self) -> u64 {
93    self.size
94  }
95  #[getter]
96  fn usage(&self) -> u32 {
97    self.usage
98  }
99
100  #[getter]
101  #[string]
102  fn map_state(&self) -> &'static str {
103    *self.map_state.borrow()
104  }
105
106  #[async_method]
107  async fn map_async(
108    &self,
109    #[webidl(options(enforce_range = true))] mode: u32,
110    #[webidl(default = 0)] offset: u64,
111    #[webidl] size: Option<u64>,
112  ) -> Result<(), BufferError> {
113    let read_mode = (mode & 0x0001) == 0x0001;
114    let write_mode = (mode & 0x0002) == 0x0002;
115    if (read_mode && write_mode) || (!read_mode && !write_mode) {
116      return Err(BufferError::Operation(
117        "exactly one of READ or WRITE map mode must be set",
118      ));
119    }
120
121    let mode = if read_mode {
122      MapMode::Read
123    } else {
124      assert!(write_mode);
125      MapMode::Write
126    };
127
128    {
129      *self.map_state.borrow_mut() = "pending";
130    }
131
132    let (sender, receiver) =
133      oneshot::channel::<wgpu_core::resource::BufferAccessResult>();
134
135    {
136      let callback = Box::new(move |status| {
137        sender.send(status).unwrap();
138      });
139
140      let err = self
141        .instance
142        .buffer_map_async(
143          self.id,
144          offset,
145          size,
146          wgpu_core::resource::BufferMapOperation {
147            host: mode,
148            callback: Some(callback),
149          },
150        )
151        .err();
152
153      if err.is_some() {
154        self.error_handler.push_error(err);
155        return Err(BufferError::Operation("validation error occurred"));
156      }
157    }
158
159    let done = Rc::new(RefCell::new(false));
160    let done_ = done.clone();
161    let device_poll_fut = async move {
162      while !*done.borrow() {
163        {
164          self
165            .instance
166            .device_poll(self.device, wgpu_types::Maintain::wait())
167            .unwrap();
168        }
169        tokio::time::sleep(Duration::from_millis(10)).await;
170      }
171      Ok::<(), BufferError>(())
172    };
173
174    let receiver_fut = async move {
175      receiver.await??;
176      let mut done = done_.borrow_mut();
177      *done = true;
178      Ok::<(), BufferError>(())
179    };
180
181    tokio::try_join!(device_poll_fut, receiver_fut)?;
182
183    *self.map_state.borrow_mut() = "mapped";
184    *self.map_mode.borrow_mut() = Some(mode);
185
186    Ok(())
187  }
188
189  fn get_mapped_range<'s>(
190    &self,
191    scope: &mut v8::HandleScope<'s>,
192    #[webidl(default = 0)] offset: u64,
193    #[webidl] size: Option<u64>,
194  ) -> Result<v8::Local<'s, v8::ArrayBuffer>, BufferError> {
195    let (slice_pointer, range_size) = self
196      .instance
197      .buffer_get_mapped_range(self.id, offset, size)
198      .map_err(BufferError::Access)?;
199
200    let mode = self.map_mode.borrow();
201    let mode = mode.as_ref().unwrap();
202
203    let bs = if mode == &MapMode::Write {
204      unsafe extern "C" fn noop_deleter_callback(
205        _data: *mut std::ffi::c_void,
206        _byte_length: usize,
207        _deleter_data: *mut std::ffi::c_void,
208      ) {
209      }
210
211      // SAFETY: creating a backing store from the pointer and length provided by wgpu
212      unsafe {
213        v8::ArrayBuffer::new_backing_store_from_ptr(
214          slice_pointer.as_ptr() as _,
215          range_size as usize,
216          noop_deleter_callback,
217          std::ptr::null_mut(),
218        )
219      }
220    } else {
221      // SAFETY: creating a vector from the pointer and length provided by wgpu
222      let slice = unsafe {
223        std::slice::from_raw_parts(slice_pointer.as_ptr(), range_size as usize)
224      };
225      v8::ArrayBuffer::new_backing_store_from_vec(slice.to_vec())
226    };
227
228    let shared_bs = bs.make_shared();
229    let ab = v8::ArrayBuffer::with_backing_store(scope, &shared_bs);
230
231    if mode == &MapMode::Write {
232      self
233        .mapped_js_buffers
234        .borrow_mut()
235        .push(v8::Global::new(scope, ab));
236    }
237
238    Ok(ab)
239  }
240
241  #[nofast]
242  fn unmap(&self, scope: &mut v8::HandleScope) -> Result<(), BufferError> {
243    for ab in self.mapped_js_buffers.replace(vec![]) {
244      let ab = ab.open(scope);
245      ab.detach(None);
246    }
247
248    self
249      .instance
250      .buffer_unmap(self.id)
251      .map_err(BufferError::Access)?;
252
253    *self.map_state.borrow_mut() = "unmapped";
254
255    Ok(())
256  }
257
258  #[fast]
259  fn destroy(&self) -> Result<(), JsErrorBox> {
260    self
261      .instance
262      .buffer_destroy(self.id)
263      .map_err(|e| JsErrorBox::generic(e.to_string()))
264  }
265}