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