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