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