Skip to main content

script_webgpu/
gpurenderbundleencoder.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 std::borrow::Cow;
6
7use dom_struct::dom_struct;
8use js::context::{JSContext, NoGC};
9use log::warn;
10use malloc_size_of_derive::MallocSizeOf;
11use script_bindings::DomTypes;
12use script_bindings::cell::DomRefCell;
13use script_bindings::codegen::GenericBindings::WebGPUBinding::{
14    GPUIndexFormat, GPURenderBundleDescriptor, GPURenderBundleEncoderDescriptor,
15    GPURenderBundleEncoderMethods, GPURenderBundleEncoderWrap,
16};
17use script_bindings::interfaces::PromiseHelpers;
18use script_bindings::reflector::{DomGlobalGeneric, Reflector, reflect_dom_object_with_wrap};
19use webgpu_traits::{
20    BindingCommand, BufferSize, DebugCommand, RenderBundleDepthStencil, RenderBundleDescriptor,
21    RenderBundleEncoderCommand, RenderBundleEncoderDescriptor, RenderCommand, WebGPU,
22    WebGPURenderBundle, WebGPURenderBundleEncoder, WebGPURequest,
23};
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::gpubindgroup::GPUBindGroup;
30use crate::gpubuffer::GPUBuffer;
31use crate::gpuconvert::WebGPUConvert;
32use crate::gpurenderbundle::GPURenderBundle;
33use crate::gpurenderpipeline::GPURenderPipeline;
34use crate::traits::{Equivalence, WebGPUGlobalTrait, WebGPUPromise};
35
36#[derive(JSTraceable, MallocSizeOf)]
37struct DroppableGPURenderBundleEncoder {
38    #[no_trace]
39    channel: WebGPU,
40    #[no_trace]
41    render_bundle_encoder: WebGPURenderBundleEncoder,
42}
43
44impl Drop for DroppableGPURenderBundleEncoder {
45    fn drop(&mut self) {
46        if let Err(error) = self.channel.0.send(WebGPURequest::DropRenderBundleEncoder(
47            self.render_bundle_encoder.0,
48        )) {
49            warn!(
50                "Failed to send WebGPURequest::DropRenderBundleEncoder({:?}) ({error})",
51                self.render_bundle_encoder.0
52            );
53        }
54    }
55}
56
57#[dom_struct]
58pub struct GPURenderBundleEncoder<D: DomTypes> {
59    reflector_: Reflector,
60    device: Dom<D::GPUDevice>,
61    label: DomRefCell<USVString>,
62    droppable: DroppableGPURenderBundleEncoder,
63}
64
65impl<D> GPURenderBundleEncoder<D>
66where
67    D: Equivalence,
68{
69    fn new_inherited(
70        device: &D::GPUDevice,
71        channel: WebGPU,
72        label: USVString,
73        render_bundle_encoder: WebGPURenderBundleEncoder,
74    ) -> Self {
75        Self {
76            reflector_: Reflector::new(),
77            device: Dom::from_ref(device),
78            droppable: DroppableGPURenderBundleEncoder {
79                channel,
80                render_bundle_encoder,
81            },
82            label: DomRefCell::new(label),
83        }
84    }
85
86    pub(crate) fn new(
87        cx: &mut JSContext,
88        global: &D::GlobalScope,
89        render_bundle_encoder: WebGPURenderBundleEncoder,
90        device: &D::GPUDevice,
91        channel: WebGPU,
92        label: USVString,
93    ) -> DomRoot<Self> {
94        reflect_dom_object_with_wrap::<D, _, _>(
95            cx,
96            Box::new(GPURenderBundleEncoder::new_inherited(
97                device,
98                channel,
99                label,
100                render_bundle_encoder,
101            )),
102            global,
103            GPURenderBundleEncoderWrap::<D>,
104        )
105    }
106}
107
108impl<D> GPURenderBundleEncoder<D>
109where
110    D: Equivalence,
111    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
112{
113    /// <https://gpuweb.github.io/gpuweb/#dom-gpudevice-createrenderbundleencoder>
114    pub(crate) fn create(
115        cx: &mut JSContext,
116        device: &D::GPUDevice,
117        descriptor: &GPURenderBundleEncoderDescriptor,
118    ) -> Fallible<DomRoot<GPURenderBundleEncoder<D>>> {
119        let desc = RenderBundleEncoderDescriptor {
120            label: (&descriptor.parent.parent).convert(),
121            color_formats: Cow::Owned(
122                descriptor
123                    .parent
124                    .colorFormats
125                    .iter()
126                    .map(|format| {
127                        device
128                            .validate_texture_format_required_features(format)
129                            .map(Some)
130                    })
131                    .collect::<Fallible<Vec<_>>>()?,
132            ),
133            depth_stencil: descriptor
134                .parent
135                .depthStencilFormat
136                .map(|dsf| {
137                    device
138                        .validate_texture_format_required_features(&dsf)
139                        .map(|format| RenderBundleDepthStencil {
140                            format,
141                            depth_read_only: descriptor.depthReadOnly,
142                            stencil_read_only: descriptor.stencilReadOnly,
143                        })
144                })
145                .transpose()?,
146            sample_count: descriptor.parent.sampleCount,
147            multiview: None,
148        };
149
150        let id = device
151            .global_from_reflector()
152            .global_wgpu_id_hub()
153            .create_render_bundle_encoder_id();
154        let render_bundle_encoder = WebGPURenderBundleEncoder(id);
155
156        let channel = device.channel();
157
158        if let Err(error) = channel.0.send(WebGPURequest::CreateRenderBundleEncoder {
159            device_id: device.id().0,
160            desc,
161            render_bundle_encoder_id: render_bundle_encoder.0,
162        }) {
163            warn!(
164                "Failed to send WebGPURequest::CreateRenderBundleEncoder({:?}) ({error})",
165                render_bundle_encoder.0
166            );
167        }
168
169        Ok(GPURenderBundleEncoder::new(
170            cx,
171            &*device.global_from_reflector(),
172            render_bundle_encoder,
173            device,
174            device.channel(),
175            descriptor.parent.parent.label.clone(),
176        ))
177    }
178
179    pub(crate) fn id(&self) -> WebGPURenderBundleEncoder {
180        self.droppable.render_bundle_encoder
181    }
182}
183
184impl<D> GPURenderBundleEncoderMethods<D> for GPURenderBundleEncoder<D>
185where
186    D: Equivalence,
187    <D::Promise as PromiseHelpers<D>>::StackRoot: WebGPUPromise<D>,
188{
189    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
190    fn Label(&self) -> USVString {
191        self.label.borrow().clone()
192    }
193
194    /// <https://gpuweb.github.io/gpuweb/#dom-gpuobjectbase-label>
195    fn SetLabel(&self, no_gc: &NoGC, value: USVString) {
196        *self.label.safe_borrow_mut(no_gc) = value;
197    }
198
199    /// <https://gpuweb.github.io/gpuweb/#dom-gpuprogrammablepassencoder-setbindgroup>
200    fn SetBindGroup(&self, index: u32, bind_group: &GPUBindGroup<D>, dynamic_offsets: Vec<u32>) {
201        if let Err(error) =
202            self.droppable
203                .channel
204                .0
205                .send(WebGPURequest::RenderBundleEncoderCommand {
206                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
207                    render_command: RenderBundleEncoderCommand::BindingCommand(
208                        BindingCommand::SetBindGroup {
209                            index,
210                            bind_group: Some(bind_group.id().0),
211                            dynamic_offsets,
212                        },
213                    ),
214                    device_id: self.device.id().0,
215                })
216        {
217            warn!(
218                "Failed to send WebGPURequest::RenderBundleEncoderSetBindGroup({:?}) ({error})",
219                self.droppable.render_bundle_encoder.0
220            );
221        }
222    }
223
224    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setpipeline>
225    fn SetPipeline(&self, pipeline: &GPURenderPipeline<D>) {
226        if let Err(error) =
227            self.droppable
228                .channel
229                .0
230                .send(WebGPURequest::RenderBundleEncoderCommand {
231                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
232                    render_command: RenderBundleEncoderCommand::RenderCommand(
233                        RenderCommand::SetPipeline(pipeline.id().0),
234                    ),
235                    device_id: self.device.id().0,
236                })
237        {
238            warn!(
239                "Failed to send WebGPURequest::RenderBundleEncoderSetPipeline({:?}) ({error})",
240                self.droppable.render_bundle_encoder.0
241            );
242        }
243    }
244
245    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setindexbuffer>
246    fn SetIndexBuffer(
247        &self,
248        buffer: &D::GPUBuffer,
249        index_format: GPUIndexFormat,
250        offset: u64,
251        size: u64,
252    ) {
253        if let Err(error) =
254            self.droppable
255                .channel
256                .0
257                .send(WebGPURequest::RenderBundleEncoderCommand {
258                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
259                    render_command: RenderBundleEncoderCommand::RenderCommand(
260                        RenderCommand::SetIndexBuffer {
261                            buffer: buffer.id().0,
262                            index_format: index_format.convert(),
263                            offset,
264                            size: BufferSize::new(size),
265                        },
266                    ),
267                    device_id: self.device.id().0,
268                })
269        {
270            warn!(
271                "Failed to send WebGPURequest::RenderBundleEncoderSetIndexBuffer({:?}) ({error})",
272                self.droppable.render_bundle_encoder.0
273            );
274        }
275    }
276
277    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-setvertexbuffer>
278    fn SetVertexBuffer(&self, slot: u32, buffer: Option<&GPUBuffer<D>>, offset: u64, size: u64) {
279        if let Err(error) =
280            self.droppable
281                .channel
282                .0
283                .send(WebGPURequest::RenderBundleEncoderCommand {
284                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
285                    render_command: RenderBundleEncoderCommand::RenderCommand(
286                        RenderCommand::SetVertexBuffer {
287                            slot,
288                            buffer: buffer.map(|b| b.id().0),
289                            offset,
290                            size: BufferSize::new(size),
291                        },
292                    ),
293                    device_id: self.device.id().0,
294                })
295        {
296            warn!(
297                "Failed to send WebGPURequest::RenderBundleEncoderSetVertexBuffer({:?}) ({error})",
298                self.droppable.render_bundle_encoder.0
299            );
300        }
301    }
302
303    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-draw>
304    fn Draw(&self, vertex_count: u32, instance_count: u32, first_vertex: u32, first_instance: u32) {
305        if let Err(error) =
306            self.droppable
307                .channel
308                .0
309                .send(WebGPURequest::RenderBundleEncoderCommand {
310                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
311                    render_command: RenderBundleEncoderCommand::RenderCommand(
312                        RenderCommand::Draw {
313                            vertex_count,
314                            instance_count,
315                            first_vertex,
316                            first_instance,
317                        },
318                    ),
319                    device_id: self.device.id().0,
320                })
321        {
322            warn!(
323                "Failed to send WebGPURequest::RenderBundleEncoderDraw({:?}) ({error})",
324                self.droppable.render_bundle_encoder.0
325            );
326        }
327    }
328
329    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexed>
330    fn DrawIndexed(
331        &self,
332        index_count: u32,
333        instance_count: u32,
334        first_index: u32,
335        base_vertex: i32,
336        first_instance: u32,
337    ) {
338        if let Err(error) =
339            self.droppable
340                .channel
341                .0
342                .send(WebGPURequest::RenderBundleEncoderCommand {
343                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
344                    render_command: RenderBundleEncoderCommand::RenderCommand(
345                        RenderCommand::DrawIndexed {
346                            index_count,
347                            instance_count,
348                            first_index,
349                            base_vertex,
350                            first_instance,
351                        },
352                    ),
353                    device_id: self.device.id().0,
354                })
355        {
356            warn!(
357                "Failed to send WebGPURequest::RenderBundleEncoderDrawIndexed({:?}) ({error})",
358                self.droppable.render_bundle_encoder.0
359            );
360        }
361    }
362
363    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindirect>
364    fn DrawIndirect(&self, indirect_buffer: &GPUBuffer<D>, indirect_offset: u64) {
365        if let Err(error) =
366            self.droppable
367                .channel
368                .0
369                .send(WebGPURequest::RenderBundleEncoderCommand {
370                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
371                    render_command: RenderBundleEncoderCommand::RenderCommand(
372                        RenderCommand::DrawIndirect {
373                            indirect_buffer: indirect_buffer.id().0,
374                            indirect_offset,
375                        },
376                    ),
377                    device_id: self.device.id().0,
378                })
379        {
380            warn!(
381                "Failed to send WebGPURequest::RenderBundleEncoderDrawIndirect({:?}) ({error})",
382                self.droppable.render_bundle_encoder.0
383            );
384        }
385    }
386
387    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderencoderbase-drawindexedindirect>
388    fn DrawIndexedIndirect(&self, indirect_buffer: &GPUBuffer<D>, indirect_offset: u64) {
389        if let Err(error) =
390            self.droppable
391                .channel
392                .0
393                .send(WebGPURequest::RenderBundleEncoderCommand {
394                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
395                    render_command: RenderBundleEncoderCommand::RenderCommand(
396                        RenderCommand::DrawIndexedIndirect {
397                            indirect_buffer: indirect_buffer.id().0,
398                            indirect_offset,
399                        },
400                    ),
401                    device_id: self.device.id().0,
402                })
403        {
404            warn!(
405                "Failed to send WebGPURequest::RenderBundleEncoderDrawIndexedIndirect({:?}) ({error})",
406                self.droppable.render_bundle_encoder.0
407            );
408        }
409    }
410
411    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-pushdebuggroup>
412    fn PushDebugGroup(&self, group_label: USVString) {
413        if let Err(error) =
414            self.droppable
415                .channel
416                .0
417                .send(WebGPURequest::RenderBundleEncoderCommand {
418                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
419                    render_command: RenderBundleEncoderCommand::DebugCommand(
420                        DebugCommand::PushDebugGroup(group_label.to_string()),
421                    ),
422                    device_id: self.device.id().0,
423                })
424        {
425            warn!(
426                "Failed to send WebGPURequest::RenderBundleEncoderPushDebugGroup({:?}) ({error})",
427                self.droppable.render_bundle_encoder.0
428            );
429        }
430    }
431
432    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-popdebuggroup>
433    fn PopDebugGroup(&self) {
434        if let Err(error) =
435            self.droppable
436                .channel
437                .0
438                .send(WebGPURequest::RenderBundleEncoderCommand {
439                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
440                    render_command: RenderBundleEncoderCommand::DebugCommand(
441                        DebugCommand::PopDebugGroup,
442                    ),
443                    device_id: self.device.id().0,
444                })
445        {
446            warn!(
447                "Failed to send WebGPURequest::RenderBundleEncoderPopDebugGroup({:?}) ({error})",
448                self.id()
449            );
450        }
451    }
452
453    /// <https://gpuweb.github.io/gpuweb/#dom-gpudebugcommandsmixin-insertdebugmarker>
454    fn InsertDebugMarker(&self, marker_label: USVString) {
455        if let Err(error) =
456            self.droppable
457                .channel
458                .0
459                .send(WebGPURequest::RenderBundleEncoderCommand {
460                    render_bundle_encoder_id: self.droppable.render_bundle_encoder.0,
461                    render_command: RenderBundleEncoderCommand::DebugCommand(
462                        DebugCommand::InsertDebugMarker(marker_label.to_string()),
463                    ),
464                    device_id: self.device.id().0,
465                })
466        {
467            warn!(
468                "Failed to send WebGPURequest::RenderBundleEncoderInsertDebugMarker({:?}) ({error})",
469                self.id()
470            );
471        }
472    }
473
474    /// <https://gpuweb.github.io/gpuweb/#dom-gpurenderbundleencoder-finish>
475    fn Finish(
476        &self,
477        cx: &mut JSContext,
478        descriptor: &GPURenderBundleDescriptor,
479    ) -> DomRoot<D::GPURenderBundle> {
480        let desc = RenderBundleDescriptor {
481            label: (&descriptor.parent).convert(),
482        };
483        let render_bundle_id = self
484            .global_from_reflector()
485            .global_wgpu_id_hub()
486            .create_render_bundle_id();
487
488        if let Err(error) =
489            self.droppable
490                .channel
491                .0
492                .send(WebGPURequest::RenderBundleEncoderFinish {
493                    render_bundle_encoder_id: self.id().0,
494                    descriptor: desc,
495                    render_bundle_id,
496                    device_id: self.device.id().0,
497                })
498        {
499            warn!(
500                "Failed to send WebGPURequest::RenderBundleEncoderFinish({:?}) ({error})",
501                self.id()
502            );
503        }
504
505        let render_bundle = WebGPURenderBundle(render_bundle_id);
506        GPURenderBundle::new(
507            cx,
508            &*self.global_from_reflector(),
509            render_bundle,
510            self.device.id(),
511            self.droppable.channel.clone(),
512            descriptor.parent.label.clone(),
513        )
514    }
515}