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};
9use script_webgpu::gpuconvert::{
10    WebGPUConvert, WebGPUTryConvert, convert_load_op, convert_texture_for_wgpu_with_cx,
11};
12use webgpu_traits::{
13    WebGPU, WebGPUCommandBuffer, WebGPUCommandEncoder, WebGPUComputePass, WebGPUDevice,
14    WebGPURenderPass, WebGPURequest,
15};
16use wgpu_core::command as wgpu_com;
17
18use crate::dom::bindings::codegen::Bindings::WebGPUBinding::{
19    GPUCommandBufferDescriptor, GPUCommandEncoderDescriptor, GPUCommandEncoderMethods,
20    GPUComputePassDescriptor, GPUExtent3D, GPURenderPassDescriptor, GPUSize64,
21    GPUTexelCopyBufferInfo, GPUTexelCopyTextureInfo,
22};
23use crate::dom::bindings::error::Fallible;
24use crate::dom::bindings::reflector::DomGlobal;
25use crate::dom::bindings::root::{Dom, DomRoot};
26use crate::dom::bindings::str::USVString;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::types::GPUQuerySet;
29use crate::dom::webgpu::gpubuffer::GPUBuffer;
30use crate::dom::webgpu::gpucommandbuffer::GPUCommandBuffer;
31use crate::dom::webgpu::gpucomputepassencoder::GPUComputePassEncoder;
32use crate::dom::webgpu::gpudevice::GPUDevice;
33use crate::dom::webgpu::gpurenderpassencoder::GPURenderPassEncoder;
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(
86            cx,
87            Box::new(GPUCommandEncoder::new_inherited(
88                channel, device, encoder, label,
89            )),
90            global,
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
164                    .timestampWrites
165                    .as_ref()
166                    .map(WebGPUConvert::convert),
167                device_id: self.device.id().0,
168            })
169        {
170            warn!("Failed to send WebGPURequest::BeginComputePass {error:?}");
171        }
172
173        GPUComputePassEncoder::new(
174            cx,
175            &self.global(),
176            self.droppable.channel.clone(),
177            self,
178            WebGPUComputePass(compute_pass_id),
179            descriptor.parent.label.clone(),
180        )
181    }
182
183    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-beginrenderpass>
184    fn BeginRenderPass(
185        &self,
186        cx: &mut JSContext,
187        descriptor: &GPURenderPassDescriptor,
188    ) -> Fallible<DomRoot<GPURenderPassEncoder>> {
189        let depth_stencil_attachment = descriptor.depthStencilAttachment.as_ref().map(|ds| {
190            wgpu_com::RenderPassDepthStencilAttachment {
191                depth: wgpu_com::PassChannel {
192                    load_op: ds
193                        .depthLoadOp
194                        .as_ref()
195                        .map(|l| convert_load_op(l, ds.depthClearValue.map(|v| *v))),
196                    store_op: ds.depthStoreOp.as_ref().map(WebGPUConvert::convert),
197                    read_only: ds.depthReadOnly,
198                },
199                stencil: wgpu_com::PassChannel {
200                    load_op: ds
201                        .stencilLoadOp
202                        .as_ref()
203                        .map(|l| convert_load_op(l, Some(ds.stencilClearValue))),
204                    store_op: ds.stencilStoreOp.as_ref().map(WebGPUConvert::convert),
205                    read_only: ds.stencilReadOnly,
206                },
207                view: convert_texture_for_wgpu_with_cx(cx, &ds.view).0,
208            }
209        });
210
211        let color_attachments = descriptor
212            .colorAttachments
213            .iter()
214            .map(|color| -> Fallible<_> {
215                Ok(Some(wgpu_com::RenderPassColorAttachment {
216                    resolve_target: color
217                        .resolveTarget
218                        .as_ref()
219                        .map(|t| convert_texture_for_wgpu_with_cx(cx, t).0),
220                    load_op: convert_load_op(
221                        &color.loadOp,
222                        color
223                            .clearValue
224                            .as_ref()
225                            .map(|color| (color).try_convert())
226                            .transpose()?
227                            .unwrap_or_default(),
228                    ),
229                    store_op: color.storeOp.convert(),
230                    view: convert_texture_for_wgpu_with_cx(cx, &color.view).0,
231                    depth_slice: None,
232                }))
233            })
234            .collect::<Fallible<Vec<_>>>()?;
235        let render_pass_id = self.global().wgpu_id_hub().create_render_pass_id();
236
237        if let Err(error) = self
238            .droppable
239            .channel
240            .0
241            .send(WebGPURequest::BeginRenderPass {
242                command_encoder_id: self.id().0,
243                render_pass_id,
244                label: (&descriptor.parent).convert(),
245                depth_stencil_attachment,
246                color_attachments,
247                timestamp_writes: descriptor
248                    .timestampWrites
249                    .as_ref()
250                    .map(WebGPUConvert::convert),
251                device_id: self.device.id().0,
252            })
253        {
254            warn!("Failed to send WebGPURequest::BeginRenderPass {error:?}");
255        }
256
257        Ok(GPURenderPassEncoder::new(
258            cx,
259            &self.global(),
260            self.droppable.channel.clone(),
261            WebGPURenderPass(render_pass_id),
262            self,
263            descriptor.parent.label.clone(),
264        ))
265    }
266
267    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertobuffer>
268    fn CopyBufferToBuffer(
269        &self,
270        source: &GPUBuffer,
271        source_offset: GPUSize64,
272        destination: &GPUBuffer,
273        destination_offset: GPUSize64,
274        size: GPUSize64,
275    ) {
276        self.droppable
277            .channel
278            .0
279            .send(WebGPURequest::CopyBufferToBuffer {
280                command_encoder_id: self.droppable.encoder.0,
281                source_id: source.id().0,
282                source_offset,
283                destination_id: destination.id().0,
284                destination_offset,
285                size,
286                device_id: self.device.id().0,
287            })
288            .expect("Failed to send CopyBufferToBuffer");
289    }
290
291    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture>
292    fn CopyBufferToTexture(
293        &self,
294        source: &GPUTexelCopyBufferInfo,
295        destination: &GPUTexelCopyTextureInfo,
296        copy_size: GPUExtent3D,
297    ) -> Fallible<()> {
298        self.droppable
299            .channel
300            .0
301            .send(WebGPURequest::CopyBufferToTexture {
302                command_encoder_id: self.droppable.encoder.0,
303                source: source.convert(),
304                destination: destination.try_convert()?,
305                copy_size: (&copy_size).try_convert()?,
306                device_id: self.device.id().0,
307            })
308            .expect("Failed to send CopyBufferToTexture");
309
310        Ok(())
311    }
312
313    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-copybuffertotexture>
314    fn CopyTextureToBuffer(
315        &self,
316        source: &GPUTexelCopyTextureInfo,
317        destination: &GPUTexelCopyBufferInfo,
318        copy_size: GPUExtent3D,
319    ) -> Fallible<()> {
320        self.droppable
321            .channel
322            .0
323            .send(WebGPURequest::CopyTextureToBuffer {
324                command_encoder_id: self.droppable.encoder.0,
325                source: source.try_convert()?,
326                destination: destination.convert(),
327                copy_size: (&copy_size).try_convert()?,
328                device_id: self.device.id().0,
329            })
330            .expect("Failed to send CopyTextureToBuffer");
331
332        Ok(())
333    }
334
335    /// <https://gpuweb.github.io/gpuweb/#GPUCommandEncoder-copyTextureToTexture>
336    fn CopyTextureToTexture(
337        &self,
338        source: &GPUTexelCopyTextureInfo,
339        destination: &GPUTexelCopyTextureInfo,
340        copy_size: GPUExtent3D,
341    ) -> Fallible<()> {
342        self.droppable
343            .channel
344            .0
345            .send(WebGPURequest::CopyTextureToTexture {
346                command_encoder_id: self.droppable.encoder.0,
347                source: source.try_convert()?,
348                destination: destination.try_convert()?,
349                copy_size: (&copy_size).try_convert()?,
350                device_id: self.device.id().0,
351            })
352            .expect("Failed to send CopyTextureToTexture");
353
354        Ok(())
355    }
356
357    /// <https://gpuweb.github.io/gpuweb/#dom-gpucommandencoder-finish>
358    fn Finish(
359        &self,
360        cx: &mut JSContext,
361        descriptor: &GPUCommandBufferDescriptor,
362    ) -> DomRoot<GPUCommandBuffer> {
363        let command_buffer_id = self.global().wgpu_id_hub().create_command_buffer_id();
364        self.droppable
365            .channel
366            .0
367            .send(WebGPURequest::CommandEncoderFinish {
368                command_encoder_id: self.droppable.encoder.0,
369                device_id: self.device.id().0,
370                desc: wgpu_types::CommandBufferDescriptor {
371                    label: (&descriptor.parent).convert(),
372                },
373                command_buffer_id,
374            })
375            .expect("Failed to send Finish");
376
377        let buffer = WebGPUCommandBuffer(command_buffer_id);
378        GPUCommandBuffer::new(
379            cx,
380            &self.global(),
381            self.droppable.channel.clone(),
382            buffer,
383            descriptor.parent.label.clone(),
384        )
385    }
386
387    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-pushdebuggroup>
388    fn PushDebugGroup(&self, group_label: USVString) {
389        if let Err(e) = self
390            .droppable
391            .channel
392            .0
393            .send(WebGPURequest::CommandEncoderPushDebugGroup {
394                command_encoder_id: self.droppable.encoder.0,
395                label: group_label.to_string(),
396                device_id: self.device.id().0,
397            })
398        {
399            warn!("Error sending WebGPURequest::CommandEncoderPushDebugGroup: {e:?}")
400        }
401    }
402
403    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-popdebuggroup>
404    fn PopDebugGroup(&self) {
405        if let Err(e) = self
406            .droppable
407            .channel
408            .0
409            .send(WebGPURequest::CommandEncoderPopDebugGroup {
410                command_encoder_id: self.droppable.encoder.0,
411                device_id: self.device.id().0,
412            })
413        {
414            warn!("Error sending WebGPURequest::CommandEncoderPopDebugGroup: {e:?}")
415        }
416    }
417
418    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-insertdebugmarker>
419    fn InsertDebugMarker(&self, marker_label: USVString) {
420        if let Err(e) =
421            self.droppable
422                .channel
423                .0
424                .send(WebGPURequest::CommandEncoderInsertDebugMarker {
425                    command_encoder_id: self.droppable.encoder.0,
426                    label: marker_label.to_string(),
427                    device_id: self.device.id().0,
428                })
429        {
430            warn!("Error sending WebGPURequest::CommandEncoderInsertDebugMarker: {e:?}")
431        }
432    }
433
434    fn ResolveQuerySet(
435        &self,
436        query_set: &GPUQuerySet,
437        first_query: u32,
438        query_count: u32,
439        destination: &GPUBuffer,
440        destination_offset: u64,
441    ) {
442        if let Err(error) = self
443            .droppable
444            .channel
445            .0
446            .send(WebGPURequest::ResolveQuerySet {
447                command_encoder_id: self.droppable.encoder.0,
448                query_set_id: query_set.id().0,
449                start_query: first_query,
450                query_count,
451                destination: destination.id().0,
452                destination_offset,
453                device_id: self.device.id().0,
454            })
455        {
456            warn!("Error sending WebGPURequest::ResolveQuerySet: {error:?}")
457        }
458    }
459}