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::MultiDrawIndirect {
488                    buffer_id,
489                    offset,
490                    count: None,
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::MultiDrawIndirect { .. }
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: Vec::new(),
547                push_constant_data: Vec::new(),
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::MultiDrawIndirect {
891        buffer,
892        offset,
893        count: None,
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::MultiDrawIndirect {
1105                    buffer,
1106                    offset,
1107                    count: None,
1108                    indexed: false,
1109                } => {
1110                    let buffer = buffer.try_raw(snatch_guard)?;
1111                    unsafe { raw.draw_indirect(buffer, *offset, 1) };
1112                }
1113                Cmd::MultiDrawIndirect {
1114                    buffer,
1115                    offset,
1116                    count: None,
1117                    indexed: true,
1118                } => {
1119                    let buffer = buffer.try_raw(snatch_guard)?;
1120                    unsafe { raw.draw_indexed_indirect(buffer, *offset, 1) };
1121                }
1122                Cmd::MultiDrawIndirect { .. } | 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 = match self.format {
1178            wgt::IndexFormat::Uint16 => 2,
1179            wgt::IndexFormat::Uint32 => 4,
1180        };
1181
1182        (self.range.end - self.range.start) / bytes_per_index
1183    }
1184
1185    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1186    /// command, if needed.
1187    fn flush(&mut self) -> Option<ArcRenderCommand> {
1188        if self.is_dirty {
1189            self.is_dirty = false;
1190            Some(ArcRenderCommand::SetIndexBuffer {
1191                buffer: self.buffer.clone(),
1192                index_format: self.format,
1193                offset: self.range.start,
1194                size: wgt::BufferSize::new(self.range.end - self.range.start),
1195            })
1196        } else {
1197            None
1198        }
1199    }
1200}
1201
1202/// The state of a single vertex buffer slot during render bundle encoding.
1203///
1204/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1205/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1206/// records one vertex buffer slot's state changes here, and then
1207/// calls this type's [`flush`] method just before any draw command to
1208/// produce a `SetVertexBuffer` commands if one is necessary.
1209///
1210/// [`flush`]: IndexState::flush
1211#[derive(Debug)]
1212struct VertexState {
1213    buffer: Arc<Buffer>,
1214    range: Range<wgt::BufferAddress>,
1215    is_dirty: bool,
1216}
1217
1218impl VertexState {
1219    fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1220        Self {
1221            buffer,
1222            range,
1223            is_dirty: true,
1224        }
1225    }
1226
1227    /// Generate a `SetVertexBuffer` command for this slot, if necessary.
1228    ///
1229    /// `slot` is the index of the vertex buffer slot that `self` tracks.
1230    fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1231        if self.is_dirty {
1232            self.is_dirty = false;
1233            Some(ArcRenderCommand::SetVertexBuffer {
1234                slot,
1235                buffer: self.buffer.clone(),
1236                offset: self.range.start,
1237                size: wgt::BufferSize::new(self.range.end - self.range.start),
1238            })
1239        } else {
1240            None
1241        }
1242    }
1243}
1244
1245/// A bind group that has been set at a particular index during render bundle encoding.
1246#[derive(Debug)]
1247struct BindState {
1248    /// The id of the bind group set at this index.
1249    bind_group: Arc<BindGroup>,
1250
1251    /// The range of dynamic offsets for this bind group, in the original
1252    /// command stream's `BassPass::dynamic_offsets` array.
1253    dynamic_offsets: Range<usize>,
1254
1255    /// True if this index's contents have been changed since the last time we
1256    /// generated a `SetBindGroup` command.
1257    is_dirty: bool,
1258}
1259
1260/// The bundle's current pipeline, and some cached information needed for validation.
1261struct PipelineState {
1262    /// The pipeline
1263    pipeline: Arc<RenderPipeline>,
1264
1265    /// How this pipeline's vertex shader traverses each vertex buffer, indexed
1266    /// by vertex buffer slot number.
1267    steps: Vec<VertexStep>,
1268
1269    /// Ranges of push constants this pipeline uses, copied from the pipeline
1270    /// layout.
1271    push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
1272
1273    /// The number of bind groups this pipeline uses.
1274    used_bind_groups: usize,
1275}
1276
1277impl PipelineState {
1278    fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1279        Self {
1280            pipeline: pipeline.clone(),
1281            steps: pipeline.vertex_steps.to_vec(),
1282            push_constant_ranges: pipeline
1283                .layout
1284                .push_constant_ranges
1285                .iter()
1286                .cloned()
1287                .collect(),
1288            used_bind_groups: pipeline.layout.bind_group_layouts.len(),
1289        }
1290    }
1291
1292    /// Return a sequence of commands to zero the push constant ranges this
1293    /// pipeline uses. If no initialization is necessary, return `None`.
1294    fn zero_push_constants(&self) -> Option<impl Iterator<Item = ArcRenderCommand>> {
1295        if !self.push_constant_ranges.is_empty() {
1296            let nonoverlapping_ranges =
1297                super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
1298
1299            Some(
1300                nonoverlapping_ranges
1301                    .into_iter()
1302                    .map(|range| ArcRenderCommand::SetPushConstant {
1303                        stages: range.stages,
1304                        offset: range.range.start,
1305                        size_bytes: range.range.end - range.range.start,
1306                        values_offset: None, // write zeros
1307                    }),
1308            )
1309        } else {
1310            None
1311        }
1312    }
1313}
1314
1315/// State for analyzing and cleaning up bundle command streams.
1316///
1317/// To minimize state updates, [`RenderBundleEncoder::finish`]
1318/// actually just applies commands like [`SetBindGroup`] and
1319/// [`SetIndexBuffer`] to the simulated state stored here, and then
1320/// calls the `flush_foo` methods before draw calls to produce the
1321/// update commands we actually need.
1322///
1323/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1324/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1325struct State {
1326    /// Resources used by this bundle. This will become [`RenderBundle::used`].
1327    trackers: RenderBundleScope,
1328
1329    /// The currently set pipeline, if any.
1330    pipeline: Option<PipelineState>,
1331
1332    /// The bind group set at each index, if any.
1333    bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>,
1334
1335    /// The state of each vertex buffer slot.
1336    vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>,
1337
1338    /// The current index buffer, if one has been set. We flush this state
1339    /// before indexed draw commands.
1340    index: Option<IndexState>,
1341
1342    /// Dynamic offset values used by the cleaned-up command sequence.
1343    ///
1344    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1345    /// [`dynamic_offsets`] list.
1346    ///
1347    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1348    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1349
1350    device: Arc<Device>,
1351    commands: Vec<ArcRenderCommand>,
1352    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1353    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1354    next_dynamic_offset: usize,
1355}
1356
1357impl State {
1358    /// Return the current pipeline state. Return an error if none is set.
1359    fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1360        self.pipeline
1361            .as_ref()
1362            .ok_or(DrawError::MissingPipeline.into())
1363    }
1364
1365    /// Mark all non-empty bind group table entries from `index` onwards as dirty.
1366    fn invalidate_bind_group_from(&mut self, index: usize) {
1367        for contents in self.bind[index..].iter_mut().flatten() {
1368            contents.is_dirty = true;
1369        }
1370    }
1371
1372    fn set_bind_group(
1373        &mut self,
1374        slot: u32,
1375        bind_group: &Arc<BindGroup>,
1376        dynamic_offsets: Range<usize>,
1377    ) {
1378        // If this call wouldn't actually change this index's state, we can
1379        // return early.  (If there are dynamic offsets, the range will always
1380        // be different.)
1381        if dynamic_offsets.is_empty() {
1382            if let Some(ref contents) = self.bind[slot as usize] {
1383                if contents.bind_group.is_equal(bind_group) {
1384                    return;
1385                }
1386            }
1387        }
1388
1389        // Record the index's new state.
1390        self.bind[slot as usize] = Some(BindState {
1391            bind_group: bind_group.clone(),
1392            dynamic_offsets,
1393            is_dirty: true,
1394        });
1395
1396        // Once we've changed the bind group at a particular index, all
1397        // subsequent indices need to be rewritten.
1398        self.invalidate_bind_group_from(slot as usize + 1);
1399    }
1400
1401    /// Determine which bind group slots need to be re-set after a pipeline change.
1402    ///
1403    /// Given that we are switching from the current pipeline state to `new`,
1404    /// whose layout is `layout`, mark all the bind group slots that we need to
1405    /// emit new `SetBindGroup` commands for as dirty.
1406    ///
1407    /// According to `wgpu_hal`'s rules:
1408    ///
1409    /// - If the layout of any bind group slot changes, then that slot and
1410    ///   all following slots must have their bind groups re-established.
1411    ///
1412    /// - Changing the push constant ranges at all requires re-establishing
1413    ///   all bind groups.
1414    fn invalidate_bind_groups(&mut self, new: &PipelineState, layout: &PipelineLayout) {
1415        match self.pipeline {
1416            None => {
1417                // Establishing entirely new pipeline state.
1418                self.invalidate_bind_group_from(0);
1419            }
1420            Some(ref old) => {
1421                if old.pipeline.is_equal(&new.pipeline) {
1422                    // Everything is derived from the pipeline, so if the id has
1423                    // not changed, there's no need to consider anything else.
1424                    return;
1425                }
1426
1427                // Any push constant change invalidates all groups.
1428                if old.push_constant_ranges != new.push_constant_ranges {
1429                    self.invalidate_bind_group_from(0);
1430                } else {
1431                    let first_changed = self.bind.iter().zip(&layout.bind_group_layouts).position(
1432                        |(entry, layout)| match *entry {
1433                            Some(ref contents) => !contents.bind_group.layout.is_equal(layout),
1434                            None => false,
1435                        },
1436                    );
1437                    if let Some(slot) = first_changed {
1438                        self.invalidate_bind_group_from(slot);
1439                    }
1440                }
1441            }
1442        }
1443    }
1444
1445    /// Set the bundle's current index buffer and its associated parameters.
1446    fn set_index_buffer(
1447        &mut self,
1448        buffer: Arc<Buffer>,
1449        format: wgt::IndexFormat,
1450        range: Range<wgt::BufferAddress>,
1451    ) {
1452        match self.index {
1453            Some(ref current)
1454                if current.buffer.is_equal(&buffer)
1455                    && current.format == format
1456                    && current.range == range =>
1457            {
1458                return
1459            }
1460            _ => (),
1461        }
1462
1463        self.index = Some(IndexState {
1464            buffer,
1465            format,
1466            range,
1467            is_dirty: true,
1468        });
1469    }
1470
1471    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1472    /// command, if needed.
1473    fn flush_index(&mut self) {
1474        let commands = self.index.as_mut().and_then(|index| index.flush());
1475        self.commands.extend(commands);
1476    }
1477
1478    fn flush_vertices(&mut self) {
1479        let commands = self
1480            .vertex
1481            .iter_mut()
1482            .enumerate()
1483            .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1484        self.commands.extend(commands);
1485    }
1486
1487    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1488    fn flush_binds(&mut self, used_bind_groups: usize, dynamic_offsets: &[wgt::DynamicOffset]) {
1489        // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`.
1490        for contents in self.bind[..used_bind_groups].iter().flatten() {
1491            if contents.is_dirty {
1492                self.flat_dynamic_offsets
1493                    .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
1494            }
1495        }
1496
1497        // Then, generate `SetBindGroup` commands to update the dirty bind
1498        // groups. After this, all bind groups are clean.
1499        let commands = self.bind[..used_bind_groups]
1500            .iter_mut()
1501            .enumerate()
1502            .flat_map(|(i, entry)| {
1503                if let Some(ref mut contents) = *entry {
1504                    if contents.is_dirty {
1505                        contents.is_dirty = false;
1506                        let offsets = &contents.dynamic_offsets;
1507                        return Some(ArcRenderCommand::SetBindGroup {
1508                            index: i.try_into().unwrap(),
1509                            bind_group: Some(contents.bind_group.clone()),
1510                            num_dynamic_offsets: offsets.end - offsets.start,
1511                        });
1512                    }
1513                }
1514                None
1515            });
1516
1517        self.commands.extend(commands);
1518    }
1519}
1520
1521/// Error encountered when finishing recording a render bundle.
1522#[derive(Clone, Debug, Error)]
1523pub(super) enum RenderBundleErrorInner {
1524    #[error(transparent)]
1525    Device(#[from] DeviceError),
1526    #[error(transparent)]
1527    RenderCommand(RenderCommandError),
1528    #[error(transparent)]
1529    Draw(#[from] DrawError),
1530    #[error(transparent)]
1531    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1532    #[error(transparent)]
1533    Bind(#[from] BindError),
1534    #[error(transparent)]
1535    InvalidResource(#[from] InvalidResourceError),
1536}
1537
1538impl<T> From<T> for RenderBundleErrorInner
1539where
1540    T: Into<RenderCommandError>,
1541{
1542    fn from(t: T) -> Self {
1543        Self::RenderCommand(t.into())
1544    }
1545}
1546
1547/// Error encountered when finishing recording a render bundle.
1548#[derive(Clone, Debug, Error)]
1549#[error("{scope}")]
1550pub struct RenderBundleError {
1551    pub scope: PassErrorScope,
1552    #[source]
1553    inner: RenderBundleErrorInner,
1554}
1555
1556impl RenderBundleError {
1557    pub fn from_device_error(e: DeviceError) -> Self {
1558        Self {
1559            scope: PassErrorScope::Bundle,
1560            inner: e.into(),
1561        }
1562    }
1563}
1564
1565impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
1566where
1567    E: Into<RenderBundleErrorInner>,
1568{
1569    fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
1570        self.map_err(|inner| RenderBundleError {
1571            scope,
1572            inner: inner.into(),
1573        })
1574    }
1575}
1576
1577pub mod bundle_ffi {
1578    use super::{RenderBundleEncoder, RenderCommand};
1579    use crate::{id, RawString};
1580    use std::{convert::TryInto, slice};
1581    use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1582
1583    /// # Safety
1584    ///
1585    /// This function is unsafe as there is no guarantee that the given pointer is
1586    /// valid for `offset_length` elements.
1587    pub unsafe fn wgpu_render_bundle_set_bind_group(
1588        bundle: &mut RenderBundleEncoder,
1589        index: u32,
1590        bind_group_id: Option<id::BindGroupId>,
1591        offsets: *const DynamicOffset,
1592        offset_length: usize,
1593    ) {
1594        let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1595
1596        let redundant = bundle.current_bind_groups.set_and_check_redundant(
1597            bind_group_id,
1598            index,
1599            &mut bundle.base.dynamic_offsets,
1600            offsets,
1601        );
1602
1603        if redundant {
1604            return;
1605        }
1606
1607        bundle.base.commands.push(RenderCommand::SetBindGroup {
1608            index,
1609            num_dynamic_offsets: offset_length,
1610            bind_group_id,
1611        });
1612    }
1613
1614    pub fn wgpu_render_bundle_set_pipeline(
1615        bundle: &mut RenderBundleEncoder,
1616        pipeline_id: id::RenderPipelineId,
1617    ) {
1618        if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1619            return;
1620        }
1621
1622        bundle
1623            .base
1624            .commands
1625            .push(RenderCommand::SetPipeline(pipeline_id));
1626    }
1627
1628    pub fn wgpu_render_bundle_set_vertex_buffer(
1629        bundle: &mut RenderBundleEncoder,
1630        slot: u32,
1631        buffer_id: id::BufferId,
1632        offset: BufferAddress,
1633        size: Option<BufferSize>,
1634    ) {
1635        bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1636            slot,
1637            buffer_id,
1638            offset,
1639            size,
1640        });
1641    }
1642
1643    pub fn wgpu_render_bundle_set_index_buffer(
1644        encoder: &mut RenderBundleEncoder,
1645        buffer: id::BufferId,
1646        index_format: IndexFormat,
1647        offset: BufferAddress,
1648        size: Option<BufferSize>,
1649    ) {
1650        encoder.set_index_buffer(buffer, index_format, offset, size);
1651    }
1652
1653    /// # Safety
1654    ///
1655    /// This function is unsafe as there is no guarantee that the given pointer is
1656    /// valid for `data` elements.
1657    pub unsafe fn wgpu_render_bundle_set_push_constants(
1658        pass: &mut RenderBundleEncoder,
1659        stages: wgt::ShaderStages,
1660        offset: u32,
1661        size_bytes: u32,
1662        data: *const u8,
1663    ) {
1664        assert_eq!(
1665            offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1666            0,
1667            "Push constant offset must be aligned to 4 bytes."
1668        );
1669        assert_eq!(
1670            size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1671            0,
1672            "Push constant size must be aligned to 4 bytes."
1673        );
1674        let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1675        let value_offset = pass.base.push_constant_data.len().try_into().expect(
1676            "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
1677        );
1678
1679        pass.base.push_constant_data.extend(
1680            data_slice
1681                .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
1682                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1683        );
1684
1685        pass.base.commands.push(RenderCommand::SetPushConstant {
1686            stages,
1687            offset,
1688            size_bytes,
1689            values_offset: Some(value_offset),
1690        });
1691    }
1692
1693    pub fn wgpu_render_bundle_draw(
1694        bundle: &mut RenderBundleEncoder,
1695        vertex_count: u32,
1696        instance_count: u32,
1697        first_vertex: u32,
1698        first_instance: u32,
1699    ) {
1700        bundle.base.commands.push(RenderCommand::Draw {
1701            vertex_count,
1702            instance_count,
1703            first_vertex,
1704            first_instance,
1705        });
1706    }
1707
1708    pub fn wgpu_render_bundle_draw_indexed(
1709        bundle: &mut RenderBundleEncoder,
1710        index_count: u32,
1711        instance_count: u32,
1712        first_index: u32,
1713        base_vertex: i32,
1714        first_instance: u32,
1715    ) {
1716        bundle.base.commands.push(RenderCommand::DrawIndexed {
1717            index_count,
1718            instance_count,
1719            first_index,
1720            base_vertex,
1721            first_instance,
1722        });
1723    }
1724
1725    pub fn wgpu_render_bundle_draw_indirect(
1726        bundle: &mut RenderBundleEncoder,
1727        buffer_id: id::BufferId,
1728        offset: BufferAddress,
1729    ) {
1730        bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1731            buffer_id,
1732            offset,
1733            count: None,
1734            indexed: false,
1735        });
1736    }
1737
1738    pub fn wgpu_render_bundle_draw_indexed_indirect(
1739        bundle: &mut RenderBundleEncoder,
1740        buffer_id: id::BufferId,
1741        offset: BufferAddress,
1742    ) {
1743        bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1744            buffer_id,
1745            offset,
1746            count: None,
1747            indexed: true,
1748        });
1749    }
1750
1751    /// # Safety
1752    ///
1753    /// This function is unsafe as there is no guarantee that the given `label`
1754    /// is a valid null-terminated string.
1755    pub unsafe fn wgpu_render_bundle_push_debug_group(
1756        _bundle: &mut RenderBundleEncoder,
1757        _label: RawString,
1758    ) {
1759        //TODO
1760    }
1761
1762    pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1763        //TODO
1764    }
1765
1766    /// # Safety
1767    ///
1768    /// This function is unsafe as there is no guarantee that the given `label`
1769    /// is a valid null-terminated string.
1770    pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1771        _bundle: &mut RenderBundleEncoder,
1772        _label: RawString,
1773    ) {
1774        //TODO
1775    }
1776}