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