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