li_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::BeginOcclusionQuery { .. }
641                | RenderCommand::EndOcclusionQuery
642                | RenderCommand::BeginPipelineStatisticsQuery { .. }
643                | RenderCommand::EndPipelineStatisticsQuery => unimplemented!(),
644                RenderCommand::ExecuteBundle(_)
645                | RenderCommand::SetBlendConstant(_)
646                | RenderCommand::SetStencilReference(_)
647                | RenderCommand::SetViewport { .. }
648                | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
649            }
650        }
651
652        Ok(RenderBundle {
653            base: BasePass {
654                label: desc.label.as_ref().map(|cow| cow.to_string()),
655                commands,
656                dynamic_offsets: state.flat_dynamic_offsets,
657                string_data: Vec::new(),
658                push_constant_data: Vec::new(),
659            },
660            is_depth_read_only: self.is_depth_read_only,
661            is_stencil_read_only: self.is_stencil_read_only,
662            device_id: Stored {
663                value: id::Valid(self.parent_id),
664                ref_count: device.life_guard.add_ref(),
665            },
666            used: state.trackers,
667            buffer_memory_init_actions,
668            texture_memory_init_actions,
669            context: self.context,
670            life_guard: LifeGuard::new(desc.label.borrow_or_default()),
671            discard_hal_labels: device
672                .instance_flags
673                .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS),
674        })
675    }
676
677    fn check_valid_to_use(
678        &self,
679        device_id: id::Valid<id::DeviceId>,
680    ) -> Result<(), RenderBundleErrorInner> {
681        if device_id.0 != self.parent_id {
682            return Err(RenderBundleErrorInner::NotValidToUse);
683        }
684
685        Ok(())
686    }
687
688    pub fn set_index_buffer(
689        &mut self,
690        buffer_id: id::BufferId,
691        index_format: wgt::IndexFormat,
692        offset: wgt::BufferAddress,
693        size: Option<wgt::BufferSize>,
694    ) {
695        self.base.commands.push(RenderCommand::SetIndexBuffer {
696            buffer_id,
697            index_format,
698            offset,
699            size,
700        });
701    }
702}
703
704/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
705#[derive(Clone, Debug, Error)]
706#[non_exhaustive]
707pub enum CreateRenderBundleError {
708    #[error(transparent)]
709    ColorAttachment(#[from] ColorAttachmentError),
710    #[error("Invalid number of samples {0}")]
711    InvalidSampleCount(u32),
712}
713
714/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
715#[derive(Clone, Debug, Error)]
716#[non_exhaustive]
717pub enum ExecutionError {
718    #[error("Buffer {0:?} is destroyed")]
719    DestroyedBuffer(id::BufferId),
720    #[error("Using {0} in a render bundle is not implemented")]
721    Unimplemented(&'static str),
722}
723impl PrettyError for ExecutionError {
724    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
725        fmt.error(self);
726        match *self {
727            Self::DestroyedBuffer(id) => {
728                fmt.buffer_label(&id);
729            }
730            Self::Unimplemented(_reason) => {}
731        };
732    }
733}
734
735pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
736
737//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
738// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
739// or Metal indirect command buffer.
740pub struct RenderBundle<A: HalApi> {
741    // Normalized command stream. It can be executed verbatim,
742    // without re-binding anything on the pipeline change.
743    base: BasePass<RenderCommand>,
744    pub(super) is_depth_read_only: bool,
745    pub(super) is_stencil_read_only: bool,
746    pub(crate) device_id: Stored<id::DeviceId>,
747    pub(crate) used: RenderBundleScope<A>,
748    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
749    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
750    pub(super) context: RenderPassContext,
751    pub(crate) life_guard: LifeGuard,
752    discard_hal_labels: bool,
753}
754
755#[cfg(any(
756    not(target_arch = "wasm32"),
757    all(
758        feature = "fragile-send-sync-non-atomic-wasm",
759        not(target_feature = "atomics")
760    )
761))]
762unsafe impl<A: HalApi> Send for RenderBundle<A> {}
763#[cfg(any(
764    not(target_arch = "wasm32"),
765    all(
766        feature = "fragile-send-sync-non-atomic-wasm",
767        not(target_feature = "atomics")
768    )
769))]
770unsafe impl<A: HalApi> Sync for RenderBundle<A> {}
771
772impl<A: HalApi> RenderBundle<A> {
773    /// Actually encode the contents into a native command buffer.
774    ///
775    /// This is partially duplicating the logic of `command_encoder_run_render_pass`.
776    /// However the point of this function is to be lighter, since we already had
777    /// a chance to go through the commands in `render_bundle_encoder_finish`.
778    ///
779    /// Note that the function isn't expected to fail, generally.
780    /// All the validation has already been done by this point.
781    /// The only failure condition is if some of the used buffers are destroyed.
782    pub(super) unsafe fn execute(
783        &self,
784        raw: &mut A::CommandEncoder,
785        pipeline_layout_guard: &Storage<
786            crate::binding_model::PipelineLayout<A>,
787            id::PipelineLayoutId,
788        >,
789        bind_group_guard: &Storage<crate::binding_model::BindGroup<A>, id::BindGroupId>,
790        pipeline_guard: &Storage<crate::pipeline::RenderPipeline<A>, id::RenderPipelineId>,
791        buffer_guard: &Storage<crate::resource::Buffer<A>, id::BufferId>,
792    ) -> Result<(), ExecutionError> {
793        let mut offsets = self.base.dynamic_offsets.as_slice();
794        let mut pipeline_layout_id = None::<id::Valid<id::PipelineLayoutId>>;
795        if !self.discard_hal_labels {
796            if let Some(ref label) = self.base.label {
797                unsafe { raw.begin_debug_marker(label) };
798            }
799        }
800
801        for command in self.base.commands.iter() {
802            match *command {
803                RenderCommand::SetBindGroup {
804                    index,
805                    num_dynamic_offsets,
806                    bind_group_id,
807                } => {
808                    let bind_group = bind_group_guard.get(bind_group_id).unwrap();
809                    unsafe {
810                        raw.set_bind_group(
811                            &pipeline_layout_guard[pipeline_layout_id.unwrap()].raw,
812                            index,
813                            &bind_group.raw,
814                            &offsets[..num_dynamic_offsets as usize],
815                        )
816                    };
817                    offsets = &offsets[num_dynamic_offsets as usize..];
818                }
819                RenderCommand::SetPipeline(pipeline_id) => {
820                    let pipeline = pipeline_guard.get(pipeline_id).unwrap();
821                    unsafe { raw.set_render_pipeline(&pipeline.raw) };
822
823                    pipeline_layout_id = Some(pipeline.layout_id.value);
824                }
825                RenderCommand::SetIndexBuffer {
826                    buffer_id,
827                    index_format,
828                    offset,
829                    size,
830                } => {
831                    let buffer = buffer_guard
832                        .get(buffer_id)
833                        .unwrap()
834                        .raw
835                        .as_ref()
836                        .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
837                    let bb = hal::BufferBinding {
838                        buffer,
839                        offset,
840                        size,
841                    };
842                    unsafe { raw.set_index_buffer(bb, index_format) };
843                }
844                RenderCommand::SetVertexBuffer {
845                    slot,
846                    buffer_id,
847                    offset,
848                    size,
849                } => {
850                    let buffer = buffer_guard
851                        .get(buffer_id)
852                        .unwrap()
853                        .raw
854                        .as_ref()
855                        .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
856                    let bb = hal::BufferBinding {
857                        buffer,
858                        offset,
859                        size,
860                    };
861                    unsafe { raw.set_vertex_buffer(slot, bb) };
862                }
863                RenderCommand::SetPushConstant {
864                    stages,
865                    offset,
866                    size_bytes,
867                    values_offset,
868                } => {
869                    let pipeline_layout_id = pipeline_layout_id.unwrap();
870                    let pipeline_layout = &pipeline_layout_guard[pipeline_layout_id];
871
872                    if let Some(values_offset) = values_offset {
873                        let values_end_offset =
874                            (values_offset + size_bytes / wgt::PUSH_CONSTANT_ALIGNMENT) as usize;
875                        let data_slice = &self.base.push_constant_data
876                            [(values_offset as usize)..values_end_offset];
877
878                        unsafe {
879                            raw.set_push_constants(&pipeline_layout.raw, stages, offset, data_slice)
880                        }
881                    } else {
882                        super::push_constant_clear(
883                            offset,
884                            size_bytes,
885                            |clear_offset, clear_data| {
886                                unsafe {
887                                    raw.set_push_constants(
888                                        &pipeline_layout.raw,
889                                        stages,
890                                        clear_offset,
891                                        clear_data,
892                                    )
893                                };
894                            },
895                        );
896                    }
897                }
898                RenderCommand::Draw {
899                    vertex_count,
900                    instance_count,
901                    first_vertex,
902                    first_instance,
903                } => {
904                    unsafe { raw.draw(first_vertex, vertex_count, first_instance, instance_count) };
905                }
906                RenderCommand::DrawIndexed {
907                    index_count,
908                    instance_count,
909                    first_index,
910                    base_vertex,
911                    first_instance,
912                } => {
913                    unsafe {
914                        raw.draw_indexed(
915                            first_index,
916                            index_count,
917                            base_vertex,
918                            first_instance,
919                            instance_count,
920                        )
921                    };
922                }
923                RenderCommand::MultiDrawIndirect {
924                    buffer_id,
925                    offset,
926                    count: None,
927                    indexed: false,
928                } => {
929                    let buffer = buffer_guard
930                        .get(buffer_id)
931                        .unwrap()
932                        .raw
933                        .as_ref()
934                        .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
935                    unsafe { raw.draw_indirect(buffer, offset, 1) };
936                }
937                RenderCommand::MultiDrawIndirect {
938                    buffer_id,
939                    offset,
940                    count: None,
941                    indexed: true,
942                } => {
943                    let buffer = buffer_guard
944                        .get(buffer_id)
945                        .unwrap()
946                        .raw
947                        .as_ref()
948                        .ok_or(ExecutionError::DestroyedBuffer(buffer_id))?;
949                    unsafe { raw.draw_indexed_indirect(buffer, offset, 1) };
950                }
951                RenderCommand::MultiDrawIndirect { .. }
952                | RenderCommand::MultiDrawIndirectCount { .. } => {
953                    return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
954                }
955                RenderCommand::PushDebugGroup { .. }
956                | RenderCommand::InsertDebugMarker { .. }
957                | RenderCommand::PopDebugGroup => {
958                    return Err(ExecutionError::Unimplemented("debug-markers"))
959                }
960                RenderCommand::WriteTimestamp { .. }
961                | RenderCommand::BeginOcclusionQuery { .. }
962                | RenderCommand::EndOcclusionQuery
963                | RenderCommand::BeginPipelineStatisticsQuery { .. }
964                | RenderCommand::EndPipelineStatisticsQuery => {
965                    return Err(ExecutionError::Unimplemented("queries"))
966                }
967                RenderCommand::ExecuteBundle(_)
968                | RenderCommand::SetBlendConstant(_)
969                | RenderCommand::SetStencilReference(_)
970                | RenderCommand::SetViewport { .. }
971                | RenderCommand::SetScissor(_) => unreachable!(),
972            }
973        }
974
975        if !self.discard_hal_labels {
976            if let Some(_) = self.base.label {
977                unsafe { raw.end_debug_marker() };
978            }
979        }
980
981        Ok(())
982    }
983}
984
985impl<A: HalApi> Resource for RenderBundle<A> {
986    const TYPE: &'static str = "RenderBundle";
987
988    fn life_guard(&self) -> &LifeGuard {
989        &self.life_guard
990    }
991}
992
993/// A render bundle's current index buffer state.
994///
995/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
996/// and calls [`State::flush_index`] before any indexed draw command to produce
997/// a `SetIndexBuffer` command if one is necessary.
998#[derive(Debug)]
999struct IndexState {
1000    buffer: id::BufferId,
1001    format: wgt::IndexFormat,
1002    range: Range<wgt::BufferAddress>,
1003    is_dirty: bool,
1004}
1005
1006impl IndexState {
1007    /// Return the number of entries in the current index buffer.
1008    ///
1009    /// Panic if no index buffer has been set.
1010    fn limit(&self) -> u32 {
1011        let bytes_per_index = match self.format {
1012            wgt::IndexFormat::Uint16 => 2,
1013            wgt::IndexFormat::Uint32 => 4,
1014        };
1015        ((self.range.end - self.range.start) / bytes_per_index) as u32
1016    }
1017
1018    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1019    /// command, if needed.
1020    fn flush(&mut self) -> Option<RenderCommand> {
1021        if self.is_dirty {
1022            self.is_dirty = false;
1023            Some(RenderCommand::SetIndexBuffer {
1024                buffer_id: self.buffer,
1025                index_format: self.format,
1026                offset: self.range.start,
1027                size: wgt::BufferSize::new(self.range.end - self.range.start),
1028            })
1029        } else {
1030            None
1031        }
1032    }
1033}
1034
1035/// The state of a single vertex buffer slot during render bundle encoding.
1036///
1037/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1038/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1039/// records one vertex buffer slot's state changes here, and then
1040/// calls this type's [`flush`] method just before any draw command to
1041/// produce a `SetVertexBuffer` commands if one is necessary.
1042///
1043/// [`flush`]: IndexState::flush
1044#[derive(Debug)]
1045struct VertexState {
1046    buffer: id::BufferId,
1047    range: Range<wgt::BufferAddress>,
1048    is_dirty: bool,
1049}
1050
1051impl VertexState {
1052    fn new(buffer: id::BufferId, range: Range<wgt::BufferAddress>) -> Self {
1053        Self {
1054            buffer,
1055            range,
1056            is_dirty: true,
1057        }
1058    }
1059
1060    /// Generate a `SetVertexBuffer` command for this slot, if necessary.
1061    ///
1062    /// `slot` is the index of the vertex buffer slot that `self` tracks.
1063    fn flush(&mut self, slot: u32) -> Option<RenderCommand> {
1064        if self.is_dirty {
1065            self.is_dirty = false;
1066            Some(RenderCommand::SetVertexBuffer {
1067                slot,
1068                buffer_id: self.buffer,
1069                offset: self.range.start,
1070                size: wgt::BufferSize::new(self.range.end - self.range.start),
1071            })
1072        } else {
1073            None
1074        }
1075    }
1076}
1077
1078/// A bind group that has been set at a particular index during render bundle encoding.
1079#[derive(Debug)]
1080struct BindState {
1081    /// The id of the bind group set at this index.
1082    bind_group_id: id::BindGroupId,
1083
1084    /// The layout of `group`.
1085    layout_id: id::Valid<id::BindGroupLayoutId>,
1086
1087    /// The range of dynamic offsets for this bind group, in the original
1088    /// command stream's `BassPass::dynamic_offsets` array.
1089    dynamic_offsets: Range<usize>,
1090
1091    /// True if this index's contents have been changed since the last time we
1092    /// generated a `SetBindGroup` command.
1093    is_dirty: bool,
1094}
1095
1096#[derive(Debug)]
1097struct VertexLimitState {
1098    /// Length of the shortest vertex rate vertex buffer
1099    vertex_limit: u32,
1100    /// Buffer slot which the shortest vertex rate vertex buffer is bound to
1101    vertex_limit_slot: u32,
1102    /// Length of the shortest instance rate vertex buffer
1103    instance_limit: u32,
1104    /// Buffer slot which the shortest instance rate vertex buffer is bound to
1105    instance_limit_slot: u32,
1106}
1107
1108/// The bundle's current pipeline, and some cached information needed for validation.
1109struct PipelineState {
1110    /// The pipeline's id.
1111    id: id::RenderPipelineId,
1112
1113    /// The id of the pipeline's layout.
1114    layout_id: id::Valid<id::PipelineLayoutId>,
1115
1116    /// How this pipeline's vertex shader traverses each vertex buffer, indexed
1117    /// by vertex buffer slot number.
1118    steps: Vec<pipeline::VertexStep>,
1119
1120    /// Ranges of push constants this pipeline uses, copied from the pipeline
1121    /// layout.
1122    push_constant_ranges: ArrayVec<wgt::PushConstantRange, { SHADER_STAGE_COUNT }>,
1123
1124    /// The number of bind groups this pipeline uses.
1125    used_bind_groups: usize,
1126}
1127
1128impl PipelineState {
1129    fn new<A: HalApi>(
1130        pipeline_id: id::RenderPipelineId,
1131        pipeline: &pipeline::RenderPipeline<A>,
1132        layout: &binding_model::PipelineLayout<A>,
1133    ) -> Self {
1134        Self {
1135            id: pipeline_id,
1136            layout_id: pipeline.layout_id.value,
1137            steps: pipeline.vertex_steps.to_vec(),
1138            push_constant_ranges: layout.push_constant_ranges.iter().cloned().collect(),
1139            used_bind_groups: layout.bind_group_layout_ids.len(),
1140        }
1141    }
1142
1143    /// Return a sequence of commands to zero the push constant ranges this
1144    /// pipeline uses. If no initialization is necessary, return `None`.
1145    fn zero_push_constants(&self) -> Option<impl Iterator<Item = RenderCommand>> {
1146        if !self.push_constant_ranges.is_empty() {
1147            let nonoverlapping_ranges =
1148                super::bind::compute_nonoverlapping_ranges(&self.push_constant_ranges);
1149
1150            Some(
1151                nonoverlapping_ranges
1152                    .into_iter()
1153                    .map(|range| RenderCommand::SetPushConstant {
1154                        stages: range.stages,
1155                        offset: range.range.start,
1156                        size_bytes: range.range.end - range.range.start,
1157                        values_offset: None, // write zeros
1158                    }),
1159            )
1160        } else {
1161            None
1162        }
1163    }
1164}
1165
1166/// State for analyzing and cleaning up bundle command streams.
1167///
1168/// To minimize state updates, [`RenderBundleEncoder::finish`]
1169/// actually just applies commands like [`SetBindGroup`] and
1170/// [`SetIndexBuffer`] to the simulated state stored here, and then
1171/// calls the `flush_foo` methods before draw calls to produce the
1172/// update commands we actually need.
1173///
1174/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1175/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1176struct State<A: HalApi> {
1177    /// Resources used by this bundle. This will become [`RenderBundle::used`].
1178    trackers: RenderBundleScope<A>,
1179
1180    /// The currently set pipeline, if any.
1181    pipeline: Option<PipelineState>,
1182
1183    /// The bind group set at each index, if any.
1184    bind: ArrayVec<Option<BindState>, { hal::MAX_BIND_GROUPS }>,
1185
1186    /// The state of each vertex buffer slot.
1187    vertex: ArrayVec<Option<VertexState>, { hal::MAX_VERTEX_BUFFERS }>,
1188
1189    /// The current index buffer, if one has been set. We flush this state
1190    /// before indexed draw commands.
1191    index: Option<IndexState>,
1192
1193    /// Dynamic offset values used by the cleaned-up command sequence.
1194    ///
1195    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1196    /// [`dynamic_offsets`] list.
1197    ///
1198    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1199    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1200}
1201
1202impl<A: HalApi> State<A> {
1203    fn vertex_limits(&self, pipeline: &PipelineState) -> VertexLimitState {
1204        let mut vert_state = VertexLimitState {
1205            vertex_limit: u32::MAX,
1206            vertex_limit_slot: 0,
1207            instance_limit: u32::MAX,
1208            instance_limit_slot: 0,
1209        };
1210        for (idx, (vbs, step)) in self.vertex.iter().zip(&pipeline.steps).enumerate() {
1211            if let Some(ref vbs) = *vbs {
1212                let limit = ((vbs.range.end - vbs.range.start) / step.stride) as u32;
1213                match step.mode {
1214                    wgt::VertexStepMode::Vertex => {
1215                        if limit < vert_state.vertex_limit {
1216                            vert_state.vertex_limit = limit;
1217                            vert_state.vertex_limit_slot = idx as _;
1218                        }
1219                    }
1220                    wgt::VertexStepMode::Instance => {
1221                        if limit < vert_state.instance_limit {
1222                            vert_state.instance_limit = limit;
1223                            vert_state.instance_limit_slot = idx as _;
1224                        }
1225                    }
1226                }
1227            }
1228        }
1229        vert_state
1230    }
1231
1232    /// Return the id of the current pipeline, if any.
1233    fn pipeline_id(&self) -> Option<id::RenderPipelineId> {
1234        self.pipeline.as_ref().map(|p| p.id)
1235    }
1236
1237    /// Return the current pipeline state. Return an error if none is set.
1238    fn pipeline(&self, scope: PassErrorScope) -> Result<&PipelineState, RenderBundleError> {
1239        self.pipeline
1240            .as_ref()
1241            .ok_or(DrawError::MissingPipeline)
1242            .map_pass_err(scope)
1243    }
1244
1245    /// Mark all non-empty bind group table entries from `index` onwards as dirty.
1246    fn invalidate_bind_group_from(&mut self, index: usize) {
1247        for contents in self.bind[index..].iter_mut().flatten() {
1248            contents.is_dirty = true;
1249        }
1250    }
1251
1252    fn set_bind_group(
1253        &mut self,
1254        slot: u32,
1255        bind_group_id: id::BindGroupId,
1256        layout_id: id::Valid<id::BindGroupLayoutId>,
1257        dynamic_offsets: Range<usize>,
1258    ) {
1259        // If this call wouldn't actually change this index's state, we can
1260        // return early.  (If there are dynamic offsets, the range will always
1261        // be different.)
1262        if dynamic_offsets.is_empty() {
1263            if let Some(ref contents) = self.bind[slot as usize] {
1264                if contents.bind_group_id == bind_group_id {
1265                    return;
1266                }
1267            }
1268        }
1269
1270        // Record the index's new state.
1271        self.bind[slot as usize] = Some(BindState {
1272            bind_group_id,
1273            layout_id,
1274            dynamic_offsets,
1275            is_dirty: true,
1276        });
1277
1278        // Once we've changed the bind group at a particular index, all
1279        // subsequent indices need to be rewritten.
1280        self.invalidate_bind_group_from(slot as usize + 1);
1281    }
1282
1283    /// Determine which bind group slots need to be re-set after a pipeline change.
1284    ///
1285    /// Given that we are switching from the current pipeline state to `new`,
1286    /// whose layout is `layout`, mark all the bind group slots that we need to
1287    /// emit new `SetBindGroup` commands for as dirty.
1288    ///
1289    /// According to `wgpu_hal`'s rules:
1290    ///
1291    /// - If the layout of any bind group slot changes, then that slot and
1292    ///   all following slots must have their bind groups re-established.
1293    ///
1294    /// - Changing the push constant ranges at all requires re-establishing
1295    ///   all bind groups.
1296    fn invalidate_bind_groups(
1297        &mut self,
1298        new: &PipelineState,
1299        layout: &binding_model::PipelineLayout<A>,
1300    ) {
1301        match self.pipeline {
1302            None => {
1303                // Establishing entirely new pipeline state.
1304                self.invalidate_bind_group_from(0);
1305            }
1306            Some(ref old) => {
1307                if old.id == new.id {
1308                    // Everything is derived from the pipeline, so if the id has
1309                    // not changed, there's no need to consider anything else.
1310                    return;
1311                }
1312
1313                // Any push constant change invalidates all groups.
1314                if old.push_constant_ranges != new.push_constant_ranges {
1315                    self.invalidate_bind_group_from(0);
1316                } else {
1317                    let first_changed = self
1318                        .bind
1319                        .iter()
1320                        .zip(&layout.bind_group_layout_ids)
1321                        .position(|(entry, &layout_id)| match *entry {
1322                            Some(ref contents) => contents.layout_id != layout_id,
1323                            None => false,
1324                        });
1325                    if let Some(slot) = first_changed {
1326                        self.invalidate_bind_group_from(slot);
1327                    }
1328                }
1329            }
1330        }
1331    }
1332
1333    /// Set the bundle's current index buffer and its associated parameters.
1334    fn set_index_buffer(
1335        &mut self,
1336        buffer: id::BufferId,
1337        format: wgt::IndexFormat,
1338        range: Range<wgt::BufferAddress>,
1339    ) {
1340        match self.index {
1341            Some(ref current)
1342                if current.buffer == buffer
1343                    && current.format == format
1344                    && current.range == range =>
1345            {
1346                return
1347            }
1348            _ => (),
1349        }
1350
1351        self.index = Some(IndexState {
1352            buffer,
1353            format,
1354            range,
1355            is_dirty: true,
1356        });
1357    }
1358
1359    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1360    /// command, if needed.
1361    fn flush_index(&mut self) -> Option<RenderCommand> {
1362        self.index.as_mut().and_then(|index| index.flush())
1363    }
1364
1365    fn flush_vertices(&mut self) -> impl Iterator<Item = RenderCommand> + '_ {
1366        self.vertex
1367            .iter_mut()
1368            .enumerate()
1369            .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)))
1370    }
1371
1372    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1373    fn flush_binds(
1374        &mut self,
1375        used_bind_groups: usize,
1376        dynamic_offsets: &[wgt::DynamicOffset],
1377    ) -> impl Iterator<Item = RenderCommand> + '_ {
1378        // Append each dirty bind group's dynamic offsets to `flat_dynamic_offsets`.
1379        for contents in self.bind[..used_bind_groups].iter().flatten() {
1380            if contents.is_dirty {
1381                self.flat_dynamic_offsets
1382                    .extend_from_slice(&dynamic_offsets[contents.dynamic_offsets.clone()]);
1383            }
1384        }
1385
1386        // Then, generate `SetBindGroup` commands to update the dirty bind
1387        // groups. After this, all bind groups are clean.
1388        self.bind[..used_bind_groups]
1389            .iter_mut()
1390            .enumerate()
1391            .flat_map(|(i, entry)| {
1392                if let Some(ref mut contents) = *entry {
1393                    if contents.is_dirty {
1394                        contents.is_dirty = false;
1395                        let offsets = &contents.dynamic_offsets;
1396                        return Some(RenderCommand::SetBindGroup {
1397                            index: i.try_into().unwrap(),
1398                            bind_group_id: contents.bind_group_id,
1399                            num_dynamic_offsets: (offsets.end - offsets.start) as u8,
1400                        });
1401                    }
1402                }
1403                None
1404            })
1405    }
1406}
1407
1408/// Error encountered when finishing recording a render bundle.
1409#[derive(Clone, Debug, Error)]
1410pub(super) enum RenderBundleErrorInner {
1411    #[error("Resource is not valid to use with this render bundle because the resource and the bundle come from different devices")]
1412    NotValidToUse,
1413    #[error(transparent)]
1414    Device(#[from] DeviceError),
1415    #[error(transparent)]
1416    RenderCommand(RenderCommandError),
1417    #[error(transparent)]
1418    Draw(#[from] DrawError),
1419    #[error(transparent)]
1420    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1421}
1422
1423impl<T> From<T> for RenderBundleErrorInner
1424where
1425    T: Into<RenderCommandError>,
1426{
1427    fn from(t: T) -> Self {
1428        Self::RenderCommand(t.into())
1429    }
1430}
1431
1432/// Error encountered when finishing recording a render bundle.
1433#[derive(Clone, Debug, Error)]
1434#[error("{scope}")]
1435pub struct RenderBundleError {
1436    pub scope: PassErrorScope,
1437    #[source]
1438    inner: RenderBundleErrorInner,
1439}
1440
1441impl RenderBundleError {
1442    pub(crate) const INVALID_DEVICE: Self = RenderBundleError {
1443        scope: PassErrorScope::Bundle,
1444        inner: RenderBundleErrorInner::Device(DeviceError::Invalid),
1445    };
1446}
1447impl PrettyError for RenderBundleError {
1448    fn fmt_pretty(&self, fmt: &mut ErrorFormatter) {
1449        // This error is wrapper for the inner error,
1450        // but the scope has useful labels
1451        fmt.error(self);
1452        self.scope.fmt_pretty(fmt);
1453    }
1454}
1455
1456impl<T, E> MapPassErr<T, RenderBundleError> for Result<T, E>
1457where
1458    E: Into<RenderBundleErrorInner>,
1459{
1460    fn map_pass_err(self, scope: PassErrorScope) -> Result<T, RenderBundleError> {
1461        self.map_err(|inner| RenderBundleError {
1462            scope,
1463            inner: inner.into(),
1464        })
1465    }
1466}
1467
1468pub mod bundle_ffi {
1469    use super::{RenderBundleEncoder, RenderCommand};
1470    use crate::{id, RawString};
1471    use std::{convert::TryInto, slice};
1472    use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1473
1474    /// # Safety
1475    ///
1476    /// This function is unsafe as there is no guarantee that the given pointer is
1477    /// valid for `offset_length` elements.
1478    #[no_mangle]
1479    pub unsafe extern "C" fn wgpu_render_bundle_set_bind_group(
1480        bundle: &mut RenderBundleEncoder,
1481        index: u32,
1482        bind_group_id: id::BindGroupId,
1483        offsets: *const DynamicOffset,
1484        offset_length: usize,
1485    ) {
1486        let redundant = unsafe {
1487            bundle.current_bind_groups.set_and_check_redundant(
1488                bind_group_id,
1489                index,
1490                &mut bundle.base.dynamic_offsets,
1491                offsets,
1492                offset_length,
1493            )
1494        };
1495
1496        if redundant {
1497            return;
1498        }
1499
1500        bundle.base.commands.push(RenderCommand::SetBindGroup {
1501            index,
1502            num_dynamic_offsets: offset_length.try_into().unwrap(),
1503            bind_group_id,
1504        });
1505    }
1506
1507    #[no_mangle]
1508    pub extern "C" fn wgpu_render_bundle_set_pipeline(
1509        bundle: &mut RenderBundleEncoder,
1510        pipeline_id: id::RenderPipelineId,
1511    ) {
1512        if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1513            return;
1514        }
1515
1516        bundle
1517            .base
1518            .commands
1519            .push(RenderCommand::SetPipeline(pipeline_id));
1520    }
1521
1522    #[no_mangle]
1523    pub extern "C" fn wgpu_render_bundle_set_vertex_buffer(
1524        bundle: &mut RenderBundleEncoder,
1525        slot: u32,
1526        buffer_id: id::BufferId,
1527        offset: BufferAddress,
1528        size: Option<BufferSize>,
1529    ) {
1530        bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1531            slot,
1532            buffer_id,
1533            offset,
1534            size,
1535        });
1536    }
1537
1538    #[no_mangle]
1539    pub extern "C" fn wgpu_render_bundle_set_index_buffer(
1540        encoder: &mut RenderBundleEncoder,
1541        buffer: id::BufferId,
1542        index_format: IndexFormat,
1543        offset: BufferAddress,
1544        size: Option<BufferSize>,
1545    ) {
1546        encoder.set_index_buffer(buffer, index_format, offset, size);
1547    }
1548
1549    /// # Safety
1550    ///
1551    /// This function is unsafe as there is no guarantee that the given pointer is
1552    /// valid for `data` elements.
1553    #[no_mangle]
1554    pub unsafe extern "C" fn wgpu_render_bundle_set_push_constants(
1555        pass: &mut RenderBundleEncoder,
1556        stages: wgt::ShaderStages,
1557        offset: u32,
1558        size_bytes: u32,
1559        data: *const u8,
1560    ) {
1561        assert_eq!(
1562            offset & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1563            0,
1564            "Push constant offset must be aligned to 4 bytes."
1565        );
1566        assert_eq!(
1567            size_bytes & (wgt::PUSH_CONSTANT_ALIGNMENT - 1),
1568            0,
1569            "Push constant size must be aligned to 4 bytes."
1570        );
1571        let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1572        let value_offset = pass.base.push_constant_data.len().try_into().expect(
1573            "Ran out of push constant space. Don't set 4gb of push constants per RenderBundle.",
1574        );
1575
1576        pass.base.push_constant_data.extend(
1577            data_slice
1578                .chunks_exact(wgt::PUSH_CONSTANT_ALIGNMENT as usize)
1579                .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1580        );
1581
1582        pass.base.commands.push(RenderCommand::SetPushConstant {
1583            stages,
1584            offset,
1585            size_bytes,
1586            values_offset: Some(value_offset),
1587        });
1588    }
1589
1590    #[no_mangle]
1591    pub extern "C" fn wgpu_render_bundle_draw(
1592        bundle: &mut RenderBundleEncoder,
1593        vertex_count: u32,
1594        instance_count: u32,
1595        first_vertex: u32,
1596        first_instance: u32,
1597    ) {
1598        bundle.base.commands.push(RenderCommand::Draw {
1599            vertex_count,
1600            instance_count,
1601            first_vertex,
1602            first_instance,
1603        });
1604    }
1605
1606    #[no_mangle]
1607    pub extern "C" fn wgpu_render_bundle_draw_indexed(
1608        bundle: &mut RenderBundleEncoder,
1609        index_count: u32,
1610        instance_count: u32,
1611        first_index: u32,
1612        base_vertex: i32,
1613        first_instance: u32,
1614    ) {
1615        bundle.base.commands.push(RenderCommand::DrawIndexed {
1616            index_count,
1617            instance_count,
1618            first_index,
1619            base_vertex,
1620            first_instance,
1621        });
1622    }
1623
1624    #[no_mangle]
1625    pub extern "C" fn wgpu_render_bundle_draw_indirect(
1626        bundle: &mut RenderBundleEncoder,
1627        buffer_id: id::BufferId,
1628        offset: BufferAddress,
1629    ) {
1630        bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1631            buffer_id,
1632            offset,
1633            count: None,
1634            indexed: false,
1635        });
1636    }
1637
1638    #[no_mangle]
1639    pub extern "C" fn wgpu_render_bundle_draw_indexed_indirect(
1640        bundle: &mut RenderBundleEncoder,
1641        buffer_id: id::BufferId,
1642        offset: BufferAddress,
1643    ) {
1644        bundle.base.commands.push(RenderCommand::MultiDrawIndirect {
1645            buffer_id,
1646            offset,
1647            count: None,
1648            indexed: true,
1649        });
1650    }
1651
1652    /// # Safety
1653    ///
1654    /// This function is unsafe as there is no guarantee that the given `label`
1655    /// is a valid null-terminated string.
1656    #[no_mangle]
1657    pub unsafe extern "C" fn wgpu_render_bundle_push_debug_group(
1658        _bundle: &mut RenderBundleEncoder,
1659        _label: RawString,
1660    ) {
1661        //TODO
1662    }
1663
1664    #[no_mangle]
1665    pub extern "C" fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1666        //TODO
1667    }
1668
1669    /// # Safety
1670    ///
1671    /// This function is unsafe as there is no guarantee that the given `label`
1672    /// is a valid null-terminated string.
1673    #[no_mangle]
1674    pub unsafe extern "C" fn wgpu_render_bundle_insert_debug_marker(
1675        _bundle: &mut RenderBundleEncoder,
1676        _label: RawString,
1677    ) {
1678        //TODO
1679    }
1680}