/* 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_traits::{ PopError, WebGPU, WebGPUComputePipeline, WebGPUComputePipelineResponse, WebGPUDevice, WebGPUPoppedErrorScopeResponse, WebGPUQueue, WebGPURenderPipeline, WebGPURenderPipelineResponse, WebGPURequest, }; use wgpu_core::id::{BindGroupLayoutId, PipelineLayoutId}; use wgpu_core::pipeline as wgpu_pipe; use wgpu_core::pipeline::RenderPipelineDescriptor; use wgpu_types::{self, TextureFormat}; use super::gpudevicelostinfo::GPUDeviceLostInfo; use super::gpuerror::AsWebGpu; use super::gpupipelineerror::GPUPipelineError; use super::gpusupportedlimits::GPUSupportedLimits; use crate::conversions::Convert; 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::{DomGlobal, reflect_dom_object}; 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::promise::Promise; use crate::dom::types::GPUError; use crate::dom::webgpu::gpuadapter::GPUAdapter; use crate::dom::webgpu::gpubindgroup::GPUBindGroup; use crate::dom::webgpu::gpubindgrouplayout::GPUBindGroupLayout; use crate::dom::webgpu::gpubuffer::GPUBuffer; use crate::dom::webgpu::gpucommandencoder::GPUCommandEncoder; use crate::dom::webgpu::gpucomputepipeline::GPUComputePipeline; use crate::dom::webgpu::gpupipelinelayout::GPUPipelineLayout; use crate::dom::webgpu::gpuqueue::GPUQueue; use crate::dom::webgpu::gpurenderbundleencoder::GPURenderBundleEncoder; use crate::dom::webgpu::gpurenderpipeline::GPURenderPipeline; use crate::dom::webgpu::gpusampler::GPUSampler; use crate::dom::webgpu::gpushadermodule::GPUShaderModule; use crate::dom::webgpu::gpusupportedfeatures::GPUSupportedFeatures; use crate::dom::webgpu::gputexture::GPUTexture; use crate::dom::webgpu::gpuuncapturederrorevent::GPUUncapturedErrorEvent; use crate::realms::InRealm; use crate::routed_promise::{RoutedPromiseListener, route_promise}; use crate::script_runtime::CanGc; #[dom_struct] pub(crate) struct GPUDevice { eventtarget: EventTarget, #[ignore_malloc_size_of = "channels are hard"] #[no_trace] channel: WebGPU, adapter: Dom, #[ignore_malloc_size_of = "mozjs"] extensions: Heap<*mut JSObject>, features: Dom, limits: Dom, label: DomRefCell, #[no_trace] device: WebGPUDevice, default_queue: Dom, /// #[ignore_malloc_size_of = "promises are hard"] lost_promise: DomRefCell>, valid: Cell, } pub(crate) enum PipelineLayout { Implicit(PipelineLayoutId, Vec), Explicit(PipelineLayoutId), } impl PipelineLayout { pub(crate) fn explicit(&self) -> Option { match self { PipelineLayout::Explicit(layout_id) => Some(*layout_id), _ => None, } } pub(crate) fn implicit(self) -> Option<(PipelineLayoutId, Vec)> { 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: WebGPUDevice, queue: &GPUQueue, label: String, lost_promise: Rc, ) -> 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(crate) fn new( global: &GlobalScope, channel: WebGPU, adapter: &GPUAdapter, extensions: Heap<*mut JSObject>, features: wgpu_types::Features, limits: wgpu_types::Limits, device: WebGPUDevice, queue: WebGPUQueue, label: String, can_gc: CanGc, ) -> DomRoot { let queue = GPUQueue::new(global, channel.clone(), queue, can_gc); let limits = GPUSupportedLimits::new(global, limits, can_gc); 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, can_gc, ); queue.set_device(&device); device } } impl GPUDevice { pub(crate) fn id(&self) -> WebGPUDevice { self.device } pub(crate) fn queue_id(&self) -> WebGPUQueue { self.default_queue.id() } pub(crate) fn channel(&self) -> WebGPU { self.channel.clone() } pub(crate) fn dispatch_error(&self, error: webgpu_traits::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(crate) fn fire_uncaptured_error(&self, error: webgpu_traits::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); } /// /// /// Validates that the device suppports required features, /// and if so returns an ok containing wgpu's `TextureFormat` pub(crate) fn validate_texture_format_required_features( &self, format: &GPUTextureFormat, ) -> Fallible { let texture_format: TextureFormat = (*format).convert(); 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(crate) fn is_lost(&self) -> bool { self.lost_promise.borrow().is_fulfilled() } pub(crate) fn get_pipeline_layout_data( &self, layout: &GPUPipelineLayoutOrGPUAutoLayoutMode, ) -> PipelineLayout { if let GPUPipelineLayoutOrGPUAutoLayoutMode::GPUPipelineLayout(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(crate) 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).convert(), layout: pipeline_layout.explicit(), cache: None, vertex: wgpu_pipe::VertexState { stage: (&descriptor.vertex.parent).convert(), buffers: Cow::Owned( descriptor .vertex .buffers .iter() .map(|buffer| wgpu_pipe::VertexBufferLayout { array_stride: buffer.arrayStride, step_mode: match buffer.stepMode { GPUVertexStepMode::Vertex => wgpu_types::VertexStepMode::Vertex, GPUVertexStepMode::Instance => wgpu_types::VertexStepMode::Instance, }, attributes: Cow::Owned( buffer .attributes .iter() .map(|att| wgpu_types::VertexAttribute { format: att.format.convert(), offset: att.offset, shader_location: att.shaderLocation, }) .collect::>(), ), }) .collect::>(), ), }, fragment: descriptor .fragment .as_ref() .map(|stage| -> Fallible { Ok(wgpu_pipe::FragmentState { stage: (&stage.parent).convert(), targets: Cow::Owned( stage .targets .iter() .map(|state| { self.validate_texture_format_required_features(&state.format) .map(|format| { Some(wgpu_types::ColorTargetState { format, write_mask: wgpu_types::ColorWrites::from_bits_retain( state.writeMask, ), blend: state.blend.as_ref().map(|blend| { wgpu_types::BlendState { color: (&blend.color).convert(), alpha: (&blend.alpha).convert(), } }), }) }) }) .collect::, _>>()?, ), }) }) .transpose()?, primitive: (&descriptor.primitive).convert(), depth_stencil: descriptor .depthStencil .as_ref() .map(|dss_desc| { self.validate_texture_format_required_features(&dss_desc.format) .map(|format| wgpu_types::DepthStencilState { format, depth_write_enabled: dss_desc.depthWriteEnabled, depth_compare: dss_desc.depthCompare.convert(), stencil: wgpu_types::StencilState { front: wgpu_types::StencilFaceState { compare: dss_desc.stencilFront.compare.convert(), fail_op: dss_desc.stencilFront.failOp.convert(), depth_fail_op: dss_desc.stencilFront.depthFailOp.convert(), pass_op: dss_desc.stencilFront.passOp.convert(), }, back: wgpu_types::StencilFaceState { compare: dss_desc.stencilBack.compare.convert(), fail_op: dss_desc.stencilBack.failOp.convert(), depth_fail_op: dss_desc.stencilBack.depthFailOp.convert(), pass_op: dss_desc.stencilBack.passOp.convert(), }, read_mask: dss_desc.stencilReadMask, write_mask: dss_desc.stencilWriteMask, }, bias: wgpu_types::DepthBiasState { constant: dss_desc.depthBias, slope_scale: *dss_desc.depthBiasSlopeScale, clamp: *dss_desc.depthBiasClamp, }, }) }) .transpose()?, multisample: wgpu_types::MultisampleState { count: descriptor.multisample.count, mask: descriptor.multisample.mask as u64, alpha_to_coverage_enabled: descriptor.multisample.alphaToCoverageEnabled, }, multiview: None, }; Ok((pipeline_layout, desc)) } /// pub(crate) fn lose(&self, reason: GPUDeviceLostReason, msg: String, can_gc: CanGc) { let lost_promise = &(*self.lost_promise.borrow()); let global = &self.global(); let lost = GPUDeviceLostInfo::new(global, msg.into(), reason, can_gc); lost_promise.resolve_native(&*lost, can_gc); } } impl GPUDeviceMethods for GPUDevice { /// fn Features(&self) -> DomRoot { DomRoot::from_ref(&self.features) } /// fn Limits(&self) -> DomRoot { DomRoot::from_ref(&self.limits) } /// fn GetQueue(&self) -> DomRoot { DomRoot::from_ref(&self.default_queue) } /// fn Label(&self) -> USVString { self.label.borrow().clone() } /// fn SetLabel(&self, value: USVString) { *self.label.borrow_mut() = value; } /// fn Lost(&self) -> Rc { self.lost_promise.borrow().clone() } /// fn CreateBuffer(&self, descriptor: &GPUBufferDescriptor) -> Fallible> { GPUBuffer::create(self, descriptor, CanGc::note()) } /// #[allow(non_snake_case)] fn CreateBindGroupLayout( &self, descriptor: &GPUBindGroupLayoutDescriptor, ) -> Fallible> { GPUBindGroupLayout::create(self, descriptor, CanGc::note()) } /// fn CreatePipelineLayout( &self, descriptor: &GPUPipelineLayoutDescriptor, ) -> DomRoot { GPUPipelineLayout::create(self, descriptor, CanGc::note()) } /// fn CreateBindGroup(&self, descriptor: &GPUBindGroupDescriptor) -> DomRoot { GPUBindGroup::create(self, descriptor, CanGc::note()) } /// fn CreateShaderModule( &self, descriptor: RootedTraceableBox, comp: InRealm, can_gc: CanGc, ) -> DomRoot { GPUShaderModule::create(self, descriptor, comp, can_gc) } /// fn CreateComputePipeline( &self, descriptor: &GPUComputePipelineDescriptor, ) -> DomRoot { let compute_pipeline = GPUComputePipeline::create(self, descriptor, None); GPUComputePipeline::new( &self.global(), compute_pipeline, descriptor.parent.parent.label.clone(), self, CanGc::note(), ) } /// fn CreateComputePipelineAsync( &self, descriptor: &GPUComputePipelineDescriptor, comp: InRealm, can_gc: CanGc, ) -> Rc { let promise = Promise::new_in_current_realm(comp, can_gc); let sender = route_promise(&promise, self); GPUComputePipeline::create(self, descriptor, Some(sender)); promise } /// fn CreateCommandEncoder( &self, descriptor: &GPUCommandEncoderDescriptor, ) -> DomRoot { GPUCommandEncoder::create(self, descriptor, CanGc::note()) } /// fn CreateTexture(&self, descriptor: &GPUTextureDescriptor) -> Fallible> { GPUTexture::create(self, descriptor, CanGc::note()) } /// fn CreateSampler(&self, descriptor: &GPUSamplerDescriptor) -> DomRoot { GPUSampler::create(self, descriptor, CanGc::note()) } /// fn CreateRenderPipeline( &self, descriptor: &GPURenderPipelineDescriptor, ) -> Fallible> { 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, CanGc::note(), )) } /// fn CreateRenderPipelineAsync( &self, descriptor: &GPURenderPipelineDescriptor, comp: InRealm, can_gc: CanGc, ) -> Fallible> { let (implicit_ids, desc) = self.parse_render_pipeline(descriptor)?; let promise = Promise::new_in_current_realm(comp, can_gc); let sender = route_promise(&promise, self); GPURenderPipeline::create(self, implicit_ids, desc, Some(sender))?; Ok(promise) } /// fn CreateRenderBundleEncoder( &self, descriptor: &GPURenderBundleEncoderDescriptor, ) -> Fallible> { GPURenderBundleEncoder::create(self, descriptor, CanGc::note()) } /// 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"); } } /// fn PopErrorScope(&self, comp: InRealm, can_gc: CanGc) -> Rc { let promise = Promise::new_in_current_realm(comp, can_gc); let sender = route_promise(&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); /// 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 RoutedPromiseListener for GPUDevice { fn handle_response( &self, response: WebGPUPoppedErrorScopeResponse, promise: &Rc, can_gc: CanGc, ) { match response { Ok(None) | Err(PopError::Lost) => { promise.resolve_native(&None::>, can_gc) }, Err(PopError::Empty) => promise.reject_error(Error::Operation, can_gc), Ok(Some(error)) => { let error = GPUError::from_error(&self.global(), error, can_gc); promise.resolve_native(&error, can_gc); }, } } } impl RoutedPromiseListener for GPUDevice { fn handle_response( &self, response: WebGPUComputePipelineResponse, promise: &Rc, can_gc: CanGc, ) { match response { Ok(pipeline) => promise.resolve_native( &GPUComputePipeline::new( &self.global(), WebGPUComputePipeline(pipeline.id), pipeline.label.into(), self, can_gc, ), can_gc, ), Err(webgpu_traits::Error::Validation(msg)) => promise.reject_native( &GPUPipelineError::new( &self.global(), msg.into(), GPUPipelineErrorReason::Validation, can_gc, ), can_gc, ), Err(webgpu_traits::Error::OutOfMemory(msg) | webgpu_traits::Error::Internal(msg)) => { promise.reject_native( &GPUPipelineError::new( &self.global(), msg.into(), GPUPipelineErrorReason::Internal, can_gc, ), can_gc, ) }, } } } impl RoutedPromiseListener for GPUDevice { fn handle_response( &self, response: WebGPURenderPipelineResponse, promise: &Rc, can_gc: CanGc, ) { match response { Ok(pipeline) => promise.resolve_native( &GPURenderPipeline::new( &self.global(), WebGPURenderPipeline(pipeline.id), pipeline.label.into(), self, can_gc, ), can_gc, ), Err(webgpu_traits::Error::Validation(msg)) => promise.reject_native( &GPUPipelineError::new( &self.global(), msg.into(), GPUPipelineErrorReason::Validation, can_gc, ), can_gc, ), Err(webgpu_traits::Error::OutOfMemory(msg) | webgpu_traits::Error::Internal(msg)) => { promise.reject_native( &GPUPipelineError::new( &self.global(), msg.into(), GPUPipelineErrorReason::Internal, can_gc, ), can_gc, ) }, } } } 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); } } }