Skip to main content

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