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