wgpu_core/command/
bundle.rs

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