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