diff options
author | atbrakhi <atbrakhi@igalia.com> | 2024-11-28 15:24:15 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-11-28 14:24:15 +0000 |
commit | d2d3407501b83d03db522b5dde5e159073fd9e4b (patch) | |
tree | 299be7d04ca2470f1c6c8741e46784a2c7e85dd7 /components/script/dom/webgpu | |
parent | a37ccc3e64c92e8ba10a3cdc48ebd7f031bb7298 (diff) | |
download | servo-d2d3407501b83d03db522b5dde5e159073fd9e4b.tar.gz servo-d2d3407501b83d03db522b5dde5e159073fd9e4b.zip |
Move script gpu files into webgpu folder (#34415)
Signed-off-by: atbrakhi <atbrakhi@igalia.com>
Diffstat (limited to 'components/script/dom/webgpu')
41 files changed, 6725 insertions, 0 deletions
diff --git a/components/script/dom/webgpu/gpu.rs b/components/script/dom/webgpu/gpu.rs new file mode 100644 index 00000000000..f955db7c350 --- /dev/null +++ b/components/script/dom/webgpu/gpu.rs @@ -0,0 +1,180 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::rc::Rc; + +use dom_struct::dom_struct; +use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::router::ROUTER; +use js::jsapi::Heap; +use script_traits::ScriptMsg; +use webgpu::wgt::PowerPreference; +use webgpu::{wgc, WebGPUResponse}; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUMethods, GPUPowerPreference, GPURequestAdapterOptions, GPUTextureFormat, +}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuadapter::GPUAdapter; +use crate::dom::promise::Promise; +use crate::realms::InRealm; +use crate::script_runtime::CanGc; +use crate::task_source::{TaskSource, TaskSourceName}; + +#[dom_struct] +#[allow(clippy::upper_case_acronyms)] +pub struct GPU { + reflector_: Reflector, +} + +impl GPU { + pub fn new_inherited() -> GPU { + GPU { + reflector_: Reflector::new(), + } + } + + pub fn new(global: &GlobalScope) -> DomRoot<GPU> { + reflect_dom_object(Box::new(GPU::new_inherited()), global) + } +} + +pub trait AsyncWGPUListener { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, can_gc: CanGc); +} + +struct WGPUResponse<T: AsyncWGPUListener + DomObject> { + trusted: TrustedPromise, + receiver: Trusted<T>, +} + +impl<T: AsyncWGPUListener + DomObject> WGPUResponse<T> { + #[allow(crown::unrooted_must_root)] + fn response(self, response: WebGPUResponse, can_gc: CanGc) { + let promise = self.trusted.root(); + self.receiver + .root() + .handle_response(response, &promise, can_gc); + } +} + +pub fn response_async<T: AsyncWGPUListener + DomObject + 'static>( + promise: &Rc<Promise>, + receiver: &T, +) -> IpcSender<WebGPUResponse> { + let (action_sender, action_receiver) = ipc::channel().unwrap(); + let task_source = receiver.global().dom_manipulation_task_source(); + let canceller = receiver + .global() + .task_canceller(TaskSourceName::DOMManipulation); + let mut trusted: Option<TrustedPromise> = Some(TrustedPromise::new(promise.clone())); + let trusted_receiver = Trusted::new(receiver); + ROUTER.add_typed_route( + action_receiver, + Box::new(move |message| { + let trusted = if let Some(trusted) = trusted.take() { + trusted + } else { + error!("WebGPU callback called twice!"); + return; + }; + + let context = WGPUResponse { + trusted, + receiver: trusted_receiver.clone(), + }; + let result = task_source.queue_with_canceller( + task!(process_webgpu_task: move|| { + context.response(message.unwrap(), CanGc::note()); + }), + &canceller, + ); + if let Err(err) = result { + error!("Failed to queue GPU listener-task: {:?}", err); + } + }), + ); + action_sender +} + +impl GPUMethods<crate::DomTypeHolder> for GPU { + // https://gpuweb.github.io/gpuweb/#dom-gpu-requestadapter + fn RequestAdapter( + &self, + options: &GPURequestAdapterOptions, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + let global = &self.global(); + let promise = Promise::new_in_current_realm(comp, can_gc); + let sender = response_async(&promise, self); + let power_preference = match options.powerPreference { + Some(GPUPowerPreference::Low_power) => PowerPreference::LowPower, + Some(GPUPowerPreference::High_performance) => PowerPreference::HighPerformance, + None => PowerPreference::default(), + }; + let ids = global.wgpu_id_hub().create_adapter_id(); + + let script_to_constellation_chan = global.script_to_constellation_chan(); + if script_to_constellation_chan + .send(ScriptMsg::RequestAdapter( + sender, + wgc::instance::RequestAdapterOptions { + power_preference, + compatible_surface: None, + force_fallback_adapter: options.forceFallbackAdapter, + }, + ids, + )) + .is_err() + { + promise.reject_error(Error::Operation); + } + promise + } + + // https://gpuweb.github.io/gpuweb/#dom-gpu-getpreferredcanvasformat + fn GetPreferredCanvasFormat(&self) -> GPUTextureFormat { + // TODO: real implementation + GPUTextureFormat::Rgba8unorm + } +} + +impl AsyncWGPUListener for GPU { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, can_gc: CanGc) { + match response { + WebGPUResponse::Adapter(Ok(adapter)) => { + let adapter = GPUAdapter::new( + &self.global(), + adapter.channel, + DOMString::from(format!( + "{} ({:?})", + adapter.adapter_info.name, adapter.adapter_id.0 + )), + Heap::default(), + adapter.features, + adapter.limits, + adapter.adapter_info, + adapter.adapter_id, + can_gc, + ); + promise.resolve_native(&adapter); + }, + WebGPUResponse::Adapter(Err(e)) => { + warn!("Could not get GPUAdapter ({:?})", e); + promise.resolve_native(&None::<GPUAdapter>); + }, + WebGPUResponse::None => { + warn!("Couldn't get a response, because WebGPU is disabled"); + promise.resolve_native(&None::<GPUAdapter>); + }, + _ => unreachable!("GPU received wrong WebGPUResponse"), + } + } +} diff --git a/components/script/dom/webgpu/gpuadapter.rs b/components/script/dom/webgpu/gpuadapter.rs new file mode 100644 index 00000000000..da81c8d7d4c --- /dev/null +++ b/components/script/dom/webgpu/gpuadapter.rs @@ -0,0 +1,254 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSObject}; +use webgpu::wgc::instance::RequestDeviceError; +use webgpu::wgt::MemoryHints; +use webgpu::{wgt, WebGPU, WebGPUAdapter, WebGPURequest, WebGPUResponse}; + +use super::gpusupportedfeatures::GPUSupportedFeatures; +use super::gpusupportedlimits::set_limit; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUAdapterMethods, GPUDeviceDescriptor, GPUDeviceLostReason, +}; +use crate::dom::bindings::error::Error; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::gpusupportedfeatures::gpu_to_wgt_feature; +use crate::dom::promise::Promise; +use crate::dom::types::{GPUAdapterInfo, GPUSupportedLimits}; +use crate::dom::webgpu::gpu::{response_async, AsyncWGPUListener}; +use crate::realms::InRealm; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUAdapter { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + name: DOMString, + #[ignore_malloc_size_of = "mozjs"] + extensions: Heap<*mut JSObject>, + features: Dom<GPUSupportedFeatures>, + limits: Dom<GPUSupportedLimits>, + info: Dom<GPUAdapterInfo>, + #[no_trace] + adapter: WebGPUAdapter, +} + +impl GPUAdapter { + fn new_inherited( + channel: WebGPU, + name: DOMString, + extensions: Heap<*mut JSObject>, + features: &GPUSupportedFeatures, + limits: &GPUSupportedLimits, + info: &GPUAdapterInfo, + adapter: WebGPUAdapter, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + name, + extensions, + features: Dom::from_ref(features), + limits: Dom::from_ref(limits), + info: Dom::from_ref(info), + adapter, + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + global: &GlobalScope, + channel: WebGPU, + name: DOMString, + extensions: Heap<*mut JSObject>, + features: wgt::Features, + limits: wgt::Limits, + info: wgt::AdapterInfo, + adapter: WebGPUAdapter, + can_gc: CanGc, + ) -> DomRoot<Self> { + let features = GPUSupportedFeatures::Constructor(global, None, features, can_gc).unwrap(); + let limits = GPUSupportedLimits::new(global, limits); + let info = GPUAdapterInfo::new(global, info); + reflect_dom_object( + Box::new(GPUAdapter::new_inherited( + channel, name, extensions, &features, &limits, &info, adapter, + )), + global, + ) + } +} + +impl Drop for GPUAdapter { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropAdapter(self.adapter.0)) + { + warn!( + "Failed to send WebGPURequest::DropAdapter({:?}) ({})", + self.adapter.0, e + ); + }; + } +} + +impl GPUAdapterMethods<crate::DomTypeHolder> for GPUAdapter { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestdevice> + fn RequestDevice( + &self, + descriptor: &GPUDeviceDescriptor, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + // Step 2 + let promise = Promise::new_in_current_realm(comp, can_gc); + let sender = response_async(&promise, self); + let mut required_features = wgt::Features::empty(); + for &ext in descriptor.requiredFeatures.iter() { + if let Some(feature) = gpu_to_wgt_feature(ext) { + required_features.insert(feature); + } else { + promise.reject_error(Error::Type(format!( + "{} is not supported feature", + ext.as_str() + ))); + return promise; + } + } + + let mut required_limits = wgt::Limits::default(); + if let Some(limits) = &descriptor.requiredLimits { + for (limit, value) in (*limits).iter() { + if !set_limit(&mut required_limits, limit.as_ref(), *value) { + warn!("Unknown GPUDevice limit: {limit}"); + promise.reject_error(Error::Operation); + return promise; + } + } + } + + let desc = wgt::DeviceDescriptor { + required_features, + required_limits, + label: Some(descriptor.parent.label.to_string()), + memory_hints: MemoryHints::MemoryUsage, + }; + let device_id = self.global().wgpu_id_hub().create_device_id(); + let queue_id = self.global().wgpu_id_hub().create_queue_id(); + let pipeline_id = self.global().pipeline_id(); + if self + .channel + .0 + .send(WebGPURequest::RequestDevice { + sender, + adapter_id: self.adapter, + descriptor: desc, + device_id, + queue_id, + pipeline_id, + }) + .is_err() + { + promise.reject_error(Error::Operation); + } + // Step 5 + promise + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-isfallbackadapter> + fn IsFallbackAdapter(&self) -> bool { + //TODO + false + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-requestadapterinfo> + fn RequestAdapterInfo( + &self, + unmask_hints: Vec<DOMString>, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + // XXX: Adapter info should be generated here ... + // Step 1 + let promise = Promise::new_in_current_realm(comp, can_gc); + // Step 4 + if !unmask_hints.is_empty() { + todo!("unmaskHints on RequestAdapterInfo"); + } + promise.resolve_native(&*self.info); + // Step 5 + promise + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-features> + fn Features(&self) -> DomRoot<GPUSupportedFeatures> { + DomRoot::from_ref(&self.features) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapter-limits> + fn Limits(&self) -> DomRoot<GPUSupportedLimits> { + DomRoot::from_ref(&self.limits) + } +} + +impl AsyncWGPUListener for GPUAdapter { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, can_gc: CanGc) { + match response { + WebGPUResponse::Device((device_id, queue_id, Ok(descriptor))) => { + let device = GPUDevice::new( + &self.global(), + self.channel.clone(), + self, + Heap::default(), + descriptor.required_features, + descriptor.required_limits, + device_id, + queue_id, + descriptor.label.unwrap_or_default(), + can_gc, + ); + self.global().add_gpu_device(&device); + promise.resolve_native(&device); + }, + WebGPUResponse::Device((_, _, Err(RequestDeviceError::UnsupportedFeature(f)))) => { + promise.reject_error(Error::Type( + RequestDeviceError::UnsupportedFeature(f).to_string(), + )) + }, + WebGPUResponse::Device((_, _, Err(RequestDeviceError::LimitsExceeded(_)))) => { + promise.reject_error(Error::Operation) + }, + WebGPUResponse::Device((device_id, queue_id, Err(e))) => { + let device = GPUDevice::new( + &self.global(), + self.channel.clone(), + self, + Heap::default(), + wgt::Features::default(), + wgt::Limits::default(), + device_id, + queue_id, + String::new(), + can_gc, + ); + device.lose(GPUDeviceLostReason::Unknown, e.to_string()); + promise.resolve_native(&device); + }, + WebGPUResponse::None => unreachable!("Failed to get a response for RequestDevice"), + _ => unreachable!("GPUAdapter received wrong WebGPUResponse"), + } + } +} diff --git a/components/script/dom/webgpu/gpuadapterinfo.rs b/components/script/dom/webgpu/gpuadapterinfo.rs new file mode 100644 index 00000000000..0cda8bed2d7 --- /dev/null +++ b/components/script/dom/webgpu/gpuadapterinfo.rs @@ -0,0 +1,56 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::wgt::AdapterInfo; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUAdapterInfoMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; +use crate::test::DOMString; + +#[dom_struct] +pub struct GPUAdapterInfo { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in wgpu-types"] + #[no_trace] + info: AdapterInfo, +} + +impl GPUAdapterInfo { + fn new_inherited(info: AdapterInfo) -> Self { + Self { + reflector_: Reflector::new(), + info, + } + } + + pub fn new(global: &GlobalScope, info: AdapterInfo) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited(info)), global) + } +} + +// TODO: wgpu does not expose right fields right now +impl GPUAdapterInfoMethods<crate::DomTypeHolder> for GPUAdapterInfo { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapterinfo-vendor> + fn Vendor(&self) -> DOMString { + DOMString::new() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapterinfo-architecture> + fn Architecture(&self) -> DOMString { + DOMString::new() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapterinfo-device> + fn Device(&self) -> DOMString { + DOMString::new() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuadapterinfo-description> + fn Description(&self) -> DOMString { + DOMString::from_string(self.info.driver_info.clone()) + } +} diff --git a/components/script/dom/webgpu/gpubindgroup.rs b/components/script/dom/webgpu/gpubindgroup.rs new file mode 100644 index 00000000000..7992e3825d0 --- /dev/null +++ b/components/script/dom/webgpu/gpubindgroup.rs @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; + +use dom_struct::dom_struct; +use webgpu::wgc::binding_model::BindGroupDescriptor; +use webgpu::{WebGPU, WebGPUBindGroup, WebGPUDevice, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBindGroupDescriptor, GPUBindGroupMethods, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpudevice::GPUDevice; + +#[dom_struct] +pub struct GPUBindGroup { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + bind_group: WebGPUBindGroup, + #[no_trace] + device: WebGPUDevice, + layout: Dom<GPUBindGroupLayout>, +} + +impl GPUBindGroup { + fn new_inherited( + channel: WebGPU, + bind_group: WebGPUBindGroup, + device: WebGPUDevice, + layout: &GPUBindGroupLayout, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + bind_group, + device, + layout: Dom::from_ref(layout), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + bind_group: WebGPUBindGroup, + device: WebGPUDevice, + layout: &GPUBindGroupLayout, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBindGroup::new_inherited( + channel, bind_group, device, layout, label, + )), + global, + ) + } +} + +impl GPUBindGroup { + pub fn id(&self) -> &WebGPUBindGroup { + &self.bind_group + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbindgroup> + pub fn create( + device: &GPUDevice, + descriptor: &GPUBindGroupDescriptor, + ) -> DomRoot<GPUBindGroup> { + let entries = descriptor + .entries + .iter() + .map(|bind| bind.into()) + .collect::<Vec<_>>(); + + let desc = BindGroupDescriptor { + label: (&descriptor.parent).into(), + layout: descriptor.layout.id().0, + entries: Cow::Owned(entries), + }; + + let bind_group_id = device.global().wgpu_id_hub().create_bind_group_id(); + device + .channel() + .0 + .send(WebGPURequest::CreateBindGroup { + device_id: device.id().0, + bind_group_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU BindGroup"); + + let bind_group = WebGPUBindGroup(bind_group_id); + + GPUBindGroup::new( + &device.global(), + device.channel().clone(), + bind_group, + device.id(), + &descriptor.layout, + descriptor.parent.label.clone(), + ) + } +} + +impl Drop for GPUBindGroup { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropBindGroup(self.bind_group.0)) + { + warn!( + "Failed to send WebGPURequest::DropBindGroup({:?}) ({})", + self.bind_group.0, e + ); + }; + } +} + +impl GPUBindGroupMethods<crate::DomTypeHolder> for GPUBindGroup { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/webgpu/gpubindgrouplayout.rs b/components/script/dom/webgpu/gpubindgrouplayout.rs new file mode 100644 index 00000000000..c09c36ade58 --- /dev/null +++ b/components/script/dom/webgpu/gpubindgrouplayout.rs @@ -0,0 +1,139 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; + +use dom_struct::dom_struct; +use webgpu::wgc::binding_model::BindGroupLayoutDescriptor; +use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBindGroupLayoutDescriptor, GPUBindGroupLayoutMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuconvert::convert_bind_group_layout_entry; +use crate::dom::gpudevice::GPUDevice; + +#[dom_struct] +pub struct GPUBindGroupLayout { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + bind_group_layout: WebGPUBindGroupLayout, +} + +impl GPUBindGroupLayout { + fn new_inherited( + channel: WebGPU, + bind_group_layout: WebGPUBindGroupLayout, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + bind_group_layout, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + bind_group_layout: WebGPUBindGroupLayout, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBindGroupLayout::new_inherited( + channel, + bind_group_layout, + label, + )), + global, + ) + } +} + +impl GPUBindGroupLayout { + pub fn id(&self) -> WebGPUBindGroupLayout { + self.bind_group_layout + } + + /// <https://gpuweb.github.io/gpuweb/#GPUDevice-createBindGroupLayout> + pub fn create( + device: &GPUDevice, + descriptor: &GPUBindGroupLayoutDescriptor, + ) -> Fallible<DomRoot<GPUBindGroupLayout>> { + let entries = descriptor + .entries + .iter() + .map(|bgle| convert_bind_group_layout_entry(bgle, device)) + .collect::<Fallible<Result<Vec<_>, _>>>()?; + + let desc = match entries { + Ok(entries) => Some(BindGroupLayoutDescriptor { + label: (&descriptor.parent).into(), + entries: Cow::Owned(entries), + }), + Err(error) => { + device.dispatch_error(error); + None + }, + }; + + let bind_group_layout_id = device.global().wgpu_id_hub().create_bind_group_layout_id(); + device + .channel() + .0 + .send(WebGPURequest::CreateBindGroupLayout { + device_id: device.id().0, + bind_group_layout_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU BindGroupLayout"); + + let bgl = WebGPUBindGroupLayout(bind_group_layout_id); + + Ok(GPUBindGroupLayout::new( + &device.global(), + device.channel().clone(), + bgl, + descriptor.parent.label.clone(), + )) + } +} + +impl Drop for GPUBindGroupLayout { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropBindGroupLayout(self.bind_group_layout.0)) + { + warn!( + "Failed to send WebGPURequest::DropBindGroupLayout({:?}) ({})", + self.bind_group_layout.0, e + ); + }; + } +} + +impl GPUBindGroupLayoutMethods<crate::DomTypeHolder> for GPUBindGroupLayout { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/webgpu/gpubuffer.rs b/components/script/dom/webgpu/gpubuffer.rs new file mode 100644 index 00000000000..cab38d067c2 --- /dev/null +++ b/components/script/dom/webgpu/gpubuffer.rs @@ -0,0 +1,425 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::ops::Range; +use std::rc::Rc; +use std::string::String; + +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; +use js::typedarray::ArrayBuffer; +use webgpu::wgc::device::HostMap; +use webgpu::{wgt, Mapping, WebGPU, WebGPUBuffer, WebGPURequest, WebGPUResponse}; + +use crate::dom::bindings::buffer_source::DataBlock; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBufferDescriptor, GPUBufferMapState, GPUBufferMethods, GPUFlagsConstant, + GPUMapModeConstants, GPUMapModeFlags, GPUSize64, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::promise::Promise; +use crate::dom::webgpu::gpu::{response_async, AsyncWGPUListener}; +use crate::realms::InRealm; +use crate::script_runtime::{CanGc, JSContext}; + +#[derive(JSTraceable, MallocSizeOf)] +pub struct ActiveBufferMapping { + // TODO(sagudev): Use IpcSharedMemory when https://github.com/servo/ipc-channel/pull/356 lands + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-data> + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-views> + pub data: DataBlock, + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-mode> + mode: GPUMapModeFlags, + /// <https://gpuweb.github.io/gpuweb/#active-buffer-mapping-range> + range: Range<u64>, +} + +impl ActiveBufferMapping { + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-initialize-an-active-buffer-mapping> + pub fn new(mode: GPUMapModeFlags, range: Range<u64>) -> Fallible<Self> { + // Step 1 + let size = range.end - range.start; + // Step 2 + if size > (1 << 53) - 1 { + return Err(Error::Range("Over MAX_SAFE_INTEGER".to_string())); + } + let size: usize = size + .try_into() + .map_err(|_| Error::Range("Over usize".to_string()))?; + Ok(Self { + data: DataBlock::new_zeroed(size), + mode, + range, + }) + } +} + +#[dom_struct] +pub struct GPUBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + buffer: WebGPUBuffer, + device: Dom<GPUDevice>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size> + size: GPUSize64, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage> + usage: GPUFlagsConstant, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-pending_map-slot> + #[ignore_malloc_size_of = "promises are hard"] + pending_map: DomRefCell<Option<Rc<Promise>>>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapping-slot> + mapping: DomRefCell<Option<ActiveBufferMapping>>, +} + +impl GPUBuffer { + fn new_inherited( + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + size: GPUSize64, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + device: Dom::from_ref(device), + buffer, + pending_map: DomRefCell::new(None), + size, + usage, + mapping: DomRefCell::new(mapping), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + global: &GlobalScope, + channel: WebGPU, + buffer: WebGPUBuffer, + device: &GPUDevice, + size: GPUSize64, + usage: GPUFlagsConstant, + mapping: Option<ActiveBufferMapping>, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUBuffer::new_inherited( + channel, buffer, device, size, usage, mapping, label, + )), + global, + ) + } +} + +impl GPUBuffer { + pub fn id(&self) -> WebGPUBuffer { + self.buffer + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer> + pub fn create( + device: &GPUDevice, + descriptor: &GPUBufferDescriptor, + ) -> Fallible<DomRoot<GPUBuffer>> { + let desc = wgt::BufferDescriptor { + label: (&descriptor.parent).into(), + size: descriptor.size as wgt::BufferAddress, + usage: wgt::BufferUsages::from_bits_retain(descriptor.usage), + mapped_at_creation: descriptor.mappedAtCreation, + }; + let id = device.global().wgpu_id_hub().create_buffer_id(); + + device + .channel() + .0 + .send(WebGPURequest::CreateBuffer { + device_id: device.id().0, + buffer_id: id, + descriptor: desc, + }) + .expect("Failed to create WebGPU buffer"); + + let buffer = WebGPUBuffer(id); + let mapping = if descriptor.mappedAtCreation { + Some(ActiveBufferMapping::new( + GPUMapModeConstants::WRITE, + 0..descriptor.size, + )?) + } else { + None + }; + + Ok(GPUBuffer::new( + &device.global(), + device.channel().clone(), + buffer, + device, + descriptor.size, + descriptor.usage, + mapping, + descriptor.parent.label.clone(), + )) + } +} + +impl Drop for GPUBuffer { + fn drop(&mut self) { + self.Destroy() + } +} + +impl GPUBufferMethods<crate::DomTypeHolder> for GPUBuffer { + #[allow(unsafe_code)] + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-unmap> + fn Unmap(&self) { + // Step 1 + if let Some(promise) = self.pending_map.borrow_mut().take() { + promise.reject_error(Error::Abort); + } + // Step 2 + let mut mapping = self.mapping.borrow_mut().take(); + let mapping = if let Some(mapping) = mapping.as_mut() { + mapping + } else { + return; + }; + + // Step 3 + mapping.data.clear_views(); + // Step 5&7 + if let Err(e) = self.channel.0.send(WebGPURequest::UnmapBuffer { + buffer_id: self.id().0, + mapping: if mapping.mode >= GPUMapModeConstants::WRITE { + Some(Mapping { + data: IpcSharedMemory::from_bytes(mapping.data.data()), + range: mapping.range.clone(), + mode: HostMap::Write, + }) + } else { + None + }, + }) { + warn!("Failed to send Buffer unmap ({:?}) ({})", self.buffer.0, e); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-destroy> + fn Destroy(&self) { + // Step 1 + self.Unmap(); + // Step 2 + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DestroyBuffer(self.buffer.0)) + { + warn!( + "Failed to send WebGPURequest::DestroyBuffer({:?}) ({})", + self.buffer.0, e + ); + }; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapasync> + fn MapAsync( + &self, + mode: u32, + offset: GPUSize64, + size: Option<GPUSize64>, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(comp, can_gc); + // Step 2 + if self.pending_map.borrow().is_some() { + promise.reject_error(Error::Operation); + return promise; + } + // Step 4 + *self.pending_map.borrow_mut() = Some(promise.clone()); + // Step 5 + let host_map = match mode { + GPUMapModeConstants::READ => HostMap::Read, + GPUMapModeConstants::WRITE => HostMap::Write, + _ => { + self.device + .dispatch_error(webgpu::Error::Validation(String::from( + "Invalid MapModeFlags", + ))); + self.map_failure(&promise); + return promise; + }, + }; + + let sender = response_async(&promise, self); + if let Err(e) = self.channel.0.send(WebGPURequest::BufferMapAsync { + sender, + buffer_id: self.buffer.0, + device_id: self.device.id().0, + host_map, + offset, + size, + }) { + warn!( + "Failed to send BufferMapAsync ({:?}) ({})", + self.buffer.0, e + ); + self.map_failure(&promise); + return promise; + } + // Step 6 + promise + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-getmappedrange> + #[allow(unsafe_code)] + fn GetMappedRange( + &self, + _cx: JSContext, + offset: GPUSize64, + size: Option<GPUSize64>, + ) -> Fallible<ArrayBuffer> { + let range_size = if let Some(s) = size { + s + } else { + self.size.saturating_sub(offset) + }; + // Step 2: validation + let mut mapping = self.mapping.borrow_mut(); + let mapping = mapping.as_mut().ok_or(Error::Operation)?; + + let valid = offset % wgt::MAP_ALIGNMENT == 0 && + range_size % wgt::COPY_BUFFER_ALIGNMENT == 0 && + offset >= mapping.range.start && + offset + range_size <= mapping.range.end; + if !valid { + return Err(Error::Operation); + } + + // Step 4 + // only mapping.range is mapped with mapping.range.start at 0 + // so we need to rebase range to mapped.range + let rebased_offset = (offset - mapping.range.start) as usize; + mapping + .data + .view(rebased_offset..rebased_offset + range_size as usize) + .map(|view| view.array_buffer()) + .map_err(|()| Error::Operation) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-size> + fn Size(&self) -> GPUSize64 { + self.size + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-usage> + fn Usage(&self) -> GPUFlagsConstant { + self.usage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpubuffer-mapstate> + fn MapState(&self) -> GPUBufferMapState { + // Step 1&2&3 + if self.mapping.borrow().is_some() { + GPUBufferMapState::Mapped + } else if self.pending_map.borrow().is_some() { + GPUBufferMapState::Pending + } else { + GPUBufferMapState::Unmapped + } + } +} + +impl GPUBuffer { + fn map_failure(&self, p: &Rc<Promise>) { + let mut pending_map = self.pending_map.borrow_mut(); + // Step 1 + if pending_map.as_ref() != Some(p) { + assert!(p.is_rejected()); + return; + } + // Step 2 + assert!(p.is_pending()); + // Step 3 + pending_map.take(); + // Step 4 + if self.device.is_lost() { + p.reject_error(Error::Abort); + } else { + p.reject_error(Error::Operation); + } + } + + fn map_success(&self, p: &Rc<Promise>, wgpu_mapping: Mapping) { + let mut pending_map = self.pending_map.borrow_mut(); + + // Step 1 + if pending_map.as_ref() != Some(p) { + assert!(p.is_rejected()); + return; + } + + // Step 2 + assert!(p.is_pending()); + + // Step 4 + let mapping = ActiveBufferMapping::new( + match wgpu_mapping.mode { + HostMap::Read => GPUMapModeConstants::READ, + HostMap::Write => GPUMapModeConstants::WRITE, + }, + wgpu_mapping.range, + ); + + match mapping { + Err(error) => { + *pending_map = None; + p.reject_error(error.clone()); + }, + Ok(mut mapping) => { + // Step 5 + mapping.data.load(&wgpu_mapping.data); + // Step 6 + self.mapping.borrow_mut().replace(mapping); + // Step 7 + pending_map.take(); + p.resolve_native(&()); + }, + } + } +} + +impl AsyncWGPUListener for GPUBuffer { + #[allow(unsafe_code)] + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, _can_gc: CanGc) { + match response { + WebGPUResponse::BufferMapAsync(Ok(mapping)) => self.map_success(promise, mapping), + WebGPUResponse::BufferMapAsync(Err(_)) => self.map_failure(promise), + _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUBuffer"), + } + } +} diff --git a/components/script/dom/webgpu/gpubufferusage.rs b/components/script/dom/webgpu/gpubufferusage.rs new file mode 100644 index 00000000000..b35768d25d5 --- /dev/null +++ b/components/script/dom/webgpu/gpubufferusage.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; + +use crate::dom::bindings::reflector::Reflector; + +#[dom_struct] +pub struct GPUBufferUsage { + reflector_: Reflector, +} diff --git a/components/script/dom/webgpu/gpucanvascontext.rs b/components/script/dom/webgpu/gpucanvascontext.rs new file mode 100644 index 00000000000..2766f318c8a --- /dev/null +++ b/components/script/dom/webgpu/gpucanvascontext.rs @@ -0,0 +1,386 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; +use std::cell::RefCell; + +use arrayvec::ArrayVec; +use dom_struct::dom_struct; +use euclid::default::Size2D; +use ipc_channel::ipc; +use script_layout_interface::HTMLCanvasDataSource; +use webgpu::swapchain::WebGPUContextId; +use webgpu::wgc::id; +use webgpu::{ + ContextConfiguration, WebGPU, WebGPURequest, WebGPUTexture, PRESENTATION_BUFFER_COUNT, +}; +use webrender_api::units::DeviceIntSize; +use webrender_api::ImageKey; + +use super::gpuconvert::convert_texture_descriptor; +use super::gputexture::GPUTexture; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTexture_Binding::GPUTextureMethods; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUCanvasAlphaMode, GPUCanvasConfiguration, GPUCanvasContextMethods, GPUDeviceMethods, + GPUExtent3D, GPUExtent3DDict, GPUObjectDescriptorBase, GPUTextureDescriptor, + GPUTextureDimension, GPUTextureFormat, GPUTextureUsageConstants, +}; +use crate::dom::bindings::codegen::UnionTypes::HTMLCanvasElementOrOffscreenCanvas; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::inheritance::Castable; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutCanvasRenderingContextHelpers}; +use crate::dom::node::{document_from_node, Node, NodeDamage}; + +impl HTMLCanvasElementOrOffscreenCanvas { + fn size(&self) -> Size2D<u64> { + match self { + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) => { + canvas.get_size().cast() + }, + HTMLCanvasElementOrOffscreenCanvas::OffscreenCanvas(canvas) => canvas.get_size(), + } + } +} + +/// <https://gpuweb.github.io/gpuweb/#supported-context-formats> +fn supported_context_format(format: GPUTextureFormat) -> bool { + // TODO: GPUTextureFormat::Rgba16float + matches!( + format, + GPUTextureFormat::Bgra8unorm | GPUTextureFormat::Rgba8unorm + ) +} + +#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)] +/// Helps observe changes on swapchain +struct DrawingBuffer { + #[no_trace] + size: DeviceIntSize, + /// image is transparent black + cleared: bool, + #[ignore_malloc_size_of = "Defined in wgpu"] + #[no_trace] + config: Option<ContextConfiguration>, +} + +#[dom_struct] +pub struct GPUCanvasContext { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-canvas> + canvas: HTMLCanvasElementOrOffscreenCanvas, + // TODO: can we have wgpu surface that is hw accelerated inside wr ... + #[ignore_malloc_size_of = "Defined in webrender"] + #[no_trace] + webrender_image: ImageKey, + #[no_trace] + context_id: WebGPUContextId, + #[ignore_malloc_size_of = "manual writing is hard"] + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configuration-slot> + configuration: RefCell<Option<GPUCanvasConfiguration>>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-texturedescriptor-slot> + texture_descriptor: RefCell<Option<GPUTextureDescriptor>>, + /// Conceptually <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-drawingbuffer-slot> + drawing_buffer: RefCell<DrawingBuffer>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-currenttexture-slot> + current_texture: MutNullableDom<GPUTexture>, +} + +impl GPUCanvasContext { + fn new_inherited( + global: &GlobalScope, + canvas: HTMLCanvasElementOrOffscreenCanvas, + channel: WebGPU, + ) -> Self { + let (sender, receiver) = ipc::channel().unwrap(); + let size = canvas.size().cast().cast_unit(); + let mut buffer_ids = ArrayVec::<id::BufferId, PRESENTATION_BUFFER_COUNT>::new(); + for _ in 0..PRESENTATION_BUFFER_COUNT { + buffer_ids.push(global.wgpu_id_hub().create_buffer_id()); + } + if let Err(e) = channel.0.send(WebGPURequest::CreateContext { + buffer_ids, + size, + sender, + }) { + warn!("Failed to send CreateContext ({:?})", e); + } + let (external_id, webrender_image) = receiver.recv().unwrap(); + Self { + reflector_: Reflector::new(), + channel, + canvas, + webrender_image, + context_id: WebGPUContextId(external_id.0), + drawing_buffer: RefCell::new(DrawingBuffer { + size, + cleared: true, + ..Default::default() + }), + configuration: RefCell::new(None), + texture_descriptor: RefCell::new(None), + current_texture: MutNullableDom::default(), + } + } + + pub fn new(global: &GlobalScope, canvas: &HTMLCanvasElement, channel: WebGPU) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCanvasContext::new_inherited( + global, + HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(canvas)), + channel, + )), + global, + ) + } +} + +// Abstract ops from spec +impl GPUCanvasContext { + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-gputexturedescriptor-for-the-canvas-and-configuration> + fn texture_descriptor_for_canvas( + &self, + configuration: &GPUCanvasConfiguration, + ) -> GPUTextureDescriptor { + let size = self.size(); + GPUTextureDescriptor { + format: configuration.format, + // We need to add `COPY_SRC` so we can copy texture to presentation buffer + // causes FAIL on webgpu:web_platform,canvas,configure:usage:* + usage: configuration.usage | GPUTextureUsageConstants::COPY_SRC, + size: GPUExtent3D::GPUExtent3DDict(GPUExtent3DDict { + width: size.width as u32, + height: size.height as u32, + depthOrArrayLayers: 1, + }), + viewFormats: configuration.viewFormats.clone(), + // other members to default + mipLevelCount: 1, + sampleCount: 1, + parent: GPUObjectDescriptorBase { + label: USVString::default(), + }, + dimension: GPUTextureDimension::_2d, + } + } + + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-expire-the-current-texture> + fn expire_current_texture(&self) { + if let Some(current_texture) = self.current_texture.take() { + // Make copy of texture content + self.send_swap_chain_present(current_texture.id()); + // Step 1 + current_texture.Destroy() + } + } + + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-replace-the-drawing-buffer> + fn replace_drawing_buffer(&self) { + // Step 1 + self.expire_current_texture(); + // Step 2 + let configuration = self.configuration.borrow(); + // Step 3 + let mut drawing_buffer = self.drawing_buffer.borrow_mut(); + drawing_buffer.size = self.size().cast().cast_unit(); + drawing_buffer.cleared = true; + if let Some(configuration) = configuration.as_ref() { + drawing_buffer.config = Some(ContextConfiguration { + device_id: configuration.device.id().0, + queue_id: configuration.device.queue_id().0, + format: configuration.format.into(), + is_opaque: matches!(configuration.alphaMode, GPUCanvasAlphaMode::Opaque), + }); + } else { + drawing_buffer.config.take(); + }; + // TODO: send less + self.channel + .0 + .send(WebGPURequest::UpdateContext { + context_id: self.context_id, + size: drawing_buffer.size, + configuration: drawing_buffer.config, + }) + .expect("Failed to update webgpu context"); + } +} + +// Internal helper methods +impl GPUCanvasContext { + fn layout_handle(&self) -> HTMLCanvasDataSource { + if self.drawing_buffer.borrow().cleared { + HTMLCanvasDataSource::Empty + } else { + HTMLCanvasDataSource::WebGPU(self.webrender_image) + } + } + + fn send_swap_chain_present(&self, texture_id: WebGPUTexture) { + self.drawing_buffer.borrow_mut().cleared = false; + let encoder_id = self.global().wgpu_id_hub().create_command_encoder_id(); + if let Err(e) = self.channel.0.send(WebGPURequest::SwapChainPresent { + context_id: self.context_id, + texture_id: texture_id.0, + encoder_id, + }) { + warn!( + "Failed to send UpdateWebrenderData({:?}) ({})", + self.context_id, e + ); + } + } + + fn size(&self) -> Size2D<u64> { + self.canvas.size() + } +} + +// public methods for canvas handling +// these methods should probably be behind trait for all canvases +impl GPUCanvasContext { + pub(crate) fn context_id(&self) -> WebGPUContextId { + self.context_id + } + + pub(crate) fn mark_as_dirty(&self) { + if let HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(canvas) = &self.canvas { + canvas.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + let document = document_from_node(&**canvas); + document.add_dirty_webgpu_canvas(self); + } + } + + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-updating-the-rendering-of-a-webgpu-canvas> + pub(crate) fn update_rendering_of_webgpu_canvas(&self) { + // Step 1 + self.expire_current_texture(); + } + + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-update-the-canvas-size> + pub(crate) fn resize(&self) { + // Step 1 + self.replace_drawing_buffer(); + // Step 2 + let configuration = self.configuration.borrow(); + // Step 3 + if let Some(configuration) = configuration.as_ref() { + self.texture_descriptor + .replace(Some(self.texture_descriptor_for_canvas(configuration))); + } + } +} + +impl LayoutCanvasRenderingContextHelpers for LayoutDom<'_, GPUCanvasContext> { + fn canvas_data_source(self) -> HTMLCanvasDataSource { + (*self.unsafe_get()).layout_handle() + } +} + +impl GPUCanvasContextMethods<crate::DomTypeHolder> for GPUCanvasContext { + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-canvas> + fn Canvas(&self) -> HTMLCanvasElementOrOffscreenCanvas { + self.canvas.clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-configure> + fn Configure(&self, configuration: &GPUCanvasConfiguration) -> Fallible<()> { + // Step 1: Let device be configuration.device + let device = &configuration.device; + + // Step 5: Let descriptor be the GPUTextureDescriptor for the canvas and configuration. + let descriptor = self.texture_descriptor_for_canvas(configuration); + + // Step 2&3: Validate texture format required features + let (mut desc, _) = convert_texture_descriptor(&descriptor, device)?; + desc.label = Some(Cow::Borrowed( + "dummy texture for texture descriptor validation", + )); + + // Step 4: If Supported context formats does not contain configuration.format, throw a TypeError + if !supported_context_format(configuration.format) { + return Err(Error::Type(format!( + "Unsupported context format: {:?}", + configuration.format + ))); + } + + // Step 5 + self.configuration.replace(Some(configuration.clone())); + + // Step 6 + self.texture_descriptor.replace(Some(descriptor)); + + // Step 7 + self.replace_drawing_buffer(); + + // Step 8: Validate texture descriptor + let texture_id = self.global().wgpu_id_hub().create_texture_id(); + self.channel + .0 + .send(WebGPURequest::ValidateTextureDescriptor { + device_id: device.id().0, + texture_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU SwapChain"); + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-unconfigure> + fn Unconfigure(&self) { + // Step 1 + self.configuration.take(); + // Step 2 + self.current_texture.take(); + // Step 3 + self.replace_drawing_buffer(); + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucanvascontext-getcurrenttexture> + fn GetCurrentTexture(&self) -> Fallible<DomRoot<GPUTexture>> { + // Step 1 + let configuration = self.configuration.borrow(); + let Some(configuration) = configuration.as_ref() else { + return Err(Error::InvalidState); + }; + // Step 2 + let texture_descriptor = self.texture_descriptor.borrow(); + let texture_descriptor = texture_descriptor.as_ref().unwrap(); + // Step 6 + let current_texture = if let Some(current_texture) = self.current_texture.get() { + current_texture + } else { + // Step 3&4 + self.replace_drawing_buffer(); + let current_texture = configuration.device.CreateTexture(texture_descriptor)?; + self.current_texture.set(Some(¤t_texture)); + current_texture + }; + // Step 5 + self.mark_as_dirty(); + // Step 6 + Ok(current_texture) + } +} + +impl Drop for GPUCanvasContext { + fn drop(&mut self) { + if let Err(e) = self.channel.0.send(WebGPURequest::DestroyContext { + context_id: self.context_id, + }) { + warn!( + "Failed to send DestroySwapChain-ImageKey({:?}) ({})", + self.webrender_image, e + ); + } + } +} diff --git a/components/script/dom/webgpu/gpucolorwrite.rs b/components/script/dom/webgpu/gpucolorwrite.rs new file mode 100644 index 00000000000..29e19826342 --- /dev/null +++ b/components/script/dom/webgpu/gpucolorwrite.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; + +use crate::dom::bindings::reflector::Reflector; + +#[dom_struct] +pub struct GPUColorWrite { + reflector_: Reflector, +} diff --git a/components/script/dom/webgpu/gpucommandbuffer.rs b/components/script/dom/webgpu/gpucommandbuffer.rs new file mode 100644 index 00000000000..120af69a480 --- /dev/null +++ b/components/script/dom/webgpu/gpucommandbuffer.rs @@ -0,0 +1,88 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPUCommandBuffer, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUCommandBufferMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; + +#[dom_struct] +pub struct GPUCommandBuffer { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + command_buffer: WebGPUCommandBuffer, +} + +impl GPUCommandBuffer { + fn new_inherited( + channel: WebGPU, + command_buffer: WebGPUCommandBuffer, + label: USVString, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + command_buffer, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + command_buffer: WebGPUCommandBuffer, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCommandBuffer::new_inherited( + channel, + command_buffer, + label, + )), + global, + ) + } +} + +impl Drop for GPUCommandBuffer { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropCommandBuffer(self.command_buffer.0)) + { + warn!( + "Failed to send DropCommandBuffer({:?}) ({})", + self.command_buffer.0, e + ); + } + } +} + +impl GPUCommandBuffer { + pub fn id(&self) -> WebGPUCommandBuffer { + self.command_buffer + } +} + +impl GPUCommandBufferMethods<crate::DomTypeHolder> for GPUCommandBuffer { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} diff --git a/components/script/dom/webgpu/gpucommandencoder.rs b/components/script/dom/webgpu/gpucommandencoder.rs new file mode 100644 index 00000000000..67d78c0fcc9 --- /dev/null +++ b/components/script/dom/webgpu/gpucommandencoder.rs @@ -0,0 +1,319 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::wgc::command as wgpu_com; +use webgpu::{ + wgt, WebGPU, WebGPUCommandBuffer, WebGPUCommandEncoder, WebGPUComputePass, WebGPUDevice, + WebGPURenderPass, WebGPURequest, +}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUCommandBufferDescriptor, GPUCommandEncoderDescriptor, GPUCommandEncoderMethods, + GPUComputePassDescriptor, GPUExtent3D, GPUImageCopyBuffer, GPUImageCopyTexture, + GPURenderPassDescriptor, GPUSize64, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandbuffer::GPUCommandBuffer; +use crate::dom::gpucomputepassencoder::GPUComputePassEncoder; +use crate::dom::gpuconvert::{convert_load_op, convert_store_op}; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::gpurenderpassencoder::GPURenderPassEncoder; + +#[dom_struct] +pub struct GPUCommandEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + encoder: WebGPUCommandEncoder, + device: Dom<GPUDevice>, +} + +impl GPUCommandEncoder { + pub fn new_inherited( + channel: WebGPU, + device: &GPUDevice, + encoder: WebGPUCommandEncoder, + label: USVString, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + device: Dom::from_ref(device), + encoder, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + device: &GPUDevice, + encoder: WebGPUCommandEncoder, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUCommandEncoder::new_inherited( + channel, device, encoder, label, + )), + global, + ) + } +} + +impl GPUCommandEncoder { + pub fn id(&self) -> WebGPUCommandEncoder { + self.encoder + } + + pub fn device_id(&self) -> WebGPUDevice { + self.device.id() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcommandencoder> + pub fn create( + device: &GPUDevice, + descriptor: &GPUCommandEncoderDescriptor, + ) -> DomRoot<GPUCommandEncoder> { + let command_encoder_id = device.global().wgpu_id_hub().create_command_encoder_id(); + device + .channel() + .0 + .send(WebGPURequest::CreateCommandEncoder { + device_id: device.id().0, + command_encoder_id, + desc: wgt::CommandEncoderDescriptor { + label: (&descriptor.parent).into(), + }, + }) + .expect("Failed to create WebGPU command encoder"); + + let encoder = WebGPUCommandEncoder(command_encoder_id); + + GPUCommandEncoder::new( + &device.global(), + device.channel().clone(), + device, + encoder, + descriptor.parent.label.clone(), + ) + } +} + +impl GPUCommandEncoderMethods<crate::DomTypeHolder> for GPUCommandEncoder { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-begincomputepass> + fn BeginComputePass( + &self, + descriptor: &GPUComputePassDescriptor, + ) -> DomRoot<GPUComputePassEncoder> { + let compute_pass_id = self.global().wgpu_id_hub().create_compute_pass_id(); + + if let Err(e) = self.channel.0.send(WebGPURequest::BeginComputePass { + command_encoder_id: self.id().0, + compute_pass_id, + label: (&descriptor.parent).into(), + device_id: self.device.id().0, + }) { + warn!("Failed to send WebGPURequest::BeginComputePass {e:?}"); + } + + GPUComputePassEncoder::new( + &self.global(), + self.channel.clone(), + self, + WebGPUComputePass(compute_pass_id), + descriptor.parent.label.clone(), + ) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-beginrenderpass> + fn BeginRenderPass( + &self, + descriptor: &GPURenderPassDescriptor, + ) -> Fallible<DomRoot<GPURenderPassEncoder>> { + let depth_stencil_attachment = descriptor.depthStencilAttachment.as_ref().map(|depth| { + wgpu_com::RenderPassDepthStencilAttachment { + depth: wgpu_com::PassChannel { + load_op: convert_load_op(depth.depthLoadOp), + store_op: convert_store_op(depth.depthStoreOp), + clear_value: *depth.depthClearValue.unwrap_or_default(), + read_only: depth.depthReadOnly, + }, + stencil: wgpu_com::PassChannel { + load_op: convert_load_op(depth.stencilLoadOp), + store_op: convert_store_op(depth.stencilStoreOp), + clear_value: depth.stencilClearValue, + read_only: depth.stencilReadOnly, + }, + view: depth.view.id().0, + } + }); + + let color_attachments = descriptor + .colorAttachments + .iter() + .map(|color| -> Fallible<_> { + let channel = wgpu_com::PassChannel { + load_op: convert_load_op(Some(color.loadOp)), + store_op: convert_store_op(Some(color.storeOp)), + clear_value: color + .clearValue + .as_ref() + .map(|color| (color).try_into()) + .transpose()? + .unwrap_or_default(), + read_only: false, + }; + Ok(Some(wgpu_com::RenderPassColorAttachment { + resolve_target: color.resolveTarget.as_ref().map(|t| t.id().0), + channel, + view: color.view.id().0, + })) + }) + .collect::<Fallible<Vec<_>>>()?; + let render_pass_id = self.global().wgpu_id_hub().create_render_pass_id(); + + if let Err(e) = self.channel.0.send(WebGPURequest::BeginRenderPass { + command_encoder_id: self.id().0, + render_pass_id, + label: (&descriptor.parent).into(), + depth_stencil_attachment, + color_attachments, + device_id: self.device.id().0, + }) { + warn!("Failed to send WebGPURequest::BeginRenderPass {e:?}"); + } + + Ok(GPURenderPassEncoder::new( + &self.global(), + self.channel.clone(), + WebGPURenderPass(render_pass_id), + self, + descriptor.parent.label.clone(), + )) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertobuffer> + fn CopyBufferToBuffer( + &self, + source: &GPUBuffer, + source_offset: GPUSize64, + destination: &GPUBuffer, + destination_offset: GPUSize64, + size: GPUSize64, + ) { + self.channel + .0 + .send(WebGPURequest::CopyBufferToBuffer { + command_encoder_id: self.encoder.0, + source_id: source.id().0, + source_offset, + destination_id: destination.id().0, + destination_offset, + size, + }) + .expect("Failed to send CopyBufferToBuffer"); + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture> + fn CopyBufferToTexture( + &self, + source: &GPUImageCopyBuffer, + destination: &GPUImageCopyTexture, + copy_size: GPUExtent3D, + ) -> Fallible<()> { + self.channel + .0 + .send(WebGPURequest::CopyBufferToTexture { + command_encoder_id: self.encoder.0, + source: source.into(), + destination: destination.try_into()?, + copy_size: (©_size).try_into()?, + }) + .expect("Failed to send CopyBufferToTexture"); + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture> + fn CopyTextureToBuffer( + &self, + source: &GPUImageCopyTexture, + destination: &GPUImageCopyBuffer, + copy_size: GPUExtent3D, + ) -> Fallible<()> { + self.channel + .0 + .send(WebGPURequest::CopyTextureToBuffer { + command_encoder_id: self.encoder.0, + source: source.try_into()?, + destination: destination.into(), + copy_size: (©_size).try_into()?, + }) + .expect("Failed to send CopyTextureToBuffer"); + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#GPUCommandEncoder-copyTextureToTexture> + fn CopyTextureToTexture( + &self, + source: &GPUImageCopyTexture, + destination: &GPUImageCopyTexture, + copy_size: GPUExtent3D, + ) -> Fallible<()> { + self.channel + .0 + .send(WebGPURequest::CopyTextureToTexture { + command_encoder_id: self.encoder.0, + source: source.try_into()?, + destination: destination.try_into()?, + copy_size: (©_size).try_into()?, + }) + .expect("Failed to send CopyTextureToTexture"); + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-finish> + fn Finish(&self, descriptor: &GPUCommandBufferDescriptor) -> DomRoot<GPUCommandBuffer> { + self.channel + .0 + .send(WebGPURequest::CommandEncoderFinish { + command_encoder_id: self.encoder.0, + device_id: self.device.id().0, + desc: wgt::CommandBufferDescriptor { + label: (&descriptor.parent).into(), + }, + }) + .expect("Failed to send Finish"); + + let buffer = WebGPUCommandBuffer(self.encoder.0.into_command_buffer_id()); + GPUCommandBuffer::new( + &self.global(), + self.channel.clone(), + buffer, + descriptor.parent.label.clone(), + ) + } +} diff --git a/components/script/dom/webgpu/gpucompilationinfo.rs b/components/script/dom/webgpu/gpucompilationinfo.rs new file mode 100644 index 00000000000..08751c03d57 --- /dev/null +++ b/components/script/dom/webgpu/gpucompilationinfo.rs @@ -0,0 +1,63 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::MutableHandleValue; +use webgpu::ShaderCompilationInfo; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUCompilationInfoMethods; +use crate::dom::bindings::import::module::DomRoot; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::globalscope::GlobalScope; +use crate::dom::types::GPUCompilationMessage; +use crate::script_runtime::{CanGc, JSContext}; + +#[dom_struct] +pub struct GPUCompilationInfo { + reflector_: Reflector, + // currently we only get one message from wgpu + msg: Vec<DomRoot<GPUCompilationMessage>>, +} + +impl GPUCompilationInfo { + pub fn new_inherited(msg: Vec<DomRoot<GPUCompilationMessage>>) -> Self { + Self { + reflector_: Reflector::new(), + msg, + } + } + + #[allow(dead_code)] + pub fn new( + global: &GlobalScope, + msg: Vec<DomRoot<GPUCompilationMessage>>, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto(Box::new(Self::new_inherited(msg)), global, None, can_gc) + } + + pub fn from( + global: &GlobalScope, + error: Option<ShaderCompilationInfo>, + can_gc: CanGc, + ) -> DomRoot<Self> { + Self::new( + global, + if let Some(error) = error { + vec![GPUCompilationMessage::from(global, error)] + } else { + Vec::new() + }, + can_gc, + ) + } +} + +impl GPUCompilationInfoMethods<crate::DomTypeHolder> for GPUCompilationInfo { + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationinfo-messages> + fn Messages(&self, cx: JSContext, retval: MutableHandleValue) { + to_frozen_array(self.msg.as_slice(), cx, retval) + } +} diff --git a/components/script/dom/webgpu/gpucompilationmessage.rs b/components/script/dom/webgpu/gpucompilationmessage.rs new file mode 100644 index 00000000000..d811e8baf6e --- /dev/null +++ b/components/script/dom/webgpu/gpucompilationmessage.rs @@ -0,0 +1,110 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![allow(dead_code)] // this file is stub as wgpu does not provide info + +use dom_struct::dom_struct; +use webgpu::ShaderCompilationInfo; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUCompilationMessageMethods, GPUCompilationMessageType, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::types::GlobalScope; +use crate::test::DOMString; + +#[dom_struct] +pub struct GPUCompilationMessage { + reflector_: Reflector, + // #[ignore_malloc_size_of = "defined in wgpu-types"] + message: DOMString, + mtype: GPUCompilationMessageType, + line_num: u64, + line_pos: u64, + offset: u64, + length: u64, +} + +impl GPUCompilationMessage { + fn new_inherited( + message: DOMString, + mtype: GPUCompilationMessageType, + line_num: u64, + line_pos: u64, + offset: u64, + length: u64, + ) -> Self { + Self { + reflector_: Reflector::new(), + message, + mtype, + line_num, + line_pos, + offset, + length, + } + } + + pub fn new( + global: &GlobalScope, + message: DOMString, + mtype: GPUCompilationMessageType, + line_num: u64, + line_pos: u64, + offset: u64, + length: u64, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(Self::new_inherited( + message, mtype, line_num, line_pos, offset, length, + )), + global, + ) + } + + pub fn from(global: &GlobalScope, info: ShaderCompilationInfo) -> DomRoot<Self> { + GPUCompilationMessage::new( + global, + info.message.into(), + GPUCompilationMessageType::Error, + info.line_number, + info.line_pos, + info.offset, + info.length, + ) + } +} + +impl GPUCompilationMessageMethods<crate::DomTypeHolder> for GPUCompilationMessage { + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-message> + fn Message(&self) -> DOMString { + self.message.to_owned() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-type> + fn Type(&self) -> GPUCompilationMessageType { + self.mtype + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-linenum> + fn LineNum(&self) -> u64 { + self.line_num + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-linepos> + fn LinePos(&self) -> u64 { + self.line_pos + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-offset> + fn Offset(&self) -> u64 { + self.offset + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucompilationmessage-length> + fn Length(&self) -> u64 { + self.length + } +} diff --git a/components/script/dom/webgpu/gpucomputepassencoder.rs b/components/script/dom/webgpu/gpucomputepassencoder.rs new file mode 100644 index 00000000000..e75157b32fc --- /dev/null +++ b/components/script/dom/webgpu/gpucomputepassencoder.rs @@ -0,0 +1,156 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPUComputePass, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUComputePassEncoderMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandencoder::GPUCommandEncoder; +use crate::dom::gpucomputepipeline::GPUComputePipeline; + +#[dom_struct] +pub struct GPUComputePassEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + compute_pass: WebGPUComputePass, + command_encoder: Dom<GPUCommandEncoder>, +} + +impl GPUComputePassEncoder { + fn new_inherited( + channel: WebGPU, + parent: &GPUCommandEncoder, + compute_pass: WebGPUComputePass, + label: USVString, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + compute_pass, + command_encoder: Dom::from_ref(parent), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + parent: &GPUCommandEncoder, + compute_pass: WebGPUComputePass, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUComputePassEncoder::new_inherited( + channel, + parent, + compute_pass, + label, + )), + global, + ) + } +} + +impl GPUComputePassEncoderMethods<crate::DomTypeHolder> for GPUComputePassEncoder { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatchworkgroups> + fn DispatchWorkgroups(&self, x: u32, y: u32, z: u32) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::ComputePassDispatchWorkgroups { + compute_pass_id: self.compute_pass.0, + x, + y, + z, + device_id: self.command_encoder.device_id().0, + }) + { + warn!("Error sending WebGPURequest::ComputePassDispatchWorkgroups: {e:?}") + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-dispatchworkgroupsindirect> + fn DispatchWorkgroupsIndirect(&self, buffer: &GPUBuffer, offset: u64) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::ComputePassDispatchWorkgroupsIndirect { + compute_pass_id: self.compute_pass.0, + buffer_id: buffer.id().0, + offset, + device_id: self.command_encoder.device_id().0, + }) + { + warn!("Error sending WebGPURequest::ComputePassDispatchWorkgroupsIndirect: {e:?}") + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-endpass> + fn End(&self) { + if let Err(e) = self.channel.0.send(WebGPURequest::EndComputePass { + compute_pass_id: self.compute_pass.0, + device_id: self.command_encoder.device_id().0, + command_encoder_id: self.command_encoder.id().0, + }) { + warn!("Failed to send WebGPURequest::EndComputePass: {e:?}"); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup> + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, offsets: Vec<u32>) { + if let Err(e) = self.channel.0.send(WebGPURequest::ComputePassSetBindGroup { + compute_pass_id: self.compute_pass.0, + index, + bind_group_id: bind_group.id().0, + offsets, + device_id: self.command_encoder.device_id().0, + }) { + warn!("Error sending WebGPURequest::ComputePassSetBindGroup: {e:?}") + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpucomputepassencoder-setpipeline> + fn SetPipeline(&self, pipeline: &GPUComputePipeline) { + if let Err(e) = self.channel.0.send(WebGPURequest::ComputePassSetPipeline { + compute_pass_id: self.compute_pass.0, + pipeline_id: pipeline.id().0, + device_id: self.command_encoder.device_id().0, + }) { + warn!("Error sending WebGPURequest::ComputePassSetPipeline: {e:?}") + } + } +} + +impl Drop for GPUComputePassEncoder { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropComputePass(self.compute_pass.0)) + { + warn!("Failed to send WebGPURequest::DropComputePass with {e:?}"); + } + } +} diff --git a/components/script/dom/webgpu/gpucomputepipeline.rs b/components/script/dom/webgpu/gpucomputepipeline.rs new file mode 100644 index 00000000000..4d905126c71 --- /dev/null +++ b/components/script/dom/webgpu/gpucomputepipeline.rs @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSender; +use webgpu::wgc::pipeline::ComputePipelineDescriptor; +use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPUComputePipeline, WebGPURequest, WebGPUResponse}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUComputePipelineDescriptor, GPUComputePipelineMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpudevice::GPUDevice; + +#[dom_struct] +pub struct GPUComputePipeline { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + compute_pipeline: WebGPUComputePipeline, + device: Dom<GPUDevice>, +} + +impl GPUComputePipeline { + fn new_inherited( + compute_pipeline: WebGPUComputePipeline, + label: USVString, + device: &GPUDevice, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel: device.channel(), + label: DomRefCell::new(label), + compute_pipeline, + device: Dom::from_ref(device), + } + } + + pub fn new( + global: &GlobalScope, + compute_pipeline: WebGPUComputePipeline, + label: USVString, + device: &GPUDevice, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUComputePipeline::new_inherited( + compute_pipeline, + label, + device, + )), + global, + ) + } +} + +impl GPUComputePipeline { + pub fn id(&self) -> &WebGPUComputePipeline { + &self.compute_pipeline + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcomputepipeline> + pub fn create( + device: &GPUDevice, + descriptor: &GPUComputePipelineDescriptor, + async_sender: Option<IpcSender<WebGPUResponse>>, + ) -> WebGPUComputePipeline { + let compute_pipeline_id = device.global().wgpu_id_hub().create_compute_pipeline_id(); + + let pipeline_layout = device.get_pipeline_layout_data(&descriptor.parent.layout); + + let desc = ComputePipelineDescriptor { + label: (&descriptor.parent.parent).into(), + layout: pipeline_layout.explicit(), + stage: (&descriptor.compute).into(), + cache: None, + }; + + device + .channel() + .0 + .send(WebGPURequest::CreateComputePipeline { + device_id: device.id().0, + compute_pipeline_id, + descriptor: desc, + implicit_ids: pipeline_layout.implicit(), + async_sender, + }) + .expect("Failed to create WebGPU ComputePipeline"); + + WebGPUComputePipeline(compute_pipeline_id) + } +} + +impl GPUComputePipelineMethods<crate::DomTypeHolder> for GPUComputePipeline { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpupipelinebase-getbindgrouplayout> + fn GetBindGroupLayout(&self, index: u32) -> Fallible<DomRoot<GPUBindGroupLayout>> { + let id = self.global().wgpu_id_hub().create_bind_group_layout_id(); + + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::ComputeGetBindGroupLayout { + device_id: self.device.id().0, + pipeline_id: self.compute_pipeline.0, + index, + id, + }) + { + warn!("Failed to send WebGPURequest::ComputeGetBindGroupLayout {e:?}"); + } + + Ok(GPUBindGroupLayout::new( + &self.global(), + self.channel.clone(), + WebGPUBindGroupLayout(id), + USVString::default(), + )) + } +} + +impl Drop for GPUComputePipeline { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropComputePipeline(self.compute_pipeline.0)) + { + warn!( + "Failed to send WebGPURequest::DropComputePipeline({:?}) ({})", + self.compute_pipeline.0, e + ); + }; + } +} diff --git a/components/script/dom/webgpu/gpuconvert.rs b/components/script/dom/webgpu/gpuconvert.rs new file mode 100644 index 00000000000..db2390a03b0 --- /dev/null +++ b/components/script/dom/webgpu/gpuconvert.rs @@ -0,0 +1,680 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; +use std::num::NonZeroU64; + +use webgpu::wgc::binding_model::{BindGroupEntry, BindingResource, BufferBinding}; +use webgpu::wgc::command as wgpu_com; +use webgpu::wgc::pipeline::ProgrammableStageDescriptor; +use webgpu::wgc::resource::TextureDescriptor; +use webgpu::wgt::{self, AstcBlock, AstcChannel}; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUAddressMode, GPUBindGroupEntry, GPUBindGroupLayoutEntry, GPUBindingResource, + GPUBlendComponent, GPUBlendFactor, GPUBlendOperation, GPUBufferBindingType, GPUColor, + GPUCompareFunction, GPUCullMode, GPUExtent3D, GPUFilterMode, GPUFrontFace, GPUImageCopyBuffer, + GPUImageCopyTexture, GPUImageDataLayout, GPUIndexFormat, GPULoadOp, GPUObjectDescriptorBase, + GPUOrigin3D, GPUPrimitiveState, GPUPrimitiveTopology, GPUProgrammableStage, + GPUSamplerBindingType, GPUStencilOperation, GPUStorageTextureAccess, GPUStoreOp, + GPUTextureAspect, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat, + GPUTextureSampleType, GPUTextureViewDimension, GPUVertexFormat, +}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::types::GPUDevice; + +impl From<GPUTextureFormat> for wgt::TextureFormat { + fn from(format: GPUTextureFormat) -> Self { + match format { + GPUTextureFormat::R8unorm => wgt::TextureFormat::R8Unorm, + GPUTextureFormat::R8snorm => wgt::TextureFormat::R8Snorm, + GPUTextureFormat::R8uint => wgt::TextureFormat::R8Uint, + GPUTextureFormat::R8sint => wgt::TextureFormat::R8Sint, + GPUTextureFormat::R16uint => wgt::TextureFormat::R16Uint, + GPUTextureFormat::R16sint => wgt::TextureFormat::R16Sint, + GPUTextureFormat::R16float => wgt::TextureFormat::R16Float, + GPUTextureFormat::Rg8unorm => wgt::TextureFormat::Rg8Unorm, + GPUTextureFormat::Rg8snorm => wgt::TextureFormat::Rg8Snorm, + GPUTextureFormat::Rg8uint => wgt::TextureFormat::Rg8Uint, + GPUTextureFormat::Rg8sint => wgt::TextureFormat::Rg8Sint, + GPUTextureFormat::R32uint => wgt::TextureFormat::R32Uint, + GPUTextureFormat::R32sint => wgt::TextureFormat::R32Sint, + GPUTextureFormat::R32float => wgt::TextureFormat::R32Float, + GPUTextureFormat::Rg16uint => wgt::TextureFormat::Rg16Uint, + GPUTextureFormat::Rg16sint => wgt::TextureFormat::Rg16Sint, + GPUTextureFormat::Rg16float => wgt::TextureFormat::Rg16Float, + GPUTextureFormat::Rgba8unorm => wgt::TextureFormat::Rgba8Unorm, + GPUTextureFormat::Rgba8unorm_srgb => wgt::TextureFormat::Rgba8UnormSrgb, + GPUTextureFormat::Rgba8snorm => wgt::TextureFormat::Rgba8Snorm, + GPUTextureFormat::Rgba8uint => wgt::TextureFormat::Rgba8Uint, + GPUTextureFormat::Rgba8sint => wgt::TextureFormat::Rgba8Sint, + GPUTextureFormat::Bgra8unorm => wgt::TextureFormat::Bgra8Unorm, + GPUTextureFormat::Bgra8unorm_srgb => wgt::TextureFormat::Bgra8UnormSrgb, + GPUTextureFormat::Rgb10a2unorm => wgt::TextureFormat::Rgb10a2Unorm, + GPUTextureFormat::Rg32uint => wgt::TextureFormat::Rg32Uint, + GPUTextureFormat::Rg32sint => wgt::TextureFormat::Rg32Sint, + GPUTextureFormat::Rg32float => wgt::TextureFormat::Rg32Float, + GPUTextureFormat::Rgba16uint => wgt::TextureFormat::Rgba16Uint, + GPUTextureFormat::Rgba16sint => wgt::TextureFormat::Rgba16Sint, + GPUTextureFormat::Rgba16float => wgt::TextureFormat::Rgba16Float, + GPUTextureFormat::Rgba32uint => wgt::TextureFormat::Rgba32Uint, + GPUTextureFormat::Rgba32sint => wgt::TextureFormat::Rgba32Sint, + GPUTextureFormat::Rgba32float => wgt::TextureFormat::Rgba32Float, + GPUTextureFormat::Depth32float => wgt::TextureFormat::Depth32Float, + GPUTextureFormat::Depth24plus => wgt::TextureFormat::Depth24Plus, + GPUTextureFormat::Depth24plus_stencil8 => wgt::TextureFormat::Depth24PlusStencil8, + GPUTextureFormat::Bc1_rgba_unorm => wgt::TextureFormat::Bc1RgbaUnorm, + GPUTextureFormat::Bc1_rgba_unorm_srgb => wgt::TextureFormat::Bc1RgbaUnormSrgb, + GPUTextureFormat::Bc2_rgba_unorm => wgt::TextureFormat::Bc2RgbaUnorm, + GPUTextureFormat::Bc2_rgba_unorm_srgb => wgt::TextureFormat::Bc2RgbaUnormSrgb, + GPUTextureFormat::Bc3_rgba_unorm => wgt::TextureFormat::Bc3RgbaUnorm, + GPUTextureFormat::Bc3_rgba_unorm_srgb => wgt::TextureFormat::Bc3RgbaUnormSrgb, + GPUTextureFormat::Bc4_r_unorm => wgt::TextureFormat::Bc4RUnorm, + GPUTextureFormat::Bc4_r_snorm => wgt::TextureFormat::Bc4RSnorm, + GPUTextureFormat::Bc5_rg_unorm => wgt::TextureFormat::Bc5RgUnorm, + GPUTextureFormat::Bc5_rg_snorm => wgt::TextureFormat::Bc5RgSnorm, + GPUTextureFormat::Bc6h_rgb_ufloat => wgt::TextureFormat::Bc6hRgbUfloat, + GPUTextureFormat::Bc7_rgba_unorm => wgt::TextureFormat::Bc7RgbaUnorm, + GPUTextureFormat::Bc7_rgba_unorm_srgb => wgt::TextureFormat::Bc7RgbaUnormSrgb, + GPUTextureFormat::Bc6h_rgb_float => wgt::TextureFormat::Bc6hRgbFloat, + GPUTextureFormat::Rgb9e5ufloat => wgt::TextureFormat::Rgb9e5Ufloat, + GPUTextureFormat::Rgb10a2uint => wgt::TextureFormat::Rgb10a2Uint, + GPUTextureFormat::Rg11b10ufloat => wgt::TextureFormat::Rg11b10Ufloat, + GPUTextureFormat::Stencil8 => wgt::TextureFormat::Stencil8, + GPUTextureFormat::Depth16unorm => wgt::TextureFormat::Depth16Unorm, + GPUTextureFormat::Depth32float_stencil8 => wgt::TextureFormat::Depth32FloatStencil8, + GPUTextureFormat::Etc2_rgb8unorm => wgt::TextureFormat::Etc2Rgb8Unorm, + GPUTextureFormat::Etc2_rgb8unorm_srgb => wgt::TextureFormat::Etc2Rgb8UnormSrgb, + GPUTextureFormat::Etc2_rgb8a1unorm => wgt::TextureFormat::Etc2Rgb8A1Unorm, + GPUTextureFormat::Etc2_rgb8a1unorm_srgb => wgt::TextureFormat::Etc2Rgb8A1UnormSrgb, + GPUTextureFormat::Etc2_rgba8unorm => wgt::TextureFormat::Etc2Rgba8Unorm, + GPUTextureFormat::Etc2_rgba8unorm_srgb => wgt::TextureFormat::Etc2Rgba8UnormSrgb, + GPUTextureFormat::Eac_r11unorm => wgt::TextureFormat::EacR11Unorm, + GPUTextureFormat::Eac_r11snorm => wgt::TextureFormat::EacR11Snorm, + GPUTextureFormat::Eac_rg11unorm => wgt::TextureFormat::EacRg11Unorm, + GPUTextureFormat::Eac_rg11snorm => wgt::TextureFormat::EacRg11Snorm, + GPUTextureFormat::Astc_4x4_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B4x4, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_4x4_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B4x4, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_5x4_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B5x4, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_5x4_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B5x4, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_5x5_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B5x5, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_5x5_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B5x5, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_6x5_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B6x5, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_6x5_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B6x5, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_6x6_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B6x6, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_6x6_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B6x6, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_8x5_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B8x5, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_8x5_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B8x5, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_8x6_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B8x6, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_8x6_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B8x6, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_8x8_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B8x8, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_8x8_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B8x8, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_10x5_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B10x5, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_10x5_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B10x5, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_10x6_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B10x6, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_10x6_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B10x6, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_10x8_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B10x8, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_10x8_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B10x8, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_10x10_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B10x10, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_10x10_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B10x10, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_12x10_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B12x10, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_12x10_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B12x10, + channel: AstcChannel::UnormSrgb, + }, + GPUTextureFormat::Astc_12x12_unorm => wgt::TextureFormat::Astc { + block: AstcBlock::B12x12, + channel: AstcChannel::Unorm, + }, + GPUTextureFormat::Astc_12x12_unorm_srgb => wgt::TextureFormat::Astc { + block: AstcBlock::B12x12, + channel: AstcChannel::UnormSrgb, + }, + } + } +} + +impl TryFrom<&GPUExtent3D> for wgt::Extent3d { + type Error = Error; + + fn try_from(size: &GPUExtent3D) -> Result<Self, Self::Error> { + match *size { + GPUExtent3D::GPUExtent3DDict(ref dict) => Ok(wgt::Extent3d { + width: dict.width, + height: dict.height, + depth_or_array_layers: dict.depthOrArrayLayers, + }), + GPUExtent3D::RangeEnforcedUnsignedLongSequence(ref v) => { + // https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-gpuextent3d-shape + if v.is_empty() || v.len() > 3 { + Err(Error::Type( + "GPUExtent3D size must be between 1 and 3 (inclusive)".to_string(), + )) + } else { + Ok(wgt::Extent3d { + width: v[0], + height: v.get(1).copied().unwrap_or(1), + depth_or_array_layers: v.get(2).copied().unwrap_or(1), + }) + } + }, + } + } +} + +impl From<&GPUImageDataLayout> for wgt::ImageDataLayout { + fn from(data_layout: &GPUImageDataLayout) -> Self { + wgt::ImageDataLayout { + offset: data_layout.offset as wgt::BufferAddress, + bytes_per_row: data_layout.bytesPerRow, + rows_per_image: data_layout.rowsPerImage, + } + } +} + +impl From<GPUVertexFormat> for wgt::VertexFormat { + fn from(format: GPUVertexFormat) -> Self { + match format { + GPUVertexFormat::Uint8x2 => wgt::VertexFormat::Uint8x2, + GPUVertexFormat::Uint8x4 => wgt::VertexFormat::Uint8x4, + GPUVertexFormat::Sint8x2 => wgt::VertexFormat::Sint8x2, + GPUVertexFormat::Sint8x4 => wgt::VertexFormat::Sint8x4, + GPUVertexFormat::Unorm8x2 => wgt::VertexFormat::Unorm8x2, + GPUVertexFormat::Unorm8x4 => wgt::VertexFormat::Unorm8x4, + GPUVertexFormat::Snorm8x2 => wgt::VertexFormat::Unorm8x2, + GPUVertexFormat::Snorm8x4 => wgt::VertexFormat::Unorm8x4, + GPUVertexFormat::Uint16x2 => wgt::VertexFormat::Uint16x2, + GPUVertexFormat::Uint16x4 => wgt::VertexFormat::Uint16x4, + GPUVertexFormat::Sint16x2 => wgt::VertexFormat::Sint16x2, + GPUVertexFormat::Sint16x4 => wgt::VertexFormat::Sint16x4, + GPUVertexFormat::Unorm16x2 => wgt::VertexFormat::Unorm16x2, + GPUVertexFormat::Unorm16x4 => wgt::VertexFormat::Unorm16x4, + GPUVertexFormat::Snorm16x2 => wgt::VertexFormat::Snorm16x2, + GPUVertexFormat::Snorm16x4 => wgt::VertexFormat::Snorm16x4, + GPUVertexFormat::Float16x2 => wgt::VertexFormat::Float16x2, + GPUVertexFormat::Float16x4 => wgt::VertexFormat::Float16x4, + GPUVertexFormat::Float32 => wgt::VertexFormat::Float32, + GPUVertexFormat::Float32x2 => wgt::VertexFormat::Float32x2, + GPUVertexFormat::Float32x3 => wgt::VertexFormat::Float32x3, + GPUVertexFormat::Float32x4 => wgt::VertexFormat::Float32x4, + GPUVertexFormat::Uint32 => wgt::VertexFormat::Uint32, + GPUVertexFormat::Uint32x2 => wgt::VertexFormat::Uint32x2, + GPUVertexFormat::Uint32x3 => wgt::VertexFormat::Uint32x3, + GPUVertexFormat::Uint32x4 => wgt::VertexFormat::Uint32x4, + GPUVertexFormat::Sint32 => wgt::VertexFormat::Sint32, + GPUVertexFormat::Sint32x2 => wgt::VertexFormat::Sint32x2, + GPUVertexFormat::Sint32x3 => wgt::VertexFormat::Sint32x3, + GPUVertexFormat::Sint32x4 => wgt::VertexFormat::Sint32x4, + } + } +} + +impl From<&GPUPrimitiveState> for wgt::PrimitiveState { + fn from(primitive_state: &GPUPrimitiveState) -> Self { + wgt::PrimitiveState { + topology: wgt::PrimitiveTopology::from(&primitive_state.topology), + strip_index_format: primitive_state.stripIndexFormat.map(|index_format| { + match index_format { + GPUIndexFormat::Uint16 => wgt::IndexFormat::Uint16, + GPUIndexFormat::Uint32 => wgt::IndexFormat::Uint32, + } + }), + front_face: match primitive_state.frontFace { + GPUFrontFace::Ccw => wgt::FrontFace::Ccw, + GPUFrontFace::Cw => wgt::FrontFace::Cw, + }, + cull_mode: match primitive_state.cullMode { + GPUCullMode::None => None, + GPUCullMode::Front => Some(wgt::Face::Front), + GPUCullMode::Back => Some(wgt::Face::Back), + }, + unclipped_depth: primitive_state.clampDepth, + ..Default::default() + } + } +} + +impl From<&GPUPrimitiveTopology> for wgt::PrimitiveTopology { + fn from(primitive_topology: &GPUPrimitiveTopology) -> Self { + match primitive_topology { + GPUPrimitiveTopology::Point_list => wgt::PrimitiveTopology::PointList, + GPUPrimitiveTopology::Line_list => wgt::PrimitiveTopology::LineList, + GPUPrimitiveTopology::Line_strip => wgt::PrimitiveTopology::LineStrip, + GPUPrimitiveTopology::Triangle_list => wgt::PrimitiveTopology::TriangleList, + GPUPrimitiveTopology::Triangle_strip => wgt::PrimitiveTopology::TriangleStrip, + } + } +} + +impl From<GPUAddressMode> for wgt::AddressMode { + fn from(address_mode: GPUAddressMode) -> Self { + match address_mode { + GPUAddressMode::Clamp_to_edge => wgt::AddressMode::ClampToEdge, + GPUAddressMode::Repeat => wgt::AddressMode::Repeat, + GPUAddressMode::Mirror_repeat => wgt::AddressMode::MirrorRepeat, + } + } +} + +impl From<GPUFilterMode> for wgt::FilterMode { + fn from(filter_mode: GPUFilterMode) -> Self { + match filter_mode { + GPUFilterMode::Nearest => wgt::FilterMode::Nearest, + GPUFilterMode::Linear => wgt::FilterMode::Linear, + } + } +} + +impl From<GPUTextureViewDimension> for wgt::TextureViewDimension { + fn from(view_dimension: GPUTextureViewDimension) -> Self { + match view_dimension { + GPUTextureViewDimension::_1d => wgt::TextureViewDimension::D1, + GPUTextureViewDimension::_2d => wgt::TextureViewDimension::D2, + GPUTextureViewDimension::_2d_array => wgt::TextureViewDimension::D2Array, + GPUTextureViewDimension::Cube => wgt::TextureViewDimension::Cube, + GPUTextureViewDimension::Cube_array => wgt::TextureViewDimension::CubeArray, + GPUTextureViewDimension::_3d => wgt::TextureViewDimension::D3, + } + } +} + +impl From<GPUCompareFunction> for wgt::CompareFunction { + fn from(compare: GPUCompareFunction) -> Self { + match compare { + GPUCompareFunction::Never => wgt::CompareFunction::Never, + GPUCompareFunction::Less => wgt::CompareFunction::Less, + GPUCompareFunction::Equal => wgt::CompareFunction::Equal, + GPUCompareFunction::Less_equal => wgt::CompareFunction::LessEqual, + GPUCompareFunction::Greater => wgt::CompareFunction::Greater, + GPUCompareFunction::Not_equal => wgt::CompareFunction::NotEqual, + GPUCompareFunction::Greater_equal => wgt::CompareFunction::GreaterEqual, + GPUCompareFunction::Always => wgt::CompareFunction::Always, + } + } +} + +impl From<&GPUBlendFactor> for wgt::BlendFactor { + fn from(factor: &GPUBlendFactor) -> Self { + match factor { + GPUBlendFactor::Zero => wgt::BlendFactor::Zero, + GPUBlendFactor::One => wgt::BlendFactor::One, + GPUBlendFactor::Src => wgt::BlendFactor::Src, + GPUBlendFactor::One_minus_src => wgt::BlendFactor::OneMinusSrc, + GPUBlendFactor::Src_alpha => wgt::BlendFactor::SrcAlpha, + GPUBlendFactor::One_minus_src_alpha => wgt::BlendFactor::OneMinusSrcAlpha, + GPUBlendFactor::Dst => wgt::BlendFactor::Dst, + GPUBlendFactor::One_minus_dst => wgt::BlendFactor::OneMinusDst, + GPUBlendFactor::Dst_alpha => wgt::BlendFactor::DstAlpha, + GPUBlendFactor::One_minus_dst_alpha => wgt::BlendFactor::OneMinusDstAlpha, + GPUBlendFactor::Src_alpha_saturated => wgt::BlendFactor::SrcAlphaSaturated, + GPUBlendFactor::Constant => wgt::BlendFactor::Constant, + GPUBlendFactor::One_minus_constant => wgt::BlendFactor::OneMinusConstant, + } + } +} + +impl From<&GPUBlendComponent> for wgt::BlendComponent { + fn from(blend_component: &GPUBlendComponent) -> Self { + wgt::BlendComponent { + src_factor: wgt::BlendFactor::from(&blend_component.srcFactor), + dst_factor: wgt::BlendFactor::from(&blend_component.dstFactor), + operation: match blend_component.operation { + GPUBlendOperation::Add => wgt::BlendOperation::Add, + GPUBlendOperation::Subtract => wgt::BlendOperation::Subtract, + GPUBlendOperation::Reverse_subtract => wgt::BlendOperation::ReverseSubtract, + GPUBlendOperation::Min => wgt::BlendOperation::Min, + GPUBlendOperation::Max => wgt::BlendOperation::Max, + }, + } + } +} + +pub fn convert_load_op(op: Option<GPULoadOp>) -> wgpu_com::LoadOp { + match op { + Some(GPULoadOp::Load) => wgpu_com::LoadOp::Load, + Some(GPULoadOp::Clear) => wgpu_com::LoadOp::Clear, + None => wgpu_com::LoadOp::Clear, + } +} + +pub fn convert_store_op(op: Option<GPUStoreOp>) -> wgpu_com::StoreOp { + match op { + Some(GPUStoreOp::Store) => wgpu_com::StoreOp::Store, + Some(GPUStoreOp::Discard) => wgpu_com::StoreOp::Discard, + None => wgpu_com::StoreOp::Discard, + } +} + +impl From<GPUStencilOperation> for wgt::StencilOperation { + fn from(operation: GPUStencilOperation) -> Self { + match operation { + GPUStencilOperation::Keep => wgt::StencilOperation::Keep, + GPUStencilOperation::Zero => wgt::StencilOperation::Zero, + GPUStencilOperation::Replace => wgt::StencilOperation::Replace, + GPUStencilOperation::Invert => wgt::StencilOperation::Invert, + GPUStencilOperation::Increment_clamp => wgt::StencilOperation::IncrementClamp, + GPUStencilOperation::Decrement_clamp => wgt::StencilOperation::DecrementClamp, + GPUStencilOperation::Increment_wrap => wgt::StencilOperation::IncrementWrap, + GPUStencilOperation::Decrement_wrap => wgt::StencilOperation::DecrementWrap, + } + } +} + +impl From<&GPUImageCopyBuffer> for wgpu_com::ImageCopyBuffer { + fn from(ic_buffer: &GPUImageCopyBuffer) -> Self { + wgpu_com::ImageCopyBuffer { + buffer: ic_buffer.buffer.id().0, + layout: wgt::ImageDataLayout::from(&ic_buffer.parent), + } + } +} + +impl TryFrom<&GPUOrigin3D> for wgt::Origin3d { + type Error = Error; + + fn try_from(origin: &GPUOrigin3D) -> Result<Self, Self::Error> { + match origin { + GPUOrigin3D::RangeEnforcedUnsignedLongSequence(v) => { + // https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-gpuorigin3d-shape + if v.len() > 3 { + Err(Error::Type( + "sequence is too long for GPUOrigin3D".to_string(), + )) + } else { + Ok(wgt::Origin3d { + x: v.first().copied().unwrap_or(0), + y: v.get(1).copied().unwrap_or(0), + z: v.get(2).copied().unwrap_or(0), + }) + } + }, + GPUOrigin3D::GPUOrigin3DDict(d) => Ok(wgt::Origin3d { + x: d.x, + y: d.y, + z: d.z, + }), + } + } +} + +impl TryFrom<&GPUImageCopyTexture> for wgpu_com::ImageCopyTexture { + type Error = Error; + + fn try_from(ic_texture: &GPUImageCopyTexture) -> Result<Self, Self::Error> { + Ok(wgpu_com::ImageCopyTexture { + texture: ic_texture.texture.id().0, + mip_level: ic_texture.mipLevel, + origin: ic_texture + .origin + .as_ref() + .map(wgt::Origin3d::try_from) + .transpose()? + .unwrap_or_default(), + aspect: match ic_texture.aspect { + GPUTextureAspect::All => wgt::TextureAspect::All, + GPUTextureAspect::Stencil_only => wgt::TextureAspect::StencilOnly, + GPUTextureAspect::Depth_only => wgt::TextureAspect::DepthOnly, + }, + }) + } +} + +impl<'a> From<&GPUObjectDescriptorBase> for Option<Cow<'a, str>> { + fn from(val: &GPUObjectDescriptorBase) -> Self { + if val.label.is_empty() { + None + } else { + Some(Cow::Owned(val.label.to_string())) + } + } +} + +pub fn convert_bind_group_layout_entry( + bgle: &GPUBindGroupLayoutEntry, + device: &GPUDevice, +) -> Fallible<Result<wgt::BindGroupLayoutEntry, webgpu::Error>> { + let number_of_provided_bindings = bgle.buffer.is_some() as u8 + + bgle.sampler.is_some() as u8 + + bgle.storageTexture.is_some() as u8 + + bgle.texture.is_some() as u8; + let ty = if let Some(buffer) = &bgle.buffer { + Some(wgt::BindingType::Buffer { + ty: match buffer.type_ { + GPUBufferBindingType::Uniform => wgt::BufferBindingType::Uniform, + GPUBufferBindingType::Storage => { + wgt::BufferBindingType::Storage { read_only: false } + }, + GPUBufferBindingType::Read_only_storage => { + wgt::BufferBindingType::Storage { read_only: true } + }, + }, + has_dynamic_offset: buffer.hasDynamicOffset, + min_binding_size: NonZeroU64::new(buffer.minBindingSize), + }) + } else if let Some(sampler) = &bgle.sampler { + Some(wgt::BindingType::Sampler(match sampler.type_ { + GPUSamplerBindingType::Filtering => wgt::SamplerBindingType::Filtering, + GPUSamplerBindingType::Non_filtering => wgt::SamplerBindingType::NonFiltering, + GPUSamplerBindingType::Comparison => wgt::SamplerBindingType::Comparison, + })) + } else if let Some(storage) = &bgle.storageTexture { + Some(wgt::BindingType::StorageTexture { + access: match storage.access { + GPUStorageTextureAccess::Write_only => wgt::StorageTextureAccess::WriteOnly, + GPUStorageTextureAccess::Read_only => wgt::StorageTextureAccess::ReadOnly, + GPUStorageTextureAccess::Read_write => wgt::StorageTextureAccess::ReadWrite, + }, + format: device.validate_texture_format_required_features(&storage.format)?, + view_dimension: storage.viewDimension.into(), + }) + } else if let Some(texture) = &bgle.texture { + Some(wgt::BindingType::Texture { + sample_type: match texture.sampleType { + GPUTextureSampleType::Float => wgt::TextureSampleType::Float { filterable: true }, + GPUTextureSampleType::Unfilterable_float => { + wgt::TextureSampleType::Float { filterable: false } + }, + GPUTextureSampleType::Depth => wgt::TextureSampleType::Depth, + GPUTextureSampleType::Sint => wgt::TextureSampleType::Sint, + GPUTextureSampleType::Uint => wgt::TextureSampleType::Uint, + }, + view_dimension: texture.viewDimension.into(), + multisampled: texture.multisampled, + }) + } else { + assert_eq!(number_of_provided_bindings, 0); + None + }; + // Check for number of bindings should actually be done in device-timeline, + // but we do it last on content-timeline to have some visible effect + let ty = if number_of_provided_bindings != 1 { + None + } else { + ty + } + .ok_or(webgpu::Error::Validation( + "Exactly on entry type must be provided".to_string(), + )); + + Ok(ty.map(|ty| wgt::BindGroupLayoutEntry { + binding: bgle.binding, + visibility: wgt::ShaderStages::from_bits_retain(bgle.visibility), + ty, + count: None, + })) +} + +pub fn convert_texture_descriptor( + descriptor: &GPUTextureDescriptor, + device: &GPUDevice, +) -> Fallible<(TextureDescriptor<'static>, wgt::Extent3d)> { + let size = (&descriptor.size).try_into()?; + let desc = TextureDescriptor { + label: (&descriptor.parent).into(), + size, + mip_level_count: descriptor.mipLevelCount, + sample_count: descriptor.sampleCount, + dimension: descriptor.dimension.into(), + format: device.validate_texture_format_required_features(&descriptor.format)?, + usage: wgt::TextureUsages::from_bits_retain(descriptor.usage), + view_formats: descriptor + .viewFormats + .iter() + .map(|tf| device.validate_texture_format_required_features(tf)) + .collect::<Fallible<_>>()?, + }; + Ok((desc, size)) +} + +impl TryFrom<&GPUColor> for wgt::Color { + type Error = Error; + + fn try_from(color: &GPUColor) -> Result<Self, Self::Error> { + match color { + GPUColor::DoubleSequence(s) => { + // https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-gpucolor-shape + if s.len() != 4 { + Err(Error::Type("GPUColor sequence must be len 4".to_string())) + } else { + Ok(wgt::Color { + r: *s[0], + g: *s[1], + b: *s[2], + a: *s[3], + }) + } + }, + GPUColor::GPUColorDict(d) => Ok(wgt::Color { + r: *d.r, + g: *d.g, + b: *d.b, + a: *d.a, + }), + } + } +} + +impl<'a> From<&GPUProgrammableStage> for ProgrammableStageDescriptor<'a> { + fn from(stage: &GPUProgrammableStage) -> Self { + Self { + module: stage.module.id().0, + entry_point: stage + .entryPoint + .as_ref() + .map(|ep| Cow::Owned(ep.to_string())), + constants: Cow::Owned( + stage + .constants + .as_ref() + .map(|records| records.iter().map(|(k, v)| (k.0.clone(), **v)).collect()) + .unwrap_or_default(), + ), + zero_initialize_workgroup_memory: true, + } + } +} + +impl From<&GPUBindGroupEntry> for BindGroupEntry<'_> { + fn from(entry: &GPUBindGroupEntry) -> Self { + Self { + binding: entry.binding, + resource: match entry.resource { + GPUBindingResource::GPUSampler(ref s) => BindingResource::Sampler(s.id().0), + GPUBindingResource::GPUTextureView(ref t) => BindingResource::TextureView(t.id().0), + GPUBindingResource::GPUBufferBinding(ref b) => { + BindingResource::Buffer(BufferBinding { + buffer_id: b.buffer.id().0, + offset: b.offset, + size: b.size.and_then(wgt::BufferSize::new), + }) + }, + }, + } + } +} + +impl From<GPUTextureDimension> for wgt::TextureDimension { + fn from(dimension: GPUTextureDimension) -> Self { + match dimension { + GPUTextureDimension::_1d => wgt::TextureDimension::D1, + GPUTextureDimension::_2d => wgt::TextureDimension::D2, + GPUTextureDimension::_3d => wgt::TextureDimension::D3, + } + } +} diff --git a/components/script/dom/webgpu/gpudevice.rs b/components/script/dom/webgpu/gpudevice.rs new file mode 100644 index 00000000000..945c7491874 --- /dev/null +++ b/components/script/dom/webgpu/gpudevice.rs @@ -0,0 +1,652 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![allow(unsafe_code)] + +use std::borrow::Cow; +use std::cell::Cell; +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSObject}; +use webgpu::wgc::id::{BindGroupLayoutId, PipelineLayoutId}; +use webgpu::wgc::pipeline as wgpu_pipe; +use webgpu::wgc::pipeline::RenderPipelineDescriptor; +use webgpu::wgt::TextureFormat; +use webgpu::{ + wgt, PopError, WebGPU, WebGPUComputePipeline, WebGPURenderPipeline, WebGPURequest, + WebGPUResponse, +}; + +use super::gpu::AsyncWGPUListener; +use super::gpudevicelostinfo::GPUDeviceLostInfo; +use super::gpupipelineerror::GPUPipelineError; +use super::gpusupportedlimits::GPUSupportedLimits; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit; +use crate::dom::bindings::codegen::Bindings::EventTargetBinding::EventTargetMethods; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUBindGroupDescriptor, GPUBindGroupLayoutDescriptor, GPUBufferDescriptor, + GPUCommandEncoderDescriptor, GPUComputePipelineDescriptor, GPUDeviceLostReason, + GPUDeviceMethods, GPUErrorFilter, GPUPipelineErrorReason, GPUPipelineLayoutDescriptor, + GPURenderBundleEncoderDescriptor, GPURenderPipelineDescriptor, GPUSamplerDescriptor, + GPUShaderModuleDescriptor, GPUSupportedLimitsMethods, GPUTextureDescriptor, GPUTextureFormat, + GPUUncapturedErrorEventInit, GPUVertexStepMode, +}; +use crate::dom::bindings::codegen::UnionTypes::GPUPipelineLayoutOrGPUAutoLayoutMode; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::{DOMString, USVString}; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::eventtarget::EventTarget; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuadapter::GPUAdapter; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandencoder::GPUCommandEncoder; +use crate::dom::gpucomputepipeline::GPUComputePipeline; +use crate::dom::gpupipelinelayout::GPUPipelineLayout; +use crate::dom::gpuqueue::GPUQueue; +use crate::dom::gpurenderbundleencoder::GPURenderBundleEncoder; +use crate::dom::gpurenderpipeline::GPURenderPipeline; +use crate::dom::gpusampler::GPUSampler; +use crate::dom::gpushadermodule::GPUShaderModule; +use crate::dom::gpusupportedfeatures::GPUSupportedFeatures; +use crate::dom::gputexture::GPUTexture; +use crate::dom::gpuuncapturederrorevent::GPUUncapturedErrorEvent; +use crate::dom::promise::Promise; +use crate::dom::types::GPUError; +use crate::dom::webgpu::gpu::response_async; +use crate::realms::InRealm; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUDevice { + eventtarget: EventTarget, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + adapter: Dom<GPUAdapter>, + #[ignore_malloc_size_of = "mozjs"] + extensions: Heap<*mut JSObject>, + features: Dom<GPUSupportedFeatures>, + limits: Dom<GPUSupportedLimits>, + label: DomRefCell<USVString>, + #[no_trace] + device: webgpu::WebGPUDevice, + default_queue: Dom<GPUQueue>, + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-lost> + #[ignore_malloc_size_of = "promises are hard"] + lost_promise: DomRefCell<Rc<Promise>>, + valid: Cell<bool>, +} + +pub enum PipelineLayout { + Implicit(PipelineLayoutId, Vec<BindGroupLayoutId>), + Explicit(PipelineLayoutId), +} + +impl PipelineLayout { + pub fn explicit(&self) -> Option<PipelineLayoutId> { + match self { + PipelineLayout::Explicit(layout_id) => Some(*layout_id), + _ => None, + } + } + + pub fn implicit(self) -> Option<(PipelineLayoutId, Vec<BindGroupLayoutId>)> { + match self { + PipelineLayout::Implicit(layout_id, bind_group_layout_ids) => { + Some((layout_id, bind_group_layout_ids)) + }, + _ => None, + } + } +} + +impl GPUDevice { + #[allow(clippy::too_many_arguments)] + fn new_inherited( + channel: WebGPU, + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + features: &GPUSupportedFeatures, + limits: &GPUSupportedLimits, + device: webgpu::WebGPUDevice, + queue: &GPUQueue, + label: String, + lost_promise: Rc<Promise>, + ) -> Self { + Self { + eventtarget: EventTarget::new_inherited(), + channel, + adapter: Dom::from_ref(adapter), + extensions, + features: Dom::from_ref(features), + limits: Dom::from_ref(limits), + label: DomRefCell::new(USVString::from(label)), + device, + default_queue: Dom::from_ref(queue), + lost_promise: DomRefCell::new(lost_promise), + valid: Cell::new(true), + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + global: &GlobalScope, + channel: WebGPU, + adapter: &GPUAdapter, + extensions: Heap<*mut JSObject>, + features: wgt::Features, + limits: wgt::Limits, + device: webgpu::WebGPUDevice, + queue: webgpu::WebGPUQueue, + label: String, + can_gc: CanGc, + ) -> DomRoot<Self> { + let queue = GPUQueue::new(global, channel.clone(), queue); + let limits = GPUSupportedLimits::new(global, limits); + let features = GPUSupportedFeatures::Constructor(global, None, features, can_gc).unwrap(); + let lost_promise = Promise::new(global, can_gc); + let device = reflect_dom_object( + Box::new(GPUDevice::new_inherited( + channel, + adapter, + extensions, + &features, + &limits, + device, + &queue, + label, + lost_promise, + )), + global, + ); + queue.set_device(&device); + device + } +} + +impl GPUDevice { + pub fn id(&self) -> webgpu::WebGPUDevice { + self.device + } + + pub fn queue_id(&self) -> webgpu::WebGPUQueue { + self.default_queue.id() + } + + pub fn channel(&self) -> WebGPU { + self.channel.clone() + } + + pub fn dispatch_error(&self, error: webgpu::Error) { + if let Err(e) = self.channel.0.send(WebGPURequest::DispatchError { + device_id: self.device.0, + error, + }) { + warn!("Failed to send WebGPURequest::DispatchError due to {e:?}"); + } + } + + pub fn fire_uncaptured_error(&self, error: webgpu::Error, can_gc: CanGc) { + let error = GPUError::from_error(&self.global(), error, can_gc); + let ev = GPUUncapturedErrorEvent::new( + &self.global(), + DOMString::from("uncapturederror"), + &GPUUncapturedErrorEventInit { + error, + parent: EventInit::empty(), + }, + can_gc, + ); + let _ = self.eventtarget.DispatchEvent(ev.event(), can_gc); + } + + /// <https://gpuweb.github.io/gpuweb/#abstract-opdef-validate-texture-format-required-features> + /// + /// Validates that the device suppports required features, + /// and if so returns an ok containing wgpu's `TextureFormat` + pub fn validate_texture_format_required_features( + &self, + format: &GPUTextureFormat, + ) -> Fallible<TextureFormat> { + let texture_format: TextureFormat = (*format).into(); + if self + .features + .wgpu_features() + .contains(texture_format.required_features()) + { + Ok(texture_format) + } else { + Err(Error::Type(format!( + "{texture_format:?} is not supported by this GPUDevice" + ))) + } + } + + pub fn is_lost(&self) -> bool { + self.lost_promise.borrow().is_fulfilled() + } + + pub fn get_pipeline_layout_data( + &self, + layout: &GPUPipelineLayoutOrGPUAutoLayoutMode, + ) -> PipelineLayout { + if let GPUPipelineLayoutOrGPUAutoLayoutMode::GPUPipelineLayout(ref layout) = layout { + PipelineLayout::Explicit(layout.id().0) + } else { + let layout_id = self.global().wgpu_id_hub().create_pipeline_layout_id(); + let max_bind_grps = self.limits.MaxBindGroups(); + let mut bgl_ids = Vec::with_capacity(max_bind_grps as usize); + for _ in 0..max_bind_grps { + let bgl = self.global().wgpu_id_hub().create_bind_group_layout_id(); + bgl_ids.push(bgl); + } + PipelineLayout::Implicit(layout_id, bgl_ids) + } + } + + pub fn parse_render_pipeline<'a>( + &self, + descriptor: &GPURenderPipelineDescriptor, + ) -> Fallible<(PipelineLayout, RenderPipelineDescriptor<'a>)> { + let pipeline_layout = self.get_pipeline_layout_data(&descriptor.parent.layout); + + let desc = wgpu_pipe::RenderPipelineDescriptor { + label: (&descriptor.parent.parent).into(), + layout: pipeline_layout.explicit(), + cache: None, + vertex: wgpu_pipe::VertexState { + stage: (&descriptor.vertex.parent).into(), + buffers: Cow::Owned( + descriptor + .vertex + .buffers + .iter() + .map(|buffer| wgpu_pipe::VertexBufferLayout { + array_stride: buffer.arrayStride, + step_mode: match buffer.stepMode { + GPUVertexStepMode::Vertex => wgt::VertexStepMode::Vertex, + GPUVertexStepMode::Instance => wgt::VertexStepMode::Instance, + }, + attributes: Cow::Owned( + buffer + .attributes + .iter() + .map(|att| wgt::VertexAttribute { + format: att.format.into(), + offset: att.offset, + shader_location: att.shaderLocation, + }) + .collect::<Vec<_>>(), + ), + }) + .collect::<Vec<_>>(), + ), + }, + fragment: descriptor + .fragment + .as_ref() + .map(|stage| -> Fallible<wgpu_pipe::FragmentState> { + Ok(wgpu_pipe::FragmentState { + stage: (&stage.parent).into(), + targets: Cow::Owned( + stage + .targets + .iter() + .map(|state| { + self.validate_texture_format_required_features(&state.format) + .map(|format| { + Some(wgt::ColorTargetState { + format, + write_mask: wgt::ColorWrites::from_bits_retain( + state.writeMask, + ), + blend: state.blend.as_ref().map(|blend| { + wgt::BlendState { + color: (&blend.color).into(), + alpha: (&blend.alpha).into(), + } + }), + }) + }) + }) + .collect::<Result<Vec<_>, _>>()?, + ), + }) + }) + .transpose()?, + primitive: (&descriptor.primitive).into(), + depth_stencil: descriptor + .depthStencil + .as_ref() + .map(|dss_desc| { + self.validate_texture_format_required_features(&dss_desc.format) + .map(|format| wgt::DepthStencilState { + format, + depth_write_enabled: dss_desc.depthWriteEnabled, + depth_compare: dss_desc.depthCompare.into(), + stencil: wgt::StencilState { + front: wgt::StencilFaceState { + compare: dss_desc.stencilFront.compare.into(), + + fail_op: dss_desc.stencilFront.failOp.into(), + depth_fail_op: dss_desc.stencilFront.depthFailOp.into(), + pass_op: dss_desc.stencilFront.passOp.into(), + }, + back: wgt::StencilFaceState { + compare: dss_desc.stencilBack.compare.into(), + fail_op: dss_desc.stencilBack.failOp.into(), + depth_fail_op: dss_desc.stencilBack.depthFailOp.into(), + pass_op: dss_desc.stencilBack.passOp.into(), + }, + read_mask: dss_desc.stencilReadMask, + write_mask: dss_desc.stencilWriteMask, + }, + bias: wgt::DepthBiasState { + constant: dss_desc.depthBias, + slope_scale: *dss_desc.depthBiasSlopeScale, + clamp: *dss_desc.depthBiasClamp, + }, + }) + }) + .transpose()?, + multisample: wgt::MultisampleState { + count: descriptor.multisample.count, + mask: descriptor.multisample.mask as u64, + alpha_to_coverage_enabled: descriptor.multisample.alphaToCoverageEnabled, + }, + multiview: None, + }; + Ok((pipeline_layout, desc)) + } + + /// <https://gpuweb.github.io/gpuweb/#lose-the-device> + pub fn lose(&self, reason: GPUDeviceLostReason, msg: String) { + let lost_promise = &(*self.lost_promise.borrow()); + let global = &self.global(); + let lost = GPUDeviceLostInfo::new(global, msg.into(), reason); + lost_promise.resolve_native(&*lost); + } +} + +impl GPUDeviceMethods<crate::DomTypeHolder> for GPUDevice { + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-features> + fn Features(&self) -> DomRoot<GPUSupportedFeatures> { + DomRoot::from_ref(&self.features) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-limits> + fn Limits(&self) -> DomRoot<GPUSupportedLimits> { + DomRoot::from_ref(&self.limits) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-queue> + fn GetQueue(&self) -> DomRoot<GPUQueue> { + DomRoot::from_ref(&self.default_queue) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-lost> + fn Lost(&self) -> Rc<Promise> { + self.lost_promise.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbuffer> + fn CreateBuffer(&self, descriptor: &GPUBufferDescriptor) -> Fallible<DomRoot<GPUBuffer>> { + GPUBuffer::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#GPUDevice-createBindGroupLayout> + #[allow(non_snake_case)] + fn CreateBindGroupLayout( + &self, + descriptor: &GPUBindGroupLayoutDescriptor, + ) -> Fallible<DomRoot<GPUBindGroupLayout>> { + GPUBindGroupLayout::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createpipelinelayout> + fn CreatePipelineLayout( + &self, + descriptor: &GPUPipelineLayoutDescriptor, + ) -> DomRoot<GPUPipelineLayout> { + GPUPipelineLayout::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createbindgroup> + fn CreateBindGroup(&self, descriptor: &GPUBindGroupDescriptor) -> DomRoot<GPUBindGroup> { + GPUBindGroup::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createshadermodule> + fn CreateShaderModule( + &self, + descriptor: RootedTraceableBox<GPUShaderModuleDescriptor>, + comp: InRealm, + can_gc: CanGc, + ) -> DomRoot<GPUShaderModule> { + GPUShaderModule::create(self, descriptor, comp, can_gc) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcomputepipeline> + fn CreateComputePipeline( + &self, + descriptor: &GPUComputePipelineDescriptor, + ) -> DomRoot<GPUComputePipeline> { + let compute_pipeline = GPUComputePipeline::create(self, descriptor, None); + GPUComputePipeline::new( + &self.global(), + compute_pipeline, + descriptor.parent.parent.label.clone(), + self, + ) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcomputepipelineasync> + fn CreateComputePipelineAsync( + &self, + descriptor: &GPUComputePipelineDescriptor, + comp: InRealm, + can_gc: CanGc, + ) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(comp, can_gc); + let sender = response_async(&promise, self); + GPUComputePipeline::create(self, descriptor, Some(sender)); + promise + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcommandencoder> + fn CreateCommandEncoder( + &self, + descriptor: &GPUCommandEncoderDescriptor, + ) -> DomRoot<GPUCommandEncoder> { + GPUCommandEncoder::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createtexture> + fn CreateTexture(&self, descriptor: &GPUTextureDescriptor) -> Fallible<DomRoot<GPUTexture>> { + GPUTexture::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createsampler> + fn CreateSampler(&self, descriptor: &GPUSamplerDescriptor) -> DomRoot<GPUSampler> { + GPUSampler::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderpipeline> + fn CreateRenderPipeline( + &self, + descriptor: &GPURenderPipelineDescriptor, + ) -> Fallible<DomRoot<GPURenderPipeline>> { + let (pipeline_layout, desc) = self.parse_render_pipeline(descriptor)?; + let render_pipeline = GPURenderPipeline::create(self, pipeline_layout, desc, None)?; + Ok(GPURenderPipeline::new( + &self.global(), + render_pipeline, + descriptor.parent.parent.label.clone(), + self, + )) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderpipelineasync> + fn CreateRenderPipelineAsync( + &self, + descriptor: &GPURenderPipelineDescriptor, + comp: InRealm, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + let (implicit_ids, desc) = self.parse_render_pipeline(descriptor)?; + let promise = Promise::new_in_current_realm(comp, can_gc); + let sender = response_async(&promise, self); + GPURenderPipeline::create(self, implicit_ids, desc, Some(sender))?; + Ok(promise) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderbundleencoder> + fn CreateRenderBundleEncoder( + &self, + descriptor: &GPURenderBundleEncoderDescriptor, + ) -> Fallible<DomRoot<GPURenderBundleEncoder>> { + GPURenderBundleEncoder::create(self, descriptor) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-pusherrorscope> + fn PushErrorScope(&self, filter: GPUErrorFilter) { + if self + .channel + .0 + .send(WebGPURequest::PushErrorScope { + device_id: self.device.0, + filter: filter.as_webgpu(), + }) + .is_err() + { + warn!("Failed sending WebGPURequest::PushErrorScope"); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-poperrorscope> + fn PopErrorScope(&self, comp: InRealm, can_gc: CanGc) -> Rc<Promise> { + let promise = Promise::new_in_current_realm(comp, can_gc); + let sender = response_async(&promise, self); + if self + .channel + .0 + .send(WebGPURequest::PopErrorScope { + device_id: self.device.0, + sender, + }) + .is_err() + { + warn!("Error when sending WebGPURequest::PopErrorScope"); + } + promise + } + + // https://gpuweb.github.io/gpuweb/#dom-gpudevice-onuncapturederror + event_handler!(uncapturederror, GetOnuncapturederror, SetOnuncapturederror); + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-destroy> + fn Destroy(&self) { + if self.valid.get() { + self.valid.set(false); + + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DestroyDevice(self.device.0)) + { + warn!("Failed to send DestroyDevice ({:?}) ({})", self.device.0, e); + } + } + } +} + +impl AsyncWGPUListener for GPUDevice { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, can_gc: CanGc) { + match response { + WebGPUResponse::PoppedErrorScope(result) => match result { + Ok(None) | Err(PopError::Lost) => promise.resolve_native(&None::<Option<GPUError>>), + Err(PopError::Empty) => promise.reject_error(Error::Operation), + Ok(Some(error)) => { + let error = GPUError::from_error(&self.global(), error, can_gc); + promise.resolve_native(&error); + }, + }, + WebGPUResponse::ComputePipeline(result) => match result { + Ok(pipeline) => promise.resolve_native(&GPUComputePipeline::new( + &self.global(), + WebGPUComputePipeline(pipeline.id), + pipeline.label.into(), + self, + )), + Err(webgpu::Error::Validation(msg)) => { + promise.reject_native(&GPUPipelineError::new( + &self.global(), + msg.into(), + GPUPipelineErrorReason::Validation, + can_gc, + )) + }, + Err(webgpu::Error::OutOfMemory(msg) | webgpu::Error::Internal(msg)) => promise + .reject_native(&GPUPipelineError::new( + &self.global(), + msg.into(), + GPUPipelineErrorReason::Internal, + can_gc, + )), + }, + WebGPUResponse::RenderPipeline(result) => match result { + Ok(pipeline) => promise.resolve_native(&GPURenderPipeline::new( + &self.global(), + WebGPURenderPipeline(pipeline.id), + pipeline.label.into(), + self, + )), + Err(webgpu::Error::Validation(msg)) => { + promise.reject_native(&GPUPipelineError::new( + &self.global(), + msg.into(), + GPUPipelineErrorReason::Validation, + can_gc, + )) + }, + Err(webgpu::Error::OutOfMemory(msg) | webgpu::Error::Internal(msg)) => promise + .reject_native(&GPUPipelineError::new( + &self.global(), + msg.into(), + GPUPipelineErrorReason::Internal, + can_gc, + )), + }, + _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUDevice"), + } + } +} + +impl Drop for GPUDevice { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropDevice(self.device.0)) + { + warn!("Failed to send DropDevice ({:?}) ({})", self.device.0, e); + } + } +} diff --git a/components/script/dom/webgpu/gpudevicelostinfo.rs b/components/script/dom/webgpu/gpudevicelostinfo.rs new file mode 100644 index 00000000000..ea33986a84f --- /dev/null +++ b/components/script/dom/webgpu/gpudevicelostinfo.rs @@ -0,0 +1,55 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![allow(dead_code)] + +use dom_struct::dom_struct; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUDeviceLostInfoMethods, GPUDeviceLostReason, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; + +#[dom_struct] +pub struct GPUDeviceLostInfo { + reflector_: Reflector, + message: DOMString, + reason: GPUDeviceLostReason, +} + +impl GPUDeviceLostInfo { + fn new_inherited(message: DOMString, reason: GPUDeviceLostReason) -> Self { + Self { + reflector_: Reflector::new(), + message, + reason, + } + } + + pub fn new( + global: &GlobalScope, + message: DOMString, + reason: GPUDeviceLostReason, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUDeviceLostInfo::new_inherited(message, reason)), + global, + ) + } +} + +impl GPUDeviceLostInfoMethods<crate::DomTypeHolder> for GPUDeviceLostInfo { + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevicelostinfo-message> + fn Message(&self) -> DOMString { + self.message.clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevicelostinfo-reason> + fn Reason(&self) -> GPUDeviceLostReason { + self.reason + } +} diff --git a/components/script/dom/webgpu/gpuerror.rs b/components/script/dom/webgpu/gpuerror.rs new file mode 100644 index 00000000000..a4de08cc18b --- /dev/null +++ b/components/script/dom/webgpu/gpuerror.rs @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; +use webgpu::{Error, ErrorFilter}; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{GPUErrorFilter, GPUErrorMethods}; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::types::{GPUInternalError, GPUOutOfMemoryError, GPUValidationError}; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUError { + reflector_: Reflector, + message: DOMString, +} + +impl GPUError { + pub fn new_inherited(message: DOMString) -> Self { + Self { + reflector_: Reflector::new(), + message, + } + } + + #[allow(dead_code)] + pub fn new(global: &GlobalScope, message: DOMString, can_gc: CanGc) -> DomRoot<Self> { + Self::new_with_proto(global, None, message, can_gc) + } + + #[allow(dead_code)] + pub fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + message: DOMString, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(GPUError::new_inherited(message)), + global, + proto, + can_gc, + ) + } + + pub fn from_error(global: &GlobalScope, error: Error, can_gc: CanGc) -> DomRoot<Self> { + match error { + Error::Validation(msg) => DomRoot::upcast(GPUValidationError::new_with_proto( + global, + None, + DOMString::from_string(msg), + can_gc, + )), + Error::OutOfMemory(msg) => DomRoot::upcast(GPUOutOfMemoryError::new_with_proto( + global, + None, + DOMString::from_string(msg), + can_gc, + )), + Error::Internal(msg) => DomRoot::upcast(GPUInternalError::new_with_proto( + global, + None, + DOMString::from_string(msg), + can_gc, + )), + } + } +} + +impl GPUErrorMethods<crate::DomTypeHolder> for GPUError { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuerror-message> + fn Message(&self) -> DOMString { + self.message.clone() + } +} + +impl From<ErrorFilter> for GPUErrorFilter { + fn from(filter: ErrorFilter) -> Self { + match filter { + ErrorFilter::Validation => GPUErrorFilter::Validation, + ErrorFilter::OutOfMemory => GPUErrorFilter::Out_of_memory, + ErrorFilter::Internal => GPUErrorFilter::Internal, + } + } +} + +impl GPUErrorFilter { + pub fn as_webgpu(&self) -> ErrorFilter { + match self { + GPUErrorFilter::Validation => ErrorFilter::Validation, + GPUErrorFilter::Out_of_memory => ErrorFilter::OutOfMemory, + GPUErrorFilter::Internal => ErrorFilter::Internal, + } + } +} diff --git a/components/script/dom/webgpu/gpuinternalerror.rs b/components/script/dom/webgpu/gpuinternalerror.rs new file mode 100644 index 00000000000..c622d4ff495 --- /dev/null +++ b/components/script/dom/webgpu/gpuinternalerror.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUInternalError_Binding::GPUInternalErrorMethods; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::types::GPUError; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUInternalError { + gpu_error: GPUError, +} + +impl GPUInternalError { + fn new_inherited(message: DOMString) -> Self { + Self { + gpu_error: GPUError::new_inherited(message), + } + } + + pub fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + message: DOMString, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited(message)), + global, + proto, + can_gc, + ) + } +} + +impl GPUInternalErrorMethods<crate::DomTypeHolder> for GPUInternalError { + /// <https://gpuweb.github.io/gpuweb/#dom-GPUInternalError-GPUInternalError> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + message: DOMString, + ) -> DomRoot<Self> { + Self::new_with_proto(global, proto, message, can_gc) + } +} diff --git a/components/script/dom/webgpu/gpumapmode.rs b/components/script/dom/webgpu/gpumapmode.rs new file mode 100644 index 00000000000..4db3455483d --- /dev/null +++ b/components/script/dom/webgpu/gpumapmode.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; + +use crate::dom::bindings::reflector::Reflector; + +#[dom_struct] +pub struct GPUMapMode { + reflector_: Reflector, +} diff --git a/components/script/dom/webgpu/gpuoutofmemoryerror.rs b/components/script/dom/webgpu/gpuoutofmemoryerror.rs new file mode 100644 index 00000000000..01c77ef8185 --- /dev/null +++ b/components/script/dom/webgpu/gpuoutofmemoryerror.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUOutOfMemoryError_Binding::GPUOutOfMemoryErrorMethods; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::types::GPUError; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUOutOfMemoryError { + gpu_error: GPUError, +} + +impl GPUOutOfMemoryError { + fn new_inherited(message: DOMString) -> Self { + Self { + gpu_error: GPUError::new_inherited(message), + } + } + + pub fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + message: DOMString, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited(message)), + global, + proto, + can_gc, + ) + } +} + +impl GPUOutOfMemoryErrorMethods<crate::DomTypeHolder> for GPUOutOfMemoryError { + /// <https://gpuweb.github.io/gpuweb/#dom-GPUOutOfMemoryError-GPUOutOfMemoryError> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + message: DOMString, + ) -> DomRoot<Self> { + Self::new_with_proto(global, proto, message, can_gc) + } +} diff --git a/components/script/dom/webgpu/gpupipelineerror.rs b/components/script/dom/webgpu/gpupipelineerror.rs new file mode 100644 index 00000000000..ae2b57bc781 --- /dev/null +++ b/components/script/dom/webgpu/gpupipelineerror.rs @@ -0,0 +1,74 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUPipelineErrorInit, GPUPipelineErrorMethods, GPUPipelineErrorReason, +}; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::domexception::DOMException; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::CanGc; + +/// <https://gpuweb.github.io/gpuweb/#gpupipelineerror> +#[dom_struct] +pub struct GPUPipelineError { + exception: DOMException, + reason: GPUPipelineErrorReason, +} + +impl GPUPipelineError { + fn new_inherited(message: DOMString, reason: GPUPipelineErrorReason) -> Self { + Self { + exception: DOMException::new_inherited(message, "GPUPipelineError".into()), + reason, + } + } + + pub fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + message: DOMString, + reason: GPUPipelineErrorReason, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited(message, reason)), + global, + proto, + can_gc, + ) + } + + pub fn new( + global: &GlobalScope, + message: DOMString, + reason: GPUPipelineErrorReason, + can_gc: CanGc, + ) -> DomRoot<Self> { + Self::new_with_proto(global, None, message, reason, can_gc) + } +} + +impl GPUPipelineErrorMethods<crate::DomTypeHolder> for GPUPipelineError { + /// <https://gpuweb.github.io/gpuweb/#dom-gpupipelineerror-constructor> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + message: DOMString, + options: &GPUPipelineErrorInit, + ) -> DomRoot<Self> { + Self::new_with_proto(global, proto, message, options.reason, can_gc) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpupipelineerror-reason> + fn Reason(&self) -> GPUPipelineErrorReason { + self.reason + } +} diff --git a/components/script/dom/webgpu/gpupipelinelayout.rs b/components/script/dom/webgpu/gpupipelinelayout.rs new file mode 100644 index 00000000000..c1cbbdb72ab --- /dev/null +++ b/components/script/dom/webgpu/gpupipelinelayout.rs @@ -0,0 +1,142 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; + +use dom_struct::dom_struct; +use webgpu::wgc::binding_model::PipelineLayoutDescriptor; +use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPUPipelineLayout, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUPipelineLayoutDescriptor, GPUPipelineLayoutMethods, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::GPUDevice; + +#[dom_struct] +pub struct GPUPipelineLayout { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + pipeline_layout: WebGPUPipelineLayout, + #[no_trace] + bind_group_layouts: Vec<WebGPUBindGroupLayout>, +} + +impl GPUPipelineLayout { + fn new_inherited( + channel: WebGPU, + pipeline_layout: WebGPUPipelineLayout, + label: USVString, + bgls: Vec<WebGPUBindGroupLayout>, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + pipeline_layout, + bind_group_layouts: bgls, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + pipeline_layout: WebGPUPipelineLayout, + label: USVString, + bgls: Vec<WebGPUBindGroupLayout>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUPipelineLayout::new_inherited( + channel, + pipeline_layout, + label, + bgls, + )), + global, + ) + } +} + +impl GPUPipelineLayout { + pub fn id(&self) -> WebGPUPipelineLayout { + self.pipeline_layout + } + + pub fn bind_group_layouts(&self) -> Vec<WebGPUBindGroupLayout> { + self.bind_group_layouts.clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createpipelinelayout> + pub fn create( + device: &GPUDevice, + descriptor: &GPUPipelineLayoutDescriptor, + ) -> DomRoot<GPUPipelineLayout> { + let bgls = descriptor + .bindGroupLayouts + .iter() + .map(|each| each.id()) + .collect::<Vec<_>>(); + + let desc = PipelineLayoutDescriptor { + label: (&descriptor.parent).into(), + bind_group_layouts: Cow::Owned(bgls.iter().map(|l| l.0).collect::<Vec<_>>()), + push_constant_ranges: Cow::Owned(vec![]), + }; + + let pipeline_layout_id = device.global().wgpu_id_hub().create_pipeline_layout_id(); + device + .channel() + .0 + .send(WebGPURequest::CreatePipelineLayout { + device_id: device.id().0, + pipeline_layout_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU PipelineLayout"); + + let pipeline_layout = WebGPUPipelineLayout(pipeline_layout_id); + GPUPipelineLayout::new( + &device.global(), + device.channel().clone(), + pipeline_layout, + descriptor.parent.label.clone(), + bgls, + ) + } +} + +impl GPUPipelineLayoutMethods<crate::DomTypeHolder> for GPUPipelineLayout { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} + +impl Drop for GPUPipelineLayout { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropPipelineLayout(self.pipeline_layout.0)) + { + warn!( + "Failed to send DropPipelineLayout ({:?}) ({})", + self.pipeline_layout.0, e + ); + } + } +} diff --git a/components/script/dom/webgpu/gpuqueryset.rs b/components/script/dom/webgpu/gpuqueryset.rs new file mode 100644 index 00000000000..d2c3eb03336 --- /dev/null +++ b/components/script/dom/webgpu/gpuqueryset.rs @@ -0,0 +1,35 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +#![allow(dead_code)] // this file is stub + +use dom_struct::dom_struct; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUQuerySetMethods; +use crate::dom::bindings::reflector::Reflector; +use crate::dom::bindings::str::USVString; + +#[dom_struct] +pub struct GPUQuerySet { + reflector_: Reflector, + // #[ignore_malloc_size_of = "defined in wgpu-types"] +} + +// TODO: wgpu does not expose right fields right now +impl GPUQuerySetMethods<crate::DomTypeHolder> for GPUQuerySet { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueryset-destroy> + fn Destroy(&self) { + todo!() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + todo!() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, _value: USVString) { + todo!() + } +} diff --git a/components/script/dom/webgpu/gpuqueue.rs b/components/script/dom/webgpu/gpuqueue.rs new file mode 100644 index 00000000000..403628737d3 --- /dev/null +++ b/components/script/dom/webgpu/gpuqueue.rs @@ -0,0 +1,225 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::rc::Rc; + +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; +use webgpu::{wgt, WebGPU, WebGPUQueue, WebGPURequest, WebGPUResponse}; + +use super::gpu::{response_async, AsyncWGPUListener}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUExtent3D, GPUImageCopyTexture, GPUImageDataLayout, GPUQueueMethods, GPUSize64, +}; +use crate::dom::bindings::codegen::UnionTypes::ArrayBufferViewOrArrayBuffer as BufferSource; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandbuffer::GPUCommandBuffer; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::promise::Promise; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUQueue { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + device: DomRefCell<Option<Dom<GPUDevice>>>, + label: DomRefCell<USVString>, + #[no_trace] + queue: WebGPUQueue, +} + +impl GPUQueue { + fn new_inherited(channel: WebGPU, queue: WebGPUQueue) -> Self { + GPUQueue { + channel, + reflector_: Reflector::new(), + device: DomRefCell::new(None), + label: DomRefCell::new(USVString::default()), + queue, + } + } + + pub fn new(global: &GlobalScope, channel: WebGPU, queue: WebGPUQueue) -> DomRoot<Self> { + reflect_dom_object(Box::new(GPUQueue::new_inherited(channel, queue)), global) + } +} + +impl GPUQueue { + pub fn set_device(&self, device: &GPUDevice) { + *self.device.borrow_mut() = Some(Dom::from_ref(device)); + } + + pub fn id(&self) -> WebGPUQueue { + self.queue + } +} + +impl GPUQueueMethods<crate::DomTypeHolder> for GPUQueue { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-submit> + fn Submit(&self, command_buffers: Vec<DomRoot<GPUCommandBuffer>>) { + let command_buffers = command_buffers.iter().map(|cb| cb.id().0).collect(); + self.channel + .0 + .send(WebGPURequest::Submit { + device_id: self.device.borrow().as_ref().unwrap().id().0, + queue_id: self.queue.0, + command_buffers, + }) + .unwrap(); + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writebuffer> + #[allow(unsafe_code)] + fn WriteBuffer( + &self, + buffer: &GPUBuffer, + buffer_offset: GPUSize64, + data: BufferSource, + data_offset: GPUSize64, + size: Option<GPUSize64>, + ) -> Fallible<()> { + // Step 1 + let sizeof_element: usize = match data { + BufferSource::ArrayBufferView(ref d) => d.get_array_type().byte_size().unwrap_or(1), + BufferSource::ArrayBuffer(_) => 1, + }; + let data = match data { + BufferSource::ArrayBufferView(d) => d.to_vec(), + BufferSource::ArrayBuffer(d) => d.to_vec(), + }; + // Step 2 + let data_size: usize = data.len() / sizeof_element; + debug_assert_eq!(data.len() % sizeof_element, 0); + // Step 3 + let content_size = if let Some(s) = size { + s + } else { + (data_size as GPUSize64) + .checked_sub(data_offset) + .ok_or(Error::Operation)? + }; + + // Step 4 + let valid = data_offset + content_size <= data_size as u64 && + content_size * sizeof_element as u64 % wgt::COPY_BUFFER_ALIGNMENT == 0; + if !valid { + return Err(Error::Operation); + } + + // Step 5&6 + let contents = IpcSharedMemory::from_bytes( + &data[(data_offset as usize) * sizeof_element.. + ((data_offset + content_size) as usize) * sizeof_element], + ); + if let Err(e) = self.channel.0.send(WebGPURequest::WriteBuffer { + device_id: self.device.borrow().as_ref().unwrap().id().0, + queue_id: self.queue.0, + buffer_id: buffer.id().0, + buffer_offset, + data: contents, + }) { + warn!("Failed to send WriteBuffer({:?}) ({})", buffer.id(), e); + return Err(Error::Operation); + } + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-writetexture> + fn WriteTexture( + &self, + destination: &GPUImageCopyTexture, + data: BufferSource, + data_layout: &GPUImageDataLayout, + size: GPUExtent3D, + ) -> Fallible<()> { + let (bytes, len) = match data { + BufferSource::ArrayBufferView(d) => (d.to_vec(), d.len() as u64), + BufferSource::ArrayBuffer(d) => (d.to_vec(), d.len() as u64), + }; + let valid = data_layout.offset <= len; + + if !valid { + return Err(Error::Operation); + } + + let texture_cv = destination.try_into()?; + let texture_layout = data_layout.into(); + let write_size = (&size).try_into()?; + let final_data = IpcSharedMemory::from_bytes(&bytes); + + if let Err(e) = self.channel.0.send(WebGPURequest::WriteTexture { + device_id: self.device.borrow().as_ref().unwrap().id().0, + queue_id: self.queue.0, + texture_cv, + data_layout: texture_layout, + size: write_size, + data: final_data, + }) { + warn!( + "Failed to send WriteTexture({:?}) ({})", + destination.texture.id().0, + e + ); + return Err(Error::Operation); + } + + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuqueue-onsubmittedworkdone> + fn OnSubmittedWorkDone(&self, can_gc: CanGc) -> Rc<Promise> { + let global = self.global(); + let promise = Promise::new(&global, can_gc); + let sender = response_async(&promise, self); + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::QueueOnSubmittedWorkDone { + sender, + queue_id: self.queue.0, + }) + { + warn!("QueueOnSubmittedWorkDone failed with {e}") + } + promise + } +} + +impl AsyncWGPUListener for GPUQueue { + fn handle_response( + &self, + response: webgpu::WebGPUResponse, + promise: &Rc<Promise>, + _can_gc: CanGc, + ) { + match response { + WebGPUResponse::SubmittedWorkDone => { + promise.resolve_native(&()); + }, + _ => { + warn!("GPUQueue received wrong WebGPUResponse"); + promise.reject_error(Error::Operation); + }, + } + } +} diff --git a/components/script/dom/webgpu/gpurenderbundle.rs b/components/script/dom/webgpu/gpurenderbundle.rs new file mode 100644 index 00000000000..aae120b970b --- /dev/null +++ b/components/script/dom/webgpu/gpurenderbundle.rs @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPUDevice, WebGPURenderBundle, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPURenderBundleMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; + +#[dom_struct] +pub struct GPURenderBundle { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + #[no_trace] + device: WebGPUDevice, + #[no_trace] + render_bundle: WebGPURenderBundle, + label: DomRefCell<USVString>, +} + +impl GPURenderBundle { + fn new_inherited( + render_bundle: WebGPURenderBundle, + device: WebGPUDevice, + channel: WebGPU, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + render_bundle, + device, + channel, + label: DomRefCell::new(label), + } + } + + pub fn new( + global: &GlobalScope, + render_bundle: WebGPURenderBundle, + device: WebGPUDevice, + channel: WebGPU, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderBundle::new_inherited( + render_bundle, + device, + channel, + label, + )), + global, + ) + } +} + +impl GPURenderBundle { + pub fn id(&self) -> WebGPURenderBundle { + self.render_bundle + } +} + +impl GPURenderBundleMethods<crate::DomTypeHolder> for GPURenderBundle { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} + +impl Drop for GPURenderBundle { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropRenderBundle(self.render_bundle.0)) + { + warn!( + "Failed to send DropRenderBundle ({:?}) ({})", + self.render_bundle.0, e + ); + } + } +} diff --git a/components/script/dom/webgpu/gpurenderbundleencoder.rs b/components/script/dom/webgpu/gpurenderbundleencoder.rs new file mode 100644 index 00000000000..3f23db60706 --- /dev/null +++ b/components/script/dom/webgpu/gpurenderbundleencoder.rs @@ -0,0 +1,279 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::borrow::Cow; + +use dom_struct::dom_struct; +use webgpu::wgc::command::{ + bundle_ffi as wgpu_bundle, RenderBundleEncoder, RenderBundleEncoderDescriptor, +}; +use webgpu::{wgt, WebGPU, WebGPURenderBundle, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUIndexFormat, GPURenderBundleDescriptor, GPURenderBundleEncoderDescriptor, + GPURenderBundleEncoderMethods, +}; +use crate::dom::bindings::import::module::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::gpurenderbundle::GPURenderBundle; +use crate::dom::gpurenderpipeline::GPURenderPipeline; + +#[dom_struct] +pub struct GPURenderBundleEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + device: Dom<GPUDevice>, + #[ignore_malloc_size_of = "defined in wgpu-core"] + #[no_trace] + render_bundle_encoder: DomRefCell<Option<RenderBundleEncoder>>, + label: DomRefCell<USVString>, +} + +impl GPURenderBundleEncoder { + fn new_inherited( + render_bundle_encoder: RenderBundleEncoder, + device: &GPUDevice, + channel: WebGPU, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + render_bundle_encoder: DomRefCell::new(Some(render_bundle_encoder)), + device: Dom::from_ref(device), + channel, + label: DomRefCell::new(label), + } + } + + pub fn new( + global: &GlobalScope, + render_bundle_encoder: RenderBundleEncoder, + device: &GPUDevice, + channel: WebGPU, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderBundleEncoder::new_inherited( + render_bundle_encoder, + device, + channel, + label, + )), + global, + ) + } +} + +impl GPURenderBundleEncoder { + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderbundleencoder> + pub fn create( + device: &GPUDevice, + descriptor: &GPURenderBundleEncoderDescriptor, + ) -> Fallible<DomRoot<GPURenderBundleEncoder>> { + let desc = RenderBundleEncoderDescriptor { + label: (&descriptor.parent.parent).into(), + color_formats: Cow::Owned( + descriptor + .parent + .colorFormats + .iter() + .map(|format| { + device + .validate_texture_format_required_features(format) + .map(Some) + }) + .collect::<Fallible<Vec<_>>>()?, + ), + depth_stencil: descriptor + .parent + .depthStencilFormat + .map(|dsf| { + device + .validate_texture_format_required_features(&dsf) + .map(|format| wgt::RenderBundleDepthStencil { + format, + depth_read_only: descriptor.depthReadOnly, + stencil_read_only: descriptor.stencilReadOnly, + }) + }) + .transpose()?, + sample_count: descriptor.parent.sampleCount, + multiview: None, + }; + + // Handle error gracefully + let render_bundle_encoder = RenderBundleEncoder::new(&desc, device.id().0, None).unwrap(); + + Ok(GPURenderBundleEncoder::new( + &device.global(), + render_bundle_encoder, + device, + device.channel().clone(), + descriptor.parent.parent.label.clone(), + )) + } +} + +impl GPURenderBundleEncoderMethods<crate::DomTypeHolder> for GPURenderBundleEncoder { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup> + #[allow(unsafe_code)] + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, dynamic_offsets: Vec<u32>) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + unsafe { + wgpu_bundle::wgpu_render_bundle_set_bind_group( + encoder, + index, + Some(bind_group.id().0), + dynamic_offsets.as_ptr(), + dynamic_offsets.len(), + ) + }; + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setpipeline> + fn SetPipeline(&self, pipeline: &GPURenderPipeline) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_pipeline(encoder, pipeline.id().0); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setindexbuffer> + fn SetIndexBuffer( + &self, + buffer: &GPUBuffer, + index_format: GPUIndexFormat, + offset: u64, + size: u64, + ) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_index_buffer( + encoder, + buffer.id().0, + match index_format { + GPUIndexFormat::Uint16 => wgt::IndexFormat::Uint16, + GPUIndexFormat::Uint32 => wgt::IndexFormat::Uint32, + }, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setvertexbuffer> + fn SetVertexBuffer(&self, slot: u32, buffer: &GPUBuffer, offset: u64, size: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_set_vertex_buffer( + encoder, + slot, + buffer.id().0, + offset, + wgt::BufferSize::new(size), + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-draw> + fn Draw(&self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw( + encoder, + vertex_count, + instance_count, + first_vertex, + first_instance, + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexed> + fn DrawIndexed( + &self, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, + ) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw_indexed( + encoder, + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindirect> + fn DrawIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw_indirect( + encoder, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexedindirect> + fn DrawIndexedIndirect(&self, indirect_buffer: &GPUBuffer, indirect_offset: u64) { + if let Some(encoder) = self.render_bundle_encoder.borrow_mut().as_mut() { + wgpu_bundle::wgpu_render_bundle_draw_indexed_indirect( + encoder, + indirect_buffer.id().0, + indirect_offset, + ); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderbundleencoder-finish> + fn Finish(&self, descriptor: &GPURenderBundleDescriptor) -> DomRoot<GPURenderBundle> { + let desc = wgt::RenderBundleDescriptor { + label: (&descriptor.parent).into(), + }; + let encoder = self.render_bundle_encoder.borrow_mut().take().unwrap(); + let render_bundle_id = self.global().wgpu_id_hub().create_render_bundle_id(); + + self.channel + .0 + .send(WebGPURequest::RenderBundleEncoderFinish { + render_bundle_encoder: encoder, + descriptor: desc, + render_bundle_id, + device_id: self.device.id().0, + }) + .expect("Failed to send RenderBundleEncoderFinish"); + + let render_bundle = WebGPURenderBundle(render_bundle_id); + GPURenderBundle::new( + &self.global(), + render_bundle, + self.device.id(), + self.channel.clone(), + descriptor.parent.label.clone(), + ) + } +} diff --git a/components/script/dom/webgpu/gpurenderpassencoder.rs b/components/script/dom/webgpu/gpurenderpassencoder.rs new file mode 100644 index 00000000000..ea77df69d02 --- /dev/null +++ b/components/script/dom/webgpu/gpurenderpassencoder.rs @@ -0,0 +1,249 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::{wgt, RenderCommand, WebGPU, WebGPURenderPass, WebGPURequest}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUColor, GPUIndexFormat, GPURenderPassEncoderMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::num::Finite; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgroup::GPUBindGroup; +use crate::dom::gpubuffer::GPUBuffer; +use crate::dom::gpucommandencoder::GPUCommandEncoder; +use crate::dom::gpurenderbundle::GPURenderBundle; +use crate::dom::gpurenderpipeline::GPURenderPipeline; + +#[dom_struct] +pub struct GPURenderPassEncoder { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + render_pass: WebGPURenderPass, + command_encoder: Dom<GPUCommandEncoder>, +} + +impl GPURenderPassEncoder { + fn new_inherited( + channel: WebGPU, + render_pass: WebGPURenderPass, + parent: &GPUCommandEncoder, + label: USVString, + ) -> Self { + Self { + channel, + reflector_: Reflector::new(), + label: DomRefCell::new(label), + render_pass, + command_encoder: Dom::from_ref(parent), + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + render_pass: WebGPURenderPass, + parent: &GPUCommandEncoder, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderPassEncoder::new_inherited( + channel, + render_pass, + parent, + label, + )), + global, + ) + } + + fn send_render_command(&self, render_command: RenderCommand) { + if let Err(e) = self.channel.0.send(WebGPURequest::RenderPassCommand { + render_pass_id: self.render_pass.0, + render_command, + device_id: self.command_encoder.device_id().0, + }) { + warn!("Error sending WebGPURequest::RenderPassCommand: {e:?}") + } + } +} + +impl GPURenderPassEncoderMethods<crate::DomTypeHolder> for GPURenderPassEncoder { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup> + fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup, offsets: Vec<u32>) { + self.send_render_command(RenderCommand::SetBindGroup { + index, + bind_group_id: bind_group.id().0, + offsets, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setviewport> + fn SetViewport( + &self, + x: Finite<f32>, + y: Finite<f32>, + width: Finite<f32>, + height: Finite<f32>, + min_depth: Finite<f32>, + max_depth: Finite<f32>, + ) { + self.send_render_command(RenderCommand::SetViewport { + x: *x, + y: *y, + width: *width, + height: *height, + min_depth: *min_depth, + max_depth: *max_depth, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setscissorrect> + fn SetScissorRect(&self, x: u32, y: u32, width: u32, height: u32) { + self.send_render_command(RenderCommand::SetScissorRect { + x, + y, + width, + height, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setblendcolor> + fn SetBlendConstant(&self, color: GPUColor) -> Fallible<()> { + self.send_render_command(RenderCommand::SetBlendConstant((&color).try_into()?)); + Ok(()) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-setstencilreference> + fn SetStencilReference(&self, reference: u32) { + self.send_render_command(RenderCommand::SetStencilReference(reference)) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-end> + fn End(&self) { + if let Err(e) = self.channel.0.send(WebGPURequest::EndRenderPass { + render_pass_id: self.render_pass.0, + device_id: self.command_encoder.device_id().0, + command_encoder_id: self.command_encoder.id().0, + }) { + warn!("Failed to send WebGPURequest::EndRenderPass: {e:?}"); + } + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setpipeline> + fn SetPipeline(&self, pipeline: &GPURenderPipeline) { + self.send_render_command(RenderCommand::SetPipeline(pipeline.id().0)) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurendercommandsmixin-setindexbuffer> + fn SetIndexBuffer( + &self, + buffer: &GPUBuffer, + index_format: GPUIndexFormat, + offset: u64, + size: u64, + ) { + self.send_render_command(RenderCommand::SetIndexBuffer { + buffer_id: buffer.id().0, + index_format: match index_format { + GPUIndexFormat::Uint16 => wgt::IndexFormat::Uint16, + GPUIndexFormat::Uint32 => wgt::IndexFormat::Uint32, + }, + offset, + size: wgt::BufferSize::new(size), + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setvertexbuffer> + fn SetVertexBuffer(&self, slot: u32, buffer: &GPUBuffer, offset: u64, size: u64) { + self.send_render_command(RenderCommand::SetVertexBuffer { + slot, + buffer_id: buffer.id().0, + offset, + size: wgt::BufferSize::new(size), + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-draw> + fn Draw(&self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) { + self.send_render_command(RenderCommand::Draw { + vertex_count, + instance_count, + first_vertex, + first_instance, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexed> + fn DrawIndexed( + &self, + index_count: u32, + instance_count: u32, + first_index: u32, + base_vertex: i32, + first_instance: u32, + ) { + self.send_render_command(RenderCommand::DrawIndexed { + index_count, + instance_count, + first_index, + base_vertex, + first_instance, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindirect> + fn DrawIndirect(&self, buffer: &GPUBuffer, offset: u64) { + self.send_render_command(RenderCommand::DrawIndirect { + buffer_id: buffer.id().0, + offset, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexedindirect> + fn DrawIndexedIndirect(&self, buffer: &GPUBuffer, offset: u64) { + self.send_render_command(RenderCommand::DrawIndexedIndirect { + buffer_id: buffer.id().0, + offset, + }) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderpassencoder-executebundles> + #[allow(unsafe_code)] + fn ExecuteBundles(&self, bundles: Vec<DomRoot<GPURenderBundle>>) { + let bundle_ids: Vec<_> = bundles.iter().map(|b| b.id().0).collect(); + self.send_render_command(RenderCommand::ExecuteBundles(bundle_ids)) + } +} + +impl Drop for GPURenderPassEncoder { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropRenderPass(self.render_pass.0)) + { + warn!("Failed to send WebGPURequest::DropRenderPass with {e:?}"); + } + } +} diff --git a/components/script/dom/webgpu/gpurenderpipeline.rs b/components/script/dom/webgpu/gpurenderpipeline.rs new file mode 100644 index 00000000000..0cf8754ecbf --- /dev/null +++ b/components/script/dom/webgpu/gpurenderpipeline.rs @@ -0,0 +1,144 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSender; +use webgpu::wgc::pipeline::RenderPipelineDescriptor; +use webgpu::{WebGPU, WebGPUBindGroupLayout, WebGPURenderPipeline, WebGPURequest, WebGPUResponse}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPURenderPipelineMethods; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpubindgrouplayout::GPUBindGroupLayout; +use crate::dom::gpudevice::{GPUDevice, PipelineLayout}; + +#[dom_struct] +pub struct GPURenderPipeline { + reflector_: Reflector, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + render_pipeline: WebGPURenderPipeline, + device: Dom<GPUDevice>, +} + +impl GPURenderPipeline { + fn new_inherited( + render_pipeline: WebGPURenderPipeline, + label: USVString, + device: &GPUDevice, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel: device.channel(), + label: DomRefCell::new(label), + render_pipeline, + device: Dom::from_ref(device), + } + } + + pub fn new( + global: &GlobalScope, + render_pipeline: WebGPURenderPipeline, + label: USVString, + device: &GPUDevice, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPURenderPipeline::new_inherited( + render_pipeline, + label, + device, + )), + global, + ) + } +} + +impl GPURenderPipeline { + pub fn id(&self) -> WebGPURenderPipeline { + self.render_pipeline + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderpipeline> + pub fn create( + device: &GPUDevice, + pipeline_layout: PipelineLayout, + descriptor: RenderPipelineDescriptor<'static>, + async_sender: Option<IpcSender<WebGPUResponse>>, + ) -> Fallible<WebGPURenderPipeline> { + let render_pipeline_id = device.global().wgpu_id_hub().create_render_pipeline_id(); + + device + .channel() + .0 + .send(WebGPURequest::CreateRenderPipeline { + device_id: device.id().0, + render_pipeline_id, + descriptor, + implicit_ids: pipeline_layout.implicit(), + async_sender, + }) + .expect("Failed to create WebGPU render pipeline"); + + Ok(WebGPURenderPipeline(render_pipeline_id)) + } +} + +impl GPURenderPipelineMethods<crate::DomTypeHolder> for GPURenderPipeline { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpupipelinebase-getbindgrouplayout> + fn GetBindGroupLayout(&self, index: u32) -> Fallible<DomRoot<GPUBindGroupLayout>> { + let id = self.global().wgpu_id_hub().create_bind_group_layout_id(); + + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::RenderGetBindGroupLayout { + device_id: self.device.id().0, + pipeline_id: self.render_pipeline.0, + index, + id, + }) + { + warn!("Failed to send WebGPURequest::RenderGetBindGroupLayout {e:?}"); + } + + Ok(GPUBindGroupLayout::new( + &self.global(), + self.channel.clone(), + WebGPUBindGroupLayout(id), + USVString::default(), + )) + } +} + +impl Drop for GPURenderPipeline { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropRenderPipeline(self.render_pipeline.0)) + { + warn!( + "Failed to send WebGPURequest::DropRenderPipeline({:?}) ({})", + self.render_pipeline.0, e + ); + }; + } +} diff --git a/components/script/dom/webgpu/gpusampler.rs b/components/script/dom/webgpu/gpusampler.rs new file mode 100644 index 00000000000..ad7aa3e8b89 --- /dev/null +++ b/components/script/dom/webgpu/gpusampler.rs @@ -0,0 +1,143 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::wgc::resource::SamplerDescriptor; +use webgpu::{WebGPU, WebGPUDevice, WebGPURequest, WebGPUSampler}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUSamplerDescriptor, GPUSamplerMethods, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::GPUDevice; + +#[dom_struct] +pub struct GPUSampler { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + device: WebGPUDevice, + compare_enable: bool, + #[no_trace] + sampler: WebGPUSampler, +} + +impl GPUSampler { + fn new_inherited( + channel: WebGPU, + device: WebGPUDevice, + compare_enable: bool, + sampler: WebGPUSampler, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + device, + sampler, + compare_enable, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + device: WebGPUDevice, + compare_enable: bool, + sampler: WebGPUSampler, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUSampler::new_inherited( + channel, + device, + compare_enable, + sampler, + label, + )), + global, + ) + } +} + +impl GPUSampler { + pub fn id(&self) -> WebGPUSampler { + self.sampler + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createsampler> + pub fn create(device: &GPUDevice, descriptor: &GPUSamplerDescriptor) -> DomRoot<GPUSampler> { + let sampler_id = device.global().wgpu_id_hub().create_sampler_id(); + let compare_enable = descriptor.compare.is_some(); + let desc = SamplerDescriptor { + label: (&descriptor.parent).into(), + address_modes: [ + descriptor.addressModeU.into(), + descriptor.addressModeV.into(), + descriptor.addressModeW.into(), + ], + mag_filter: descriptor.magFilter.into(), + min_filter: descriptor.minFilter.into(), + mipmap_filter: descriptor.mipmapFilter.into(), + lod_min_clamp: *descriptor.lodMinClamp, + lod_max_clamp: *descriptor.lodMaxClamp, + compare: descriptor.compare.map(Into::into), + anisotropy_clamp: 1, + border_color: None, + }; + + device + .channel() + .0 + .send(WebGPURequest::CreateSampler { + device_id: device.id().0, + sampler_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU sampler"); + + let sampler = WebGPUSampler(sampler_id); + + GPUSampler::new( + &device.global(), + device.channel().clone(), + device.id(), + compare_enable, + sampler, + descriptor.parent.label.clone(), + ) + } +} + +impl GPUSamplerMethods<crate::DomTypeHolder> for GPUSampler { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} + +impl Drop for GPUSampler { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropSampler(self.sampler.0)) + { + warn!("Failed to send DropSampler ({:?}) ({})", self.sampler.0, e); + } + } +} diff --git a/components/script/dom/webgpu/gpushadermodule.rs b/components/script/dom/webgpu/gpushadermodule.rs new file mode 100644 index 00000000000..44cc8fa8115 --- /dev/null +++ b/components/script/dom/webgpu/gpushadermodule.rs @@ -0,0 +1,154 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::rc::Rc; + +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPURequest, WebGPUResponse, WebGPUShaderModule}; + +use super::gpu::AsyncWGPUListener; +use super::gpucompilationinfo::GPUCompilationInfo; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUShaderModuleDescriptor, GPUShaderModuleMethods, +}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::USVString; +use crate::dom::bindings::trace::RootedTraceableBox; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::types::GPUDevice; +use crate::dom::webgpu::gpu::response_async; +use crate::realms::InRealm; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUShaderModule { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + shader_module: WebGPUShaderModule, + #[ignore_malloc_size_of = "promise"] + compilation_info_promise: Rc<Promise>, +} + +impl GPUShaderModule { + fn new_inherited( + channel: WebGPU, + shader_module: WebGPUShaderModule, + label: USVString, + promise: Rc<Promise>, + ) -> Self { + Self { + reflector_: Reflector::new(), + channel, + label: DomRefCell::new(label), + shader_module, + compilation_info_promise: promise, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + shader_module: WebGPUShaderModule, + label: USVString, + promise: Rc<Promise>, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUShaderModule::new_inherited( + channel, + shader_module, + label, + promise, + )), + global, + ) + } +} + +impl GPUShaderModule { + pub fn id(&self) -> WebGPUShaderModule { + self.shader_module + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createshadermodule> + pub fn create( + device: &GPUDevice, + descriptor: RootedTraceableBox<GPUShaderModuleDescriptor>, + comp: InRealm, + can_gc: CanGc, + ) -> DomRoot<GPUShaderModule> { + let program_id = device.global().wgpu_id_hub().create_shader_module_id(); + let promise = Promise::new_in_current_realm(comp, can_gc); + let shader_module = GPUShaderModule::new( + &device.global(), + device.channel().clone(), + WebGPUShaderModule(program_id), + descriptor.parent.label.clone(), + promise.clone(), + ); + let sender = response_async(&promise, &*shader_module); + device + .channel() + .0 + .send(WebGPURequest::CreateShaderModule { + device_id: device.id().0, + program_id, + program: descriptor.code.0.clone(), + label: None, + sender, + }) + .expect("Failed to create WebGPU ShaderModule"); + shader_module + } +} + +impl GPUShaderModuleMethods<crate::DomTypeHolder> for GPUShaderModule { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpushadermodule-getcompilationinfo> + fn GetCompilationInfo(&self) -> Rc<Promise> { + self.compilation_info_promise.clone() + } +} + +impl AsyncWGPUListener for GPUShaderModule { + fn handle_response(&self, response: WebGPUResponse, promise: &Rc<Promise>, can_gc: CanGc) { + match response { + WebGPUResponse::CompilationInfo(info) => { + let info = GPUCompilationInfo::from(&self.global(), info, can_gc); + promise.resolve_native(&info); + }, + _ => unreachable!("Wrong response received on AsyncWGPUListener for GPUShaderModule"), + } + } +} + +impl Drop for GPUShaderModule { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropShaderModule(self.shader_module.0)) + { + warn!( + "Failed to send DropShaderModule ({:?}) ({})", + self.shader_module.0, e + ); + } + } +} diff --git a/components/script/dom/webgpu/gpushaderstage.rs b/components/script/dom/webgpu/gpushaderstage.rs new file mode 100644 index 00000000000..611ffb12283 --- /dev/null +++ b/components/script/dom/webgpu/gpushaderstage.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; + +use crate::dom::bindings::reflector::Reflector; + +#[dom_struct] +pub struct GPUShaderStage { + reflector_: Reflector, +} diff --git a/components/script/dom/webgpu/gpusupportedfeatures.rs b/components/script/dom/webgpu/gpusupportedfeatures.rs new file mode 100644 index 00000000000..8cc161e172c --- /dev/null +++ b/components/script/dom/webgpu/gpusupportedfeatures.rs @@ -0,0 +1,177 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// check-tidy: no specs after this line + +use dom_struct::dom_struct; +use indexmap::IndexSet; +use js::rust::HandleObject; +use webgpu::wgt::Features; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUFeatureName, GPUSupportedFeaturesMethods, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::like::Setlike; +use crate::dom::bindings::reflector::{reflect_dom_object_with_proto, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUSupportedFeatures { + reflector: Reflector, + // internal storage for features + #[custom_trace] + internal: DomRefCell<IndexSet<GPUFeatureName>>, + #[ignore_malloc_size_of = "defined in wgpu-types"] + #[no_trace] + features: Features, +} + +impl GPUSupportedFeatures { + fn new( + global: &GlobalScope, + proto: Option<HandleObject>, + features: Features, + can_gc: CanGc, + ) -> DomRoot<GPUSupportedFeatures> { + let mut set = IndexSet::new(); + if features.contains(Features::DEPTH_CLIP_CONTROL) { + set.insert(GPUFeatureName::Depth_clip_control); + } + if features.contains(Features::DEPTH32FLOAT_STENCIL8) { + set.insert(GPUFeatureName::Depth32float_stencil8); + } + if features.contains(Features::TEXTURE_COMPRESSION_BC) { + set.insert(GPUFeatureName::Texture_compression_bc); + } + // TODO: texture-compression-bc-sliced-3d when wgpu supports it + if features.contains(Features::TEXTURE_COMPRESSION_ETC2) { + set.insert(GPUFeatureName::Texture_compression_etc2); + } + if features.contains(Features::TEXTURE_COMPRESSION_ASTC) { + set.insert(GPUFeatureName::Texture_compression_astc); + } + if features.contains(Features::TIMESTAMP_QUERY) { + set.insert(GPUFeatureName::Timestamp_query); + } + if features.contains(Features::INDIRECT_FIRST_INSTANCE) { + set.insert(GPUFeatureName::Indirect_first_instance); + } + // While this feature exists in wgpu, it's not supported by naga yet + // https://github.com/gfx-rs/wgpu/issues/4384 + /* + if features.contains(Features::SHADER_F16) { + set.insert(GPUFeatureName::Shader_f16); + } + */ + if features.contains(Features::RG11B10UFLOAT_RENDERABLE) { + set.insert(GPUFeatureName::Rg11b10ufloat_renderable); + } + if features.contains(Features::BGRA8UNORM_STORAGE) { + set.insert(GPUFeatureName::Bgra8unorm_storage); + } + if features.contains(Features::FLOAT32_FILTERABLE) { + set.insert(GPUFeatureName::Float32_filterable); + } + // TODO: clip-distances when wgpu supports it + if features.contains(Features::DUAL_SOURCE_BLENDING) { + set.insert(GPUFeatureName::Dual_source_blending); + } + + reflect_dom_object_with_proto( + Box::new(GPUSupportedFeatures { + reflector: Reflector::new(), + internal: DomRefCell::new(set), + features, + }), + global, + proto, + can_gc, + ) + } + + #[allow(non_snake_case)] + pub fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + features: Features, + can_gc: CanGc, + ) -> Fallible<DomRoot<GPUSupportedFeatures>> { + Ok(GPUSupportedFeatures::new(global, proto, features, can_gc)) + } +} + +impl GPUSupportedFeatures { + pub fn wgpu_features(&self) -> Features { + self.features + } +} + +impl GPUSupportedFeaturesMethods<crate::DomTypeHolder> for GPUSupportedFeatures { + fn Size(&self) -> u32 { + self.internal.size() + } +} + +pub fn gpu_to_wgt_feature(feature: GPUFeatureName) -> Option<Features> { + match feature { + GPUFeatureName::Depth_clip_control => Some(Features::DEPTH_CLIP_CONTROL), + GPUFeatureName::Depth32float_stencil8 => Some(Features::DEPTH32FLOAT_STENCIL8), + GPUFeatureName::Texture_compression_bc => Some(Features::TEXTURE_COMPRESSION_BC), + GPUFeatureName::Texture_compression_etc2 => Some(Features::TEXTURE_COMPRESSION_ETC2), + GPUFeatureName::Texture_compression_astc => Some(Features::TEXTURE_COMPRESSION_ASTC), + GPUFeatureName::Timestamp_query => Some(Features::TIMESTAMP_QUERY), + GPUFeatureName::Indirect_first_instance => Some(Features::INDIRECT_FIRST_INSTANCE), + // While this feature exists in wgpu, it's not supported by naga yet + // https://github.com/gfx-rs/wgpu/issues/4384 + GPUFeatureName::Shader_f16 => None, + GPUFeatureName::Rg11b10ufloat_renderable => Some(Features::RG11B10UFLOAT_RENDERABLE), + GPUFeatureName::Bgra8unorm_storage => Some(Features::BGRA8UNORM_STORAGE), + GPUFeatureName::Float32_filterable => Some(Features::FLOAT32_FILTERABLE), + GPUFeatureName::Dual_source_blending => Some(Features::DUAL_SOURCE_BLENDING), + GPUFeatureName::Texture_compression_bc_sliced_3d => None, + GPUFeatureName::Clip_distances => None, + } +} + +// this error is wrong because if we inline Self::Key and Self::Value all errors are gone +#[allow(crown::unrooted_must_root)] +impl Setlike for GPUSupportedFeatures { + type Key = DOMString; + + #[inline(always)] + fn get_index(&self, index: u32) -> Option<Self::Key> { + self.internal + .get_index(index) + .map(|k| DOMString::from_string(k.as_str().to_owned())) + } + #[inline(always)] + fn size(&self) -> u32 { + self.internal.size() + } + #[inline(always)] + fn add(&self, _key: Self::Key) { + unreachable!("readonly"); + } + #[inline(always)] + fn has(&self, key: Self::Key) -> bool { + if let Ok(key) = key.parse() { + self.internal.has(key) + } else { + false + } + } + #[inline(always)] + fn clear(&self) { + unreachable!("readonly"); + } + #[inline(always)] + fn delete(&self, _key: Self::Key) -> bool { + unreachable!("readonly"); + } +} diff --git a/components/script/dom/webgpu/gpusupportedlimits.rs b/components/script/dom/webgpu/gpusupportedlimits.rs new file mode 100644 index 00000000000..15f1172da18 --- /dev/null +++ b/components/script/dom/webgpu/gpusupportedlimits.rs @@ -0,0 +1,316 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use num_traits::bounds::UpperBounded; +use webgpu::wgt::Limits; +use GPUSupportedLimits_Binding::GPUSupportedLimitsMethods; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUSupportedLimits_Binding; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::globalscope::GlobalScope; + +#[dom_struct] +pub struct GPUSupportedLimits { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in wgpu-types"] + #[no_trace] + limits: Limits, +} + +impl GPUSupportedLimits { + fn new_inherited(limits: Limits) -> Self { + Self { + reflector_: Reflector::new(), + limits, + } + } + + pub fn new(global: &GlobalScope, limits: Limits) -> DomRoot<Self> { + reflect_dom_object(Box::new(Self::new_inherited(limits)), global) + } +} + +impl GPUSupportedLimitsMethods<crate::DomTypeHolder> for GPUSupportedLimits { + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxtexturedimension1d> + fn MaxTextureDimension1D(&self) -> u32 { + self.limits.max_texture_dimension_1d + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxtexturedimension2d> + fn MaxTextureDimension2D(&self) -> u32 { + self.limits.max_texture_dimension_2d + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxtexturedimension3d> + fn MaxTextureDimension3D(&self) -> u32 { + self.limits.max_texture_dimension_3d + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxtexturearraylayers> + fn MaxTextureArrayLayers(&self) -> u32 { + self.limits.max_texture_array_layers + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxbindgroups> + fn MaxBindGroups(&self) -> u32 { + self.limits.max_bind_groups + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxbindingsperbindgroup> + fn MaxBindingsPerBindGroup(&self) -> u32 { + self.limits.max_bindings_per_bind_group + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxdynamicuniformbuffersperpipelinelayout> + fn MaxDynamicUniformBuffersPerPipelineLayout(&self) -> u32 { + self.limits.max_dynamic_uniform_buffers_per_pipeline_layout + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxdynamicstoragebuffersperpipelinelayout> + fn MaxDynamicStorageBuffersPerPipelineLayout(&self) -> u32 { + self.limits.max_dynamic_storage_buffers_per_pipeline_layout + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxsampledtexturespershaderstage> + fn MaxSampledTexturesPerShaderStage(&self) -> u32 { + self.limits.max_sampled_textures_per_shader_stage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxsamplerspershaderstage> + fn MaxSamplersPerShaderStage(&self) -> u32 { + self.limits.max_samplers_per_shader_stage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxstoragebufferspershaderstage> + fn MaxStorageBuffersPerShaderStage(&self) -> u32 { + self.limits.max_storage_buffers_per_shader_stage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxstoragetexturespershaderstage> + fn MaxStorageTexturesPerShaderStage(&self) -> u32 { + self.limits.max_storage_textures_per_shader_stage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxuniformbufferspershaderstage> + fn MaxUniformBuffersPerShaderStage(&self) -> u32 { + self.limits.max_uniform_buffers_per_shader_stage + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxuniformbufferbindingsize> + fn MaxUniformBufferBindingSize(&self) -> u64 { + self.limits.max_uniform_buffer_binding_size as u64 + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxstoragebufferbindingsize> + fn MaxStorageBufferBindingSize(&self) -> u64 { + self.limits.max_storage_buffer_binding_size as u64 + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-minuniformbufferoffsetalignment> + fn MinUniformBufferOffsetAlignment(&self) -> u32 { + self.limits.min_uniform_buffer_offset_alignment + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-minstoragebufferoffsetalignment> + fn MinStorageBufferOffsetAlignment(&self) -> u32 { + self.limits.min_storage_buffer_offset_alignment + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxvertexbuffers> + fn MaxVertexBuffers(&self) -> u32 { + self.limits.max_vertex_buffers + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxbuffersize> + fn MaxBufferSize(&self) -> u64 { + self.limits.max_buffer_size + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxvertexattributes> + fn MaxVertexAttributes(&self) -> u32 { + self.limits.max_vertex_attributes + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxvertexbufferarraystride> + fn MaxVertexBufferArrayStride(&self) -> u32 { + self.limits.max_vertex_buffer_array_stride + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxinterstageshadercomponents> + fn MaxInterStageShaderComponents(&self) -> u32 { + self.limits.max_inter_stage_shader_components + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeworkgroupstoragesize> + fn MaxComputeWorkgroupStorageSize(&self) -> u32 { + self.limits.max_compute_workgroup_storage_size + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeinvocationsperworkgroup> + fn MaxComputeInvocationsPerWorkgroup(&self) -> u32 { + self.limits.max_compute_invocations_per_workgroup + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeworkgroupsizex> + fn MaxComputeWorkgroupSizeX(&self) -> u32 { + self.limits.max_compute_workgroup_size_x + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeworkgroupsizey> + fn MaxComputeWorkgroupSizeY(&self) -> u32 { + self.limits.max_compute_workgroup_size_y + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeworkgroupsizez> + fn MaxComputeWorkgroupSizeZ(&self) -> u32 { + self.limits.max_compute_workgroup_size_z + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcomputeworkgroupsperdimension> + fn MaxComputeWorkgroupsPerDimension(&self) -> u32 { + self.limits.max_compute_workgroups_per_dimension + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxbindgroupsplusvertexbuffers> + fn MaxBindGroupsPlusVertexBuffers(&self) -> u32 { + // Not on wgpu yet, so we craft it manually + self.limits.max_bind_groups + self.limits.max_vertex_buffers + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxinterstageshadervariables> + fn MaxInterStageShaderVariables(&self) -> u32 { + // Not in wgpu yet, so we use default value from spec + 16 + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcolorattachments> + fn MaxColorAttachments(&self) -> u32 { + self.limits.max_color_attachments + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpusupportedlimits-maxcolorattachmentbytespersample> + fn MaxColorAttachmentBytesPerSample(&self) -> u32 { + self.limits.max_color_attachment_bytes_per_sample + } +} + +/// Returns false if unknown limit or other value error +pub fn set_limit(limits: &mut Limits, limit: &str, value: u64) -> bool { + /// per spec defaults are lower bounds for values + /// + /// <https://www.w3.org/TR/webgpu/#limit-class-maximum> + fn set_maximum<T>(limit: &mut T, value: u64) -> bool + where + T: Ord + Copy + TryFrom<u64> + UpperBounded, + { + if let Ok(value) = T::try_from(value) { + *limit = value.max(*limit); + true + } else { + false + } + } + + /// per spec defaults are higher bounds for values + /// + /// <https://www.w3.org/TR/webgpu/#limit-class-alignment> + fn set_alignment<T>(limit: &mut T, value: u64) -> bool + where + T: Ord + Copy + TryFrom<u64> + UpperBounded, + { + if !value.is_power_of_two() { + return false; + } + if let Ok(value) = T::try_from(value) { + *limit = value.min(*limit); + true + } else { + false + } + } + + match limit { + "maxTextureDimension1D" => set_maximum(&mut limits.max_texture_dimension_1d, value), + "maxTextureDimension2D" => set_maximum(&mut limits.max_texture_dimension_2d, value), + "maxTextureDimension3D" => set_maximum(&mut limits.max_texture_dimension_3d, value), + "maxTextureArrayLayers" => set_maximum(&mut limits.max_texture_array_layers, value), + "maxBindGroups" => set_maximum(&mut limits.max_bind_groups, value), + "maxBindGroupsPlusVertexBuffers" => { + // not in wgpu but we're allowed to give back better limits than requested. + // we use dummy value to still produce value verification + let mut v: u32 = 0; + set_maximum(&mut v, value) + }, + "maxBindingsPerBindGroup" => set_maximum(&mut limits.max_bindings_per_bind_group, value), + "maxDynamicUniformBuffersPerPipelineLayout" => set_maximum( + &mut limits.max_dynamic_uniform_buffers_per_pipeline_layout, + value, + ), + "maxDynamicStorageBuffersPerPipelineLayout" => set_maximum( + &mut limits.max_dynamic_storage_buffers_per_pipeline_layout, + value, + ), + "maxSampledTexturesPerShaderStage" => { + set_maximum(&mut limits.max_sampled_textures_per_shader_stage, value) + }, + "maxSamplersPerShaderStage" => { + set_maximum(&mut limits.max_samplers_per_shader_stage, value) + }, + "maxStorageBuffersPerShaderStage" => { + set_maximum(&mut limits.max_storage_buffers_per_shader_stage, value) + }, + "maxStorageTexturesPerShaderStage" => { + set_maximum(&mut limits.max_storage_textures_per_shader_stage, value) + }, + "maxUniformBuffersPerShaderStage" => { + set_maximum(&mut limits.max_uniform_buffers_per_shader_stage, value) + }, + "maxUniformBufferBindingSize" => { + set_maximum(&mut limits.max_uniform_buffer_binding_size, value) + }, + "maxStorageBufferBindingSize" => { + set_maximum(&mut limits.max_storage_buffer_binding_size, value) + }, + "minUniformBufferOffsetAlignment" => { + set_alignment(&mut limits.min_uniform_buffer_offset_alignment, value) + }, + "minStorageBufferOffsetAlignment" => { + set_alignment(&mut limits.min_storage_buffer_offset_alignment, value) + }, + "maxVertexBuffers" => set_maximum(&mut limits.max_vertex_buffers, value), + "maxBufferSize" => set_maximum(&mut limits.max_buffer_size, value), + "maxVertexAttributes" => set_maximum(&mut limits.max_vertex_attributes, value), + "maxVertexBufferArrayStride" => { + set_maximum(&mut limits.max_vertex_buffer_array_stride, value) + }, + "maxInterStageShaderComponents" => { + set_maximum(&mut limits.max_inter_stage_shader_components, value) + }, + "maxInterStageShaderVariables" => { + // not in wgpu but we're allowed to give back better limits than requested. + // we use dummy value to still produce value verification + let mut v: u32 = 0; + set_maximum(&mut v, value) + }, + "maxColorAttachments" => set_maximum(&mut limits.max_color_attachments, value), + "maxColorAttachmentBytesPerSample" => { + set_maximum(&mut limits.max_color_attachment_bytes_per_sample, value) + }, + "maxComputeWorkgroupStorageSize" => { + set_maximum(&mut limits.max_compute_workgroup_storage_size, value) + }, + "maxComputeInvocationsPerWorkgroup" => { + set_maximum(&mut limits.max_compute_invocations_per_workgroup, value) + }, + "maxComputeWorkgroupSizeX" => set_maximum(&mut limits.max_compute_workgroup_size_x, value), + "maxComputeWorkgroupSizeY" => set_maximum(&mut limits.max_compute_workgroup_size_y, value), + "maxComputeWorkgroupSizeZ" => set_maximum(&mut limits.max_compute_workgroup_size_z, value), + "maxComputeWorkgroupsPerDimension" => { + set_maximum(&mut limits.max_compute_workgroups_per_dimension, value) + }, + _ => false, + } +} diff --git a/components/script/dom/webgpu/gputexture.rs b/components/script/dom/webgpu/gputexture.rs new file mode 100644 index 00000000000..b37197f4a90 --- /dev/null +++ b/components/script/dom/webgpu/gputexture.rs @@ -0,0 +1,285 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::string::String; + +use dom_struct::dom_struct; +use webgpu::wgc::resource; +use webgpu::{wgt, WebGPU, WebGPURequest, WebGPUTexture, WebGPUTextureView}; + +use super::gpuconvert::convert_texture_descriptor; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUTextureAspect, GPUTextureDescriptor, GPUTextureDimension, GPUTextureFormat, + GPUTextureMethods, GPUTextureViewDescriptor, +}; +use crate::dom::bindings::error::Fallible; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpudevice::GPUDevice; +use crate::dom::gputextureview::GPUTextureView; + +#[dom_struct] +pub struct GPUTexture { + reflector_: Reflector, + #[no_trace] + texture: WebGPUTexture, + label: DomRefCell<USVString>, + device: Dom<GPUDevice>, + #[ignore_malloc_size_of = "channels are hard"] + #[no_trace] + channel: WebGPU, + #[ignore_malloc_size_of = "defined in wgpu"] + #[no_trace] + texture_size: wgt::Extent3d, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, +} + +impl GPUTexture { + #[allow(clippy::too_many_arguments)] + fn new_inherited( + texture: WebGPUTexture, + device: &GPUDevice, + channel: WebGPU, + texture_size: wgt::Extent3d, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, + label: USVString, + ) -> Self { + Self { + reflector_: Reflector::new(), + texture, + label: DomRefCell::new(label), + device: Dom::from_ref(device), + channel, + texture_size, + mip_level_count, + sample_count, + dimension, + format, + texture_usage, + } + } + + #[allow(clippy::too_many_arguments)] + pub fn new( + global: &GlobalScope, + texture: WebGPUTexture, + device: &GPUDevice, + channel: WebGPU, + texture_size: wgt::Extent3d, + mip_level_count: u32, + sample_count: u32, + dimension: GPUTextureDimension, + format: GPUTextureFormat, + texture_usage: u32, + label: USVString, + ) -> DomRoot<Self> { + reflect_dom_object( + Box::new(GPUTexture::new_inherited( + texture, + device, + channel, + texture_size, + mip_level_count, + sample_count, + dimension, + format, + texture_usage, + label, + )), + global, + ) + } +} + +impl Drop for GPUTexture { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropTexture(self.texture.0)) + { + warn!( + "Failed to send WebGPURequest::DropTexture({:?}) ({})", + self.texture.0, e + ); + }; + } +} + +impl GPUTexture { + pub fn id(&self) -> WebGPUTexture { + self.texture + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createtexture> + pub fn create( + device: &GPUDevice, + descriptor: &GPUTextureDescriptor, + ) -> Fallible<DomRoot<GPUTexture>> { + let (desc, size) = convert_texture_descriptor(descriptor, device)?; + + let texture_id = device.global().wgpu_id_hub().create_texture_id(); + + device + .channel() + .0 + .send(WebGPURequest::CreateTexture { + device_id: device.id().0, + texture_id, + descriptor: desc, + }) + .expect("Failed to create WebGPU Texture"); + + let texture = WebGPUTexture(texture_id); + + Ok(GPUTexture::new( + &device.global(), + texture, + device, + device.channel().clone(), + size, + descriptor.mipLevelCount, + descriptor.sampleCount, + descriptor.dimension, + descriptor.format, + descriptor.usage, + descriptor.parent.label.clone(), + )) + } +} + +impl GPUTextureMethods<crate::DomTypeHolder> for GPUTexture { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-createview> + fn CreateView( + &self, + descriptor: &GPUTextureViewDescriptor, + ) -> Fallible<DomRoot<GPUTextureView>> { + let desc = if !matches!(descriptor.mipLevelCount, Some(0)) && + !matches!(descriptor.arrayLayerCount, Some(0)) + { + Some(resource::TextureViewDescriptor { + label: (&descriptor.parent).into(), + format: descriptor + .format + .map(|f| self.device.validate_texture_format_required_features(&f)) + .transpose()?, + dimension: descriptor.dimension.map(|dimension| dimension.into()), + range: wgt::ImageSubresourceRange { + aspect: match descriptor.aspect { + GPUTextureAspect::All => wgt::TextureAspect::All, + GPUTextureAspect::Stencil_only => wgt::TextureAspect::StencilOnly, + GPUTextureAspect::Depth_only => wgt::TextureAspect::DepthOnly, + }, + base_mip_level: descriptor.baseMipLevel, + mip_level_count: descriptor.mipLevelCount, + base_array_layer: descriptor.baseArrayLayer, + array_layer_count: descriptor.arrayLayerCount, + }, + }) + } else { + self.device + .dispatch_error(webgpu::Error::Validation(String::from( + "arrayLayerCount and mipLevelCount cannot be 0", + ))); + None + }; + + let texture_view_id = self.global().wgpu_id_hub().create_texture_view_id(); + + self.channel + .0 + .send(WebGPURequest::CreateTextureView { + texture_id: self.texture.0, + texture_view_id, + device_id: self.device.id().0, + descriptor: desc, + }) + .expect("Failed to create WebGPU texture view"); + + let texture_view = WebGPUTextureView(texture_view_id); + + Ok(GPUTextureView::new( + &self.global(), + self.channel.clone(), + texture_view, + self, + descriptor.parent.label.clone(), + )) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-destroy> + fn Destroy(&self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DestroyTexture(self.texture.0)) + { + warn!( + "Failed to send WebGPURequest::DestroyTexture({:?}) ({})", + self.texture.0, e + ); + }; + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-width> + fn Width(&self) -> u32 { + self.texture_size.width + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-height> + fn Height(&self) -> u32 { + self.texture_size.height + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-depthorarraylayers> + fn DepthOrArrayLayers(&self) -> u32 { + self.texture_size.depth_or_array_layers + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-miplevelcount> + fn MipLevelCount(&self) -> u32 { + self.mip_level_count + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-samplecount> + fn SampleCount(&self) -> u32 { + self.sample_count + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-dimension> + fn Dimension(&self) -> GPUTextureDimension { + self.dimension + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-format> + fn Format(&self) -> GPUTextureFormat { + self.format + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gputexture-usage> + fn Usage(&self) -> u32 { + self.texture_usage + } +} diff --git a/components/script/dom/webgpu/gputextureusage.rs b/components/script/dom/webgpu/gputextureusage.rs new file mode 100644 index 00000000000..f2d9645364b --- /dev/null +++ b/components/script/dom/webgpu/gputextureusage.rs @@ -0,0 +1,12 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; + +use crate::dom::bindings::reflector::Reflector; + +#[dom_struct] +pub struct GPUTextureUsage { + reflector_: Reflector, +} diff --git a/components/script/dom/webgpu/gputextureview.rs b/components/script/dom/webgpu/gputextureview.rs new file mode 100644 index 00000000000..3c8d484fca3 --- /dev/null +++ b/components/script/dom/webgpu/gputextureview.rs @@ -0,0 +1,94 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use webgpu::{WebGPU, WebGPURequest, WebGPUTextureView}; + +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUTextureViewMethods; +use crate::dom::bindings::reflector::{reflect_dom_object, Reflector}; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::USVString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gputexture::GPUTexture; + +#[dom_struct] +pub struct GPUTextureView { + reflector_: Reflector, + #[ignore_malloc_size_of = "defined in webgpu"] + #[no_trace] + channel: WebGPU, + label: DomRefCell<USVString>, + #[no_trace] + texture_view: WebGPUTextureView, + texture: Dom<GPUTexture>, +} + +impl GPUTextureView { + fn new_inherited( + channel: WebGPU, + texture_view: WebGPUTextureView, + texture: &GPUTexture, + label: USVString, + ) -> GPUTextureView { + Self { + reflector_: Reflector::new(), + channel, + texture: Dom::from_ref(texture), + label: DomRefCell::new(label), + texture_view, + } + } + + pub fn new( + global: &GlobalScope, + channel: WebGPU, + texture_view: WebGPUTextureView, + texture: &GPUTexture, + label: USVString, + ) -> DomRoot<GPUTextureView> { + reflect_dom_object( + Box::new(GPUTextureView::new_inherited( + channel, + texture_view, + texture, + label, + )), + global, + ) + } +} + +impl GPUTextureView { + pub fn id(&self) -> WebGPUTextureView { + self.texture_view + } +} + +impl GPUTextureViewMethods<crate::DomTypeHolder> for GPUTextureView { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn Label(&self) -> USVString { + self.label.borrow().clone() + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label> + fn SetLabel(&self, value: USVString) { + *self.label.borrow_mut() = value; + } +} + +impl Drop for GPUTextureView { + fn drop(&mut self) { + if let Err(e) = self + .channel + .0 + .send(WebGPURequest::DropTextureView(self.texture_view.0)) + { + warn!( + "Failed to send DropTextureView ({:?}) ({})", + self.texture_view.0, e + ); + } + } +} diff --git a/components/script/dom/webgpu/gpuuncapturederrorevent.rs b/components/script/dom/webgpu/gpuuncapturederrorevent.rs new file mode 100644 index 00000000000..0ee3871d98d --- /dev/null +++ b/components/script/dom/webgpu/gpuuncapturederrorevent.rs @@ -0,0 +1,92 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; +use servo_atoms::Atom; + +use crate::dom::bindings::codegen::Bindings::EventBinding::Event_Binding::EventMethods; +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{ + GPUUncapturedErrorEventInit, GPUUncapturedErrorEventMethods, +}; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::bindings::root::{Dom, DomRoot}; +use crate::dom::bindings::str::DOMString; +use crate::dom::event::Event; +use crate::dom::globalscope::GlobalScope; +use crate::dom::gpuerror::GPUError; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUUncapturedErrorEvent { + event: Event, + #[ignore_malloc_size_of = "Because it is non-owning"] + gpu_error: Dom<GPUError>, +} + +impl GPUUncapturedErrorEvent { + fn new_inherited(init: &GPUUncapturedErrorEventInit) -> Self { + Self { + gpu_error: Dom::from_ref(&init.error), + event: Event::new_inherited(), + } + } + + pub fn new( + global: &GlobalScope, + type_: DOMString, + init: &GPUUncapturedErrorEventInit, + can_gc: CanGc, + ) -> DomRoot<Self> { + Self::new_with_proto(global, None, type_, init, can_gc) + } + + fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + type_: DOMString, + init: &GPUUncapturedErrorEventInit, + can_gc: CanGc, + ) -> DomRoot<Self> { + let ev = reflect_dom_object_with_proto( + Box::new(GPUUncapturedErrorEvent::new_inherited(init)), + global, + proto, + can_gc, + ); + ev.event.init_event( + Atom::from(type_), + init.parent.bubbles, + init.parent.cancelable, + ); + ev + } + + pub fn event(&self) -> &Event { + &self.event + } +} + +impl GPUUncapturedErrorEventMethods<crate::DomTypeHolder> for GPUUncapturedErrorEvent { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuuncapturederrorevent-gpuuncapturederrorevent> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + type_: DOMString, + init: &GPUUncapturedErrorEventInit, + ) -> DomRoot<Self> { + GPUUncapturedErrorEvent::new_with_proto(global, proto, type_, init, can_gc) + } + + /// <https://gpuweb.github.io/gpuweb/#dom-gpuuncapturederrorevent-error> + fn Error(&self) -> DomRoot<GPUError> { + DomRoot::from_ref(&self.gpu_error) + } + + /// <https://dom.spec.whatwg.org/#dom-event-istrusted> + fn IsTrusted(&self) -> bool { + self.event.IsTrusted() + } +} diff --git a/components/script/dom/webgpu/gpuvalidationerror.rs b/components/script/dom/webgpu/gpuvalidationerror.rs new file mode 100644 index 00000000000..0b20375e728 --- /dev/null +++ b/components/script/dom/webgpu/gpuvalidationerror.rs @@ -0,0 +1,53 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use dom_struct::dom_struct; +use js::rust::HandleObject; + +use crate::dom::bindings::codegen::Bindings::WebGPUBinding::GPUValidationError_Binding::GPUValidationErrorMethods; +use crate::dom::bindings::reflector::reflect_dom_object_with_proto; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::globalscope::GlobalScope; +use crate::dom::types::GPUError; +use crate::script_runtime::CanGc; + +#[dom_struct] +pub struct GPUValidationError { + gpu_error: GPUError, +} + +impl GPUValidationError { + fn new_inherited(message: DOMString) -> Self { + Self { + gpu_error: GPUError::new_inherited(message), + } + } + + pub fn new_with_proto( + global: &GlobalScope, + proto: Option<HandleObject>, + message: DOMString, + can_gc: CanGc, + ) -> DomRoot<Self> { + reflect_dom_object_with_proto( + Box::new(Self::new_inherited(message)), + global, + proto, + can_gc, + ) + } +} + +impl GPUValidationErrorMethods<crate::DomTypeHolder> for GPUValidationError { + /// <https://gpuweb.github.io/gpuweb/#dom-gpuvalidationerror-gpuvalidationerror> + fn Constructor( + global: &GlobalScope, + proto: Option<HandleObject>, + can_gc: CanGc, + message: DOMString, + ) -> DomRoot<Self> { + Self::new_with_proto(global, proto, message, can_gc) + } +} diff --git a/components/script/dom/webgpu/mod.rs b/components/script/dom/webgpu/mod.rs new file mode 100644 index 00000000000..22994a942bc --- /dev/null +++ b/components/script/dom/webgpu/mod.rs @@ -0,0 +1,44 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +pub mod gpu; +pub mod gpuadapter; +pub mod gpuadapterinfo; +pub mod gpubindgroup; +pub mod gpubindgrouplayout; +pub mod gpubuffer; +pub mod gpubufferusage; +pub mod gpucanvascontext; +pub mod gpucolorwrite; +pub mod gpucommandbuffer; +pub mod gpucommandencoder; +pub mod gpucompilationinfo; +pub mod gpucompilationmessage; +pub mod gpucomputepassencoder; +pub mod gpucomputepipeline; +pub mod gpuconvert; +pub mod gpudevice; +pub mod gpudevicelostinfo; +pub mod gpuerror; +pub mod gpuinternalerror; +pub mod gpumapmode; +pub mod gpuoutofmemoryerror; +pub mod gpupipelineerror; +pub mod gpupipelinelayout; +pub mod gpuqueryset; +pub mod gpuqueue; +pub mod gpurenderbundle; +pub mod gpurenderbundleencoder; +pub mod gpurenderpassencoder; +pub mod gpurenderpipeline; +pub mod gpusampler; +pub mod gpushadermodule; +pub mod gpushaderstage; +pub mod gpusupportedfeatures; +pub mod gpusupportedlimits; +pub mod gputexture; +pub mod gputextureusage; +pub mod gputextureview; +pub mod gpuuncapturederrorevent; +pub mod gpuvalidationerror; |