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