Skip to main content

script_webgpu/
gpucommandencoder.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use dom_struct::dom_struct;
6use js::context::{JSContext, NoGC};
7use log::warn;
8use malloc_size_of_derive::MallocSizeOf;
9use script_bindings::DomTypes;
10use script_bindings::cell::DomRefCell;
11use script_bindings::codegen::GenericBindings::WebGPUBinding::{
12    GPUCommandBufferDescriptor, GPUCommandEncoderDescriptor, GPUCommandEncoderMethods,
13    GPUCommandEncoderWrap, GPUComputePassDescriptor, GPURenderPassDescriptor, GPUSize64,
14    GPUTexelCopyBufferInfo, GPUTexelCopyTextureInfo,
15};
16use script_bindings::codegen::GenericUnionTypes::RangeEnforcedUnsignedLongSequenceOrGPUExtent3DDict as GPUExtent3D;
17use script_bindings::interfaces::PromiseHelpers;
18use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
19use webgpu_traits::{
20    WebGPU, WebGPUCommandBuffer, WebGPUCommandEncoder, WebGPUComputePass, WebGPUDevice,
21    WebGPURenderPass, WebGPURequest,
22};
23use wgpu_core::command as wgpu_com;
24
25use crate::JSTraceable;
26use crate::dom::bindings::error::Fallible;
27use crate::dom::bindings::root::{Dom, DomRoot};
28use crate::dom::bindings::str::USVString;
29use crate::gpubuffer::GPUBuffer;
30use crate::gpucommandbuffer::GPUCommandBuffer;
31use crate::gpucomputepassencoder::GPUComputePassEncoder;
32use crate::gpuconvert::{
33    WebGPUConvert, WebGPUTryConvert, convert_load_op, convert_texture_for_wgpu_with_cx,
34};
35use crate::gpuqueryset::GPUQuerySet;
36use crate::gpurenderpassencoder::GPURenderPassEncoder;
37use crate::traits::{Equivalence, GPUDeviceTrait, WebGPUGlobalTrait};
38
39#[derive(JSTraceable, MallocSizeOf)]
40struct DroppableGPUCommandEncoder {
41    #[no_trace]
42    channel: WebGPU,
43    #[no_trace]
44    encoder: WebGPUCommandEncoder,
45}
46
47#[dom_struct]
48pub struct GPUCommandEncoder<D: DomTypes> {
49    reflector_: Reflector,
50    droppable: DroppableGPUCommandEncoder,
51    label: DomRefCell<USVString>,
52    device: Dom<D::GPUDevice>,
53}
54
55impl Drop for DroppableGPUCommandEncoder {
56    fn drop(&mut self) {
57        if let Err(e) = self
58            .channel
59            .0
60            .send(WebGPURequest::DropCommandEncoder(self.encoder.0))
61        {
62            warn!("Failed to send WebGPURequest::DropCommandEncoder with {e:?}");
63        }
64    }
65}
66
67impl<D: Equivalence> GPUCommandEncoder<D> {
68    pub(crate) fn new_inherited(
69        channel: WebGPU,
70        device: &D::GPUDevice,
71        encoder: WebGPUCommandEncoder,
72        label: USVString,
73    ) -> Self {
74        Self {
75            droppable: DroppableGPUCommandEncoder { channel, encoder },
76            reflector_: Reflector::new(),
77            label: DomRefCell::new(label),
78            device: Dom::from_ref(device),
79        }
80    }
81
82    pub(crate) fn new(
83        cx: &mut JSContext,
84        global: &D::GlobalScope,
85        channel: WebGPU,
86        device: &D::GPUDevice,
87        encoder: WebGPUCommandEncoder,
88        label: USVString,
89    ) -> DomRoot<Self> {
90        reflect_dom_object_with_wrap::<D, _, _>(
91            Box::new(GPUCommandEncoder::new_inherited(
92                channel, device, encoder, label,
93            )),
94            global,
95            cx,
96            GPUCommandEncoderWrap::<D>,
97        )
98    }
99}
100
101impl<D> GPUCommandEncoder<D>
102where
103    D: Equivalence,
104    D::GPUDevice: GPUDeviceTrait<D> + DomGlobalGeneric<D>,
105    D::GlobalScope: WebGPUGlobalTrait,
106    Self: DomGlobalGeneric<D>,
107{
108    pub(crate) fn id(&self) -> WebGPUCommandEncoder {
109        self.droppable.encoder
110    }
111
112    pub(crate) fn device_id(&self) -> WebGPUDevice {
113        self.device.id()
114    }
115
116    /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createcommandencoder>
117    pub fn create(
118        cx: &mut JSContext,
119        device: &D::GPUDevice,
120        descriptor: &GPUCommandEncoderDescriptor,
121    ) -> DomRoot<GPUCommandEncoder<D>> {
122        let command_encoder_id = device
123            .global_from_reflector()
124            .global_wgpu_id_hub()
125            .create_command_encoder_id();
126        device
127            .channel()
128            .0
129            .send(WebGPURequest::CreateCommandEncoder {
130                device_id: device.id().0,
131                command_encoder_id,
132                desc: wgpu_types::CommandEncoderDescriptor {
133                    label: (&descriptor.parent).convert(),
134                },
135            })
136            .expect("Failed to create WebGPU command encoder");
137
138        let encoder = WebGPUCommandEncoder(command_encoder_id);
139
140        GPUCommandEncoder::new(
141            cx,
142            &*device.global_from_reflector(),
143            device.channel(),
144            device,
145            encoder,
146            descriptor.parent.label.clone(),
147        )
148    }
149}
150
151impl<D> GPUCommandEncoderMethods<D> for GPUCommandEncoder<D>
152where
153    D: Equivalence,
154    D::GPUDevice: GPUDeviceTrait<D>,
155    D::GlobalScope: WebGPUGlobalTrait,
156    D::Promise: PromiseHelpers<D>,
157    Self: DomGlobalGeneric<D>,
158{
159    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
160    fn Label(&self) -> USVString {
161        self.label.borrow().clone()
162    }
163
164    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
165    fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
166        *self.label.safe_borrow_mut(no_gc) = value;
167    }
168
169    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-begincomputepass>
170    fn BeginComputePass(
171        &self,
172        cx: &mut JSContext,
173        descriptor: &GPUComputePassDescriptor<D>,
174    ) -> DomRoot<GPUComputePassEncoder<D>> {
175        let compute_pass_id = self
176            .global_from_reflector()
177            .global_wgpu_id_hub()
178            .create_compute_pass_id();
179
180        if let Err(error) = self
181            .droppable
182            .channel
183            .0
184            .send(WebGPURequest::BeginComputePass {
185                command_encoder_id: self.id().0,
186                compute_pass_id,
187                label: (&descriptor.parent).convert(),
188                timestamp_writes: descriptor
189                    .timestampWrites
190                    .as_ref()
191                    .map(WebGPUConvert::convert),
192                device_id: self.device.id().0,
193            })
194        {
195            warn!("Failed to send WebGPURequest::BeginComputePass {error:?}");
196        }
197
198        GPUComputePassEncoder::new(
199            cx,
200            &*self.global_from_reflector(),
201            self.droppable.channel.clone(),
202            self,
203            WebGPUComputePass(compute_pass_id),
204            descriptor.parent.label.clone(),
205        )
206    }
207
208    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-beginrenderpass>
209    fn BeginRenderPass(
210        &self,
211        cx: &mut JSContext,
212        descriptor: &GPURenderPassDescriptor<D>,
213    ) -> Fallible<DomRoot<GPURenderPassEncoder<D>>> {
214        let depth_stencil_attachment = descriptor.depthStencilAttachment.as_ref().map(|ds| {
215            wgpu_com::RenderPassDepthStencilAttachment {
216                depth: wgpu_com::PassChannel {
217                    load_op: ds
218                        .depthLoadOp
219                        .as_ref()
220                        .map(|l| convert_load_op(l, ds.depthClearValue.map(|v| *v))),
221                    store_op: ds.depthStoreOp.as_ref().map(WebGPUConvert::convert),
222                    read_only: ds.depthReadOnly,
223                },
224                stencil: wgpu_com::PassChannel {
225                    load_op: ds
226                        .stencilLoadOp
227                        .as_ref()
228                        .map(|l| convert_load_op(l, Some(ds.stencilClearValue))),
229                    store_op: ds.stencilStoreOp.as_ref().map(WebGPUConvert::convert),
230                    read_only: ds.stencilReadOnly,
231                },
232                view: convert_texture_for_wgpu_with_cx(cx, &ds.view).0,
233            }
234        });
235
236        let color_attachments = descriptor
237            .colorAttachments
238            .iter()
239            .map(|color| -> Fallible<_> {
240                Ok(Some(wgpu_com::RenderPassColorAttachment {
241                    resolve_target: color
242                        .resolveTarget
243                        .as_ref()
244                        .map(|t| convert_texture_for_wgpu_with_cx(cx, t).0),
245                    load_op: convert_load_op(
246                        &color.loadOp,
247                        color
248                            .clearValue
249                            .as_ref()
250                            .map(|color| (color).try_convert())
251                            .transpose()?
252                            .unwrap_or_default(),
253                    ),
254                    store_op: color.storeOp.convert(),
255                    view: convert_texture_for_wgpu_with_cx(cx, &color.view).0,
256                    depth_slice: None,
257                }))
258            })
259            .collect::<Fallible<Vec<_>>>()?;
260        let render_pass_id = self
261            .global_from_reflector()
262            .global_wgpu_id_hub()
263            .create_render_pass_id();
264
265        if let Err(error) = self
266            .droppable
267            .channel
268            .0
269            .send(WebGPURequest::BeginRenderPass {
270                command_encoder_id: self.id().0,
271                render_pass_id,
272                label: (&descriptor.parent).convert(),
273                depth_stencil_attachment,
274                color_attachments,
275                timestamp_writes: descriptor
276                    .timestampWrites
277                    .as_ref()
278                    .map(WebGPUConvert::convert),
279                device_id: self.device.id().0,
280            })
281        {
282            warn!("Failed to send WebGPURequest::BeginRenderPass {error:?}");
283        }
284
285        Ok(GPURenderPassEncoder::new(
286            cx,
287            &*self.global_from_reflector(),
288            self.droppable.channel.clone(),
289            WebGPURenderPass(render_pass_id),
290            self,
291            descriptor.parent.label.clone(),
292        ))
293    }
294
295    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertobuffer>
296    fn CopyBufferToBuffer(
297        &self,
298        source: &GPUBuffer<D>,
299        source_offset: GPUSize64,
300        destination: &GPUBuffer<D>,
301        destination_offset: GPUSize64,
302        size: GPUSize64,
303    ) {
304        self.droppable
305            .channel
306            .0
307            .send(WebGPURequest::CopyBufferToBuffer {
308                command_encoder_id: self.droppable.encoder.0,
309                source_id: source.id().0,
310                source_offset,
311                destination_id: destination.id().0,
312                destination_offset,
313                size,
314                device_id: self.device.id().0,
315            })
316            .expect("Failed to send CopyBufferToBuffer");
317    }
318
319    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture>
320    fn CopyBufferToTexture(
321        &self,
322        source: &GPUTexelCopyBufferInfo<D>,
323        destination: &GPUTexelCopyTextureInfo<D>,
324        copy_size: GPUExtent3D,
325    ) -> Fallible<()> {
326        self.droppable
327            .channel
328            .0
329            .send(WebGPURequest::CopyBufferToTexture {
330                command_encoder_id: self.droppable.encoder.0,
331                source: source.convert(),
332                destination: destination.try_convert()?,
333                copy_size: (&copy_size).try_convert()?,
334                device_id: self.device.id().0,
335            })
336            .expect("Failed to send CopyBufferToTexture");
337
338        Ok(())
339    }
340
341    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture>
342    fn CopyTextureToBuffer(
343        &self,
344        source: &GPUTexelCopyTextureInfo<D>,
345        destination: &GPUTexelCopyBufferInfo<D>,
346        copy_size: GPUExtent3D,
347    ) -> Fallible<()> {
348        self.droppable
349            .channel
350            .0
351            .send(WebGPURequest::CopyTextureToBuffer {
352                command_encoder_id: self.droppable.encoder.0,
353                source: source.try_convert()?,
354                destination: destination.convert(),
355                copy_size: (&copy_size).try_convert()?,
356                device_id: self.device.id().0,
357            })
358            .expect("Failed to send CopyTextureToBuffer");
359
360        Ok(())
361    }
362
363    /// <https://gpuweb.github.io/gpuweb/#GPUCommandEncoder-copyTextureToTexture>
364    fn CopyTextureToTexture(
365        &self,
366        source: &GPUTexelCopyTextureInfo<D>,
367        destination: &GPUTexelCopyTextureInfo<D>,
368        copy_size: GPUExtent3D,
369    ) -> Fallible<()> {
370        self.droppable
371            .channel
372            .0
373            .send(WebGPURequest::CopyTextureToTexture {
374                command_encoder_id: self.droppable.encoder.0,
375                source: source.try_convert()?,
376                destination: destination.try_convert()?,
377                copy_size: (&copy_size).try_convert()?,
378                device_id: self.device.id().0,
379            })
380            .expect("Failed to send CopyTextureToTexture");
381
382        Ok(())
383    }
384
385    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-finish>
386    fn Finish(
387        &self,
388        cx: &mut JSContext,
389        descriptor: &GPUCommandBufferDescriptor,
390    ) -> DomRoot<GPUCommandBuffer<D>> {
391        let command_buffer_id = self
392            .global_from_reflector()
393            .global_wgpu_id_hub()
394            .create_command_buffer_id();
395        self.droppable
396            .channel
397            .0
398            .send(WebGPURequest::CommandEncoderFinish {
399                command_encoder_id: self.droppable.encoder.0,
400                device_id: self.device.id().0,
401                desc: wgpu_types::CommandBufferDescriptor {
402                    label: (&descriptor.parent).convert(),
403                },
404                command_buffer_id,
405            })
406            .expect("Failed to send Finish");
407
408        let buffer = WebGPUCommandBuffer(command_buffer_id);
409        GPUCommandBuffer::new(
410            cx,
411            &*self.global_from_reflector(),
412            self.droppable.channel.clone(),
413            buffer,
414            descriptor.parent.label.clone(),
415        )
416    }
417
418    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-pushdebuggroup>
419    fn PushDebugGroup(&self, group_label: USVString) {
420        if let Err(e) = self
421            .droppable
422            .channel
423            .0
424            .send(WebGPURequest::CommandEncoderPushDebugGroup {
425                command_encoder_id: self.droppable.encoder.0,
426                label: group_label.to_string(),
427                device_id: self.device.id().0,
428            })
429        {
430            warn!("Error sending WebGPURequest::CommandEncoderPushDebugGroup: {e:?}")
431        }
432    }
433
434    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-popdebuggroup>
435    fn PopDebugGroup(&self) {
436        if let Err(e) = self
437            .droppable
438            .channel
439            .0
440            .send(WebGPURequest::CommandEncoderPopDebugGroup {
441                command_encoder_id: self.droppable.encoder.0,
442                device_id: self.device.id().0,
443            })
444        {
445            warn!("Error sending WebGPURequest::CommandEncoderPopDebugGroup: {e:?}")
446        }
447    }
448
449    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-insertdebugmarker>
450    fn InsertDebugMarker(&self, marker_label: USVString) {
451        if let Err(e) =
452            self.droppable
453                .channel
454                .0
455                .send(WebGPURequest::CommandEncoderInsertDebugMarker {
456                    command_encoder_id: self.droppable.encoder.0,
457                    label: marker_label.to_string(),
458                    device_id: self.device.id().0,
459                })
460        {
461            warn!("Error sending WebGPURequest::CommandEncoderInsertDebugMarker: {e:?}")
462        }
463    }
464
465    fn ResolveQuerySet(
466        &self,
467        query_set: &GPUQuerySet<D>,
468        first_query: u32,
469        query_count: u32,
470        destination: &GPUBuffer<D>,
471        destination_offset: u64,
472    ) {
473        if let Err(error) = self
474            .droppable
475            .channel
476            .0
477            .send(WebGPURequest::ResolveQuerySet {
478                command_encoder_id: self.droppable.encoder.0,
479                query_set_id: query_set.id().0,
480                start_query: first_query,
481                query_count,
482                destination: destination.id().0,
483                destination_offset,
484                device_id: self.device.id().0,
485            })
486        {
487            warn!("Error sending WebGPURequest::ResolveQuerySet: {error:?}")
488        }
489    }
490}