wgpu_core/command/
bundle.rs

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