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::try_from(index_format.byte_size()).unwrap()) {
701        return Err(RenderCommandError::UnalignedIndexBuffer {
702            offset,
703            alignment: index_format.byte_size(),
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        wgt::BufferUses::STORAGE_READ_ONLY
973    } else {
974        wgt::BufferUses::INDIRECT
975    };
976
977    state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
978
979    state.flush_vertex_buffers();
980    state.flush_bindings();
981    state.commands.push(ArcRenderCommand::DrawIndirect {
982        buffer,
983        offset,
984        count: 1,
985        family,
986
987        vertex_or_index_limit: Some(vertex_or_index_limit),
988        instance_limit: Some(instance_limit),
989    });
990    Ok(())
991}
992
993/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
994#[derive(Clone, Debug, Error)]
995#[non_exhaustive]
996pub enum CreateRenderBundleError {
997    #[error(transparent)]
998    ColorAttachment(#[from] ColorAttachmentError),
999    #[error("Format {0:?} does not have a color aspect")]
1000    FormatNotColor(wgt::TextureFormat),
1001    #[error("Color attachment format {0:?} is not renderable")]
1002    FormatNotRenderable(wgt::TextureFormat),
1003    #[error("Format {0:?} is not a depth/stencil format")]
1004    FormatNotDepthOrStencil(wgt::TextureFormat),
1005    #[error("Render bundle must have at least one attachment (color or depth/stencil)")]
1006    NoAttachment,
1007    #[error("Invalid number of samples {0}")]
1008    InvalidSampleCount(u32),
1009    #[error(transparent)]
1010    MissingFeatures(#[from] MissingFeatures),
1011}
1012
1013impl WebGpuError for CreateRenderBundleError {
1014    fn webgpu_error_type(&self) -> ErrorType {
1015        match self {
1016            Self::ColorAttachment(e) => e.webgpu_error_type(),
1017            Self::FormatNotColor(_)
1018            | Self::FormatNotRenderable(_)
1019            | Self::FormatNotDepthOrStencil(_)
1020            | Self::NoAttachment
1021            | Self::InvalidSampleCount(_) => ErrorType::Validation,
1022            Self::MissingFeatures(e) => e.webgpu_error_type(),
1023        }
1024    }
1025}
1026
1027/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
1028#[derive(Clone, Debug, Error)]
1029#[non_exhaustive]
1030pub enum ExecutionError {
1031    #[error(transparent)]
1032    Device(#[from] DeviceError),
1033    #[error(transparent)]
1034    DestroyedResource(#[from] DestroyedResourceError),
1035    #[error("Using {0} in a render bundle is not implemented")]
1036    Unimplemented(&'static str),
1037}
1038
1039pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
1040
1041//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
1042// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
1043// or Metal indirect command buffer.
1044/// cbindgen:ignore
1045#[derive(Debug)]
1046pub struct RenderBundle {
1047    // Normalized command stream. It can be executed verbatim,
1048    // without re-binding anything on the pipeline change.
1049    base: BasePass<ArcRenderCommand, Infallible>,
1050    pub(super) is_depth_read_only: bool,
1051    pub(super) is_stencil_read_only: bool,
1052    pub(crate) device: Arc<Device>,
1053    pub(crate) used: RenderBundleScope,
1054    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1055    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1056    pub(super) context: RenderPassContext,
1057    /// The `label` from the descriptor used to create the resource.
1058    label: String,
1059    pub(crate) tracking_data: TrackingData,
1060    discard_hal_labels: bool,
1061}
1062
1063impl Drop for RenderBundle {
1064    fn drop(&mut self) {
1065        resource_log!("Drop {}", self.error_ident());
1066    }
1067}
1068
1069#[cfg(send_sync)]
1070unsafe impl Send for RenderBundle {}
1071#[cfg(send_sync)]
1072unsafe impl Sync for RenderBundle {}
1073
1074impl RenderBundle {
1075    #[cfg(feature = "trace")]
1076    pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
1077        self.base.clone()
1078    }
1079
1080    /// Actually encode the contents into a native command buffer.
1081    ///
1082    /// This is partially duplicating the logic of `render_pass_end`.
1083    /// However the point of this function is to be lighter, since we already had
1084    /// a chance to go through the commands in `render_bundle_encoder_finish`.
1085    ///
1086    /// Note that the function isn't expected to fail, generally.
1087    /// All the validation has already been done by this point.
1088    /// The only failure condition is if some of the used buffers are destroyed.
1089    pub(super) unsafe fn execute(
1090        &self,
1091        raw: &mut dyn hal::DynCommandEncoder,
1092        indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
1093        indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
1094        snatch_guard: &SnatchGuard,
1095    ) -> Result<(), ExecutionError> {
1096        let mut offsets = self.base.dynamic_offsets.as_slice();
1097        let mut pipeline_layout = None::<Arc<PipelineLayout>>;
1098        if !self.discard_hal_labels {
1099            if let Some(ref label) = self.base.label {
1100                unsafe { raw.begin_debug_marker(label) };
1101            }
1102        }
1103
1104        use ArcRenderCommand as Cmd;
1105        for command in self.base.commands.iter() {
1106            match command {
1107                Cmd::SetBindGroup {
1108                    index,
1109                    num_dynamic_offsets,
1110                    bind_group,
1111                } => {
1112                    let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
1113                    unsafe {
1114                        raw.set_bind_group(
1115                            pipeline_layout.as_ref().unwrap().raw(),
1116                            *index,
1117                            raw_bg,
1118                            &offsets[..*num_dynamic_offsets],
1119                        )
1120                    };
1121                    offsets = &offsets[*num_dynamic_offsets..];
1122                }
1123                Cmd::SetPipeline(pipeline) => {
1124                    unsafe { raw.set_render_pipeline(pipeline.raw()) };
1125
1126                    pipeline_layout = Some(pipeline.layout.clone());
1127                }
1128                Cmd::SetIndexBuffer {
1129                    buffer,
1130                    index_format,
1131                    offset,
1132                    size,
1133                } => {
1134                    let buffer = buffer.try_raw(snatch_guard)?;
1135                    // SAFETY: The binding size was checked against the buffer size
1136                    // in `set_index_buffer` and again in `IndexState::flush`.
1137                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1138                    unsafe { raw.set_index_buffer(bb, *index_format) };
1139                }
1140                Cmd::SetVertexBuffer {
1141                    slot,
1142                    buffer,
1143                    offset,
1144                    size,
1145                } => {
1146                    let buffer = buffer.as_ref().unwrap().try_raw(snatch_guard)?;
1147                    // SAFETY: The binding size was checked against the buffer size
1148                    // in `set_vertex_buffer` and again in `VertexState::flush`.
1149                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1150                    unsafe { raw.set_vertex_buffer(*slot, bb) };
1151                }
1152                Cmd::SetImmediate {
1153                    offset,
1154                    size_bytes,
1155                    values_offset,
1156                } => {
1157                    let pipeline_layout = pipeline_layout.as_ref().unwrap();
1158
1159                    if let Some(values_offset) = *values_offset {
1160                        let values_end_offset =
1161                            (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1162                        let data_slice =
1163                            &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1164
1165                        unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1166                    } else {
1167                        super::immediates_clear(
1168                            *offset,
1169                            *size_bytes,
1170                            |clear_offset, clear_data| {
1171                                unsafe {
1172                                    raw.set_immediates(
1173                                        pipeline_layout.raw(),
1174                                        clear_offset,
1175                                        clear_data,
1176                                    )
1177                                };
1178                            },
1179                        );
1180                    }
1181                }
1182                Cmd::Draw {
1183                    vertex_count,
1184                    instance_count,
1185                    first_vertex,
1186                    first_instance,
1187                } => {
1188                    unsafe {
1189                        raw.draw(
1190                            *first_vertex,
1191                            *vertex_count,
1192                            *first_instance,
1193                            *instance_count,
1194                        )
1195                    };
1196                }
1197                Cmd::DrawIndexed {
1198                    index_count,
1199                    instance_count,
1200                    first_index,
1201                    base_vertex,
1202                    first_instance,
1203                } => {
1204                    unsafe {
1205                        raw.draw_indexed(
1206                            *first_index,
1207                            *index_count,
1208                            *base_vertex,
1209                            *first_instance,
1210                            *instance_count,
1211                        )
1212                    };
1213                }
1214                Cmd::DrawMeshTasks {
1215                    group_count_x,
1216                    group_count_y,
1217                    group_count_z,
1218                } => unsafe {
1219                    raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1220                },
1221                Cmd::DrawIndirect {
1222                    buffer,
1223                    offset,
1224                    count: 1,
1225                    family,
1226
1227                    vertex_or_index_limit,
1228                    instance_limit,
1229                } => {
1230                    let (buffer, offset) = if self.device.indirect_validation.is_some() {
1231                        let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1232                            indirect_draw_validation_resources,
1233                            &self.device,
1234                            buffer,
1235                            *offset,
1236                            *family,
1237                            vertex_or_index_limit
1238                                .expect("finalized render bundle missing vertex_or_index_limit"),
1239                            instance_limit.expect("finalized render bundle missing instance_limit"),
1240                        )?;
1241
1242                        let dst_buffer =
1243                            indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1244                        (dst_buffer, offset)
1245                    } else {
1246                        (buffer.try_raw(snatch_guard)?, *offset)
1247                    };
1248                    match family {
1249                        DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1250                        DrawCommandFamily::DrawIndexed => unsafe {
1251                            raw.draw_indexed_indirect(buffer, offset, 1)
1252                        },
1253                        DrawCommandFamily::DrawMeshTasks => unsafe {
1254                            raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1255                        },
1256                    }
1257                }
1258                Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1259                    return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1260                }
1261                Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1262                    return Err(ExecutionError::Unimplemented("debug-markers"))
1263                }
1264                Cmd::WriteTimestamp { .. }
1265                | Cmd::BeginOcclusionQuery { .. }
1266                | Cmd::EndOcclusionQuery
1267                | Cmd::BeginPipelineStatisticsQuery { .. }
1268                | Cmd::EndPipelineStatisticsQuery => {
1269                    return Err(ExecutionError::Unimplemented("queries"))
1270                }
1271                Cmd::ExecuteBundle(_)
1272                | Cmd::SetBlendConstant(_)
1273                | Cmd::SetStencilReference(_)
1274                | Cmd::SetViewport { .. }
1275                | Cmd::SetScissor(_) => unreachable!(),
1276            }
1277        }
1278
1279        if !self.discard_hal_labels {
1280            if let Some(_) = self.base.label {
1281                unsafe { raw.end_debug_marker() };
1282            }
1283        }
1284
1285        Ok(())
1286    }
1287}
1288
1289crate::impl_resource_type!(RenderBundle);
1290crate::impl_labeled!(RenderBundle);
1291crate::impl_parent_device!(RenderBundle);
1292crate::impl_storage_item!(RenderBundle);
1293crate::impl_trackable!(RenderBundle);
1294
1295/// A render bundle's current index buffer state.
1296///
1297/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
1298/// and calls [`State::flush_index`] before any indexed draw command to produce
1299/// a `SetIndexBuffer` command if one is necessary.
1300///
1301/// Binding ranges must be validated against the size of the buffer before
1302/// being stored in `IndexState`.
1303#[derive(Debug)]
1304struct IndexState {
1305    buffer: Arc<Buffer>,
1306    format: wgt::IndexFormat,
1307    range: Range<wgt::BufferAddress>,
1308    is_dirty: bool,
1309}
1310
1311impl IndexState {
1312    /// Return the number of entries in the current index buffer.
1313    ///
1314    /// Panic if no index buffer has been set.
1315    fn limit(&self) -> u64 {
1316        let bytes_per_index = self.format.byte_size() as u64;
1317
1318        (self.range.end - self.range.start) / bytes_per_index
1319    }
1320
1321    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1322    /// command, if needed.
1323    fn flush(&mut self) -> Option<ArcRenderCommand> {
1324        // This was all checked before, but let's check again just in case.
1325        let binding_size = self
1326            .range
1327            .end
1328            .checked_sub(self.range.start)
1329            .filter(|_| self.range.end <= self.buffer.size)
1330            .expect("index range must be contained in buffer");
1331
1332        if self.is_dirty {
1333            self.is_dirty = false;
1334            Some(ArcRenderCommand::SetIndexBuffer {
1335                buffer: self.buffer.clone(),
1336                index_format: self.format,
1337                offset: self.range.start,
1338                size: NonZeroU64::new(binding_size),
1339            })
1340        } else {
1341            None
1342        }
1343    }
1344}
1345
1346/// The state of a single vertex buffer slot during render bundle encoding.
1347///
1348/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1349/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1350/// records one vertex buffer slot's state changes here, and then
1351/// calls this type's [`flush`] method just before any draw command to
1352/// produce a `SetVertexBuffer` commands if one is necessary.
1353///
1354/// Binding ranges must be validated against the size of the buffer before
1355/// being stored in `VertexState`.
1356///
1357/// [`flush`]: IndexState::flush
1358#[derive(Debug)]
1359/// State for analyzing and cleaning up bundle command streams.
1360///
1361/// To minimize state updates, [`RenderBundleEncoder::finish`]
1362/// actually just applies commands like [`SetBindGroup`] and
1363/// [`SetIndexBuffer`] to the simulated state stored here, and then
1364/// calls the `flush_foo` methods before draw calls to produce the
1365/// update commands we actually need.
1366///
1367/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1368/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1369struct State {
1370    /// Resources used by this bundle. This will become [`RenderBundle::used`].
1371    trackers: RenderBundleScope,
1372
1373    /// The currently set pipeline, if any.
1374    pipeline: Option<Arc<RenderPipeline>>,
1375
1376    /// The state of each vertex buffer slot.
1377    vertex: super::VertexState,
1378
1379    /// The current index buffer, if one has been set. We flush this state
1380    /// before indexed draw commands.
1381    index: Option<IndexState>,
1382
1383    /// Dynamic offset values used by the cleaned-up command sequence.
1384    ///
1385    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1386    /// [`dynamic_offsets`] list.
1387    ///
1388    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1389    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1390
1391    device: Arc<Device>,
1392    commands: Vec<ArcRenderCommand>,
1393    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1394    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1395    next_dynamic_offset: usize,
1396    binder: Binder,
1397    /// A bitmask, tracking which 4-byte slots have been written via `set_immediates`.
1398    /// Checked against the pipeline's required slots before each draw call.
1399    immediate_slots_set: naga::valid::ImmediateSlots,
1400}
1401
1402impl State {
1403    /// Set the bundle's current index buffer and its associated parameters.
1404    fn set_index_buffer(
1405        &mut self,
1406        buffer: Arc<Buffer>,
1407        format: wgt::IndexFormat,
1408        range: Range<wgt::BufferAddress>,
1409    ) {
1410        match self.index {
1411            Some(ref current)
1412                if current.buffer.is_equal(&buffer)
1413                    && current.format == format
1414                    && current.range == range =>
1415            {
1416                return
1417            }
1418            _ => (),
1419        }
1420
1421        self.index = Some(IndexState {
1422            buffer,
1423            format,
1424            range,
1425            is_dirty: true,
1426        });
1427    }
1428
1429    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1430    /// command, if needed.
1431    fn flush_index(&mut self) {
1432        let commands = self.index.as_mut().and_then(|index| index.flush());
1433        self.commands.extend(commands);
1434    }
1435
1436    fn flush_vertex_buffers(&mut self) {
1437        let vertex = &mut self.vertex;
1438        let commands = &mut self.commands;
1439        vertex.flush(|slot, buffer, offset, size| {
1440            commands.push(ArcRenderCommand::SetVertexBuffer {
1441                slot,
1442                buffer: Some(buffer.clone()),
1443                offset,
1444                size,
1445            });
1446        });
1447    }
1448
1449    /// Validation for a draw command.
1450    ///
1451    /// This should be further deduplicated with similar validation on render/compute passes.
1452    fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1453        if let Some(pipeline) = self.pipeline.as_ref() {
1454            self.binder.check_compatibility(pipeline.as_ref())?;
1455            self.binder.check_late_buffer_bindings()?;
1456
1457            self.vertex.validate(pipeline.as_ref(), &self.binder)?;
1458
1459            if family == DrawCommandFamily::DrawIndexed {
1460                let index_format = match &self.index {
1461                    Some(index) => index.format,
1462                    None => return Err(DrawError::MissingIndexBuffer),
1463                };
1464
1465                if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1466                {
1467                    return Err(DrawError::UnmatchedStripIndexFormat {
1468                        pipeline: pipeline.error_ident(),
1469                        strip_index_format: pipeline.strip_index_format,
1470                        buffer_format: index_format,
1471                    });
1472                }
1473            }
1474
1475            if !self
1476                .immediate_slots_set
1477                .contains(pipeline.immediate_slots_required)
1478            {
1479                return Err(DrawError::MissingImmediateData {
1480                    missing: pipeline
1481                        .immediate_slots_required
1482                        .difference(self.immediate_slots_set),
1483                });
1484            }
1485
1486            Ok(())
1487        } else {
1488            Err(DrawError::MissingPipeline(pass::MissingPipeline))
1489        }
1490    }
1491
1492    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1493    ///
1494    /// This should be further deduplicated with similar code on render/compute passes.
1495    fn flush_bindings(&mut self) {
1496        let start = self.binder.take_rebind_start_index();
1497        let entries = self.binder.list_valid_with_start(start);
1498
1499        self.commands
1500            .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1501                self.buffer_memory_init_actions
1502                    .extend_from_slice(&bind_group.used_buffer_ranges);
1503                self.texture_memory_init_actions
1504                    .extend_from_slice(&bind_group.used_texture_ranges);
1505
1506                self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1507
1508                ArcRenderCommand::SetBindGroup {
1509                    index: i.try_into().unwrap(),
1510                    bind_group: Some(bind_group.clone()),
1511                    num_dynamic_offsets: dynamic_offsets.len(),
1512                }
1513            }));
1514    }
1515}
1516
1517/// Error encountered when finishing recording a render bundle.
1518#[derive(Clone, Debug, Error)]
1519pub enum RenderBundleErrorInner {
1520    #[error(transparent)]
1521    Create(#[from] CreateRenderBundleError),
1522    #[error(transparent)]
1523    Device(#[from] DeviceError),
1524    #[error(transparent)]
1525    RenderCommand(RenderCommandError),
1526    #[error(transparent)]
1527    Draw(#[from] DrawError),
1528    #[error(transparent)]
1529    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1530    #[error(transparent)]
1531    Bind(#[from] BindError),
1532    #[error(transparent)]
1533    InvalidResource(#[from] InvalidResourceError),
1534}
1535
1536impl<T> From<T> for RenderBundleErrorInner
1537where
1538    T: Into<RenderCommandError>,
1539{
1540    fn from(t: T) -> Self {
1541        Self::RenderCommand(t.into())
1542    }
1543}
1544
1545/// Error encountered when finishing recording a render bundle.
1546#[derive(Clone, Debug, Error)]
1547#[error("{scope}")]
1548pub struct RenderBundleError {
1549    pub scope: PassErrorScope,
1550    #[source]
1551    inner: RenderBundleErrorInner,
1552}
1553
1554impl WebGpuError for RenderBundleError {
1555    fn webgpu_error_type(&self) -> ErrorType {
1556        let Self { scope: _, inner } = self;
1557        match inner {
1558            RenderBundleErrorInner::Create(e) => e.webgpu_error_type(),
1559            RenderBundleErrorInner::Device(e) => e.webgpu_error_type(),
1560            RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(),
1561            RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(),
1562            RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
1563            RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(),
1564            RenderBundleErrorInner::InvalidResource(e) => e.webgpu_error_type(),
1565        }
1566    }
1567}
1568
1569impl RenderBundleError {
1570    pub fn from_device_error(e: DeviceError) -> Self {
1571        Self {
1572            scope: PassErrorScope::Bundle,
1573            inner: e.into(),
1574        }
1575    }
1576}
1577
1578impl<E> MapPassErr<RenderBundleError> for E
1579where
1580    E: Into<RenderBundleErrorInner>,
1581{
1582    fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1583        RenderBundleError {
1584            scope,
1585            inner: self.into(),
1586        }
1587    }
1588}
1589
1590pub mod bundle_ffi {
1591    use super::{RenderBundleEncoder, RenderCommand};
1592    use crate::{command::DrawCommandFamily, id, RawString};
1593    use core::{convert::TryInto, slice};
1594    use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1595
1596    /// # Safety
1597    ///
1598    /// This function is unsafe as there is no guarantee that the given pointer is
1599    /// valid for `offset_length` elements.
1600    pub unsafe fn wgpu_render_bundle_set_bind_group(
1601        bundle: &mut RenderBundleEncoder,
1602        index: u32,
1603        bind_group_id: Option<id::BindGroupId>,
1604        offsets: *const DynamicOffset,
1605        offset_length: usize,
1606    ) {
1607        let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1608
1609        let redundant = bundle.current_bind_groups.set_and_check_redundant(
1610            bind_group_id,
1611            index,
1612            &mut bundle.base.dynamic_offsets,
1613            offsets,
1614        );
1615
1616        if redundant {
1617            return;
1618        }
1619
1620        bundle.base.commands.push(RenderCommand::SetBindGroup {
1621            index,
1622            num_dynamic_offsets: offset_length,
1623            bind_group: bind_group_id,
1624        });
1625    }
1626
1627    pub fn wgpu_render_bundle_set_pipeline(
1628        bundle: &mut RenderBundleEncoder,
1629        pipeline_id: id::RenderPipelineId,
1630    ) {
1631        if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1632            return;
1633        }
1634
1635        bundle
1636            .base
1637            .commands
1638            .push(RenderCommand::SetPipeline(pipeline_id));
1639    }
1640
1641    pub fn wgpu_render_bundle_set_vertex_buffer(
1642        bundle: &mut RenderBundleEncoder,
1643        slot: u32,
1644        buffer_id: Option<id::BufferId>,
1645        offset: BufferAddress,
1646        size: Option<BufferSize>,
1647    ) {
1648        bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1649            slot,
1650            buffer: buffer_id,
1651            offset,
1652            size,
1653        });
1654    }
1655
1656    pub fn wgpu_render_bundle_set_index_buffer(
1657        encoder: &mut RenderBundleEncoder,
1658        buffer: id::BufferId,
1659        index_format: IndexFormat,
1660        offset: BufferAddress,
1661        size: Option<BufferSize>,
1662    ) {
1663        encoder.set_index_buffer(buffer, index_format, offset, size);
1664    }
1665
1666    /// # Safety
1667    ///
1668    /// This function is unsafe as there is no guarantee that the given pointer is
1669    /// valid for `data` elements.
1670    pub unsafe fn wgpu_render_bundle_set_immediates(
1671        pass: &mut RenderBundleEncoder,
1672        offset: u32,
1673        size_bytes: u32,
1674        data: *const u8,
1675    ) {
1676        assert_eq!(
1677            offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1678            0,
1679            "Immediate data offset must be aligned to 4 bytes."
1680        );
1681        assert_eq!(
1682            size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1683            0,
1684            "Immediate data size must be aligned to 4 bytes."
1685        );
1686        let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1687        let value_offset = pass.base.immediates_data.len().try_into().expect(
1688            "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1689        );
1690
1691        pass.base.immediates_data.extend(
1692            data_slice
1693                .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1694                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1695        );
1696
1697        pass.base.commands.push(RenderCommand::SetImmediate {
1698            offset,
1699            size_bytes,
1700            values_offset: Some(value_offset),
1701        });
1702    }
1703
1704    pub fn wgpu_render_bundle_draw(
1705        bundle: &mut RenderBundleEncoder,
1706        vertex_count: u32,
1707        instance_count: u32,
1708        first_vertex: u32,
1709        first_instance: u32,
1710    ) {
1711        bundle.base.commands.push(RenderCommand::Draw {
1712            vertex_count,
1713            instance_count,
1714            first_vertex,
1715            first_instance,
1716        });
1717    }
1718
1719    pub fn wgpu_render_bundle_draw_indexed(
1720        bundle: &mut RenderBundleEncoder,
1721        index_count: u32,
1722        instance_count: u32,
1723        first_index: u32,
1724        base_vertex: i32,
1725        first_instance: u32,
1726    ) {
1727        bundle.base.commands.push(RenderCommand::DrawIndexed {
1728            index_count,
1729            instance_count,
1730            first_index,
1731            base_vertex,
1732            first_instance,
1733        });
1734    }
1735
1736    pub fn wgpu_render_bundle_draw_indirect(
1737        bundle: &mut RenderBundleEncoder,
1738        buffer_id: id::BufferId,
1739        offset: BufferAddress,
1740    ) {
1741        bundle.base.commands.push(RenderCommand::DrawIndirect {
1742            buffer: buffer_id,
1743            offset,
1744            count: 1,
1745            family: DrawCommandFamily::Draw,
1746            vertex_or_index_limit: None,
1747            instance_limit: None,
1748        });
1749    }
1750
1751    pub fn wgpu_render_bundle_draw_indexed_indirect(
1752        bundle: &mut RenderBundleEncoder,
1753        buffer_id: id::BufferId,
1754        offset: BufferAddress,
1755    ) {
1756        bundle.base.commands.push(RenderCommand::DrawIndirect {
1757            buffer: buffer_id,
1758            offset,
1759            count: 1,
1760            family: DrawCommandFamily::DrawIndexed,
1761            vertex_or_index_limit: None,
1762            instance_limit: None,
1763        });
1764    }
1765
1766    /// # Safety
1767    ///
1768    /// This function is unsafe as there is no guarantee that the given `label`
1769    /// is a valid null-terminated string.
1770    pub unsafe fn wgpu_render_bundle_push_debug_group(
1771        _bundle: &mut RenderBundleEncoder,
1772        _label: RawString,
1773    ) {
1774        //TODO
1775    }
1776
1777    pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1778        //TODO
1779    }
1780
1781    /// # Safety
1782    ///
1783    /// This function is unsafe as there is no guarantee that the given `label`
1784    /// is a valid null-terminated string.
1785    pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1786        _bundle: &mut RenderBundleEncoder,
1787        _label: RawString,
1788    ) {
1789        //TODO
1790    }
1791}