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    if bind_group_id.is_none() {
526        // TODO: do appropriate cleanup for null bind_group.
527        return Ok(());
528    }
529
530    let bind_group_id = bind_group_id.unwrap();
531
532    let bind_group = bind_group_guard.get(bind_group_id).get()?;
533
534    bind_group.same_device(&state.device)?;
535
536    let max_bind_groups = state.device.limits.max_bind_groups;
537    if index >= max_bind_groups {
538        return Err(
539            RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
540                index,
541                max: max_bind_groups,
542            })
543            .into(),
544        );
545    }
546
547    // Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`.
548    let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
549    state.next_dynamic_offset = offsets_range.end;
550    let offsets = &dynamic_offsets[offsets_range.clone()];
551
552    bind_group.validate_dynamic_bindings(index, offsets)?;
553
554    unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
555    let bind_group = state.trackers.bind_groups.insert_single(bind_group);
556
557    state
558        .binder
559        .assign_group(index as usize, bind_group, offsets);
560
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 immediates, zero out their values.
594    if let Some(cmd) = pipeline_state.zero_immediates() {
595        state.commands.push(cmd);
596    }
597
598    state.pipeline = Some(pipeline_state);
599
600    state
601        .binder
602        .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups);
603
604    state.trackers.render_pipelines.insert_single(pipeline);
605    Ok(())
606}
607
608// This function is duplicative of `render::set_index_buffer`.
609fn set_index_buffer(
610    state: &mut State,
611    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
612    buffer_id: id::Id<id::markers::Buffer>,
613    index_format: wgt::IndexFormat,
614    offset: u64,
615    size: Option<NonZeroU64>,
616) -> Result<(), RenderBundleErrorInner> {
617    let buffer = buffer_guard.get(buffer_id).get()?;
618
619    state
620        .trackers
621        .buffers
622        .merge_single(&buffer, wgt::BufferUses::INDEX)?;
623
624    buffer.same_device(&state.device)?;
625    buffer.check_usage(wgt::BufferUsages::INDEX)?;
626
627    if offset % u64::try_from(index_format.byte_size()).unwrap() != 0 {
628        return Err(RenderCommandError::UnalignedIndexBuffer {
629            offset,
630            alignment: index_format.byte_size(),
631        }
632        .into());
633    }
634    let end = offset + buffer.resolve_binding_size(offset, size)?;
635
636    state
637        .buffer_memory_init_actions
638        .extend(buffer.initialization_status.read().create_action(
639            &buffer,
640            offset..end.get(),
641            MemoryInitKind::NeedsInitializedMemory,
642        ));
643    state.set_index_buffer(buffer, index_format, offset..end.get());
644    Ok(())
645}
646
647// This function is duplicative of `render::set_vertex_buffer`.
648fn set_vertex_buffer(
649    state: &mut State,
650    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
651    slot: u32,
652    buffer_id: id::Id<id::markers::Buffer>,
653    offset: u64,
654    size: Option<NonZeroU64>,
655) -> Result<(), RenderBundleErrorInner> {
656    let max_vertex_buffers = state.device.limits.max_vertex_buffers;
657    if slot >= max_vertex_buffers {
658        return Err(RenderCommandError::VertexBufferIndexOutOfRange {
659            index: slot,
660            max: max_vertex_buffers,
661        }
662        .into());
663    }
664
665    let buffer = buffer_guard.get(buffer_id).get()?;
666
667    state
668        .trackers
669        .buffers
670        .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
671
672    buffer.same_device(&state.device)?;
673    buffer.check_usage(wgt::BufferUsages::VERTEX)?;
674
675    if offset % wgt::VERTEX_ALIGNMENT != 0 {
676        return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
677    }
678    let end = offset + buffer.resolve_binding_size(offset, size)?;
679
680    state
681        .buffer_memory_init_actions
682        .extend(buffer.initialization_status.read().create_action(
683            &buffer,
684            offset..end.get(),
685            MemoryInitKind::NeedsInitializedMemory,
686        ));
687    state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get()));
688    Ok(())
689}
690
691fn set_immediates(
692    state: &mut State,
693    offset: u32,
694    size_bytes: u32,
695    values_offset: Option<u32>,
696) -> Result<(), RenderBundleErrorInner> {
697    let end_offset = offset + size_bytes;
698
699    let pipeline_state = state.pipeline()?;
700
701    pipeline_state
702        .pipeline
703        .layout
704        .validate_immediates_ranges(offset, end_offset)?;
705
706    state.commands.push(ArcRenderCommand::SetImmediate {
707        offset,
708        size_bytes,
709        values_offset,
710    });
711    Ok(())
712}
713
714fn draw(
715    state: &mut State,
716    vertex_count: u32,
717    instance_count: u32,
718    first_vertex: u32,
719    first_instance: u32,
720) -> Result<(), RenderBundleErrorInner> {
721    state.is_ready()?;
722    let pipeline = state.pipeline()?;
723
724    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
725    vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?;
726    vertex_limits.validate_instance_limit(first_instance, instance_count)?;
727
728    if instance_count > 0 && vertex_count > 0 {
729        state.flush_vertices();
730        state.flush_bindings();
731        state.commands.push(ArcRenderCommand::Draw {
732            vertex_count,
733            instance_count,
734            first_vertex,
735            first_instance,
736        });
737    }
738    Ok(())
739}
740
741fn draw_indexed(
742    state: &mut State,
743    index_count: u32,
744    instance_count: u32,
745    first_index: u32,
746    base_vertex: i32,
747    first_instance: u32,
748) -> Result<(), RenderBundleErrorInner> {
749    state.is_ready()?;
750    let pipeline = state.pipeline()?;
751
752    let index = match state.index {
753        Some(ref index) => index,
754        None => return Err(DrawError::MissingIndexBuffer.into()),
755    };
756
757    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
758
759    let last_index = first_index as u64 + index_count as u64;
760    let index_limit = index.limit();
761    if last_index > index_limit {
762        return Err(DrawError::IndexBeyondLimit {
763            last_index,
764            index_limit,
765        }
766        .into());
767    }
768    vertex_limits.validate_instance_limit(first_instance, instance_count)?;
769
770    if instance_count > 0 && index_count > 0 {
771        state.flush_index();
772        state.flush_vertices();
773        state.flush_bindings();
774        state.commands.push(ArcRenderCommand::DrawIndexed {
775            index_count,
776            instance_count,
777            first_index,
778            base_vertex,
779            first_instance,
780        });
781    }
782    Ok(())
783}
784
785fn draw_mesh_tasks(
786    state: &mut State,
787    group_count_x: u32,
788    group_count_y: u32,
789    group_count_z: u32,
790) -> Result<(), RenderBundleErrorInner> {
791    state.is_ready()?;
792
793    let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension;
794    let max_groups = state.device.limits.max_task_mesh_workgroup_total_count;
795    if group_count_x > groups_size_limit
796        || group_count_y > groups_size_limit
797        || group_count_z > groups_size_limit
798        || group_count_x * group_count_y * group_count_z > max_groups
799    {
800        return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize {
801            current: [group_count_x, group_count_y, group_count_z],
802            limit: groups_size_limit,
803            max_total: max_groups,
804        }));
805    }
806
807    if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 {
808        state.flush_bindings();
809        state.commands.push(ArcRenderCommand::DrawMeshTasks {
810            group_count_x,
811            group_count_y,
812            group_count_z,
813        });
814    }
815    Ok(())
816}
817
818fn multi_draw_indirect(
819    state: &mut State,
820    buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
821    buffer_id: id::Id<id::markers::Buffer>,
822    offset: u64,
823    family: DrawCommandFamily,
824) -> Result<(), RenderBundleErrorInner> {
825    state.is_ready()?;
826    state
827        .device
828        .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
829
830    let pipeline = state.pipeline()?;
831
832    let buffer = buffer_guard.get(buffer_id).get()?;
833
834    buffer.same_device(&state.device)?;
835    buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
836
837    let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
838
839    let stride = super::get_stride_of_indirect_args(family);
840    state
841        .buffer_memory_init_actions
842        .extend(buffer.initialization_status.read().create_action(
843            &buffer,
844            offset..(offset + stride),
845            MemoryInitKind::NeedsInitializedMemory,
846        ));
847
848    let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
849        let index = match state.index {
850            Some(ref mut index) => index,
851            None => return Err(DrawError::MissingIndexBuffer.into()),
852        };
853        state.commands.extend(index.flush());
854        index.limit()
855    } else {
856        vertex_limits.vertex_limit
857    };
858    let instance_limit = vertex_limits.instance_limit;
859
860    let buffer_uses = if state.device.indirect_validation.is_some() {
861        wgt::BufferUses::STORAGE_READ_ONLY
862    } else {
863        wgt::BufferUses::INDIRECT
864    };
865
866    state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
867
868    state.flush_vertices();
869    state.flush_bindings();
870    state.commands.push(ArcRenderCommand::DrawIndirect {
871        buffer,
872        offset,
873        count: 1,
874        family,
875
876        vertex_or_index_limit: Some(vertex_or_index_limit),
877        instance_limit: Some(instance_limit),
878    });
879    Ok(())
880}
881
882/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
883#[derive(Clone, Debug, Error)]
884#[non_exhaustive]
885pub enum CreateRenderBundleError {
886    #[error(transparent)]
887    ColorAttachment(#[from] ColorAttachmentError),
888    #[error("Invalid number of samples {0}")]
889    InvalidSampleCount(u32),
890}
891
892impl WebGpuError for CreateRenderBundleError {
893    fn webgpu_error_type(&self) -> ErrorType {
894        match self {
895            Self::ColorAttachment(e) => e.webgpu_error_type(),
896            Self::InvalidSampleCount(_) => ErrorType::Validation,
897        }
898    }
899}
900
901/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
902#[derive(Clone, Debug, Error)]
903#[non_exhaustive]
904pub enum ExecutionError {
905    #[error(transparent)]
906    Device(#[from] DeviceError),
907    #[error(transparent)]
908    DestroyedResource(#[from] DestroyedResourceError),
909    #[error("Using {0} in a render bundle is not implemented")]
910    Unimplemented(&'static str),
911}
912
913pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
914
915//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
916// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
917// or Metal indirect command buffer.
918/// cbindgen:ignore
919#[derive(Debug)]
920pub struct RenderBundle {
921    // Normalized command stream. It can be executed verbatim,
922    // without re-binding anything on the pipeline change.
923    base: BasePass<ArcRenderCommand, Infallible>,
924    pub(super) is_depth_read_only: bool,
925    pub(super) is_stencil_read_only: bool,
926    pub(crate) device: Arc<Device>,
927    pub(crate) used: RenderBundleScope,
928    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
929    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
930    pub(super) context: RenderPassContext,
931    /// The `label` from the descriptor used to create the resource.
932    label: String,
933    pub(crate) tracking_data: TrackingData,
934    discard_hal_labels: bool,
935}
936
937impl Drop for RenderBundle {
938    fn drop(&mut self) {
939        resource_log!("Drop {}", self.error_ident());
940    }
941}
942
943#[cfg(send_sync)]
944unsafe impl Send for RenderBundle {}
945#[cfg(send_sync)]
946unsafe impl Sync for RenderBundle {}
947
948impl RenderBundle {
949    #[cfg(feature = "trace")]
950    pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
951        self.base.clone()
952    }
953
954    /// Actually encode the contents into a native command buffer.
955    ///
956    /// This is partially duplicating the logic of `render_pass_end`.
957    /// However the point of this function is to be lighter, since we already had
958    /// a chance to go through the commands in `render_bundle_encoder_finish`.
959    ///
960    /// Note that the function isn't expected to fail, generally.
961    /// All the validation has already been done by this point.
962    /// The only failure condition is if some of the used buffers are destroyed.
963    pub(super) unsafe fn execute(
964        &self,
965        raw: &mut dyn hal::DynCommandEncoder,
966        indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
967        indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
968        snatch_guard: &SnatchGuard,
969    ) -> Result<(), ExecutionError> {
970        let mut offsets = self.base.dynamic_offsets.as_slice();
971        let mut pipeline_layout = None::<Arc<PipelineLayout>>;
972        if !self.discard_hal_labels {
973            if let Some(ref label) = self.base.label {
974                unsafe { raw.begin_debug_marker(label) };
975            }
976        }
977
978        use ArcRenderCommand as Cmd;
979        for command in self.base.commands.iter() {
980            match command {
981                Cmd::SetBindGroup {
982                    index,
983                    num_dynamic_offsets,
984                    bind_group,
985                } => {
986                    let mut bg = None;
987                    if bind_group.is_some() {
988                        let bind_group = bind_group.as_ref().unwrap();
989                        let raw_bg = bind_group.try_raw(snatch_guard)?;
990                        bg = Some(raw_bg);
991                    }
992                    unsafe {
993                        raw.set_bind_group(
994                            pipeline_layout.as_ref().unwrap().raw(),
995                            *index,
996                            bg,
997                            &offsets[..*num_dynamic_offsets],
998                        )
999                    };
1000                    offsets = &offsets[*num_dynamic_offsets..];
1001                }
1002                Cmd::SetPipeline(pipeline) => {
1003                    unsafe { raw.set_render_pipeline(pipeline.raw()) };
1004
1005                    pipeline_layout = Some(pipeline.layout.clone());
1006                }
1007                Cmd::SetIndexBuffer {
1008                    buffer,
1009                    index_format,
1010                    offset,
1011                    size,
1012                } => {
1013                    let buffer = buffer.try_raw(snatch_guard)?;
1014                    // SAFETY: The binding size was checked against the buffer size
1015                    // in `set_index_buffer` and again in `IndexState::flush`.
1016                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1017                    unsafe { raw.set_index_buffer(bb, *index_format) };
1018                }
1019                Cmd::SetVertexBuffer {
1020                    slot,
1021                    buffer,
1022                    offset,
1023                    size,
1024                } => {
1025                    let buffer = buffer.try_raw(snatch_guard)?;
1026                    // SAFETY: The binding size was checked against the buffer size
1027                    // in `set_vertex_buffer` and again in `VertexState::flush`.
1028                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1029                    unsafe { raw.set_vertex_buffer(*slot, bb) };
1030                }
1031                Cmd::SetImmediate {
1032                    offset,
1033                    size_bytes,
1034                    values_offset,
1035                } => {
1036                    let pipeline_layout = pipeline_layout.as_ref().unwrap();
1037
1038                    if let Some(values_offset) = *values_offset {
1039                        let values_end_offset =
1040                            (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1041                        let data_slice =
1042                            &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1043
1044                        unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1045                    } else {
1046                        super::immediates_clear(
1047                            *offset,
1048                            *size_bytes,
1049                            |clear_offset, clear_data| {
1050                                unsafe {
1051                                    raw.set_immediates(
1052                                        pipeline_layout.raw(),
1053                                        clear_offset,
1054                                        clear_data,
1055                                    )
1056                                };
1057                            },
1058                        );
1059                    }
1060                }
1061                Cmd::Draw {
1062                    vertex_count,
1063                    instance_count,
1064                    first_vertex,
1065                    first_instance,
1066                } => {
1067                    unsafe {
1068                        raw.draw(
1069                            *first_vertex,
1070                            *vertex_count,
1071                            *first_instance,
1072                            *instance_count,
1073                        )
1074                    };
1075                }
1076                Cmd::DrawIndexed {
1077                    index_count,
1078                    instance_count,
1079                    first_index,
1080                    base_vertex,
1081                    first_instance,
1082                } => {
1083                    unsafe {
1084                        raw.draw_indexed(
1085                            *first_index,
1086                            *index_count,
1087                            *base_vertex,
1088                            *first_instance,
1089                            *instance_count,
1090                        )
1091                    };
1092                }
1093                Cmd::DrawMeshTasks {
1094                    group_count_x,
1095                    group_count_y,
1096                    group_count_z,
1097                } => unsafe {
1098                    raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1099                },
1100                Cmd::DrawIndirect {
1101                    buffer,
1102                    offset,
1103                    count: 1,
1104                    family,
1105
1106                    vertex_or_index_limit,
1107                    instance_limit,
1108                } => {
1109                    let (buffer, offset) = if self.device.indirect_validation.is_some() {
1110                        let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1111                            indirect_draw_validation_resources,
1112                            &self.device,
1113                            buffer,
1114                            *offset,
1115                            *family,
1116                            vertex_or_index_limit
1117                                .expect("finalized render bundle missing vertex_or_index_limit"),
1118                            instance_limit.expect("finalized render bundle missing instance_limit"),
1119                        )?;
1120
1121                        let dst_buffer =
1122                            indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1123                        (dst_buffer, offset)
1124                    } else {
1125                        (buffer.try_raw(snatch_guard)?, *offset)
1126                    };
1127                    match family {
1128                        DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1129                        DrawCommandFamily::DrawIndexed => unsafe {
1130                            raw.draw_indexed_indirect(buffer, offset, 1)
1131                        },
1132                        DrawCommandFamily::DrawMeshTasks => unsafe {
1133                            raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1134                        },
1135                    }
1136                }
1137                Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1138                    return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1139                }
1140                Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1141                    return Err(ExecutionError::Unimplemented("debug-markers"))
1142                }
1143                Cmd::WriteTimestamp { .. }
1144                | Cmd::BeginOcclusionQuery { .. }
1145                | Cmd::EndOcclusionQuery
1146                | Cmd::BeginPipelineStatisticsQuery { .. }
1147                | Cmd::EndPipelineStatisticsQuery => {
1148                    return Err(ExecutionError::Unimplemented("queries"))
1149                }
1150                Cmd::ExecuteBundle(_)
1151                | Cmd::SetBlendConstant(_)
1152                | Cmd::SetStencilReference(_)
1153                | Cmd::SetViewport { .. }
1154                | Cmd::SetScissor(_) => unreachable!(),
1155            }
1156        }
1157
1158        if !self.discard_hal_labels {
1159            if let Some(_) = self.base.label {
1160                unsafe { raw.end_debug_marker() };
1161            }
1162        }
1163
1164        Ok(())
1165    }
1166}
1167
1168crate::impl_resource_type!(RenderBundle);
1169crate::impl_labeled!(RenderBundle);
1170crate::impl_parent_device!(RenderBundle);
1171crate::impl_storage_item!(RenderBundle);
1172crate::impl_trackable!(RenderBundle);
1173
1174/// A render bundle's current index buffer state.
1175///
1176/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
1177/// and calls [`State::flush_index`] before any indexed draw command to produce
1178/// a `SetIndexBuffer` command if one is necessary.
1179///
1180/// Binding ranges must be validated against the size of the buffer before
1181/// being stored in `IndexState`.
1182#[derive(Debug)]
1183struct IndexState {
1184    buffer: Arc<Buffer>,
1185    format: wgt::IndexFormat,
1186    range: Range<wgt::BufferAddress>,
1187    is_dirty: bool,
1188}
1189
1190impl IndexState {
1191    /// Return the number of entries in the current index buffer.
1192    ///
1193    /// Panic if no index buffer has been set.
1194    fn limit(&self) -> u64 {
1195        let bytes_per_index = self.format.byte_size() as u64;
1196
1197        (self.range.end - self.range.start) / bytes_per_index
1198    }
1199
1200    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1201    /// command, if needed.
1202    fn flush(&mut self) -> Option<ArcRenderCommand> {
1203        // This was all checked before, but let's check again just in case.
1204        let binding_size = self
1205            .range
1206            .end
1207            .checked_sub(self.range.start)
1208            .filter(|_| self.range.end <= self.buffer.size)
1209            .expect("index range must be contained in buffer");
1210
1211        if self.is_dirty {
1212            self.is_dirty = false;
1213            Some(ArcRenderCommand::SetIndexBuffer {
1214                buffer: self.buffer.clone(),
1215                index_format: self.format,
1216                offset: self.range.start,
1217                size: NonZeroU64::new(binding_size),
1218            })
1219        } else {
1220            None
1221        }
1222    }
1223}
1224
1225/// The state of a single vertex buffer slot during render bundle encoding.
1226///
1227/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1228/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1229/// records one vertex buffer slot's state changes here, and then
1230/// calls this type's [`flush`] method just before any draw command to
1231/// produce a `SetVertexBuffer` commands if one is necessary.
1232///
1233/// Binding ranges must be validated against the size of the buffer before
1234/// being stored in `VertexState`.
1235///
1236/// [`flush`]: IndexState::flush
1237#[derive(Debug)]
1238struct VertexState {
1239    buffer: Arc<Buffer>,
1240    range: Range<wgt::BufferAddress>,
1241    is_dirty: bool,
1242}
1243
1244impl VertexState {
1245    /// Create a new `VertexState`.
1246    ///
1247    /// The `range` must be contained within `buffer`.
1248    fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1249        Self {
1250            buffer,
1251            range,
1252            is_dirty: true,
1253        }
1254    }
1255
1256    /// Generate a `SetVertexBuffer` command for this slot, if necessary.
1257    ///
1258    /// `slot` is the index of the vertex buffer slot that `self` tracks.
1259    fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1260        let binding_size = self
1261            .range
1262            .end
1263            .checked_sub(self.range.start)
1264            .filter(|_| self.range.end <= self.buffer.size)
1265            .expect("vertex range must be contained in buffer");
1266
1267        if self.is_dirty {
1268            self.is_dirty = false;
1269            Some(ArcRenderCommand::SetVertexBuffer {
1270                slot,
1271                buffer: self.buffer.clone(),
1272                offset: self.range.start,
1273                size: NonZeroU64::new(binding_size),
1274            })
1275        } else {
1276            None
1277        }
1278    }
1279}
1280
1281/// The bundle's current pipeline, and some cached information needed for validation.
1282struct PipelineState {
1283    /// The pipeline
1284    pipeline: Arc<RenderPipeline>,
1285
1286    /// How this pipeline's vertex shader traverses each vertex buffer, indexed
1287    /// by vertex buffer slot number.
1288    steps: Vec<VertexStep>,
1289
1290    /// Size of the immediate data ranges this pipeline uses. Copied from the pipeline layout.
1291    immediate_size: u32,
1292}
1293
1294impl PipelineState {
1295    fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1296        Self {
1297            pipeline: pipeline.clone(),
1298            steps: pipeline.vertex_steps.to_vec(),
1299            immediate_size: pipeline.layout.immediate_size,
1300        }
1301    }
1302
1303    /// Return a sequence of commands to zero the immediate data ranges this
1304    /// pipeline uses. If no initialization is necessary, return `None`.
1305    fn zero_immediates(&self) -> Option<ArcRenderCommand> {
1306        if self.immediate_size == 0 {
1307            return None;
1308        }
1309
1310        Some(ArcRenderCommand::SetImmediate {
1311            offset: 0,
1312            size_bytes: self.immediate_size,
1313            values_offset: None,
1314        })
1315    }
1316}
1317
1318/// State for analyzing and cleaning up bundle command streams.
1319///
1320/// To minimize state updates, [`RenderBundleEncoder::finish`]
1321/// actually just applies commands like [`SetBindGroup`] and
1322/// [`SetIndexBuffer`] to the simulated state stored here, and then
1323/// calls the `flush_foo` methods before draw calls to produce the
1324/// update commands we actually need.
1325///
1326/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1327/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1328struct State {
1329    /// Resources used by this bundle. This will become [`RenderBundle::used`].
1330    trackers: RenderBundleScope,
1331
1332    /// The currently set pipeline, if any.
1333    pipeline: Option<PipelineState>,
1334
1335    /// The state of each vertex buffer slot.
1336    vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS],
1337
1338    /// The current index buffer, if one has been set. We flush this state
1339    /// before indexed draw commands.
1340    index: Option<IndexState>,
1341
1342    /// Dynamic offset values used by the cleaned-up command sequence.
1343    ///
1344    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1345    /// [`dynamic_offsets`] list.
1346    ///
1347    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1348    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1349
1350    device: Arc<Device>,
1351    commands: Vec<ArcRenderCommand>,
1352    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1353    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1354    next_dynamic_offset: usize,
1355    binder: Binder,
1356}
1357
1358impl State {
1359    /// Return the current pipeline state. Return an error if none is set.
1360    fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1361        self.pipeline
1362            .as_ref()
1363            .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into())
1364    }
1365
1366    /// Set the bundle's current index buffer and its associated parameters.
1367    fn set_index_buffer(
1368        &mut self,
1369        buffer: Arc<Buffer>,
1370        format: wgt::IndexFormat,
1371        range: Range<wgt::BufferAddress>,
1372    ) {
1373        match self.index {
1374            Some(ref current)
1375                if current.buffer.is_equal(&buffer)
1376                    && current.format == format
1377                    && current.range == range =>
1378            {
1379                return
1380            }
1381            _ => (),
1382        }
1383
1384        self.index = Some(IndexState {
1385            buffer,
1386            format,
1387            range,
1388            is_dirty: true,
1389        });
1390    }
1391
1392    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1393    /// command, if needed.
1394    fn flush_index(&mut self) {
1395        let commands = self.index.as_mut().and_then(|index| index.flush());
1396        self.commands.extend(commands);
1397    }
1398
1399    fn flush_vertices(&mut self) {
1400        let commands = self
1401            .vertex
1402            .iter_mut()
1403            .enumerate()
1404            .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1405        self.commands.extend(commands);
1406    }
1407
1408    /// Validation for a draw command.
1409    ///
1410    /// This should be further deduplicated with similar validation on render/compute passes.
1411    fn is_ready(&mut self) -> Result<(), DrawError> {
1412        if let Some(pipeline) = self.pipeline.as_ref() {
1413            self.binder
1414                .check_compatibility(pipeline.pipeline.as_ref())?;
1415            self.binder.check_late_buffer_bindings()?;
1416            Ok(())
1417        } else {
1418            Err(DrawError::MissingPipeline(pass::MissingPipeline))
1419        }
1420    }
1421
1422    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1423    ///
1424    /// This should be further deduplicated with similar code on render/compute passes.
1425    fn flush_bindings(&mut self) {
1426        let range = self.binder.take_rebind_range();
1427        let entries = self.binder.entries(range);
1428
1429        self.commands.extend(entries.map(|(i, entry)| {
1430            let bind_group = entry.group.as_ref().unwrap();
1431
1432            self.buffer_memory_init_actions
1433                .extend_from_slice(&bind_group.used_buffer_ranges);
1434            self.texture_memory_init_actions
1435                .extend_from_slice(&bind_group.used_texture_ranges);
1436
1437            self.flat_dynamic_offsets
1438                .extend_from_slice(&entry.dynamic_offsets);
1439
1440            ArcRenderCommand::SetBindGroup {
1441                index: i.try_into().unwrap(),
1442                bind_group: Some(bind_group.clone()),
1443                num_dynamic_offsets: entry.dynamic_offsets.len(),
1444            }
1445        }));
1446    }
1447
1448    fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ {
1449        self.vertex
1450            .iter()
1451            .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start))
1452    }
1453}
1454
1455/// Error encountered when finishing recording a render bundle.
1456#[derive(Clone, Debug, Error)]
1457pub enum RenderBundleErrorInner {
1458    #[error(transparent)]
1459    Device(#[from] DeviceError),
1460    #[error(transparent)]
1461    RenderCommand(RenderCommandError),
1462    #[error(transparent)]
1463    Draw(#[from] DrawError),
1464    #[error(transparent)]
1465    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1466    #[error(transparent)]
1467    Bind(#[from] BindError),
1468    #[error(transparent)]
1469    InvalidResource(#[from] InvalidResourceError),
1470}
1471
1472impl<T> From<T> for RenderBundleErrorInner
1473where
1474    T: Into<RenderCommandError>,
1475{
1476    fn from(t: T) -> Self {
1477        Self::RenderCommand(t.into())
1478    }
1479}
1480
1481/// Error encountered when finishing recording a render bundle.
1482#[derive(Clone, Debug, Error)]
1483#[error("{scope}")]
1484pub struct RenderBundleError {
1485    pub scope: PassErrorScope,
1486    #[source]
1487    inner: RenderBundleErrorInner,
1488}
1489
1490impl WebGpuError for RenderBundleError {
1491    fn webgpu_error_type(&self) -> ErrorType {
1492        let Self { scope: _, inner } = self;
1493        let e: &dyn WebGpuError = match inner {
1494            RenderBundleErrorInner::Device(e) => e,
1495            RenderBundleErrorInner::RenderCommand(e) => e,
1496            RenderBundleErrorInner::Draw(e) => e,
1497            RenderBundleErrorInner::MissingDownlevelFlags(e) => e,
1498            RenderBundleErrorInner::Bind(e) => e,
1499            RenderBundleErrorInner::InvalidResource(e) => e,
1500        };
1501        e.webgpu_error_type()
1502    }
1503}
1504
1505impl RenderBundleError {
1506    pub fn from_device_error(e: DeviceError) -> Self {
1507        Self {
1508            scope: PassErrorScope::Bundle,
1509            inner: e.into(),
1510        }
1511    }
1512}
1513
1514impl<E> MapPassErr<RenderBundleError> for E
1515where
1516    E: Into<RenderBundleErrorInner>,
1517{
1518    fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1519        RenderBundleError {
1520            scope,
1521            inner: self.into(),
1522        }
1523    }
1524}
1525
1526pub mod bundle_ffi {
1527    use super::{RenderBundleEncoder, RenderCommand};
1528    use crate::{command::DrawCommandFamily, id, RawString};
1529    use core::{convert::TryInto, slice};
1530    use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1531
1532    /// # Safety
1533    ///
1534    /// This function is unsafe as there is no guarantee that the given pointer is
1535    /// valid for `offset_length` elements.
1536    pub unsafe fn wgpu_render_bundle_set_bind_group(
1537        bundle: &mut RenderBundleEncoder,
1538        index: u32,
1539        bind_group_id: Option<id::BindGroupId>,
1540        offsets: *const DynamicOffset,
1541        offset_length: usize,
1542    ) {
1543        let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1544
1545        let redundant = bundle.current_bind_groups.set_and_check_redundant(
1546            bind_group_id,
1547            index,
1548            &mut bundle.base.dynamic_offsets,
1549            offsets,
1550        );
1551
1552        if redundant {
1553            return;
1554        }
1555
1556        bundle.base.commands.push(RenderCommand::SetBindGroup {
1557            index,
1558            num_dynamic_offsets: offset_length,
1559            bind_group: bind_group_id,
1560        });
1561    }
1562
1563    pub fn wgpu_render_bundle_set_pipeline(
1564        bundle: &mut RenderBundleEncoder,
1565        pipeline_id: id::RenderPipelineId,
1566    ) {
1567        if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1568            return;
1569        }
1570
1571        bundle
1572            .base
1573            .commands
1574            .push(RenderCommand::SetPipeline(pipeline_id));
1575    }
1576
1577    pub fn wgpu_render_bundle_set_vertex_buffer(
1578        bundle: &mut RenderBundleEncoder,
1579        slot: u32,
1580        buffer_id: id::BufferId,
1581        offset: BufferAddress,
1582        size: Option<BufferSize>,
1583    ) {
1584        bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1585            slot,
1586            buffer: buffer_id,
1587            offset,
1588            size,
1589        });
1590    }
1591
1592    pub fn wgpu_render_bundle_set_index_buffer(
1593        encoder: &mut RenderBundleEncoder,
1594        buffer: id::BufferId,
1595        index_format: IndexFormat,
1596        offset: BufferAddress,
1597        size: Option<BufferSize>,
1598    ) {
1599        encoder.set_index_buffer(buffer, index_format, offset, size);
1600    }
1601
1602    /// # Safety
1603    ///
1604    /// This function is unsafe as there is no guarantee that the given pointer is
1605    /// valid for `data` elements.
1606    pub unsafe fn wgpu_render_bundle_set_immediates(
1607        pass: &mut RenderBundleEncoder,
1608        offset: u32,
1609        size_bytes: u32,
1610        data: *const u8,
1611    ) {
1612        assert_eq!(
1613            offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1614            0,
1615            "Immediate data offset must be aligned to 4 bytes."
1616        );
1617        assert_eq!(
1618            size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1619            0,
1620            "Immediate data size must be aligned to 4 bytes."
1621        );
1622        let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1623        let value_offset = pass.base.immediates_data.len().try_into().expect(
1624            "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1625        );
1626
1627        pass.base.immediates_data.extend(
1628            data_slice
1629                .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1630                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1631        );
1632
1633        pass.base.commands.push(RenderCommand::SetImmediate {
1634            offset,
1635            size_bytes,
1636            values_offset: Some(value_offset),
1637        });
1638    }
1639
1640    pub fn wgpu_render_bundle_draw(
1641        bundle: &mut RenderBundleEncoder,
1642        vertex_count: u32,
1643        instance_count: u32,
1644        first_vertex: u32,
1645        first_instance: u32,
1646    ) {
1647        bundle.base.commands.push(RenderCommand::Draw {
1648            vertex_count,
1649            instance_count,
1650            first_vertex,
1651            first_instance,
1652        });
1653    }
1654
1655    pub fn wgpu_render_bundle_draw_indexed(
1656        bundle: &mut RenderBundleEncoder,
1657        index_count: u32,
1658        instance_count: u32,
1659        first_index: u32,
1660        base_vertex: i32,
1661        first_instance: u32,
1662    ) {
1663        bundle.base.commands.push(RenderCommand::DrawIndexed {
1664            index_count,
1665            instance_count,
1666            first_index,
1667            base_vertex,
1668            first_instance,
1669        });
1670    }
1671
1672    pub fn wgpu_render_bundle_draw_indirect(
1673        bundle: &mut RenderBundleEncoder,
1674        buffer_id: id::BufferId,
1675        offset: BufferAddress,
1676    ) {
1677        bundle.base.commands.push(RenderCommand::DrawIndirect {
1678            buffer: buffer_id,
1679            offset,
1680            count: 1,
1681            family: DrawCommandFamily::Draw,
1682            vertex_or_index_limit: None,
1683            instance_limit: None,
1684        });
1685    }
1686
1687    pub fn wgpu_render_bundle_draw_indexed_indirect(
1688        bundle: &mut RenderBundleEncoder,
1689        buffer_id: id::BufferId,
1690        offset: BufferAddress,
1691    ) {
1692        bundle.base.commands.push(RenderCommand::DrawIndirect {
1693            buffer: buffer_id,
1694            offset,
1695            count: 1,
1696            family: DrawCommandFamily::DrawIndexed,
1697            vertex_or_index_limit: None,
1698            instance_limit: None,
1699        });
1700    }
1701
1702    /// # Safety
1703    ///
1704    /// This function is unsafe as there is no guarantee that the given `label`
1705    /// is a valid null-terminated string.
1706    pub unsafe fn wgpu_render_bundle_push_debug_group(
1707        _bundle: &mut RenderBundleEncoder,
1708        _label: RawString,
1709    ) {
1710        //TODO
1711    }
1712
1713    pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1714        //TODO
1715    }
1716
1717    /// # Safety
1718    ///
1719    /// This function is unsafe as there is no guarantee that the given `label`
1720    /// is a valid null-terminated string.
1721    pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1722        _bundle: &mut RenderBundleEncoder,
1723        _label: RawString,
1724    ) {
1725        //TODO
1726    }
1727}