Skip to content

Commit

Permalink
Implement mapReadAsync function of GPUBuffer
Browse files Browse the repository at this point in the history
Implemented the `mapReadAsync` and fixed the `unmap` functions of `GPUBuffer`.
Added `mapped` internal slot for tracking the ArrayBuffer/Promise.
Added more states to the `GPUBufferState` enum.
  • Loading branch information
Istvan Miklos committed Feb 25, 2020
1 parent 92f5b36 commit 2df4d9f
Show file tree
Hide file tree
Showing 5 changed files with 253 additions and 62 deletions.
4 changes: 4 additions & 0 deletions components/script/dom/bindings/codegen/Bindings.conf
Expand Up @@ -150,6 +150,10 @@ DOMInterfaces = {

'GPUAdapter': {
'inRealms': ['RequestDevice'],
},

'GPUBuffer': {
'inRealms': ['MapReadAsync'],
}

}
174 changes: 163 additions & 11 deletions components/script/dom/gpubuffer.rs
Expand Up @@ -6,25 +6,44 @@ use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::GPUBufferBinding::{
self, GPUBufferMethods, GPUBufferSize,
};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::reflector::{reflect_dom_object, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::gpu::{response_async, AsyncWGPUListener};
use crate::dom::promise::Promise;
use crate::realms::InRealm;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSObject};
use js::jsval::UndefinedValue;
use js::rust::jsapi_wrapped::{DetachArrayBuffer, IsPromiseObject, RejectPromise};
use js::rust::MutableHandle;
use js::typedarray::{ArrayBuffer, CreateWith};
use std::cell::Cell;
use webgpu::{WebGPU, WebGPUBuffer, WebGPUDevice, WebGPURequest};
use std::ptr;
use std::rc::Rc;
use webgpu::{
wgpu::resource::BufferUsage, WebGPU, WebGPUBuffer, WebGPUDevice, WebGPURequest, WebGPUResponse,
};

#[derive(MallocSizeOf)]
// https://gpuweb.github.io/gpuweb/#buffer-state
#[derive(Clone, MallocSizeOf)]
pub enum GPUBufferState {
Mapped,
MappedForReading,
MappedForWriting,
MappedPendingForReading,
MappedPendingForWriting,
Unmapped,
Destroyed,
}

