Skip to main content

wgpu_core/command/
bundle.rs

1/*! Render Bundles
2
3A render bundle is a prerecorded sequence of commands that can be replayed on a
4command encoder with a single call. A single bundle can replayed any number of
5times, on different encoders. Constructing a render bundle lets `wgpu` validate
6and analyze its commands up front, so that replaying a bundle can be more
7efficient than simply re-recording its commands each time.
8
9Not all commands are available in bundles; for example, a render bundle may not
10contain a [`RenderCommand::SetViewport`] command.
11
12Most of `wgpu`'s backend graphics APIs have something like bundles. For example,
13Vulkan calls them "secondary command buffers", and Metal calls them "indirect
14command buffers". Although we plan to take advantage of these platform features
15at some point in the future, for now `wgpu`'s implementation of render bundles
16does not use them: at the hal level, `wgpu` render bundles just replay the
17commands.
18
19## Render Bundle Isolation
20
21One important property of render bundles is that the draw calls in a render
22bundle depend solely on the pipeline and state established within the render
23bundle itself. A draw call in a bundle will never use a vertex buffer, say, that
24was set in the `RenderPass` before executing the bundle. We call this property
25'isolation', in that a render bundle is somewhat isolated from the passes that
26use it.
27
28Render passes are also isolated from the effects of bundles. After executing a
29render bundle, a render pass's pipeline, bind groups, and vertex and index
30buffers are are unset, so the bundle cannot affect later draw calls in the pass.
31
32A render pass is isolated from a bundle's effects on immediate data
33values. When encoding a render bundle, calls to `set_immediates` snapshot the immediate data
34content at encoding time, and the immediate values cannot be changed after `finish`.
35Before and after executing each individual bundle, all required immediate slots are cleared/reset,
36therefore immediate data must be set again.
37
38## Render Bundle Lifecycle
39
40To create a render bundle:
41
421) Create a [`RenderBundleEncoder`] by calling
43   [`Device::create_render_bundle_encoder`][Dcrbe].
44
452) Record commands in the `RenderBundleEncoder` using methods on [`RenderBundleEncoder`].
46
473) Call [`RenderBundleEncoder::finish`], which analyzes and cleans up
48   the command stream and returns a [`RenderBundle`].
49
504) Then, any number of times, call [`RenderPass::execute_bundles`][rpeb] to
51   execute the bundle as part of some render pass.
52
53## Implementation
54
55The most complex part of render bundles is the "finish" step, mostly implemented
56in [`RenderBundleEncoder::finish`]. This consumes the commands stored in the
57encoder's [`BasePass`], while validating everything, tracking the state,
58dropping redundant or unnecessary commands, and presenting the results as a new
59[`RenderBundle`]. It doesn't actually execute any commands.
60
61This step also enforces the 'isolation' property mentioned above: every draw
62call is checked to ensure that the resources it uses on were established since
63the last time the pipeline was set. This means the bundle can be executed
64verbatim without any state tracking.
65
66### Execution
67
68When the bundle is used in an actual render pass, `RenderBundle::execute` is
69called. It goes through the commands and issues them into the native command
70buffer. Thanks to isolation, it doesn't track any bind group invalidations or
71index format changes.
72
73[Dcrbe]: crate::device::Device::create_render_bundle_encoder
74[rpeb]: crate::command::RenderPass::execute_bundles
75!*/
76
77#![allow(clippy::reversed_empty_ranges)]
78
79use alloc::{
80    borrow::{Cow, ToOwned as _},
81    boxed::Box,
82    string::String,
83    string::ToString as _,
84    sync::Arc,
85    vec::Vec,
86};
87use core::{convert::Infallible, mem, num::NonZeroU32, ops::Range, str};
88
89use arrayvec::ArrayVec;
90use thiserror::Error;
91
92use wgt::error::{ErrorType, WebGpuError};
93
94use crate::{
95    api_log,
96    binding_model::{BindError, BindGroup, ImmediateUploadError, PipelineLayout},
97    command::{
98        bind::Binder,
99        pass::{validate_immediates_alignment, ImmediateState},
100        pass_base, ArcReferences, BasePass, BindGroupStateChange, ColorAttachmentError,
101        DebugGroupError, DrawError, EncoderStateError, MapPassErr, PassErrorScope, PassStateError,
102        RenderCommand, RenderCommandError, StateChange,
103    },
104    device::{
105        AttachmentData, Device, DeviceError, MissingDownlevelFlags, MissingFeatures,
106        RenderPassContext,
107    },
108    impl_resource_type, impl_storage_item,
109    init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
110    pipeline::{PipelineFlags, RenderPipeline},
111    resource::{
112        Buffer, DestroyedResourceError, InvalidOrDestroyedResourceError, InvalidResourceError,
113        Labeled, ParentDevice, RawResourceAccess, ResourceState, TrackingData,
114    },
115    resource_log,
116    snatch::SnatchGuard,
117    track::RenderBundleScope,
118    validation::{
119        check_color_attachment_count, validate_color_attachment_bytes_per_sample,
120        WorkgroupSizeCheck,
121    },
122    Label, LabelHelpers,
123};
124
125use super::{
126    draw::validate_mesh_draw_multiview, pass, render_command::ArcRenderCommand, DrawCommandFamily,
127    DrawKind,
128};
129
130/// Describes a [`RenderBundleEncoder`].
131#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
132#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
133pub struct RenderBundleEncoderDescriptor<'a> {
134    /// Debug label of the render bundle encoder.
135    ///
136    /// This will show up in graphics debuggers for easy identification.
137    pub label: Label<'a>,
138    /// The formats of the color attachments that this render bundle is capable
139    /// to rendering to.
140    ///
141    /// This must match the formats of the color attachments in the
142    /// renderpass this render bundle is executed in.
143    pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
144    /// Information about the depth attachment that this render bundle is
145    /// capable to rendering to.
146    ///
147    /// The format must match the format of the depth attachments in the
148    /// renderpass this render bundle is executed in.
149    pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
150    /// Sample count this render bundle is capable of rendering to.
151    ///
152    /// This must match the pipelines and the renderpasses it is used in.
153    pub sample_count: u32,
154    /// If this render bundle will rendering to multiple array layers in the
155    /// attachments at the same time.
156    pub multiview: Option<NonZeroU32>,
157}
158
159#[derive(Debug)]
160pub struct RenderBundleEncoder {
161    pub(crate) base: BasePass<RenderCommand<ArcReferences>, Infallible>,
162    device: Arc<Device>,
163    /// State of the render bundle encoder. Encoded to be compatible with pass macros.
164    ///
165    /// If this is `Some`, then the pass is in WebGPU's "open" state. If it is
166    /// `None`, then the pass is in the "ended" state.
167    /// See <https://www.w3.org/TR/webgpu/#encoder-state>
168    parent: Option<()>,
169    pub(crate) context: RenderPassContext,
170    pub(crate) is_depth_read_only: bool,
171    pub(crate) is_stencil_read_only: bool,
172
173    // Resource binding dedupe state.
174    current_bind_groups: BindGroupStateChange,
175    current_pipeline: StateChange<Arc<RenderPipeline>>,
176}
177
178impl_resource_type!(RenderBundleEncoder);
179impl_storage_item!(RenderBundleEncoder);
180
181/// Validate a render bundle descriptor.
182///
183/// Returns a tuple (is_depth_read_only, is_stencil_read_only).
184fn validate_render_bundle_encoder_descriptor(
185    desc: &RenderBundleEncoderDescriptor,
186    device: &Arc<Device>,
187) -> Result<(bool, bool), CreateRenderBundleError> {
188    let mut have_attachment = false;
189
190    let max_color_attachments = device.limits.max_color_attachments;
191    assert!(max_color_attachments <= hal::MAX_COLOR_ATTACHMENTS as u32);
192    check_color_attachment_count(desc.color_formats.len(), max_color_attachments)?;
193
194    for &format in desc.color_formats.iter().flatten() {
195        have_attachment = true;
196        if !format.has_color_aspect() {
197            return Err(CreateRenderBundleError::FormatNotColor(format));
198        }
199        let format_features = device.describe_format_features(format)?;
200        if !format_features
201            .allowed_usages
202            .contains(wgt::TextureUsages::RENDER_ATTACHMENT)
203        {
204            return Err(CreateRenderBundleError::FormatNotRenderable(format));
205        }
206    }
207
208    validate_color_attachment_bytes_per_sample(
209        desc.color_formats.iter().flatten().copied(),
210        device.limits.max_color_attachment_bytes_per_sample,
211    )?;
212
213    let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
214        Some(ds) => {
215            have_attachment = true;
216            let has_depth = ds.format.has_depth_aspect();
217            let has_stencil = ds.format.has_stencil_aspect();
218            if !has_depth && !has_stencil {
219                return Err(CreateRenderBundleError::FormatNotDepthOrStencil(ds.format));
220            } else {
221                (
222                    !has_depth || ds.depth_read_only,
223                    !has_stencil || ds.stencil_read_only,
224                )
225            }
226        }
227        // There's no depth/stencil attachment, so these values just don't
228        // matter.  Choose the most accommodating value, to simplify
229        // validation.
230        None => (true, true),
231    };
232
233    if !have_attachment {
234        return Err(CreateRenderBundleError::NoAttachment);
235    }
236
237    Ok((is_depth_read_only, is_stencil_read_only))
238}
239
240impl RenderBundleEncoder {
241    /// Create a new `RenderBundleEncoder`.
242    ///
243    /// <https://www.w3.org/TR/webgpu/#dom-gpudevice-createrenderbundleencoder>
244    pub fn new(
245        device: &Arc<Device>,
246        desc: &RenderBundleEncoderDescriptor,
247    ) -> Result<Self, CreateRenderBundleError> {
248        // 1. Validate texture format required features of each non-null element of descriptor.colorFormats with this.[[device]].
249        for &format in desc.color_formats.iter().flatten() {
250            device.require_features(format.required_features())?;
251        }
252
253        // 2. If descriptor.depthStencilFormat is provided:
254        if let Some(ds) = desc.depth_stencil {
255            // Validate texture format required features of descriptor.depthStencilFormat with this.[[device]].
256            device.require_features(ds.format.required_features())?;
257        }
258
259        device.check_is_valid()?;
260        let (is_depth_read_only, is_stencil_read_only) =
261            validate_render_bundle_encoder_descriptor(desc, device)?;
262
263        Ok(Self {
264            base: BasePass::new(&desc.label),
265            device: Arc::clone(device),
266            parent: Some(()),
267            context: RenderPassContext {
268                attachments: AttachmentData {
269                    colors: desc.color_formats.iter().cloned().collect(),
270                    resolves: ArrayVec::new(),
271                    depth_stencil: desc.depth_stencil.map(|ds| ds.format),
272                },
273                sample_count: desc.sample_count,
274                multiview_mask: desc.multiview,
275            },
276
277            is_depth_read_only,
278            is_stencil_read_only,
279            current_bind_groups: BindGroupStateChange::new(),
280            current_pipeline: StateChange::new(),
281        })
282    }
283
284    pub fn dummy(device: &Arc<Device>) -> Self {
285        Self {
286            base: BasePass::new(&None),
287            parent: None,
288            device: Arc::clone(device),
289            context: RenderPassContext::default(),
290            is_depth_read_only: false,
291            is_stencil_read_only: false,
292
293            current_bind_groups: BindGroupStateChange::new(),
294            current_pipeline: StateChange::new(),
295        }
296    }
297
298    pub fn label(&self) -> Option<&str> {
299        self.base.label.as_deref()
300    }
301
302    pub fn device(&self) -> &Arc<Device> {
303        &self.device
304    }
305
306    /// Convert this encoder's commands into a [`RenderBundle`].
307    ///
308    /// We want executing a [`RenderBundle`] to be quick, so we take
309    /// this opportunity to clean up the [`RenderBundleEncoder`]'s
310    /// command stream and gather metadata about it that will help
311    /// keep [`ExecuteBundle`] simple and fast. We remove redundant
312    /// commands (along with their side data), note resource usage,
313    /// and accumulate buffer and texture initialization actions.
314    ///
315    /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle
316    pub fn finish(&mut self, desc: &RenderBundleDescriptor) -> Arc<RenderBundle> {
317        profiling::scope!("RenderBundleEncoder::finish");
318        #[cfg(feature = "trace")]
319        let trace_desc = crate::device::trace::new_render_bundle_encoder_descriptor(
320            desc.label.clone(),
321            &self.context,
322            self.is_depth_read_only,
323            self.is_stencil_read_only,
324        );
325
326        let render_bundle = self.finish_inner(desc).unwrap_or_else(|error| {
327            self.device
328                .handle_error(error, self.label(), "RenderBundleEncoder::finish");
329            RenderBundle::invalid(Arc::clone(&self.device), desc)
330        });
331
332        #[cfg(feature = "trace")]
333        if let Some(ref mut trace) = *self.device.trace.lock() {
334            use crate::device::trace::{Action, IntoTrace};
335            trace.add(Action::CreateRenderBundle {
336                id: render_bundle.to_trace(),
337                desc: trace_desc,
338                base: render_bundle.to_base_pass().to_trace(),
339            });
340        }
341
342        api_log!(
343            "RenderBundleEncoder::finish -> {:?}",
344            Arc::as_ptr(&render_bundle)
345        );
346
347        render_bundle
348    }
349
350    /// Convert this encoder's commands into a [`RenderBundle`].
351    ///
352    /// We want executing a [`RenderBundle`] to be quick, so we take
353    /// this opportunity to clean up the [`RenderBundleEncoder`]'s
354    /// command stream and gather metadata about it that will help
355    /// keep [`ExecuteBundle`] simple and fast. We remove redundant
356    /// commands (along with their side data), note resource usage,
357    /// and accumulate buffer and texture initialization actions.
358    ///
359    /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle
360    pub(crate) fn finish_inner(
361        &mut self,
362        desc: &RenderBundleDescriptor,
363    ) -> Result<Arc<RenderBundle>, RenderBundleError> {
364        let scope = PassErrorScope::Bundle;
365
366        self.parent
367            .take()
368            .ok_or(RenderBundleErrorInner::Ended)
369            .map_pass_err(scope)?;
370
371        self.device.check_is_valid().map_pass_err(scope)?;
372
373        let mut state = State {
374            trackers: RenderBundleScope::new(),
375            pipeline: None,
376            vertex: Default::default(),
377            index: None,
378            flat_dynamic_offsets: Vec::new(),
379            device: Arc::clone(&self.device),
380            commands: Vec::new(),
381            buffer_memory_init_actions: Vec::new(),
382            texture_memory_init_actions: Vec::new(),
383            next_dynamic_offset: 0,
384            binder: Binder::new(),
385            immediate_state: ImmediateState::default(),
386            multiview_mask: self.context.multiview_mask,
387        };
388
389        let indices = &state.device.tracker_indices;
390        state.trackers.buffers.set_size(indices.buffers.size());
391        state.trackers.textures.set_size(indices.textures.size());
392
393        let mut debug_scope_depth = 0usize;
394        let mut debug_string_bytes = 0usize;
395        for command in self.base.commands.drain(..) {
396            match command {
397                RenderCommand::SetBindGroup {
398                    index,
399                    num_dynamic_offsets,
400                    bind_group,
401                } => {
402                    let scope = PassErrorScope::SetBindGroup;
403                    set_bind_group(
404                        &mut state,
405                        &self.base.dynamic_offsets,
406                        index,
407                        num_dynamic_offsets,
408                        bind_group,
409                    )
410                    .map_pass_err(scope)?;
411                }
412                RenderCommand::SetPipeline(pipeline) => {
413                    let scope = PassErrorScope::SetPipelineRender;
414                    set_pipeline(
415                        &mut state,
416                        &self.context,
417                        self.is_depth_read_only,
418                        self.is_stencil_read_only,
419                        pipeline,
420                    )
421                    .map_pass_err(scope)?;
422                }
423                RenderCommand::SetIndexBuffer {
424                    buffer,
425                    index_format,
426                    offset,
427                    size,
428                } => {
429                    let scope = PassErrorScope::SetIndexBuffer;
430                    set_index_buffer(&mut state, buffer, index_format, offset, size)
431                        .map_pass_err(scope)?;
432                }
433                RenderCommand::SetVertexBuffer {
434                    slot,
435                    buffer,
436                    offset,
437                    size,
438                } => {
439                    let scope = PassErrorScope::SetVertexBuffer;
440                    set_vertex_buffer(&mut state, slot, buffer, offset, size)
441                        .map_pass_err(scope)?;
442                }
443                RenderCommand::SetImmediate { offset, ref data } => {
444                    let scope = PassErrorScope::SetImmediate;
445                    set_immediates(&mut state, offset, data).map_pass_err(scope)?;
446                }
447                RenderCommand::Draw {
448                    vertex_count,
449                    instance_count,
450                    first_vertex,
451                    first_instance,
452                } => {
453                    let scope = PassErrorScope::Draw {
454                        kind: DrawKind::Draw,
455                        family: DrawCommandFamily::Draw,
456                    };
457                    draw(
458                        &mut state,
459                        vertex_count,
460                        instance_count,
461                        first_vertex,
462                        first_instance,
463                    )
464                    .map_pass_err(scope)?;
465                }
466                RenderCommand::DrawIndexed {
467                    index_count,
468                    instance_count,
469                    first_index,
470                    base_vertex,
471                    first_instance,
472                } => {
473                    let scope = PassErrorScope::Draw {
474                        kind: DrawKind::Draw,
475                        family: DrawCommandFamily::DrawIndexed,
476                    };
477                    draw_indexed(
478                        &mut state,
479                        index_count,
480                        instance_count,
481                        first_index,
482                        base_vertex,
483                        first_instance,
484                    )
485                    .map_pass_err(scope)?;
486                }
487                RenderCommand::DrawMeshTasks {
488                    group_count_x,
489                    group_count_y,
490                    group_count_z,
491                } => {
492                    let scope = PassErrorScope::Draw {
493                        kind: DrawKind::Draw,
494                        family: DrawCommandFamily::DrawMeshTasks,
495                    };
496                    draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z)
497                        .map_pass_err(scope)?;
498                }
499                RenderCommand::DrawIndirect {
500                    buffer,
501                    offset,
502                    count: 1,
503                    family,
504                    vertex_or_index_limit: None,
505                    instance_limit: None,
506                } => {
507                    let scope = PassErrorScope::Draw {
508                        kind: DrawKind::DrawIndirect,
509                        family,
510                    };
511                    multi_draw_indirect(&mut state, buffer, offset, family).map_pass_err(scope)?;
512                }
513                RenderCommand::DrawIndirect {
514                    count,
515                    vertex_or_index_limit,
516                    instance_limit,
517                    ..
518                } => {
519                    unreachable!("unexpected (multi-)draw indirect with count {count}, vertex_or_index_limits {vertex_or_index_limit:?}, instance_limit {instance_limit:?} found in a render bundle");
520                }
521                RenderCommand::PushDebugGroup { color, len } => {
522                    debug_scope_depth += 1;
523                    debug_string_bytes += len;
524                    state
525                        .commands
526                        .push(ArcRenderCommand::PushDebugGroup { color, len });
527                }
528                RenderCommand::InsertDebugMarker { color, len } => {
529                    debug_string_bytes += len;
530                    state
531                        .commands
532                        .push(ArcRenderCommand::InsertDebugMarker { color, len });
533                }
534                RenderCommand::PopDebugGroup => {
535                    if debug_scope_depth == 0 {
536                        return Err(DebugGroupError::InvalidPop)
537                            .map_pass_err(PassErrorScope::PopDebugGroup);
538                    }
539                    debug_scope_depth -= 1;
540                    state.commands.push(ArcRenderCommand::PopDebugGroup);
541                }
542                RenderCommand::MultiDrawIndirectCount { .. } => {
543                    unimplemented!("not supported by a render bundle")
544                }
545                // Must check the TIMESTAMP_QUERY_INSIDE_PASSES feature
546                RenderCommand::WriteTimestamp { .. }
547                | RenderCommand::BeginOcclusionQuery { .. }
548                | RenderCommand::EndOcclusionQuery
549                | RenderCommand::BeginPipelineStatisticsQuery { .. }
550                | RenderCommand::EndPipelineStatisticsQuery => {
551                    unimplemented!("not supported by a render bundle")
552                }
553                RenderCommand::ExecuteBundle(_)
554                | RenderCommand::SetBlendConstant(_)
555                | RenderCommand::SetStencilReference(_)
556                | RenderCommand::SetViewport { .. }
557                | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
558            }
559        }
560
561        if debug_scope_depth != 0 {
562            return Err(DebugGroupError::MissingPop).map_pass_err(PassErrorScope::Bundle);
563        }
564        debug_assert_eq!(debug_string_bytes, self.base.string_data.len());
565
566        let State {
567            trackers,
568            flat_dynamic_offsets,
569            device,
570            commands,
571            buffer_memory_init_actions,
572            texture_memory_init_actions,
573            ..
574        } = state;
575
576        let tracker_indices = device.tracker_indices.bundles.clone();
577        let discard_hal_labels = device
578            .instance_flags
579            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
580
581        let string_data = mem::take(&mut self.base.string_data);
582        let context = mem::take(&mut self.context);
583        let render_bundle = RenderBundle {
584            state: ResourceState::Valid(RenderBundleState {
585                context,
586                used: trackers,
587            }),
588            base: BasePass {
589                label: desc.label.as_deref().map(str::to_owned),
590                error: None,
591                commands,
592                dynamic_offsets: flat_dynamic_offsets,
593                string_data,
594            },
595            is_depth_read_only: self.is_depth_read_only,
596            is_stencil_read_only: self.is_stencil_read_only,
597            device: device.clone(),
598            buffer_memory_init_actions,
599            texture_memory_init_actions,
600            label: desc.label.to_string(),
601            tracking_data: TrackingData::new(tracker_indices),
602            discard_hal_labels,
603        };
604
605        let render_bundle = Arc::new(render_bundle);
606
607        Ok(render_bundle)
608    }
609
610    fn set_index_buffer_inner(
611        &mut self,
612        buffer: Arc<Buffer>,
613        index_format: wgt::IndexFormat,
614        offset: wgt::BufferAddress,
615        size: Option<wgt::BufferAddress>,
616    ) -> Result<(), PassStateError> {
617        pass_base!(self, PassErrorScope::SetIndexBuffer);
618        self.base.commands.push(RenderCommand::SetIndexBuffer {
619            buffer,
620            index_format,
621            offset,
622            size,
623        });
624        Ok(())
625    }
626
627    pub fn set_index_buffer(
628        &mut self,
629        buffer: Arc<Buffer>,
630        index_format: wgt::IndexFormat,
631        offset: wgt::BufferAddress,
632        size: Option<wgt::BufferAddress>,
633    ) {
634        if let Err(err) = self.set_index_buffer_inner(buffer, index_format, offset, size) {
635            self.device
636                .handle_error(err, self.label(), "RenderBundleEncoder::set_index_buffer");
637        }
638    }
639
640    fn set_bind_group_inner(
641        &mut self,
642        index: u32,
643        bind_group: Option<Arc<BindGroup>>,
644        offsets: &[wgt::DynamicOffset],
645    ) -> Result<(), PassStateError> {
646        pass_base!(self, PassErrorScope::SetBindGroup);
647        let redundant = self.current_bind_groups.set_and_check_redundant(
648            &bind_group,
649            index,
650            &mut self.base.dynamic_offsets,
651            offsets,
652        );
653
654        if redundant {
655            return Ok(());
656        }
657
658        self.base.commands.push(RenderCommand::SetBindGroup {
659            index,
660            num_dynamic_offsets: offsets.len(),
661            bind_group,
662        });
663        Ok(())
664    }
665
666    pub fn set_bind_group(
667        &mut self,
668        index: u32,
669        bind_group: Option<Arc<BindGroup>>,
670        offsets: &[wgt::DynamicOffset],
671    ) {
672        if let Err(err) = self.set_bind_group_inner(index, bind_group, offsets) {
673            self.device
674                .handle_error(err, self.label(), "RenderBundleEncoder::set_bind_group");
675        }
676    }
677
678    fn set_pipeline_inner(&mut self, pipeline: Arc<RenderPipeline>) -> Result<(), PassStateError> {
679        pass_base!(self, PassErrorScope::SetPipelineRender);
680        if self.current_pipeline.set_and_check_redundant(&pipeline) {
681            return Ok(());
682        }
683
684        self.base
685            .commands
686            .push(RenderCommand::SetPipeline(pipeline));
687        Ok(())
688    }
689
690    pub fn set_pipeline(&mut self, pipeline: Arc<RenderPipeline>) {
691        if let Err(err) = self.set_pipeline_inner(pipeline) {
692            self.device
693                .handle_error(err, self.label(), "RenderBundleEncoder::set_pipeline");
694        }
695    }
696
697    fn set_vertex_buffer_inner(
698        &mut self,
699        slot: u32,
700        buffer: Option<Arc<Buffer>>,
701        offset: wgt::BufferAddress,
702        size: Option<wgt::BufferAddress>,
703    ) -> Result<(), PassStateError> {
704        pass_base!(self, PassErrorScope::SetVertexBuffer);
705        self.base.commands.push(RenderCommand::SetVertexBuffer {
706            slot,
707            buffer,
708            offset,
709            size,
710        });
711        Ok(())
712    }
713
714    pub fn set_vertex_buffer(
715        &mut self,
716        slot: u32,
717        buffer: Option<Arc<Buffer>>,
718        offset: wgt::BufferAddress,
719        size: Option<wgt::BufferAddress>,
720    ) {
721        if let Err(err) = self.set_vertex_buffer_inner(slot, buffer, offset, size) {
722            self.device
723                .handle_error(err, self.label(), "RenderBundleEncoder::set_vertex_buffer");
724        }
725    }
726
727    fn set_immediates_inner(&mut self, offset: u32, data: &[u8]) -> Result<(), PassStateError> {
728        pass_base!(self, PassErrorScope::SetImmediate);
729
730        // This should have been validated in content timeline
731        assert!(data.len().is_multiple_of(4));
732
733        self.base.commands.push(RenderCommand::SetImmediate {
734            offset,
735            data: data
736                .chunks_exact(size_of::<u32>())
737                .map(|ck| u32::from_le_bytes(ck.try_into().unwrap()))
738                .collect(),
739        });
740        Ok(())
741    }
742
743    pub fn set_immediates(&mut self, offset: u32, data: &[u8]) {
744        if let Err(err) = self.set_immediates_inner(offset, data) {
745            self.device
746                .handle_error(err, self.label(), "RenderBundleEncoder::set_immediates");
747        }
748    }
749
750    fn draw_inner(
751        &mut self,
752        vertex_count: u32,
753        instance_count: u32,
754        first_vertex: u32,
755        first_instance: u32,
756    ) -> Result<(), PassStateError> {
757        pass_base!(
758            self,
759            PassErrorScope::Draw {
760                kind: DrawKind::Draw,
761                family: DrawCommandFamily::Draw
762            }
763        );
764        self.base.commands.push(RenderCommand::Draw {
765            vertex_count,
766            instance_count,
767            first_vertex,
768            first_instance,
769        });
770        Ok(())
771    }
772
773    pub fn draw(
774        &mut self,
775        vertex_count: u32,
776        instance_count: u32,
777        first_vertex: u32,
778        first_instance: u32,
779    ) {
780        if let Err(err) =
781            self.draw_inner(vertex_count, instance_count, first_vertex, first_instance)
782        {
783            self.device
784                .handle_error(err, self.label(), "RenderBundleEncoder::draw");
785        }
786    }
787
788    fn draw_indexed_inner(
789        &mut self,
790        index_count: u32,
791        instance_count: u32,
792        first_index: u32,
793        base_vertex: i32,
794        first_instance: u32,
795    ) -> Result<(), PassStateError> {
796        pass_base!(
797            self,
798            PassErrorScope::Draw {
799                kind: DrawKind::Draw,
800                family: DrawCommandFamily::DrawIndexed
801            }
802        );
803        self.base.commands.push(RenderCommand::DrawIndexed {
804            index_count,
805            instance_count,
806            first_index,
807            base_vertex,
808            first_instance,
809        });
810        Ok(())
811    }
812
813    pub fn draw_indexed(
814        &mut self,
815        index_count: u32,
816        instance_count: u32,
817        first_index: u32,
818        base_vertex: i32,
819        first_instance: u32,
820    ) {
821        if let Err(err) = self.draw_indexed_inner(
822            index_count,
823            instance_count,
824            first_index,
825            base_vertex,
826            first_instance,
827        ) {
828            self.device
829                .handle_error(err, self.label(), "RenderBundleEncoder::draw_indexed");
830        }
831    }
832
833    fn draw_indirect_inner(
834        &mut self,
835        buffer: Arc<Buffer>,
836        offset: wgt::BufferAddress,
837    ) -> Result<(), PassStateError> {
838        pass_base!(
839            self,
840            PassErrorScope::Draw {
841                kind: DrawKind::DrawIndirect,
842                family: DrawCommandFamily::Draw
843            }
844        );
845        self.base.commands.push(RenderCommand::DrawIndirect {
846            buffer,
847            offset,
848            count: 1,
849            family: DrawCommandFamily::Draw,
850            vertex_or_index_limit: None,
851            instance_limit: None,
852        });
853        Ok(())
854    }
855
856    pub fn draw_indirect(&mut self, buffer: Arc<Buffer>, offset: wgt::BufferAddress) {
857        if let Err(err) = self.draw_indirect_inner(buffer, offset) {
858            self.device
859                .handle_error(err, self.label(), "RenderBundleEncoder::draw_indirect");
860        }
861    }
862
863    fn draw_indexed_indirect_inner(
864        &mut self,
865        buffer: Arc<Buffer>,
866        offset: wgt::BufferAddress,
867    ) -> Result<(), PassStateError> {
868        pass_base!(
869            self,
870            PassErrorScope::Draw {
871                kind: DrawKind::DrawIndirect,
872                family: DrawCommandFamily::DrawIndexed
873            }
874        );
875        self.base.commands.push(RenderCommand::DrawIndirect {
876            buffer,
877            offset,
878            count: 1,
879            family: DrawCommandFamily::DrawIndexed,
880            vertex_or_index_limit: None,
881            instance_limit: None,
882        });
883        Ok(())
884    }
885
886    pub fn draw_indexed_indirect(&mut self, buffer: Arc<Buffer>, offset: wgt::BufferAddress) {
887        if let Err(err) = self.draw_indexed_indirect_inner(buffer, offset) {
888            self.device.handle_error(
889                err,
890                self.label(),
891                "RenderBundleEncoder::draw_indexed_indirect",
892            );
893        }
894    }
895
896    fn push_debug_group_inner(&mut self, label: &str) -> Result<(), PassStateError> {
897        pass_base!(self, PassErrorScope::PushDebugGroup);
898        self.base.string_data.extend_from_slice(label.as_bytes());
899        self.base.commands.push(RenderCommand::PushDebugGroup {
900            color: 0,
901            len: label.len(),
902        });
903        Ok(())
904    }
905
906    pub fn push_debug_group(&mut self, label: &str) {
907        if let Err(err) = self.push_debug_group_inner(label) {
908            self.device
909                .handle_error(err, self.label(), "RenderBundleEncoder::push_debug_group");
910        }
911    }
912
913    fn pop_debug_group_inner(&mut self) -> Result<(), PassStateError> {
914        pass_base!(self, PassErrorScope::PopDebugGroup);
915        self.base.commands.push(RenderCommand::PopDebugGroup);
916        Ok(())
917    }
918
919    pub fn pop_debug_group(&mut self) {
920        if let Err(err) = self.pop_debug_group_inner() {
921            self.device
922                .handle_error(err, self.label(), "RenderBundleEncoder::pop_debug_group");
923        }
924    }
925
926    fn insert_debug_marker_inner(&mut self, label: &str) -> Result<(), PassStateError> {
927        pass_base!(self, PassErrorScope::InsertDebugMarker);
928        self.base.string_data.extend_from_slice(label.as_bytes());
929        self.base.commands.push(RenderCommand::InsertDebugMarker {
930            color: 0,
931            len: label.len(),
932        });
933        Ok(())
934    }
935
936    pub fn insert_debug_marker(&mut self, label: &str) {
937        if let Err(err) = self.insert_debug_marker_inner(label) {
938            self.device.handle_error(
939                err,
940                self.label(),
941                "RenderBundleEncoder::insert_debug_marker",
942            );
943        }
944    }
945}
946
947fn set_bind_group(
948    state: &mut State,
949    dynamic_offsets: &[u32],
950    index: u32,
951    num_dynamic_offsets: usize,
952    bind_group: Option<Arc<BindGroup>>,
953) -> Result<(), RenderBundleErrorInner> {
954    let max_bind_groups = state.device.limits.max_bind_groups;
955    if index >= max_bind_groups {
956        return Err(
957            RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
958                index,
959                max: max_bind_groups,
960            })
961            .into(),
962        );
963    }
964
965    // Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`.
966    let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
967    state.next_dynamic_offset = offsets_range.end;
968    let offsets = &dynamic_offsets[offsets_range.clone()];
969
970    if let Some(bind_group) = bind_group {
971        bind_group.check_is_valid()?;
972        bind_group.same_device(&state.device)?;
973        bind_group.validate_dynamic_bindings(index, offsets)?;
974
975        unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
976        let bind_group = state.trackers.bind_groups.insert_single(bind_group);
977
978        state
979            .binder
980            .assign_group(index as usize, bind_group, offsets);
981    } else {
982        if !offsets.is_empty() {
983            return Err(RenderBundleErrorInner::Bind(
984                BindError::DynamicOffsetCountNotZero {
985                    group: index,
986                    actual: offsets.len(),
987                },
988            ));
989        }
990
991        state.binder.clear_group(index as usize);
992    }
993
994    Ok(())
995}
996
997fn set_pipeline(
998    state: &mut State,
999    context: &RenderPassContext,
1000    is_depth_read_only: bool,
1001    is_stencil_read_only: bool,
1002    pipeline: Arc<RenderPipeline>,
1003) -> Result<(), RenderBundleErrorInner> {
1004    pipeline.same_device(&state.device)?;
1005
1006    context
1007        .check_compatible(&pipeline.pass_context, pipeline.as_ref())
1008        .map_err(RenderCommandError::IncompatiblePipelineTargets)?;
1009
1010    if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
1011        return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
1012    }
1013    if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
1014        return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
1015    }
1016
1017    state
1018        .commands
1019        .push(ArcRenderCommand::SetPipeline(pipeline.clone()));
1020
1021    state.pipeline = Some(pipeline.clone());
1022
1023    state
1024        .binder
1025        .change_pipeline_layout(pipeline.layout()?, &pipeline.late_sized_buffer_groups);
1026
1027    state.vertex.update_limits(&pipeline.vertex_steps);
1028
1029    state.trackers.render_pipelines.insert_single(pipeline);
1030    Ok(())
1031}
1032
1033// This function is duplicative of `render::set_index_buffer`.
1034fn set_index_buffer(
1035    state: &mut State,
1036    buffer: Arc<Buffer>,
1037    index_format: wgt::IndexFormat,
1038    offset: u64,
1039    size: Option<wgt::BufferAddress>,
1040) -> Result<(), RenderBundleErrorInner> {
1041    buffer.check_is_valid()?;
1042
1043    state
1044        .trackers
1045        .buffers
1046        .merge_single(&buffer, wgt::BufferUses::INDEX)?;
1047
1048    buffer.same_device(&state.device)?;
1049    buffer.check_usage(wgt::BufferUsages::INDEX)?;
1050
1051    if !offset.is_multiple_of(u64::from(index_format.byte_size())) {
1052        return Err(RenderCommandError::UnalignedIndexBuffer {
1053            offset,
1054            alignment: index_format.byte_size() as usize,
1055        }
1056        .into());
1057    }
1058    let range = buffer.resolve_vertex_or_index_binding_range(offset, size)?;
1059
1060    state
1061        .buffer_memory_init_actions
1062        .extend(buffer.initialization_status.read().create_action(
1063            &buffer,
1064            range.clone(),
1065            MemoryInitKind::NeedsInitializedMemory,
1066        ));
1067    state.set_index_buffer(buffer, index_format, range);
1068    Ok(())
1069}
1070
1071// This function is duplicative of `render::set_vertex_buffer`.
1072fn set_vertex_buffer(
1073    state: &mut State,
1074    slot: u32,
1075    buffer: Option<Arc<Buffer>>,
1076    offset: u64,
1077    size: Option<wgt::BufferAddress>,
1078) -> Result<(), RenderBundleErrorInner> {
1079    let max_vertex_buffers = state.device.limits.max_vertex_buffers;
1080    if slot >= max_vertex_buffers {
1081        return Err(RenderCommandError::VertexBufferIndexOutOfRange {
1082            index: slot,
1083            max: max_vertex_buffers,
1084        }
1085        .into());
1086    }
1087
1088    if let Some(buffer) = buffer {
1089        buffer.check_is_valid()?;
1090
1091        state
1092            .trackers
1093            .buffers
1094            .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
1095
1096        buffer.same_device(&state.device)?;
1097        buffer.check_usage(wgt::BufferUsages::VERTEX)?;
1098
1099        if !offset.is_multiple_of(wgt::VERTEX_ALIGNMENT) {
1100            return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
1101        }
1102        let range = buffer.resolve_vertex_or_index_binding_range(offset, size)?;
1103
1104        state
1105            .buffer_memory_init_actions
1106            .extend(buffer.initialization_status.read().create_action(
1107                &buffer,
1108                range.clone(),
1109                MemoryInitKind::NeedsInitializedMemory,
1110            ));
1111        state.vertex.set_buffer(slot as usize, buffer, range);
1112        if let Some(pipeline) = state.pipeline.as_deref() {
1113            state.vertex.update_limits(&pipeline.vertex_steps);
1114        }
1115    } else {
1116        if offset != 0 {
1117            return Err(RenderCommandError::from(
1118                crate::binding_model::BindingError::UnbindingVertexBufferOffsetNotZero {
1119                    slot,
1120                    offset,
1121                },
1122            )
1123            .into());
1124        }
1125        match size {
1126            Some(size) if size != 0 => {
1127                return Err(RenderCommandError::from(
1128                    crate::binding_model::BindingError::UnbindingVertexBufferSizeNotZero {
1129                        slot,
1130                        size,
1131                    },
1132                )
1133                .into());
1134            }
1135            _ => {}
1136        }
1137
1138        state.vertex.clear_buffer(slot as usize);
1139        if let Some(pipeline) = state.pipeline.as_deref() {
1140            state.vertex.update_limits(&pipeline.vertex_steps);
1141        }
1142    }
1143
1144    Ok(())
1145}
1146
1147fn set_immediates(
1148    state: &mut State,
1149    offset: u32,
1150    data: &[u32],
1151) -> Result<(), ImmediateUploadError> {
1152    validate_immediates_alignment(offset, size_of_val(data))?;
1153
1154    state
1155        .immediate_state
1156        .set_immediates::<ImmediateUploadError>(&state.device.limits, offset, data)?;
1157    Ok(())
1158}
1159
1160fn draw(
1161    state: &mut State,
1162    vertex_count: u32,
1163    instance_count: u32,
1164    first_vertex: u32,
1165    first_instance: u32,
1166) -> Result<(), RenderBundleErrorInner> {
1167    state.is_ready(DrawCommandFamily::Draw)?;
1168
1169    state
1170        .vertex
1171        .limits
1172        .validate_vertex_limit(first_vertex, vertex_count)?;
1173    state
1174        .vertex
1175        .limits
1176        .validate_instance_limit(first_instance, instance_count)?;
1177
1178    if instance_count > 0 && vertex_count > 0 {
1179        state.flush_vertex_buffers();
1180        state.flush_bindings();
1181        state.flush_immediates();
1182        state.commands.push(ArcRenderCommand::Draw {
1183            vertex_count,
1184            instance_count,
1185            first_vertex,
1186            first_instance,
1187        });
1188    }
1189    Ok(())
1190}
1191
1192fn draw_indexed(
1193    state: &mut State,
1194    index_count: u32,
1195    instance_count: u32,
1196    first_index: u32,
1197    base_vertex: i32,
1198    first_instance: u32,
1199) -> Result<(), RenderBundleErrorInner> {
1200    state.is_ready(DrawCommandFamily::DrawIndexed)?;
1201
1202    let index = state.index.as_ref().unwrap();
1203
1204    let last_index = first_index as u64 + index_count as u64;
1205    let index_limit = index.limit();
1206    if last_index > index_limit {
1207        return Err(DrawError::IndexBeyondLimit {
1208            last_index,
1209            index_limit,
1210        }
1211        .into());
1212    }
1213    state
1214        .vertex
1215        .limits
1216        .validate_instance_limit(first_instance, instance_count)?;
1217
1218    if instance_count > 0 && index_count > 0 {
1219        state.flush_index();
1220        state.flush_vertex_buffers();
1221        state.flush_bindings();
1222        state.flush_immediates();
1223        state.commands.push(ArcRenderCommand::DrawIndexed {
1224            index_count,
1225            instance_count,
1226            first_index,
1227            base_vertex,
1228            first_instance,
1229        });
1230    }
1231    Ok(())
1232}
1233
1234fn draw_mesh_tasks(
1235    state: &mut State,
1236    group_count_x: u32,
1237    group_count_y: u32,
1238    group_count_z: u32,
1239) -> Result<(), RenderBundleErrorInner> {
1240    state.is_ready(DrawCommandFamily::DrawMeshTasks)?;
1241    validate_mesh_draw_multiview(&state.device, state.multiview_mask)?;
1242
1243    let limits = &state.device.limits;
1244    let (groups_size_limit, max_groups) = if state.pipeline.as_ref().unwrap().has_task_shader {
1245        (
1246            limits.max_task_workgroups_per_dimension,
1247            limits.max_task_workgroup_total_count,
1248        )
1249    } else {
1250        (
1251            limits.max_mesh_workgroups_per_dimension,
1252            limits.max_mesh_workgroup_total_count,
1253        )
1254    };
1255
1256    let total_count = WorkgroupSizeCheck {
1257        dimensions: &[group_count_x, group_count_y, group_count_z],
1258        per_dimension_limits: &[groups_size_limit, groups_size_limit, groups_size_limit],
1259        per_dimension_limits_desc: "max_task_mesh_workgroups_per_dimension",
1260
1261        total_limit: max_groups,
1262        total_limit_desc: "max_task_mesh_workgroup_total_count",
1263    }
1264    .check_and_compute_total_invocations()
1265    .map_err(|err| RenderBundleErrorInner::Draw(err.into()))?;
1266
1267    if total_count > 0 {
1268        state.flush_bindings();
1269        state.flush_immediates();
1270        state.commands.push(ArcRenderCommand::DrawMeshTasks {
1271            group_count_x,
1272            group_count_y,
1273            group_count_z,
1274        });
1275    }
1276    Ok(())
1277}
1278
1279fn multi_draw_indirect(
1280    state: &mut State,
1281    buffer: Arc<Buffer>,
1282    offset: u64,
1283    family: DrawCommandFamily,
1284) -> Result<(), RenderBundleErrorInner> {
1285    state.is_ready(family)?;
1286    state
1287        .device
1288        .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
1289
1290    if family == DrawCommandFamily::DrawMeshTasks {
1291        validate_mesh_draw_multiview(&state.device, state.multiview_mask)?;
1292    }
1293
1294    buffer.check_is_valid()?;
1295    buffer.same_device(&state.device)?;
1296    buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
1297
1298    if !offset.is_multiple_of(4) {
1299        return Err(RenderCommandError::UnalignedIndirectBufferOffset(offset).into());
1300    }
1301
1302    let stride = super::get_src_stride_of_indirect_args(family);
1303    match offset.checked_add(stride) {
1304        Some(end_offset) if end_offset <= buffer.size => {}
1305        _ => {
1306            return Err(RenderCommandError::IndirectBufferOverrun {
1307                count: 1,
1308                offset,
1309                args_size: stride,
1310                buffer_size: buffer.size,
1311            }
1312            .into());
1313        }
1314    }
1315    state
1316        .buffer_memory_init_actions
1317        .extend(buffer.initialization_status.read().create_action(
1318            &buffer,
1319            offset..(offset + stride),
1320            MemoryInitKind::NeedsInitializedMemory,
1321        ));
1322
1323    let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
1324        let index = state.index.as_mut().unwrap();
1325        state.commands.extend(index.flush());
1326        index.limit()
1327    } else {
1328        state.vertex.limits.vertex_limit
1329    };
1330    let instance_limit = state.vertex.limits.instance_limit;
1331
1332    let buffer_uses = if state.device.indirect_validation.is_some()
1333        && family != DrawCommandFamily::DrawMeshTasks
1334    {
1335        wgt::BufferUses::STORAGE_READ_ONLY
1336    } else {
1337        wgt::BufferUses::INDIRECT
1338    };
1339
1340    state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
1341
1342    state.flush_vertex_buffers();
1343    state.flush_bindings();
1344    state.flush_immediates();
1345    state.commands.push(ArcRenderCommand::DrawIndirect {
1346        buffer,
1347        offset,
1348        count: 1,
1349        family,
1350
1351        vertex_or_index_limit: Some(vertex_or_index_limit),
1352        instance_limit: Some(instance_limit),
1353    });
1354    Ok(())
1355}
1356
1357/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
1358#[derive(Clone, Debug, Error)]
1359#[non_exhaustive]
1360pub enum CreateRenderBundleError {
1361    #[error(transparent)]
1362    ColorAttachment(#[from] ColorAttachmentError),
1363    #[error("Format {0:?} does not have a color aspect")]
1364    FormatNotColor(wgt::TextureFormat),
1365    #[error("Color attachment format {0:?} is not renderable")]
1366    FormatNotRenderable(wgt::TextureFormat),
1367    #[error("Format {0:?} is not a depth/stencil format")]
1368    FormatNotDepthOrStencil(wgt::TextureFormat),
1369    #[error("Render bundle must have at least one attachment (color or depth/stencil)")]
1370    NoAttachment,
1371    #[error("Invalid number of samples {0}")]
1372    InvalidSampleCount(u32),
1373    #[error(transparent)]
1374    MissingFeatures(#[from] MissingFeatures),
1375    #[error(transparent)]
1376    Device(#[from] DeviceError),
1377}
1378
1379impl WebGpuError for CreateRenderBundleError {
1380    fn webgpu_error_type(&self) -> ErrorType {
1381        match self {
1382            Self::ColorAttachment(e) => e.webgpu_error_type(),
1383            Self::FormatNotColor(_)
1384            | Self::FormatNotRenderable(_)
1385            | Self::FormatNotDepthOrStencil(_)
1386            | Self::NoAttachment
1387            | Self::InvalidSampleCount(_) => ErrorType::Validation,
1388            Self::MissingFeatures(e) => e.webgpu_error_type(),
1389            Self::Device(e) => e.webgpu_error_type(),
1390        }
1391    }
1392}
1393
1394/// Error encountered while executing a finished render bundle.
1395#[derive(Clone, Debug, Error)]
1396#[non_exhaustive]
1397pub enum ExecutionError {
1398    #[error(transparent)]
1399    Device(#[from] DeviceError),
1400    #[error(transparent)]
1401    DestroyedResource(#[from] DestroyedResourceError),
1402    #[error(transparent)]
1403    InvalidResource(#[from] InvalidResourceError),
1404}
1405
1406impl From<InvalidOrDestroyedResourceError> for ExecutionError {
1407    fn from(e: InvalidOrDestroyedResourceError) -> Self {
1408        match e {
1409            InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
1410            InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
1411        }
1412    }
1413}
1414
1415pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
1416
1417#[derive(Debug)]
1418pub(crate) struct RenderBundleState {
1419    pub(crate) used: RenderBundleScope,
1420    pub(super) context: RenderPassContext,
1421}
1422
1423//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
1424// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
1425// or Metal indirect command buffer.
1426/// cbindgen:ignore
1427#[derive(Debug)]
1428pub struct RenderBundle {
1429    pub(crate) state: ResourceState<RenderBundleState>,
1430    // Normalized command stream. It can be executed verbatim,
1431    // without re-binding anything on the pipeline change.
1432    base: BasePass<ArcRenderCommand, Infallible>,
1433    pub(super) is_depth_read_only: bool,
1434    pub(super) is_stencil_read_only: bool,
1435    pub(crate) device: Arc<Device>,
1436    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1437    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1438    /// The `label` from the descriptor used to create the resource.
1439    label: String,
1440    pub(crate) tracking_data: TrackingData,
1441    discard_hal_labels: bool,
1442}
1443
1444impl Drop for RenderBundle {
1445    #[expect(trivial_casts)]
1446    fn drop(&mut self) {
1447        profiling::scope!("RenderBundle::drop");
1448        api_log!("RenderBundle::drop {:?}", self as *const _);
1449        resource_log!("Drop {}", self.error_ident());
1450        #[cfg(feature = "trace")]
1451        if let Some(t) = self.device.trace.lock().as_mut() {
1452            use crate::device::trace::{to_trace, Action};
1453
1454            t.add(Action::DropRenderBundle(unsafe { to_trace(self) }));
1455        }
1456    }
1457}
1458
1459#[cfg(send_sync)]
1460unsafe impl Send for RenderBundle {}
1461#[cfg(send_sync)]
1462unsafe impl Sync for RenderBundle {}
1463
1464impl RenderBundle {
1465    pub(crate) fn state(&self) -> Result<&RenderBundleState, InvalidResourceError> {
1466        self.state
1467            .as_ref()
1468            .valid()
1469            .ok_or_else(|| InvalidResourceError(self.error_ident()))
1470    }
1471
1472    pub(crate) fn check_is_valid(&self) -> Result<(), InvalidResourceError> {
1473        self.state().map(|_| ())
1474    }
1475
1476    pub fn invalid(device: Arc<Device>, desc: &RenderBundleDescriptor) -> Arc<Self> {
1477        Arc::new(RenderBundle {
1478            state: ResourceState::Invalid,
1479            base: BasePass {
1480                label: desc.label.as_ref().map(|l| l.to_string()),
1481                error: None,
1482                commands: Vec::new(),
1483                dynamic_offsets: Vec::new(),
1484                string_data: Vec::new(),
1485            },
1486            is_depth_read_only: false,
1487            is_stencil_read_only: false,
1488            buffer_memory_init_actions: Vec::new(),
1489            texture_memory_init_actions: Vec::new(),
1490            label: desc.label.to_string(),
1491            tracking_data: TrackingData::new(device.tracker_indices.bundles.clone()),
1492            discard_hal_labels: false,
1493            device,
1494        })
1495    }
1496
1497    #[cfg(feature = "trace")]
1498    pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
1499        self.base.clone()
1500    }
1501
1502    /// Actually encode the contents into a native command buffer.
1503    ///
1504    /// This is partially duplicating the logic of `render_pass_end`.
1505    /// However the point of this function is to be lighter, since we already had
1506    /// a chance to go through the commands in `render_bundle_encoder_finish`.
1507    ///
1508    /// Note that the function isn't expected to fail, generally.
1509    /// All the validation has already been done by this point.
1510    /// Execution can still fail if a resource was destroyed or indirect draw validation
1511    /// cannot allocate a buffer.
1512    pub(super) unsafe fn execute(
1513        &self,
1514        raw: &mut dyn hal::DynCommandEncoder,
1515        indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
1516        indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
1517        snatch_guard: &SnatchGuard,
1518    ) -> Result<(), ExecutionError> {
1519        let mut offsets = self.base.dynamic_offsets.as_slice();
1520        let mut pipeline_layout = None::<Arc<PipelineLayout>>;
1521        if !self.discard_hal_labels {
1522            if let Some(ref label) = self.base.label {
1523                unsafe { raw.begin_debug_marker(label) };
1524            }
1525        }
1526
1527        let mut strings = self.base.string_data.as_slice();
1528        use ArcRenderCommand as Cmd;
1529        for command in self.base.commands.iter() {
1530            match command {
1531                Cmd::SetBindGroup {
1532                    index,
1533                    num_dynamic_offsets,
1534                    bind_group,
1535                } => {
1536                    let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
1537                    unsafe {
1538                        raw.set_bind_group(
1539                            pipeline_layout
1540                                .as_ref()
1541                                .unwrap()
1542                                .raw()
1543                                .expect("PipelineLayout should be valid at this point"),
1544                            *index,
1545                            raw_bg,
1546                            &offsets[..*num_dynamic_offsets],
1547                        )
1548                    };
1549                    offsets = &offsets[*num_dynamic_offsets..];
1550                }
1551                Cmd::SetPipeline(pipeline) => {
1552                    unsafe {
1553                        raw.set_render_pipeline(
1554                            pipeline
1555                                .raw()
1556                                .expect("RenderPipeline should be valid when executing bundle"),
1557                        )
1558                    };
1559
1560                    pipeline_layout = Some(
1561                        pipeline
1562                            .layout()
1563                            .expect("PipelineLayout should be valid when executing bundle")
1564                            .clone(),
1565                    );
1566                }
1567                Cmd::SetIndexBuffer {
1568                    buffer,
1569                    index_format,
1570                    offset,
1571                    size,
1572                } => {
1573                    let buffer = buffer.try_raw(snatch_guard)?;
1574                    let size = size.expect(
1575                        "Index buffer binding size should already be resolved when executing bundle",
1576                    );
1577                    // SAFETY: The binding size was checked against the buffer size
1578                    // in `set_index_buffer` and again in `IndexState::flush`.
1579                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, size);
1580                    unsafe { raw.set_index_buffer(bb, *index_format) };
1581                }
1582                Cmd::SetVertexBuffer {
1583                    slot,
1584                    buffer,
1585                    offset,
1586                    size,
1587                } => {
1588                    let buffer = buffer.as_ref().unwrap().try_raw(snatch_guard)?;
1589                    let size = size.expect(
1590                        "Vertex buffer binding size should already be resolved when executing bundle",
1591                    );
1592                    // SAFETY: The binding size was checked against the buffer size
1593                    // in `set_vertex_buffer` and again in `VertexState::flush`.
1594                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, size);
1595                    unsafe { raw.set_vertex_buffer(*slot, bb) };
1596                }
1597                Cmd::SetImmediate { offset, data } => {
1598                    let pipeline_layout = pipeline_layout.as_ref().unwrap();
1599
1600                    // SAFETY: The range of immediates written was validated in `is_ready` before each `flush_immediates`.
1601                    unsafe { raw.set_immediates(pipeline_layout.raw().unwrap(), *offset, data) }
1602                }
1603                Cmd::Draw {
1604                    vertex_count,
1605                    instance_count,
1606                    first_vertex,
1607                    first_instance,
1608                } => {
1609                    unsafe {
1610                        raw.draw(
1611                            *first_vertex,
1612                            *vertex_count,
1613                            *first_instance,
1614                            *instance_count,
1615                        )
1616                    };
1617                }
1618                Cmd::DrawIndexed {
1619                    index_count,
1620                    instance_count,
1621                    first_index,
1622                    base_vertex,
1623                    first_instance,
1624                } => {
1625                    unsafe {
1626                        raw.draw_indexed(
1627                            *first_index,
1628                            *index_count,
1629                            *base_vertex,
1630                            *first_instance,
1631                            *instance_count,
1632                        )
1633                    };
1634                }
1635                Cmd::DrawMeshTasks {
1636                    group_count_x,
1637                    group_count_y,
1638                    group_count_z,
1639                } => unsafe {
1640                    raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1641                },
1642                Cmd::DrawIndirect {
1643                    buffer,
1644                    offset,
1645                    count: 1,
1646                    family,
1647
1648                    vertex_or_index_limit,
1649                    instance_limit,
1650                } => {
1651                    let (buffer, offset) = if self.device.indirect_validation.is_some()
1652                        && *family != DrawCommandFamily::DrawMeshTasks
1653                    {
1654                        let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1655                            indirect_draw_validation_resources,
1656                            &self.device,
1657                            buffer,
1658                            *offset,
1659                            *family,
1660                            vertex_or_index_limit
1661                                .expect("finalized render bundle missing vertex_or_index_limit"),
1662                            instance_limit.expect("finalized render bundle missing instance_limit"),
1663                        )?;
1664
1665                        let dst_buffer =
1666                            indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1667                        (dst_buffer, offset)
1668                    } else {
1669                        (buffer.try_raw(snatch_guard)?, *offset)
1670                    };
1671                    match family {
1672                        DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1673                        DrawCommandFamily::DrawIndexed => unsafe {
1674                            raw.draw_indexed_indirect(buffer, offset, 1)
1675                        },
1676                        DrawCommandFamily::DrawMeshTasks => unsafe {
1677                            raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1678                        },
1679                    }
1680                }
1681                Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1682                    unreachable!("multi-draw commands are not supported in render bundles")
1683                }
1684                Cmd::PushDebugGroup { len, .. } | Cmd::InsertDebugMarker { len, .. } => {
1685                    let (label, rest) = strings.split_at(*len);
1686                    strings = rest;
1687                    if !self.discard_hal_labels {
1688                        let label = str::from_utf8(label).unwrap();
1689                        if matches!(command, Cmd::PushDebugGroup { .. }) {
1690                            unsafe { raw.begin_debug_marker(label) };
1691                        } else {
1692                            unsafe { raw.insert_debug_marker(label) };
1693                        }
1694                    }
1695                }
1696                Cmd::PopDebugGroup => {
1697                    if !self.discard_hal_labels {
1698                        unsafe { raw.end_debug_marker() };
1699                    }
1700                }
1701                Cmd::WriteTimestamp { .. }
1702                | Cmd::BeginOcclusionQuery { .. }
1703                | Cmd::EndOcclusionQuery
1704                | Cmd::BeginPipelineStatisticsQuery { .. }
1705                | Cmd::EndPipelineStatisticsQuery => {
1706                    unreachable!("query commands are not supported in render bundles")
1707                }
1708                Cmd::ExecuteBundle(_)
1709                | Cmd::SetBlendConstant(_)
1710                | Cmd::SetStencilReference(_)
1711                | Cmd::SetViewport { .. }
1712                | Cmd::SetScissor(_) => unreachable!(),
1713            }
1714        }
1715
1716        debug_assert!(strings.is_empty());
1717
1718        if !self.discard_hal_labels && self.base.label.is_some() {
1719            unsafe { raw.end_debug_marker() };
1720        }
1721
1722        Ok(())
1723    }
1724}
1725
1726crate::impl_resource_type!(RenderBundle);
1727crate::impl_labeled!(RenderBundle);
1728crate::impl_parent_device!(RenderBundle);
1729crate::impl_storage_item!(RenderBundle);
1730crate::impl_trackable!(RenderBundle);
1731
1732/// A render bundle's current index buffer state.
1733///
1734/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
1735/// and calls [`State::flush_index`] before any indexed draw command to produce
1736/// a `SetIndexBuffer` command if one is necessary.
1737///
1738/// Binding ranges must be validated against the size of the buffer before
1739/// being stored in `IndexState`.
1740#[derive(Debug)]
1741struct IndexState {
1742    buffer: Arc<Buffer>,
1743    format: wgt::IndexFormat,
1744    range: Range<wgt::BufferAddress>,
1745    is_dirty: bool,
1746}
1747
1748impl IndexState {
1749    /// Return the number of entries in the current index buffer.
1750    ///
1751    /// Panic if no index buffer has been set.
1752    fn limit(&self) -> u64 {
1753        let bytes_per_index = self.format.byte_size() as u64;
1754
1755        (self.range.end - self.range.start) / bytes_per_index
1756    }
1757
1758    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1759    /// command, if needed.
1760    fn flush(&mut self) -> Option<ArcRenderCommand> {
1761        // This was all checked before, but let's check again just in case.
1762        let binding_size = self
1763            .range
1764            .end
1765            .checked_sub(self.range.start)
1766            .filter(|_| self.range.end <= self.buffer.size)
1767            .expect("index range must be contained in buffer");
1768
1769        if self.is_dirty {
1770            self.is_dirty = false;
1771            Some(ArcRenderCommand::SetIndexBuffer {
1772                buffer: self.buffer.clone(),
1773                index_format: self.format,
1774                offset: self.range.start,
1775                size: Some(binding_size),
1776            })
1777        } else {
1778            None
1779        }
1780    }
1781}
1782
1783/// The state of a single vertex buffer slot during render bundle encoding.
1784///
1785/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1786/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1787/// records one vertex buffer slot's state changes here, and then
1788/// calls this type's [`flush`] method just before any draw command to
1789/// produce a `SetVertexBuffer` commands if one is necessary.
1790///
1791/// Binding ranges must be validated against the size of the buffer before
1792/// being stored in `VertexState`.
1793///
1794/// [`flush`]: IndexState::flush
1795#[derive(Debug)]
1796/// State for analyzing and cleaning up bundle command streams.
1797///
1798/// To minimize state updates, [`RenderBundleEncoder::finish`]
1799/// actually just applies commands like [`SetBindGroup`] and
1800/// [`SetIndexBuffer`] to the simulated state stored here, and then
1801/// calls the `flush_foo` methods before draw calls to produce the
1802/// update commands we actually need.
1803///
1804/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1805/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1806struct State {
1807    /// Resources used by this bundle. This will become [`RenderBundleState::used`].
1808    trackers: RenderBundleScope,
1809
1810    /// The currently set pipeline, if any.
1811    pipeline: Option<Arc<RenderPipeline>>,
1812
1813    /// The state of each vertex buffer slot.
1814    vertex: super::VertexState,
1815
1816    /// The current index buffer, if one has been set. We flush this state
1817    /// before indexed draw commands.
1818    index: Option<IndexState>,
1819
1820    /// Dynamic offset values used by the cleaned-up command sequence.
1821    ///
1822    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1823    /// [`dynamic_offsets`] list.
1824    ///
1825    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1826    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1827
1828    device: Arc<Device>,
1829    commands: Vec<ArcRenderCommand>,
1830    multiview_mask: Option<NonZeroU32>,
1831    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1832    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1833    next_dynamic_offset: usize,
1834    binder: Binder,
1835    immediate_state: ImmediateState,
1836}
1837
1838impl State {
1839    /// Set the bundle's current index buffer and its associated parameters.
1840    fn set_index_buffer(
1841        &mut self,
1842        buffer: Arc<Buffer>,
1843        format: wgt::IndexFormat,
1844        range: Range<wgt::BufferAddress>,
1845    ) {
1846        match self.index {
1847            Some(ref current)
1848                if current.buffer.is_equal(&buffer)
1849                    && current.format == format
1850                    && current.range == range =>
1851            {
1852                return
1853            }
1854            _ => (),
1855        }
1856
1857        self.index = Some(IndexState {
1858            buffer,
1859            format,
1860            range,
1861            is_dirty: true,
1862        });
1863    }
1864
1865    fn flush_immediates(&mut self) {
1866        if !self.immediate_state.immediates.is_empty() && self.immediate_state.immediates_dirty {
1867            self.commands.push(ArcRenderCommand::SetImmediate {
1868                offset: 0,
1869                data: self.immediate_state.immediates.clone(),
1870            });
1871            self.immediate_state.immediates_dirty = false;
1872        }
1873    }
1874
1875    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1876    /// command, if needed.
1877    fn flush_index(&mut self) {
1878        let commands = self.index.as_mut().and_then(|index| index.flush());
1879        self.commands.extend(commands);
1880    }
1881
1882    fn flush_vertex_buffers(&mut self) {
1883        let vertex = &mut self.vertex;
1884        let commands = &mut self.commands;
1885        vertex.flush(|slot, buffer, offset, size| {
1886            commands.push(ArcRenderCommand::SetVertexBuffer {
1887                slot,
1888                buffer: Some(buffer.clone()),
1889                offset,
1890                size: Some(size),
1891            });
1892        });
1893    }
1894
1895    /// Validation for a draw command.
1896    ///
1897    /// This should be further deduplicated with similar validation on render/compute passes.
1898    fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1899        if let Some(pipeline) = self.pipeline.as_ref() {
1900            self.binder.check_compatibility(pipeline.as_ref())?;
1901            self.binder.check_late_buffer_bindings()?;
1902
1903            self.vertex.validate(pipeline.as_ref(), &self.binder)?;
1904
1905            if family == DrawCommandFamily::DrawIndexed {
1906                let index_format = match &self.index {
1907                    Some(index) => index.format,
1908                    None => return Err(DrawError::MissingIndexBuffer),
1909                };
1910
1911                if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1912                {
1913                    return Err(DrawError::UnmatchedStripIndexFormat {
1914                        pipeline: pipeline.error_ident(),
1915                        strip_index_format: pipeline.strip_index_format,
1916                        buffer_format: index_format,
1917                    });
1918                }
1919            }
1920
1921            if !self
1922                .immediate_state
1923                .immediate_slots_set
1924                .contains(pipeline.immediate_slots_required)
1925            {
1926                return Err(DrawError::MissingImmediateData {
1927                    missing: pipeline
1928                        .immediate_slots_required
1929                        .difference(self.immediate_state.immediate_slots_set),
1930                });
1931            }
1932
1933            Ok(())
1934        } else {
1935            Err(DrawError::MissingPipeline(pass::MissingPipeline))
1936        }
1937    }
1938
1939    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1940    ///
1941    /// This should be further deduplicated with similar code on render/compute passes.
1942    fn flush_bindings(&mut self) {
1943        let start = self.binder.take_rebind_start_index();
1944        let entries = self.binder.list_valid_with_start(start);
1945
1946        self.commands
1947            .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1948                self.buffer_memory_init_actions
1949                    .extend_from_slice(&bind_group.buffer_init_actions);
1950                self.texture_memory_init_actions
1951                    .extend_from_slice(&bind_group.texture_init_actions);
1952
1953                self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1954
1955                ArcRenderCommand::SetBindGroup {
1956                    index: i.try_into().unwrap(),
1957                    bind_group: Some(bind_group.clone()),
1958                    num_dynamic_offsets: dynamic_offsets.len(),
1959                }
1960            }));
1961    }
1962}
1963
1964/// Error encountered when finishing recording a render bundle.
1965#[derive(Clone, Debug, Error)]
1966pub enum RenderBundleErrorInner {
1967    #[error(transparent)]
1968    DebugGroup(#[from] DebugGroupError),
1969    #[error(transparent)]
1970    Create(#[from] CreateRenderBundleError),
1971    #[error(transparent)]
1972    Device(#[from] DeviceError),
1973    #[error(transparent)]
1974    RenderCommand(RenderCommandError),
1975    #[error(transparent)]
1976    Draw(#[from] DrawError),
1977    #[error(transparent)]
1978    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1979    #[error(transparent)]
1980    Bind(#[from] BindError),
1981    #[error("Render bundle encoder has already ended")]
1982    Ended,
1983}
1984
1985impl<T> From<T> for RenderBundleErrorInner
1986where
1987    T: Into<RenderCommandError>,
1988{
1989    fn from(t: T) -> Self {
1990        Self::RenderCommand(t.into())
1991    }
1992}
1993
1994/// Error encountered when finishing recording a render bundle.
1995#[derive(Clone, Debug, Error)]
1996#[error("{scope}")]
1997pub struct RenderBundleError {
1998    pub scope: PassErrorScope,
1999    #[source]
2000    inner: Box<RenderBundleErrorInner>,
2001}
2002
2003impl WebGpuError for RenderBundleError {
2004    fn webgpu_error_type(&self) -> ErrorType {
2005        match self.inner.as_ref() {
2006            RenderBundleErrorInner::DebugGroup(e) => e.webgpu_error_type(),
2007            RenderBundleErrorInner::Create(e) => e.webgpu_error_type(),
2008            RenderBundleErrorInner::Device(e) => e.webgpu_error_type(),
2009            RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(),
2010            RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(),
2011            RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
2012            RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(),
2013            RenderBundleErrorInner::Ended => ErrorType::Validation,
2014        }
2015    }
2016}
2017
2018impl RenderBundleError {
2019    pub fn from_device_error(e: DeviceError) -> Self {
2020        Self {
2021            scope: PassErrorScope::Bundle,
2022            inner: Box::new(e.into()),
2023        }
2024    }
2025}
2026
2027impl<E> MapPassErr<RenderBundleError> for E
2028where
2029    E: Into<RenderBundleErrorInner>,
2030{
2031    fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
2032        RenderBundleError {
2033            scope,
2034            inner: Box::new(self.into()),
2035        }
2036    }
2037}