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 not fully isolated from a bundle's effects on immediate data
33values. Draw calls following a bundle's execution will see whatever values the
34bundle writes to immediate data storage. Setting a pipeline initializes any push
35constant storage it could access to zero, and this initialization may also be
36visible after bundle execution.
37
38## Render Bundle Lifecycle
39
40To create a render bundle:
41
421) Create a [`RenderBundleEncoder`] by calling
43   [`Global::device_create_render_bundle_encoder`][Gdcrbe].
44
452) Record commands in the `RenderBundleEncoder` using functions from the
46   [`bundle_ffi`] module.
47
483) Call [`Global::render_bundle_encoder_finish`][Grbef], which analyzes and cleans up
49   the command stream and returns a `RenderBundleId`.
50
514) Then, any number of times, call [`render_pass_execute_bundles`][wrpeb] to
52   execute the bundle as part of some render pass.
53
54## Implementation
55
56The most complex part of render bundles is the "finish" step, mostly implemented
57in [`RenderBundleEncoder::finish`]. This consumes the commands stored in the
58encoder's [`BasePass`], while validating everything, tracking the state,
59dropping redundant or unnecessary commands, and presenting the results as a new
60[`RenderBundle`]. It doesn't actually execute any commands.
61
62This step also enforces the 'isolation' property mentioned above: every draw
63call is checked to ensure that the resources it uses on were established since
64the last time the pipeline was set. This means the bundle can be executed
65verbatim without any state tracking.
66
67### Execution
68
69When the bundle is used in an actual render pass, `RenderBundle::execute` is
70called. It goes through the commands and issues them into the native command
71buffer. Thanks to isolation, it doesn't track any bind group invalidations or
72index format changes.
73
74[Gdcrbe]: crate::global::Global::device_create_render_bundle_encoder
75[Grbef]: crate::global::Global::render_bundle_encoder_finish
76[wrpeb]: crate::global::Global::render_pass_execute_bundles
77!*/
78
79#![allow(clippy::reversed_empty_ranges)]
80
81use alloc::{
82    borrow::{Cow, ToOwned as _},
83    string::String,
84    sync::Arc,
85    vec::Vec,
86};
87use core::{
88    convert::Infallible,
89    num::{NonZeroU32, NonZeroU64},
90    ops::Range,
91};
92
93use arrayvec::ArrayVec;
94use thiserror::Error;
95
96use wgpu_hal::ShouldBeNonZeroExt;
97use wgt::error::{ErrorType, WebGpuError};
98
99#[cfg(feature = "trace")]
100use crate::command::ArcReferences;
101use crate::{
102    binding_model::{BindError, BindGroup, PipelineLayout},
103    command::{
104        bind::Binder, BasePass, BindGroupStateChange, ColorAttachmentError, DrawError,
105        IdReferences, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange,
106    },
107    device::{AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext},
108    hub::Hub,
109    id,
110    init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
111    pipeline::{PipelineFlags, RenderPipeline, VertexStep},
112    resource::{
113        Buffer, DestroyedResourceError, Fallible, InvalidResourceError, Labeled, ParentDevice,
114        RawResourceAccess, TrackingData,
115    },
116    resource_log,
117    snatch::SnatchGuard,
118    track::RenderBundleScope,
119    Label, LabelHelpers,
120};
121
122use super::{pass, render_command::ArcRenderCommand, DrawCommandFamily, DrawKind};
123
124/// Describes a [`RenderBundleEncoder`].
125#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127pub struct RenderBundleEncoderDescriptor<'a> {
128    /// Debug label of the render bundle encoder.
129    ///
130    /// This will show up in graphics debuggers for easy identification.
131    pub label: Label<'a>,
132    /// The formats of the color attachments that this render bundle is capable
133    /// to rendering to.
134    ///
135    /// This must match the formats of the color attachments in the
136    /// renderpass this render bundle is executed in.
137    pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
138    /// Information about the depth attachment that this render bundle is
139    /// capable to rendering to.
140    ///
141    /// The format must match the format of the depth attachments in the
142    /// renderpass this render bundle is executed in.
143    pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
144    /// Sample count this render bundle is capable of rendering to.
145    ///
146    /// This must match the pipelines and the renderpasses it is used in.
147    pub sample_count: u32,
148    /// If this render bundle will rendering to multiple array layers in the
149    /// attachments at the same time.
150    pub multiview: Option<NonZeroU32>,
151}
152
153#[derive(Debug)]
154#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
155pub struct RenderBundleEncoder {
156    base: BasePass<RenderCommand<IdReferences>, Infallible>,
157    parent_id: id::DeviceId,
158    pub(crate) context: RenderPassContext,
159    pub(crate) is_depth_read_only: bool,
160    pub(crate) is_stencil_read_only: bool,
161
162    // Resource binding dedupe state.
163    #[cfg_attr(feature = "serde", serde(skip))]
164    current_bind_groups: BindGroupStateChange,
165    #[cfg_attr(feature = "serde", serde(skip))]
166    current_pipeline: StateChange<id::RenderPipelineId>,
167}
168
169impl RenderBundleEncoder {
170    pub fn new(
171        desc: &RenderBundleEncoderDescriptor,
172        parent_id: id::DeviceId,
173    ) -> Result<Self, CreateRenderBundleError> {
174        let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
175            Some(ds) => {
176                let aspects = hal::FormatAspects::from(ds.format);
177                (
178                    !aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only,
179                    !aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only,
180                )
181            }
182            // There's no depth/stencil attachment, so these values just don't
183            // matter.  Choose the most accommodating value, to simplify
184            // validation.
185            None => (true, true),
186        };
187
188        // TODO: should be device.limits.max_color_attachments
189        let max_color_attachments = hal::MAX_COLOR_ATTACHMENTS;
190
191        //TODO: validate that attachment formats are renderable,
192        // have expected aspects, support multisampling.
193        Ok(Self {
194            base: BasePass::new(&desc.label),
195            parent_id,
196            context: RenderPassContext {
197                attachments: AttachmentData {
198                    colors: if desc.color_formats.len() > max_color_attachments {
199                        return Err(CreateRenderBundleError::ColorAttachment(
200                            ColorAttachmentError::TooMany {
201                                given: desc.color_formats.len(),
202                                limit: max_color_attachments,
203                            },
204                        ));
205                    } else {
206                        desc.color_formats.iter().cloned().collect()
207                    },
208                    resolves: ArrayVec::new(),
209                    depth_stencil: desc.depth_stencil.map(|ds| ds.format),
210                },
211                sample_count: {
212                    let sc = desc.sample_count;
213                    if sc == 0 || sc > 32 || !sc.is_power_of_two() {
214                        return Err(CreateRenderBundleError::InvalidSampleCount(sc));
215                    }
216                    sc
217                },
218                multiview_mask: desc.multiview,
219            },
220
221            is_depth_read_only,
222            is_stencil_read_only,
223            current_bind_groups: BindGroupStateChange::new(),
224            current_pipeline: StateChange::new(),
225        })
226    }
227
228    pub fn dummy(parent_id: id::DeviceId) -> Self {
229        Self {
230            base: BasePass::new(&None),
231            parent_id,
232            context: RenderPassContext {
233                attachments: AttachmentData {
234                    colors: ArrayVec::new(),
235                    resolves: ArrayVec::new(),
236                    depth_stencil: None,
237                },
238                sample_count: 0,
239                multiview_mask: None,
240            },
241            is_depth_read_only: false,
242            is_stencil_read_only: false,
243
244            current_bind_groups: BindGroupStateChange::new(),
245            current_pipeline: StateChange::new(),
246        }
247    }
248
249    pub fn parent(&self) -> id::DeviceId {
250        self.parent_id
251    }
252
253    /// Convert this encoder's commands into a [`RenderBundle`].
254    ///
255    /// We want executing a [`RenderBundle`] to be quick, so we take
256    /// this opportunity to clean up the [`RenderBundleEncoder`]'s
257    /// command stream and gather metadata about it that will help
258    /// keep [`ExecuteBundle`] simple and fast. We remove redundant
259    /// commands (along with their side data), note resource usage,
260    /// and accumulate buffer and texture initialization actions.
261    ///
262    /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle
263    pub(crate) fn finish(
264        self,
265        desc: &RenderBundleDescriptor,
266        device: &Arc<Device>,
267        hub: &Hub,
268    ) -> Result<Arc<RenderBundle>, RenderBundleError> {
269        let scope = PassErrorScope::Bundle;
270
271        device.check_is_valid().map_pass_err(scope)?;
272
273        let bind_group_guard = hub.bind_groups.read();
274        let pipeline_guard = hub.render_pipelines.read();
275        let buffer_guard = hub.buffers.read();
276
277        let mut state = State {
278            trackers: RenderBundleScope::new(),
279            pipeline: None,
280            vertex: Default::default(),
281            index: None,
282            flat_dynamic_offsets: Vec::new(),
283            device: device.clone(),
284            commands: Vec::new(),
285            buffer_memory_init_actions: Vec::new(),
286            texture_memory_init_actions: Vec::new(),
287            next_dynamic_offset: 0,
288            binder: Binder::new(),
289        };
290
291        let indices = &state.device.tracker_indices;
292        state.trackers.buffers.set_size(indices.buffers.size());
293        state.trackers.textures.set_size(indices.textures.size());
294
295        let base = &self.base;
296
297        for command in &base.commands {
298            match command {
299                &RenderCommand::SetBindGroup {
300                    index,
301                    num_dynamic_offsets,
302                    bind_group,
303                } => {
304                    let scope = PassErrorScope::SetBindGroup;
305                    set_bind_group(
306                        &mut state,
307                        &bind_group_guard,
308                        &base.dynamic_offsets,
309                        index,
310                        num_dynamic_offsets,
311                        bind_group,
312                    )
313                    .map_pass_err(scope)?;
314                }
315                &RenderCommand::SetPipeline(pipeline) => {
316                    let scope = PassErrorScope::SetPipelineRender;
317                    set_pipeline(
318                        &mut state,
319                        &pipeline_guard,
320                        &self.context,
321                        self.is_depth_read_only,
322                        self.is_stencil_read_only,
323                        pipeline,
324                    )
325                    .map_pass_err(scope)?;
326                }
327                &RenderCommand::SetIndexBuffer {
328                    buffer,
329                    index_format,
330                    offset,
331                    size,
332                } => {
333                    let scope = PassErrorScope::SetIndexBuffer;
334                    set_index_buffer(
335                        &mut state,
336                        &buffer_guard,
337                        buffer,
338                        index_format,
339                        offset,
340                        size,
341                    )
342                    .map_pass_err(scope)?;
343                }
344                &RenderCommand::SetVertexBuffer {
345                    slot,
346                    buffer,
347                    offset,
348                    size,
349                } => {
350                    let scope = PassErrorScope::SetVertexBuffer;
351                    set_vertex_buffer(&mut state, &buffer_guard, slot, buffer, offset, size)
352                        .map_pass_err(scope)?;
353                }
354                &RenderCommand::SetImmediate {
355                    offset,
356                    size_bytes,
357                    values_offset,
358                } => {
359                    let scope = PassErrorScope::SetImmediate;
360                    set_immediates(&mut state, offset, size_bytes, values_offset)
361                        .map_pass_err(scope)?;
362                }
363                &RenderCommand::Draw {
364                    vertex_count,
365                    instance_count,
366                    first_vertex,
367                    first_instance,
368                } => {
369                    let scope = PassErrorScope::Draw {
370                        kind: DrawKind::Draw,
371                        family: DrawCommandFamily::Draw,
372                    };
373                    draw(
374                        &mut state,
375                        vertex_count,
376                        instance_count,
377                        first_vertex,
378                        first_instance,
379                    )
380                    .map_pass_err(scope)?;
381                }
382                &RenderCommand::DrawIndexed {
383                    index_count,
384                    instance_count,
385                    first_index,
386                    base_vertex,
387                    first_instance,
388                } => {
389                    let scope = PassErrorScope::Draw {
390                        kind: DrawKind::Draw,
391                        family: DrawCommandFamily::DrawIndexed,
392                    };
393                    draw_indexed(
394                        &mut state,
395                        index_count,
396                        instance_count,
397                        first_index,
398                        base_vertex,
399                        first_instance,
400                    )
401                    .map_pass_err(scope)?;
402                }
403                &RenderCommand::DrawMeshTasks {
404                    group_count_x,
405                    group_count_y,
406                    group_count_z,
407                } => {
408                    let scope = PassErrorScope::Draw {
409                        kind: DrawKind::Draw,
410                        family: DrawCommandFamily::DrawMeshTasks,
411                    };
412                    draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z)
413                        .map_pass_err(scope)?;
414                }
415                &RenderCommand::DrawIndirect {
416                    buffer,
417                    offset,
418                    count: 1,
419                    family,
420                    vertex_or_index_limit: None,
421                    instance_limit: None,
422                } => {
423                    let scope = PassErrorScope::Draw {
424                        kind: DrawKind::DrawIndirect,
425                        family,
426                    };
427                    multi_draw_indirect(&mut state, &buffer_guard, buffer, offset, family)
428                        .map_pass_err(scope)?;
429                }
430                &RenderCommand::DrawIndirect {
431                    count,
432                    vertex_or_index_limit,
433                    instance_limit,
434                    ..
435                } => {
436                    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");
437                }
438                &RenderCommand::MultiDrawIndirectCount { .. }
439                | &RenderCommand::PushDebugGroup { color: _, len: _ }
440                | &RenderCommand::InsertDebugMarker { color: _, len: _ }
441                | &RenderCommand::PopDebugGroup => {
442                    unimplemented!("not supported by a render bundle")
443                }
444                // Must check the TIMESTAMP_QUERY_INSIDE_PASSES feature
445                &RenderCommand::WriteTimestamp { .. }
446                | &RenderCommand::BeginOcclusionQuery { .. }
447                | &RenderCommand::EndOcclusionQuery
448                | &RenderCommand::BeginPipelineStatisticsQuery { .. }
449                | &RenderCommand::EndPipelineStatisticsQuery => {
450                    unimplemented!("not supported by a render bundle")
451                }
452                &RenderCommand::ExecuteBundle(_)
453                | &RenderCommand::SetBlendConstant(_)
454                | &RenderCommand::SetStencilReference(_)
455                | &RenderCommand::SetViewport { .. }
456                | &RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
457            }
458        }
459
460        let State {
461            trackers,
462            flat_dynamic_offsets,
463            device,
464            commands,
465            buffer_memory_init_actions,
466            texture_memory_init_actions,
467            ..
468        } = state;
469
470        let tracker_indices = device.tracker_indices.bundles.clone();
471        let discard_hal_labels = device
472            .instance_flags
473            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
474
475        let render_bundle = RenderBundle {
476            base: BasePass {
477                label: desc.label.as_deref().map(str::to_owned),
478                error: None,
479                commands,
480                dynamic_offsets: flat_dynamic_offsets,
481                string_data: self.base.string_data,
482                immediates_data: self.base.immediates_data,
483            },
484            is_depth_read_only: self.is_depth_read_only,
485            is_stencil_read_only: self.is_stencil_read_only,
486            device: device.clone(),
487            used: trackers,
488            buffer_memory_init_actions,
489            texture_memory_init_actions,
490            context: self.context,
491            label: desc.label.to_string(),
492            tracking_data: TrackingData::new(tracker_indices),
493            discard_hal_labels,
494        };
495
496        let render_bundle = Arc::new(render_bundle);
497
498        Ok(render_bundle)
499    }
500
501    pub fn set_index_buffer(
502        &mut self,
503        buffer: id::BufferId,
504        index_format: wgt::IndexFormat,
505        offset: wgt::BufferAddress,
506        size: Option<wgt::BufferSize>,
507    ) {
508        self.base.commands.push(RenderCommand::SetIndexBuffer {
509            buffer,
510            index_format,
511            offset,
512            size,
513        });
514    }
515}
516
517fn set_bind_group(
518    state: &mut State,
519    bind_group_guard: &crate::storage::Storage<Fallible<BindGroup>>,
520    dynamic_offsets: &[u32],
521    index: u32,
522    num_dynamic_offsets: usize,
523    bind_group_id: Option<id::Id<id::markers::BindGroup>>,
524) -> Result<(), RenderBundleErrorInner> {
525    let max_bind_groups = state.device.limits.max_bind_groups;
526    if index >= max_bind_groups {
527        return Err(
528            RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
529                index,
530                max: max_bind_groups,
531            })
532            .into(),
533        );
534    }
535
536    // Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`.
537    let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
538    state.next_dynamic_offset = offsets_range.end;
539    let offsets = &dynamic_offsets[offsets_range.clone()];
540
541    let bind_group = bind_group_id.map(|id| bind_group_guard.get(id));
542
543    if let Some(bind_group) = bind_group {
544        let bind_group = bind_group.get()?;
545        bind_group.same_device(&state.device)?;
546        bind_group.validate_dynamic_bindings(index, offsets)?;
547
548        unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
549        let bind_group = state.trackers.bind_groups.insert_single(bind_group);
550
551        state
552            .binder
553            .assign_group(index as usize, bind_group, offsets);
554    } else {
555        if !offsets.is_empty() {
556            return Err(RenderBundleErrorInner::Bind(
557                BindError::DynamicOffsetCountNotZero {
558                    group: index,
559                    actual: offsets.len(),
560                },
561            ));
562        }
563
564        state.binder.clear_group(index as usize);
565    }
566
567    Ok(())
568}
569
570fn set_pipeline(
571    state: &mut State,
572    pipeline_guard: &crate::storage::Storage<Fallible<RenderPipeline>>,
573    context: &RenderPassContext,
574    is_depth_read_only: bool,
575    is_stencil_read_only: bool,
576    pipeline_id: id::Id<id::markers::RenderPipeline>,
577) -> Result<(), RenderBundleErrorInner> {
578    let pipeline = pipeline_guard.get(pipeline_id).get()?;
579
580    pipeline.same_device(&state.device)?;
581
582    context
583        .check_compatible(&pipeline.pass_context, pipeline.as_ref())
584        .map_err(RenderCommandError::IncompatiblePipelineTargets)?;
585
586    if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
587        return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
588    }
589    if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
590        return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
591    }
592
593    let pipeline_state = PipelineState::new(&pipeline);
594
595    state
596        .commands
597        .push(ArcRenderCommand::SetPipeline(pipeline.clone()));
598
599    // If this pipeline uses immediates, zero out their values.
600    if let Some(cmd) = pipeline_state.zero_immediates() {
601        state.commands.push(cmd);
602    }
603
604    state.pipeline = Some(pipeline_state);
605
606    state
607        .binder
608        .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups);
609
610    state.trackers.render_pipelines.insert_single(pipeline);
611    Ok(())
612}
613
614// This function is duplicative of `render::set_index_buffer`.
615fn set_index_buffer(
616    state: &mut State,
617    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
618    buffer_id: id::Id<id::markers::Buffer>,
619    index_format: wgt::IndexFormat,
620    offset: u64,
621    size: Option<NonZeroU64>,
622) -> Result<(), RenderBundleErrorInner> {
623    let buffer = buffer_guard.get(buffer_id).get()?;
624
625    state
626        .trackers
627        .buffers
628        .merge_single(&buffer, wgt::BufferUses::INDEX)?;
629
630    buffer.same_device(&state.device)?;
631    buffer.check_usage(wgt::BufferUsages::INDEX)?;
632
633    if !offset.is_multiple_of(u64::try_from(index_format.byte_size()).unwrap()) {
634        return Err(RenderCommandError::UnalignedIndexBuffer {
635            offset,
636            alignment: index_format.byte_size(),
637        }
638        .into());
639    }
640    let end = offset + buffer.resolve_binding_size(offset, size)?;
641
642    state
643        .buffer_memory_init_actions
644        .extend(buffer.initialization_status.read().create_action(
645            &buffer,
646            offset..end.get(),
647            MemoryInitKind::NeedsInitializedMemory,
648        ));
649    state.set_index_buffer(buffer, index_format, offset..end.get());
650    Ok(())
651}
652
653// This function is duplicative of `render::set_vertex_buffer`.
654fn set_vertex_buffer(
655    state: &mut State,
656    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
657    slot: u32,
658    buffer_id: id::Id<id::markers::Buffer>,
659    offset: u64,
660    size: Option<NonZeroU64>,
661) -> Result<(), RenderBundleErrorInner> {
662    let max_vertex_buffers = state.device.limits.max_vertex_buffers;
663    if slot >= max_vertex_buffers {
664        return Err(RenderCommandError::VertexBufferIndexOutOfRange {
665            index: slot,
666            max: max_vertex_buffers,
667        }
668        .into());
669    }
670
671    let buffer = buffer_guard.get(buffer_id).get()?;
672
673    state
674        .trackers
675        .buffers
676        .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
677
678    buffer.same_device(&state.device)?;
679    buffer.check_usage(wgt::BufferUsages::VERTEX)?;
680
681    if !offset.is_multiple_of(wgt::VERTEX_ALIGNMENT) {
682        return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
683    }
684    let end = offset + buffer.resolve_binding_size(offset, size)?;
685
686    state
687        .buffer_memory_init_actions
688        .extend(buffer.initialization_status.read().create_action(
689            &buffer,
690            offset..end.get(),
691            MemoryInitKind::NeedsInitializedMemory,
692        ));
693    state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get()));
694    Ok(())
695}
696
697fn set_immediates(
698    state: &mut State,
699    offset: u32,
700    size_bytes: u32,
701    values_offset: Option<u32>,
702) -> Result<(), RenderBundleErrorInner> {
703    let pipeline_state = state.pipeline()?;
704
705    pipeline_state
706        .pipeline
707        .layout
708        .validate_immediates_ranges(offset, size_bytes)?;
709
710    state.commands.push(ArcRenderCommand::SetImmediate {
711        offset,
712        size_bytes,
713        values_offset,
714    });
715    Ok(())
716}
717
718fn draw(
719    state: &mut State,
720    vertex_count: u32,
721    instance_count: u32,
722    first_vertex: u32,
723    first_instance: u32,
724) -> Result<(), RenderBundleErrorInner> {
725    state.is_ready(DrawCommandFamily::Draw)?;
726    let pipeline = state.pipeline()?;
727
728    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
729    vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?;
730    vertex_limits.validate_instance_limit(first_instance, instance_count)?;
731
732    if instance_count > 0 && vertex_count > 0 {
733        state.flush_vertices();
734        state.flush_bindings();
735        state.commands.push(ArcRenderCommand::Draw {
736            vertex_count,
737            instance_count,
738            first_vertex,
739            first_instance,
740        });
741    }
742    Ok(())
743}
744
745fn draw_indexed(
746    state: &mut State,
747    index_count: u32,
748    instance_count: u32,
749    first_index: u32,
750    base_vertex: i32,
751    first_instance: u32,
752) -> Result<(), RenderBundleErrorInner> {
753    state.is_ready(DrawCommandFamily::DrawIndexed)?;
754    let pipeline = state.pipeline()?;
755
756    let index = state.index.as_ref().unwrap();
757
758    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
759
760    let last_index = first_index as u64 + index_count as u64;
761    let index_limit = index.limit();
762    if last_index > index_limit {
763        return Err(DrawError::IndexBeyondLimit {
764            last_index,
765            index_limit,
766        }
767        .into());
768    }
769    vertex_limits.validate_instance_limit(first_instance, instance_count)?;
770
771    if instance_count > 0 && index_count > 0 {
772        state.flush_index();
773        state.flush_vertices();
774        state.flush_bindings();
775        state.commands.push(ArcRenderCommand::DrawIndexed {
776            index_count,
777            instance_count,
778            first_index,
779            base_vertex,
780            first_instance,
781        });
782    }
783    Ok(())
784}
785
786fn draw_mesh_tasks(
787    state: &mut State,
788    group_count_x: u32,
789    group_count_y: u32,
790    group_count_z: u32,
791) -> Result<(), RenderBundleErrorInner> {
792    state.is_ready(DrawCommandFamily::DrawMeshTasks)?;
793
794    let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension;
795    let max_groups = state.device.limits.max_task_mesh_workgroup_total_count;
796    if group_count_x > groups_size_limit
797        || group_count_y > groups_size_limit
798        || group_count_z > groups_size_limit
799        || group_count_x * group_count_y * group_count_z > max_groups
800    {
801        return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize {
802            current: [group_count_x, group_count_y, group_count_z],
803            limit: groups_size_limit,
804            max_total: max_groups,
805        }));
806    }
807
808    if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 {
809        state.flush_bindings();
810        state.commands.push(ArcRenderCommand::DrawMeshTasks {
811            group_count_x,
812            group_count_y,
813            group_count_z,
814        });
815    }
816    Ok(())
817}
818
819fn multi_draw_indirect(
820    state: &mut State,
821    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
822    buffer_id: id::Id<id::markers::Buffer>,
823    offset: u64,
824    family: DrawCommandFamily,
825) -> Result<(), RenderBundleErrorInner> {
826    state.is_ready(family)?;
827    state
828        .device
829        .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
830
831    let pipeline = state.pipeline()?;
832
833    let buffer = buffer_guard.get(buffer_id).get()?;
834
835    buffer.same_device(&state.device)?;
836    buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
837
838    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
839
840    let stride = super::get_stride_of_indirect_args(family);
841    state
842        .buffer_memory_init_actions
843        .extend(buffer.initialization_status.read().create_action(
844            &buffer,
845            offset..(offset + stride),
846            MemoryInitKind::NeedsInitializedMemory,
847        ));
848
849    let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
850        let index = state.index.as_mut().unwrap();
851        state.commands.extend(index.flush());
852        index.limit()
853    } else {
854        vertex_limits.vertex_limit
855    };
856    let instance_limit = vertex_limits.instance_limit;
857
858    let buffer_uses = if state.device.indirect_validation.is_some() {
859        wgt::BufferUses::STORAGE_READ_ONLY
860    } else {
861        wgt::BufferUses::INDIRECT
862    };
863
864    state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
865
866    state.flush_vertices();
867    state.flush_bindings();
868    state.commands.push(ArcRenderCommand::DrawIndirect {
869        buffer,
870        offset,
871        count: 1,
872        family,
873
874        vertex_or_index_limit: Some(vertex_or_index_limit),
875        instance_limit: Some(instance_limit),
876    });
877    Ok(())
878}
879
880/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
881#[derive(Clone, Debug, Error)]
882#[non_exhaustive]
883pub enum CreateRenderBundleError {
884    #[error(transparent)]
885    ColorAttachment(#[from] ColorAttachmentError),
886    #[error("Invalid number of samples {0}")]
887    InvalidSampleCount(u32),
888}
889
890impl WebGpuError for CreateRenderBundleError {
891    fn webgpu_error_type(&self) -> ErrorType {
892        match self {
893            Self::ColorAttachment(e) => e.webgpu_error_type(),
894            Self::InvalidSampleCount(_) => ErrorType::Validation,
895        }
896    }
897}
898
899/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
900#[derive(Clone, Debug, Error)]
901#[non_exhaustive]
902pub enum ExecutionError {
903    #[error(transparent)]
904    Device(#[from] DeviceError),
905    #[error(transparent)]
906    DestroyedResource(#[from] DestroyedResourceError),
907    #[error("Using {0} in a render bundle is not implemented")]
908    Unimplemented(&'static str),
909}
910
911pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
912
913//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
914// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
915// or Metal indirect command buffer.
916/// cbindgen:ignore
917#[derive(Debug)]
918pub struct RenderBundle {
919    // Normalized command stream. It can be executed verbatim,
920    // without re-binding anything on the pipeline change.
921    base: BasePass<ArcRenderCommand, Infallible>,
922    pub(super) is_depth_read_only: bool,
923    pub(super) is_stencil_read_only: bool,
924    pub(crate) device: Arc<Device>,
925    pub(crate) used: RenderBundleScope,
926    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
927    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
928    pub(super) context: RenderPassContext,
929    /// The `label` from the descriptor used to create the resource.
930    label: String,
931    pub(crate) tracking_data: TrackingData,
932    discard_hal_labels: bool,
933}
934
935impl Drop for RenderBundle {
936    fn drop(&mut self) {
937        resource_log!("Drop {}", self.error_ident());
938    }
939}
940
941#[cfg(send_sync)]
942unsafe impl Send for RenderBundle {}
943#[cfg(send_sync)]
944unsafe impl Sync for RenderBundle {}
945
946impl RenderBundle {
947    #[cfg(feature = "trace")]
948    pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
949        self.base.clone()
950    }
951
952    /// Actually encode the contents into a native command buffer.
953    ///
954    /// This is partially duplicating the logic of `render_pass_end`.
955    /// However the point of this function is to be lighter, since we already had
956    /// a chance to go through the commands in `render_bundle_encoder_finish`.
957    ///
958    /// Note that the function isn't expected to fail, generally.
959    /// All the validation has already been done by this point.
960    /// The only failure condition is if some of the used buffers are destroyed.
961    pub(super) unsafe fn execute(
962        &self,
963        raw: &mut dyn hal::DynCommandEncoder,
964        indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
965        indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
966        snatch_guard: &SnatchGuard,
967    ) -> Result<(), ExecutionError> {
968        let mut offsets = self.base.dynamic_offsets.as_slice();
969        let mut pipeline_layout = None::<Arc<PipelineLayout>>;
970        if !self.discard_hal_labels {
971            if let Some(ref label) = self.base.label {
972                unsafe { raw.begin_debug_marker(label) };
973            }
974        }
975
976        use ArcRenderCommand as Cmd;
977        for command in self.base.commands.iter() {
978            match command {
979                Cmd::SetBindGroup {
980                    index,
981                    num_dynamic_offsets,
982                    bind_group,
983                } => {
984                    let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
985                    unsafe {
986                        raw.set_bind_group(
987                            pipeline_layout.as_ref().unwrap().raw(),
988                            *index,
989                            raw_bg,
990                            &offsets[..*num_dynamic_offsets],
991                        )
992                    };
993                    offsets = &offsets[*num_dynamic_offsets..];
994                }
995                Cmd::SetPipeline(pipeline) => {
996                    unsafe { raw.set_render_pipeline(pipeline.raw()) };
997
998                    pipeline_layout = Some(pipeline.layout.clone());
999                }
1000                Cmd::SetIndexBuffer {
1001                    buffer,
1002                    index_format,
1003                    offset,
1004                    size,
1005                } => {
1006                    let buffer = buffer.try_raw(snatch_guard)?;
1007                    // SAFETY: The binding size was checked against the buffer size
1008                    // in `set_index_buffer` and again in `IndexState::flush`.
1009                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1010                    unsafe { raw.set_index_buffer(bb, *index_format) };
1011                }
1012                Cmd::SetVertexBuffer {
1013                    slot,
1014                    buffer,
1015                    offset,
1016                    size,
1017                } => {
1018                    let buffer = buffer.try_raw(snatch_guard)?;
1019                    // SAFETY: The binding size was checked against the buffer size
1020                    // in `set_vertex_buffer` and again in `VertexState::flush`.
1021                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1022                    unsafe { raw.set_vertex_buffer(*slot, bb) };
1023                }
1024                Cmd::SetImmediate {
1025                    offset,
1026                    size_bytes,
1027                    values_offset,
1028                } => {
1029                    let pipeline_layout = pipeline_layout.as_ref().unwrap();
1030
1031                    if let Some(values_offset) = *values_offset {
1032                        let values_end_offset =
1033                            (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1034                        let data_slice =
1035                            &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1036
1037                        unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1038                    } else {
1039                        super::immediates_clear(
1040                            *offset,
1041                            *size_bytes,
1042                            |clear_offset, clear_data| {
1043                                unsafe {
1044                                    raw.set_immediates(
1045                                        pipeline_layout.raw(),
1046                                        clear_offset,
1047                                        clear_data,
1048                                    )
1049                                };
1050                            },
1051                        );
1052                    }
1053                }
1054                Cmd::Draw {
1055                    vertex_count,
1056                    instance_count,
1057                    first_vertex,
1058                    first_instance,
1059                } => {
1060                    unsafe {
1061                        raw.draw(
1062                            *first_vertex,
1063                            *vertex_count,
1064                            *first_instance,
1065                            *instance_count,
1066                        )
1067                    };
1068                }
1069                Cmd::DrawIndexed {
1070                    index_count,
1071                    instance_count,
1072                    first_index,
1073                    base_vertex,
1074                    first_instance,
1075                } => {
1076                    unsafe {
1077                        raw.draw_indexed(
1078                            *first_index,
1079                            *index_count,
1080                            *base_vertex,
1081                            *first_instance,
1082                            *instance_count,
1083                        )
1084                    };
1085                }
1086                Cmd::DrawMeshTasks {
1087                    group_count_x,
1088                    group_count_y,
1089                    group_count_z,
1090                } => unsafe {
1091                    raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1092                },
1093                Cmd::DrawIndirect {
1094                    buffer,
1095                    offset,
1096                    count: 1,
1097                    family,
1098
1099                    vertex_or_index_limit,
1100                    instance_limit,
1101                } => {
1102                    let (buffer, offset) = if self.device.indirect_validation.is_some() {
1103                        let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1104                            indirect_draw_validation_resources,
1105                            &self.device,
1106                            buffer,
1107                            *offset,
1108                            *family,
1109                            vertex_or_index_limit
1110                                .expect("finalized render bundle missing vertex_or_index_limit"),
1111                            instance_limit.expect("finalized render bundle missing instance_limit"),
1112                        )?;
1113
1114                        let dst_buffer =
1115                            indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1116                        (dst_buffer, offset)
1117                    } else {
1118                        (buffer.try_raw(snatch_guard)?, *offset)
1119                    };
1120                    match family {
1121                        DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1122                        DrawCommandFamily::DrawIndexed => unsafe {
1123                            raw.draw_indexed_indirect(buffer, offset, 1)
1124                        },
1125                        DrawCommandFamily::DrawMeshTasks => unsafe {
1126                            raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1127                        },
1128                    }
1129                }
1130                Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1131                    return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1132                }
1133                Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1134                    return Err(ExecutionError::Unimplemented("debug-markers"))
1135                }
1136                Cmd::WriteTimestamp { .. }
1137                | Cmd::BeginOcclusionQuery { .. }
1138                | Cmd::EndOcclusionQuery
1139                | Cmd::BeginPipelineStatisticsQuery { .. }
1140                | Cmd::EndPipelineStatisticsQuery => {
1141                    return Err(ExecutionError::Unimplemented("queries"))
1142                }
1143                Cmd::ExecuteBundle(_)
1144                | Cmd::SetBlendConstant(_)
1145                | Cmd::SetStencilReference(_)
1146                | Cmd::SetViewport { .. }
1147                | Cmd::SetScissor(_) => unreachable!(),
1148            }
1149        }
1150
1151        if !self.discard_hal_labels {
1152            if let Some(_) = self.base.label {
1153                unsafe { raw.end_debug_marker() };
1154            }
1155        }
1156
1157        Ok(())
1158    }
1159}
1160
1161crate::impl_resource_type!(RenderBundle);
1162crate::impl_labeled!(RenderBundle);
1163crate::impl_parent_device!(RenderBundle);
1164crate::impl_storage_item!(RenderBundle);
1165crate::impl_trackable!(RenderBundle);
1166
1167/// A render bundle's current index buffer state.
1168///
1169/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
1170/// and calls [`State::flush_index`] before any indexed draw command to produce
1171/// a `SetIndexBuffer` command if one is necessary.
1172///
1173/// Binding ranges must be validated against the size of the buffer before
1174/// being stored in `IndexState`.
1175#[derive(Debug)]
1176struct IndexState {
1177    buffer: Arc<Buffer>,
1178    format: wgt::IndexFormat,
1179    range: Range<wgt::BufferAddress>,
1180    is_dirty: bool,
1181}
1182
1183impl IndexState {
1184    /// Return the number of entries in the current index buffer.
1185    ///
1186    /// Panic if no index buffer has been set.
1187    fn limit(&self) -> u64 {
1188        let bytes_per_index = self.format.byte_size() as u64;
1189
1190        (self.range.end - self.range.start) / bytes_per_index
1191    }
1192
1193    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1194    /// command, if needed.
1195    fn flush(&mut self) -> Option<ArcRenderCommand> {
1196        // This was all checked before, but let's check again just in case.
1197        let binding_size = self
1198            .range
1199            .end
1200            .checked_sub(self.range.start)
1201            .filter(|_| self.range.end <= self.buffer.size)
1202            .expect("index range must be contained in buffer");
1203
1204        if self.is_dirty {
1205            self.is_dirty = false;
1206            Some(ArcRenderCommand::SetIndexBuffer {
1207                buffer: self.buffer.clone(),
1208                index_format: self.format,
1209                offset: self.range.start,
1210                size: NonZeroU64::new(binding_size),
1211            })
1212        } else {
1213            None
1214        }
1215    }
1216}
1217
1218/// The state of a single vertex buffer slot during render bundle encoding.
1219///
1220/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1221/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1222/// records one vertex buffer slot's state changes here, and then
1223/// calls this type's [`flush`] method just before any draw command to
1224/// produce a `SetVertexBuffer` commands if one is necessary.
1225///
1226/// Binding ranges must be validated against the size of the buffer before
1227/// being stored in `VertexState`.
1228///
1229/// [`flush`]: IndexState::flush
1230#[derive(Debug)]
1231struct VertexState {
1232    buffer: Arc<Buffer>,
1233    range: Range<wgt::BufferAddress>,
1234    is_dirty: bool,
1235}
1236
1237impl VertexState {
1238    /// Create a new `VertexState`.
1239    ///
1240    /// The `range` must be contained within `buffer`.
1241    fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1242        Self {
1243            buffer,
1244            range,
1245            is_dirty: true,
1246        }
1247    }
1248
1249    /// Generate a `SetVertexBuffer` command for this slot, if necessary.
1250    ///
1251    /// `slot` is the index of the vertex buffer slot that `self` tracks.
1252    fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1253        let binding_size = self
1254            .range
1255            .end
1256            .checked_sub(self.range.start)
1257            .filter(|_| self.range.end <= self.buffer.size)
1258            .expect("vertex range must be contained in buffer");
1259
1260        if self.is_dirty {
1261            self.is_dirty = false;
1262            Some(ArcRenderCommand::SetVertexBuffer {
1263                slot,
1264                buffer: self.buffer.clone(),
1265                offset: self.range.start,
1266                size: NonZeroU64::new(binding_size),
1267            })
1268        } else {
1269            None
1270        }
1271    }
1272}
1273
1274/// The bundle's current pipeline, and some cached information needed for validation.
1275struct PipelineState {
1276    /// The pipeline
1277    pipeline: Arc<RenderPipeline>,
1278
1279    /// How this pipeline's vertex shader traverses each vertex buffer, indexed
1280    /// by vertex buffer slot number.
1281    steps: Vec<VertexStep>,
1282
1283    /// Size of the immediate data ranges this pipeline uses. Copied from the pipeline layout.
1284    immediate_size: u32,
1285}
1286
1287impl PipelineState {
1288    fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1289        Self {
1290            pipeline: pipeline.clone(),
1291            steps: pipeline.vertex_steps.to_vec(),
1292            immediate_size: pipeline.layout.immediate_size,
1293        }
1294    }
1295
1296    /// Return a sequence of commands to zero the immediate data ranges this
1297    /// pipeline uses. If no initialization is necessary, return `None`.
1298    fn zero_immediates(&self) -> Option<ArcRenderCommand> {
1299        if self.immediate_size == 0 {
1300            return None;
1301        }
1302
1303        Some(ArcRenderCommand::SetImmediate {
1304            offset: 0,
1305            size_bytes: self.immediate_size,
1306            values_offset: None,
1307        })
1308    }
1309}
1310
1311/// State for analyzing and cleaning up bundle command streams.
1312///
1313/// To minimize state updates, [`RenderBundleEncoder::finish`]
1314/// actually just applies commands like [`SetBindGroup`] and
1315/// [`SetIndexBuffer`] to the simulated state stored here, and then
1316/// calls the `flush_foo` methods before draw calls to produce the
1317/// update commands we actually need.
1318///
1319/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1320/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1321struct State {
1322    /// Resources used by this bundle. This will become [`RenderBundle::used`].
1323    trackers: RenderBundleScope,
1324
1325    /// The currently set pipeline, if any.
1326    pipeline: Option<PipelineState>,
1327
1328    /// The state of each vertex buffer slot.
1329    vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS],
1330
1331    /// The current index buffer, if one has been set. We flush this state
1332    /// before indexed draw commands.
1333    index: Option<IndexState>,
1334
1335    /// Dynamic offset values used by the cleaned-up command sequence.
1336    ///
1337    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1338    /// [`dynamic_offsets`] list.
1339    ///
1340    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1341    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1342
1343    device: Arc<Device>,
1344    commands: Vec<ArcRenderCommand>,
1345    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1346    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1347    next_dynamic_offset: usize,
1348    binder: Binder,
1349}
1350
1351impl State {
1352    /// Return the current pipeline state. Return an error if none is set.
1353    fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1354        self.pipeline
1355            .as_ref()
1356            .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into())
1357    }
1358
1359    /// Set the bundle's current index buffer and its associated parameters.
1360    fn set_index_buffer(
1361        &mut self,
1362        buffer: Arc<Buffer>,
1363        format: wgt::IndexFormat,
1364        range: Range<wgt::BufferAddress>,
1365    ) {
1366        match self.index {
1367            Some(ref current)
1368                if current.buffer.is_equal(&buffer)
1369                    && current.format == format
1370                    && current.range == range =>
1371            {
1372                return
1373            }
1374            _ => (),
1375        }
1376
1377        self.index = Some(IndexState {
1378            buffer,
1379            format,
1380            range,
1381            is_dirty: true,
1382        });
1383    }
1384
1385    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1386    /// command, if needed.
1387    fn flush_index(&mut self) {
1388        let commands = self.index.as_mut().and_then(|index| index.flush());
1389        self.commands.extend(commands);
1390    }
1391
1392    fn flush_vertices(&mut self) {
1393        let commands = self
1394            .vertex
1395            .iter_mut()
1396            .enumerate()
1397            .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1398        self.commands.extend(commands);
1399    }
1400
1401    /// Validation for a draw command.
1402    ///
1403    /// This should be further deduplicated with similar validation on render/compute passes.
1404    fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1405        if let Some(pipeline) = self.pipeline.as_ref() {
1406            self.binder
1407                .check_compatibility(pipeline.pipeline.as_ref())?;
1408            self.binder.check_late_buffer_bindings()?;
1409
1410            if family == DrawCommandFamily::DrawIndexed {
1411                let pipeline = &pipeline.pipeline;
1412                let index_format = match &self.index {
1413                    Some(index) => index.format,
1414                    None => return Err(DrawError::MissingIndexBuffer),
1415                };
1416
1417                if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1418                {
1419                    return Err(DrawError::UnmatchedStripIndexFormat {
1420                        pipeline: pipeline.error_ident(),
1421                        strip_index_format: pipeline.strip_index_format,
1422                        buffer_format: index_format,
1423                    });
1424                }
1425            }
1426
1427            Ok(())
1428        } else {
1429            Err(DrawError::MissingPipeline(pass::MissingPipeline))
1430        }
1431    }
1432
1433    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1434    ///
1435    /// This should be further deduplicated with similar code on render/compute passes.
1436    fn flush_bindings(&mut self) {
1437        let start = self.binder.take_rebind_start_index();
1438        let entries = self.binder.list_valid_with_start(start);
1439
1440        self.commands
1441            .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1442                self.buffer_memory_init_actions
1443                    .extend_from_slice(&bind_group.used_buffer_ranges);
1444                self.texture_memory_init_actions
1445                    .extend_from_slice(&bind_group.used_texture_ranges);
1446
1447                self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1448
1449                ArcRenderCommand::SetBindGroup {
1450                    index: i.try_into().unwrap(),
1451                    bind_group: Some(bind_group.clone()),
1452                    num_dynamic_offsets: dynamic_offsets.len(),
1453                }
1454            }));
1455    }
1456
1457    fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ {
1458        self.vertex
1459            .iter()
1460            .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start))
1461    }
1462}
1463
1464/// Error encountered when finishing recording a render bundle.
1465#[derive(Clone, Debug, Error)]
1466pub enum RenderBundleErrorInner {
1467    #[error(transparent)]
1468    Device(#[from] DeviceError),
1469    #[error(transparent)]
1470    RenderCommand(RenderCommandError),
1471    #[error(transparent)]
1472    Draw(#[from] DrawError),
1473    #[error(transparent)]
1474    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1475    #[error(transparent)]
1476    Bind(#[from] BindError),
1477    #[error(transparent)]
1478    InvalidResource(#[from] InvalidResourceError),
1479}
1480
1481impl<T> From<T> for RenderBundleErrorInner
1482where
1483    T: Into<RenderCommandError>,
1484{
1485    fn from(t: T) -> Self {
1486        Self::RenderCommand(t.into())
1487    }
1488}
1489
1490/// Error encountered when finishing recording a render bundle.
1491#[derive(Clone, Debug, Error)]
1492#[error("{scope}")]
1493pub struct RenderBundleError {
1494    pub scope: PassErrorScope,
1495    #[source]
1496    inner: RenderBundleErrorInner,
1497}
1498
1499impl WebGpuError for RenderBundleError {
1500    fn webgpu_error_type(&self) -> ErrorType {
1501        let Self { scope: _, inner } = self;
1502        match inner {
1503            RenderBundleErrorInner::Device(e) => e.webgpu_error_type(),
1504            RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(),
1505            RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(),
1506            RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
1507            RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(),
1508            RenderBundleErrorInner::InvalidResource(e) => e.webgpu_error_type(),
1509        }
1510    }
1511}
1512
1513impl RenderBundleError {
1514    pub fn from_device_error(e: DeviceError) -> Self {
1515        Self {
1516            scope: PassErrorScope::Bundle,
1517            inner: e.into(),
1518        }
1519    }
1520}
1521
1522impl<E> MapPassErr<RenderBundleError> for E
1523where
1524    E: Into<RenderBundleErrorInner>,
1525{
1526    fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1527        RenderBundleError {
1528            scope,
1529            inner: self.into(),
1530        }
1531    }
1532}
1533
1534pub mod bundle_ffi {
1535    use super::{RenderBundleEncoder, RenderCommand};
1536    use crate::{command::DrawCommandFamily, id, RawString};
1537    use core::{convert::TryInto, slice};
1538    use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1539
1540    /// # Safety
1541    ///
1542    /// This function is unsafe as there is no guarantee that the given pointer is
1543    /// valid for `offset_length` elements.
1544    pub unsafe fn wgpu_render_bundle_set_bind_group(
1545        bundle: &mut RenderBundleEncoder,
1546        index: u32,
1547        bind_group_id: Option<id::BindGroupId>,
1548        offsets: *const DynamicOffset,
1549        offset_length: usize,
1550    ) {
1551        let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1552
1553        let redundant = bundle.current_bind_groups.set_and_check_redundant(
1554            bind_group_id,
1555            index,
1556            &mut bundle.base.dynamic_offsets,
1557            offsets,
1558        );
1559
1560        if redundant {
1561            return;
1562        }
1563
1564        bundle.base.commands.push(RenderCommand::SetBindGroup {
1565            index,
1566            num_dynamic_offsets: offset_length,
1567            bind_group: bind_group_id,
1568        });
1569    }
1570
1571    pub fn wgpu_render_bundle_set_pipeline(
1572        bundle: &mut RenderBundleEncoder,
1573        pipeline_id: id::RenderPipelineId,
1574    ) {
1575        if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1576            return;
1577        }
1578
1579        bundle
1580            .base
1581            .commands
1582            .push(RenderCommand::SetPipeline(pipeline_id));
1583    }
1584
1585    pub fn wgpu_render_bundle_set_vertex_buffer(
1586        bundle: &mut RenderBundleEncoder,
1587        slot: u32,
1588        buffer_id: id::BufferId,
1589        offset: BufferAddress,
1590        size: Option<BufferSize>,
1591    ) {
1592        bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1593            slot,
1594            buffer: buffer_id,
1595            offset,
1596            size,
1597        });
1598    }
1599
1600    pub fn wgpu_render_bundle_set_index_buffer(
1601        encoder: &mut RenderBundleEncoder,
1602        buffer: id::BufferId,
1603        index_format: IndexFormat,
1604        offset: BufferAddress,
1605        size: Option<BufferSize>,
1606    ) {
1607        encoder.set_index_buffer(buffer, index_format, offset, size);
1608    }
1609
1610    /// # Safety
1611    ///
1612    /// This function is unsafe as there is no guarantee that the given pointer is
1613    /// valid for `data` elements.
1614    pub unsafe fn wgpu_render_bundle_set_immediates(
1615        pass: &mut RenderBundleEncoder,
1616        offset: u32,
1617        size_bytes: u32,
1618        data: *const u8,
1619    ) {
1620        assert_eq!(
1621            offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1622            0,
1623            "Immediate data offset must be aligned to 4 bytes."
1624        );
1625        assert_eq!(
1626            size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1627            0,
1628            "Immediate data size must be aligned to 4 bytes."
1629        );
1630        let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1631        let value_offset = pass.base.immediates_data.len().try_into().expect(
1632            "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1633        );
1634
1635        pass.base.immediates_data.extend(
1636            data_slice
1637                .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1638                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1639        );
1640
1641        pass.base.commands.push(RenderCommand::SetImmediate {
1642            offset,
1643            size_bytes,
1644            values_offset: Some(value_offset),
1645        });
1646    }
1647
1648    pub fn wgpu_render_bundle_draw(
1649        bundle: &mut RenderBundleEncoder,
1650        vertex_count: u32,
1651        instance_count: u32,
1652        first_vertex: u32,
1653        first_instance: u32,
1654    ) {
1655        bundle.base.commands.push(RenderCommand::Draw {
1656            vertex_count,
1657            instance_count,
1658            first_vertex,
1659            first_instance,
1660        });
1661    }
1662
1663    pub fn wgpu_render_bundle_draw_indexed(
1664        bundle: &mut RenderBundleEncoder,
1665        index_count: u32,
1666        instance_count: u32,
1667        first_index: u32,
1668        base_vertex: i32,
1669        first_instance: u32,
1670    ) {
1671        bundle.base.commands.push(RenderCommand::DrawIndexed {
1672            index_count,
1673            instance_count,
1674            first_index,
1675            base_vertex,
1676            first_instance,
1677        });
1678    }
1679
1680    pub fn wgpu_render_bundle_draw_indirect(
1681        bundle: &mut RenderBundleEncoder,
1682        buffer_id: id::BufferId,
1683        offset: BufferAddress,
1684    ) {
1685        bundle.base.commands.push(RenderCommand::DrawIndirect {
1686            buffer: buffer_id,
1687            offset,
1688            count: 1,
1689            family: DrawCommandFamily::Draw,
1690            vertex_or_index_limit: None,
1691            instance_limit: None,
1692        });
1693    }
1694
1695    pub fn wgpu_render_bundle_draw_indexed_indirect(
1696        bundle: &mut RenderBundleEncoder,
1697        buffer_id: id::BufferId,
1698        offset: BufferAddress,
1699    ) {
1700        bundle.base.commands.push(RenderCommand::DrawIndirect {
1701            buffer: buffer_id,
1702            offset,
1703            count: 1,
1704            family: DrawCommandFamily::DrawIndexed,
1705            vertex_or_index_limit: None,
1706            instance_limit: None,
1707        });
1708    }
1709
1710    /// # Safety
1711    ///
1712    /// This function is unsafe as there is no guarantee that the given `label`
1713    /// is a valid null-terminated string.
1714    pub unsafe fn wgpu_render_bundle_push_debug_group(
1715        _bundle: &mut RenderBundleEncoder,
1716        _label: RawString,
1717    ) {
1718        //TODO
1719    }
1720
1721    pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1722        //TODO
1723    }
1724
1725    /// # Safety
1726    ///
1727    /// This function is unsafe as there is no guarantee that the given `label`
1728    /// is a valid null-terminated string.
1729    pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1730        _bundle: &mut RenderBundleEncoder,
1731        _label: RawString,
1732    ) {
1733        //TODO
1734    }
1735}