#[dom_struct]
pub struct GPUBuffer {
reflector_: Reflector,
#[ignore_malloc_size_of = "channels are hard"]
#[ignore_malloc_size_of = "defined in webgpu"]
channel: WebGPU,
label: DomRefCell<Option<DOMString>>,
size: GPUBufferSize,
Expand All @@ -33,6 +52,8 @@ pub struct GPUBuffer {
buffer: WebGPUBuffer,
device: WebGPUDevice,
valid: Cell<bool>,
#[ignore_malloc_size_of = "defined in mozjs"]
mapping: RootedTraceableBox<Heap<*mut JSObject>>,
}

impl GPUBuffer {
Expand All @@ -44,6 +65,7 @@ impl GPUBuffer {
size: GPUBufferSize,
usage: u32,
valid: bool,
mapping: RootedTraceableBox<Heap<*mut JSObject>>,
) -> GPUBuffer {
Self {
reflector_: Reflector::new(),
Expand All @@ -55,6 +77,7 @@ impl GPUBuffer {
valid: Cell::new(valid),
device,
buffer,
mapping,
}
}

Expand All @@ -68,10 +91,11 @@ impl GPUBuffer {
size: GPUBufferSize,
usage: u32,
valid: bool,
mapping: RootedTraceableBox<Heap<*mut JSObject>>,
) -> DomRoot<GPUBuffer> {
reflect_dom_object(
Box::new(GPUBuffer::new_inherited(
channel, buffer, device, state, size, usage, valid,
channel, buffer, device, state, size, usage, valid, mapping,
)),
global,
GPUBufferBinding::Wrap,
Expand Down Expand Up @@ -104,19 +128,59 @@ impl Drop for GPUBuffer {
}

impl GPUBufferMethods for GPUBuffer {
#[allow(unsafe_code)]
/// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap
fn Unmap(&self) {
self.channel
.0
.send(WebGPURequest::UnmapBuffer(self.buffer))
.unwrap();
let cx = self.global().get_cx();
// Step 1
match *self.state.borrow() {
GPUBufferState::Unmapped | GPUBufferState::Destroyed => {
// TODO: Record validation error on the current scope
return;
},
GPUBufferState::MappedForWriting => {
// Step 3.1
match ArrayBuffer::from(self.mapping.get()) {
Ok(array_buffer) => {
self.channel
.0
.send(WebGPURequest::UnmapBuffer(
self.device.0,
self.id(),
array_buffer.to_vec(),
))
.unwrap();
// Step 3.2
unsafe {
DetachArrayBuffer(*cx, self.mapping.handle());
}
},
_ => {
// Step 2
unsafe {
if IsPromiseObject(self.mapping.handle()) {
let err = Error::Abort;
rooted!(in(*cx) let mut undef = UndefinedValue());
err.to_jsval(*cx, &self.global(), undef.handle_mut());
RejectPromise(*cx, self.mapping.handle(), undef.handle());
};
}
},
};
},
_ => {},
};
// Step 3.3
self.mapping.set(ptr::null_mut());
// Step 4
*self.state.borrow_mut() = GPUBufferState::Unmapped;
}

/// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy
fn Destroy(&self) {
match *self.state.borrow() {
GPUBufferState::Mapped => {
let state = self.state.borrow().clone();
match state {
GPUBufferState::MappedForReading | GPUBufferState::MappedForWriting => {
self.Unmap();
},
_ => {},
Expand All @@ -128,6 +192,72 @@ impl GPUBufferMethods for GPUBuffer {
*self.state.borrow_mut() = GPUBufferState::Destroyed;
}

#[allow(unsafe_code)]
/// https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapreadasync
fn MapReadAsync(&self, comp: InRealm) -> Rc<Promise> {
// Step 1 & 2
let promise = Promise::new_in_current_realm(&self.global(), comp);
match *self.state.borrow() {
GPUBufferState::Unmapped => {
match BufferUsage::from_bits(self.usage) {
Some(usage) => {
if !usage.contains(BufferUsage::MAP_READ) {
// TODO: Record validation error on the current scope
promise.reject_error(Error::Abort);
return promise;
};
},
None => {
promise.reject_error(Error::Abort);
return promise;
},
}
},
_ => {
promise.reject_error(Error::Abort);
return promise;
},
}
// Step 3
self.mapping.set(*promise.promise_obj());
// Step 4
*self.state.borrow_mut() = GPUBufferState::MappedPendingForReading;

// Step 5.1
if unsafe {
ArrayBuffer::create(
*self.global().get_cx(),
CreateWith::Length(self.size as u32),
MutableHandle::from_raw(self.mapping.handle_mut()),
)
}
.is_err()
{
promise.reject_error(Error::Operation);
return promise;
}

let sender = response_async(&promise, self);
if self
.channel
.0
.send(WebGPURequest::MapReadAsync(
sender,
self.buffer.0,
self.device.0,
self.usage,
self.size,
))
.is_err()
{
promise.reject_error(Error::Operation);
return promise;
}

// Step 6
promise
}

/// https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label
fn GetLabel(&self) -> Option<DOMString> {
self.label.borrow().clone()
Expand All @@ -138,3 +268,25 @@ impl GPUBufferMethods for GPUBuffer {
*self.label.borrow_mut() = value;
}
}

impl AsyncWGPUListener for GPUBuffer {
#[allow(unsafe_code)]
fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>) {
match response {
WebGPUResponse::MapReadAsync(bytes) => unsafe {
match ArrayBuffer::from(self.mapping.get()) {
Ok(mut array_buffer) => {
// Step 5.2
array_buffer.update(&bytes);
// Step 5.3
*self.state.borrow_mut() = GPUBufferState::MappedForReading;
// Step 5.4
promise.resolve_native(&array_buffer);
},
_ => promise.reject_error(Error::Operation),
};
},
_ => promise.reject_error(Error::Operation),
}
}
}
63 changes: 27 additions & 36 deletions components/script/dom/gpudevice.rs
Expand Up @@ -46,7 +46,7 @@ use webgpu::wgpu::binding_model::{
ShaderStage,
};
use webgpu::wgpu::resource::{BufferDescriptor, BufferUsage};
use webgpu::{WebGPU, WebGPUBuffer, WebGPUDevice, WebGPUQueue, WebGPURequest};
use webgpu::{WebGPU, WebGPUDevice, WebGPUQueue, WebGPURequest};

#[dom_struct]
pub struct GPUDevice {
Expand Down Expand Up @@ -106,38 +106,6 @@ impl GPUDevice {
}

impl GPUDevice {
unsafe fn resolve_create_buffer_mapped(
&self,
cx: SafeJSContext,
gpu_buffer: WebGPUBuffer,
array_buffer: Vec<u8>,
descriptor: BufferDescriptor,
valid: bool,
) -> Vec<JSVal> {
rooted!(in(*cx) let mut js_array_buffer = ptr::null_mut::<JSObject>());
let mut out = Vec::new();
assert!(ArrayBuffer::create(
*cx,
CreateWith::Slice(array_buffer.as_slice()),
js_array_buffer.handle_mut(),
)
.is_ok());

let buff = GPUBuffer::new(
&self.global(),
self.channel.clone(),
gpu_buffer,
self.device,
GPUBufferState::Mapped,
descriptor.size,
descriptor.usage.bits(),
valid,
);
out.push(ObjectValue(buff.reflector().get_jsobject().get()));
out.push(ObjectValue(js_array_buffer.get()));
out
}

fn validate_buffer_descriptor(
&self,
descriptor: &GPUBufferDescriptor,
Expand Down Expand Up @@ -223,6 +191,7 @@ impl GPUDeviceMethods for GPUDevice {
descriptor.size,
descriptor.usage,
valid,
RootedTraceableBox::new(Heap::default()),
)
}

Expand All @@ -245,11 +214,33 @@ impl GPUDeviceMethods for GPUDevice {
))
.expect("Failed to create WebGPU buffer");

let (buffer, array_buffer) = receiver.recv().unwrap();

rooted!(in(*cx) let mut js_array_buffer = ptr::null_mut::<JSObject>());
unsafe {
self.resolve_create_buffer_mapped(cx, buffer, array_buffer, wgpu_descriptor, valid)
assert!(ArrayBuffer::create(
*cx,
CreateWith::Length(descriptor.size as u32),
js_array_buffer.handle_mut(),
)
.is_ok());
}

let buffer = receiver.recv().unwrap();
let buff = GPUBuffer::new(
&self.global(),
self.channel.clone(),
buffer,
self.device,
GPUBufferState::MappedForWriting,
wgpu_descriptor.size,
wgpu_descriptor.usage.bits(),
valid,
RootedTraceableBox::from_box(Heap::boxed(js_array_buffer.get())),
);

vec![
ObjectValue(buff.reflector().get_jsobject().get()),
ObjectValue(js_array_buffer.get()),
]
}

/// https://gpuweb.github.io/gpuweb/#GPUDevice-createBindGroupLayout
Expand Down
2 changes: 1 addition & 1 deletion components/script/dom/webidls/GPUBuffer.webidl
Expand Up @@ -5,7 +5,7 @@
// https://gpuweb.github.io/gpuweb/#gpubuffer
[Exposed=(Window, DedicatedWorker), Serializable, Pref="dom.webgpu.enabled"]
interface GPUBuffer {
// Promise<ArrayBuffer> mapReadAsync();
Promise<ArrayBuffer> mapReadAsync();
// Promise<ArrayBuffer> mapWriteAsync();
void unmap();

Expand Down

0 comments on commit 2df4d9f

Please sign in to comment.