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 isolated from a bundle's effects on immediate data
33values. When encoding a render bundle, calls to `set_immediates` snapshot the immediate data
34content at encoding time, and the immediate values cannot be changed after `finish`.
35Before and after executing each individual bundle, all required immediate slots are cleared/reset,
36therefore immediate data must be set again.
37
38## Render Bundle Lifecycle
39
40To create a render bundle:
41
421) Create a [`RenderBundleEncoder`] by calling
43   [`Device::create_render_bundle_encoder`][Dcrbe].
44
452) Record commands in the `RenderBundleEncoder` using methods on [`RenderBundleEncoder`].
46
473) Call [`RenderBundleEncoder::finish`], which analyzes and cleans up
48   the command stream and returns a [`RenderBundle`].
49
504) Then, any number of times, call [`RenderPass::execute_bundles`][rpeb] to
51   execute the bundle as part of some render pass.
52
53## Implementation
54
55The most complex part of render bundles is the "finish" step, mostly implemented
56in [`RenderBundleEncoder::finish`]. This consumes the commands stored in the
57encoder's [`BasePass`], while validating everything, tracking the state,
58dropping redundant or unnecessary commands, and presenting the results as a new
59[`RenderBundle`]. It doesn't actually execute any commands.
60
61This step also enforces the 'isolation' property mentioned above: every draw
62call is checked to ensure that the resources it uses on were established since
63the last time the pipeline was set. This means the bundle can be executed
64verbatim without any state tracking.
65
66### Execution
67
68When the bundle is used in an actual render pass, `RenderBundle::execute` is
69called. It goes through the commands and issues them into the native command
70buffer. Thanks to isolation, it doesn't track any bind group invalidations or
71index format changes.
72
73[Dcrbe]: crate::device::Device::create_render_bundle_encoder
74[rpeb]: crate::command::RenderPass::execute_bundles
75!*/
76
77#![allow(clippy::reversed_empty_ranges)]
78
79use alloc::{
80    borrow::{Cow, ToOwned as _},
81    boxed::Box,
82    string::String,
83    string::ToString as _,
84    sync::Arc,
85    vec::Vec,
86};
87use core::{
88    convert::Infallible,
89    mem,
90    num::{NonZeroU32, NonZeroU64},
91    ops::Range,
92};
93
94use arrayvec::ArrayVec;
95use thiserror::Error;
96
97use wgpu_hal::ShouldBeNonZeroExt;
98use wgt::error::{ErrorType, WebGpuError};
99
100use crate::{
101    api_log,
102    binding_model::{BindError, BindGroup, ImmediateUploadError, PipelineLayout},
103    command::{
104        bind::Binder,
105        pass::{validate_immediates_alignment, ImmediateState},
106        pass_base, ArcReferences, BasePass, BindGroupStateChange, ColorAttachmentError, DrawError,
107        EncoderStateError, MapPassErr, PassErrorScope, PassStateError, RenderCommand,
108        RenderCommandError, StateChange,
109    },
110    device::{
111        AttachmentData, Device, DeviceError, MissingDownlevelFlags, MissingFeatures,
112        RenderPassContext,
113    },
114    impl_resource_type, impl_storage_item,
115    init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
116    pipeline::{PipelineFlags, RenderPipeline},
117    resource::{
118        Buffer, DestroyedResourceError, InvalidOrDestroyedResourceError, InvalidResourceError,
119        Labeled, ParentDevice, RawResourceAccess, ResourceState, TrackingData,
120    },
121    resource_log,
122    snatch::SnatchGuard,
123    track::RenderBundleScope,
124    validation::{
125        check_color_attachment_count, validate_color_attachment_bytes_per_sample,
126        WorkgroupSizeCheck,
127    },
128    Label, LabelHelpers,
129};
130
131use super::{pass, render_command::ArcRenderCommand, DrawCommandFamily, DrawKind};
132
133/// Describes a [`RenderBundleEncoder`].
134#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
135#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
136pub struct RenderBundleEncoderDescriptor<'a> {
137    /// Debug label of the render bundle encoder.
138    ///
139    /// This will show up in graphics debuggers for easy identification.
140    pub label: Label<'a>,
141    /// The formats of the color attachments that this render bundle is capable
142    /// to rendering to.
143    ///
144    /// This must match the formats of the color attachments in the
145    /// renderpass this render bundle is executed in.
146    pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
147    /// Information about the depth attachment that this render bundle is
148    /// capable to rendering to.
149    ///
150    /// The format must match the format of the depth attachments in the
151    /// renderpass this render bundle is executed in.
152    pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
153    /// Sample count this render bundle is capable of rendering to.
154    ///
155    /// This must match the pipelines and the renderpasses it is used in.
156    pub sample_count: u32,
157    /// If this render bundle will rendering to multiple array layers in the
158    /// attachments at the same time.
159    pub multiview: Option<NonZeroU32>,
160}
161
162#[derive(Debug)]
163pub struct RenderBundleEncoder {
164    pub(crate) base: BasePass<RenderCommand<ArcReferences>, Infallible>,
165    device: Arc<Device>,
166    /// State of the render bundle encoder. Encoded to be compatible with pass macros.
167    ///
168    /// If this is `Some`, then the pass is in WebGPU's "open" state. If it is
169    /// `None`, then the pass is in the "ended" state.
170    /// See <https://www.w3.org/TR/webgpu/#encoder-state>
171    parent: Option<()>,
172    pub(crate) context: RenderPassContext,
173    pub(crate) is_depth_read_only: bool,
174    pub(crate) is_stencil_read_only: bool,
175
176    // Resource binding dedupe state.
177    current_bind_groups: BindGroupStateChange,
178    current_pipeline: StateChange<Arc<RenderPipeline>>,
179}
180
181impl_resource_type!(RenderBundleEncoder);
182impl_storage_item!(RenderBundleEncoder);
183
184/// Validate a render bundle descriptor.
185///
186/// Returns a tuple (is_depth_read_only, is_stencil_read_only).
187fn validate_render_bundle_encoder_descriptor(
188    desc: &RenderBundleEncoderDescriptor,
189    device: &Arc<Device>,
190) -> Result<(bool, bool), CreateRenderBundleError> {
191    let mut have_attachment = false;
192
193    let max_color_attachments = device.limits.max_color_attachments;
194    assert!(max_color_attachments <= hal::MAX_COLOR_ATTACHMENTS as u32);
195    check_color_attachment_count(desc.color_formats.len(), max_color_attachments)?;
196
197    for &format in desc.color_formats.iter().flatten() {
198        have_attachment = true;
199        if !format.has_color_aspect() {
200            return Err(CreateRenderBundleError::FormatNotColor(format));
201        }
202        let format_features = device.describe_format_features(format)?;
203        if !format_features
204            .allowed_usages
205            .contains(wgt::TextureUsages::RENDER_ATTACHMENT)
206        {
207            return Err(CreateRenderBundleError::FormatNotRenderable(format));
208        }
209    }
210
211    validate_color_attachment_bytes_per_sample(
212        desc.color_formats.iter().flatten().copied(),
213        device.limits.max_color_attachment_bytes_per_sample,
214    )?;
215
216    let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
217        Some(ds) => {
218            have_attachment = true;
219            let has_depth = ds.format.has_depth_aspect();
220            let has_stencil = ds.format.has_stencil_aspect();
221            if !has_depth && !has_stencil {
222                return Err(CreateRenderBundleError::FormatNotDepthOrStencil(ds.format));
223            } else {
224                (
225                    !has_depth || ds.depth_read_only,
226                    !has_stencil || ds.stencil_read_only,
227                )
228            }
229        }
230        // There's no depth/stencil attachment, so these values just don't
231        // matter.  Choose the most accommodating value, to simplify
232        // validation.
233        None => (true, true),
234    };
235
236    if !have_attachment {
237        return Err(CreateRenderBundleError::NoAttachment);
238    }
239
240    Ok((is_depth_read_only, is_stencil_read_only))
241}
242
243impl RenderBundleEncoder {
244    /// Create a new `RenderBundleEncoder`.
245    ///
246    /// <https://www.w3.org/TR/webgpu/#dom-gpudevice-createrenderbundleencoder>
247    pub fn new(
248        device: &Arc<Device>,
249        desc: &RenderBundleEncoderDescriptor,
250    ) -> Result<Self, CreateRenderBundleError> {
251        // 1. Validate texture format required features of each non-null element of descriptor.colorFormats with this.[[device]].
252        for &format in desc.color_formats.iter().flatten() {
253            device.require_features(format.required_features())?;
254        }
255
256        // 2. If descriptor.depthStencilFormat is provided:
257        if let Some(ds) = desc.depth_stencil {
258            // Validate texture format required features of descriptor.depthStencilFormat with this.[[device]].
259            device.require_features(ds.format.required_features())?;
260        }
261
262        device.check_is_valid()?;
263        let (is_depth_read_only, is_stencil_read_only) =
264            validate_render_bundle_encoder_descriptor(desc, device)?;
265
266        Ok(Self {
267            base: BasePass::new(&desc.label),
268            device: Arc::clone(device),
269            parent: Some(()),
270            context: RenderPassContext {
271                attachments: AttachmentData {
272                    colors: desc.color_formats.iter().cloned().collect(),
273                    resolves: ArrayVec::new(),
274                    depth_stencil: desc.depth_stencil.map(|ds| ds.format),
275                },
276                sample_count: desc.sample_count,
277                multiview_mask: desc.multiview,
278            },
279
280            is_depth_read_only,
281            is_stencil_read_only,
282            current_bind_groups: BindGroupStateChange::new(),
283            current_pipeline: StateChange::new(),
284        })
285    }
286
287    pub fn dummy(device: &Arc<Device>) -> Self {
288        Self {
289            base: BasePass::new(&None),
290            parent: None,
291            device: Arc::clone(device),
292            context: RenderPassContext::default(),
293            is_depth_read_only: false,
294            is_stencil_read_only: false,
295
296            current_bind_groups: BindGroupStateChange::new(),
297            current_pipeline: StateChange::new(),
298        }
299    }
300
301    pub fn label(&self) -> Option<&str> {
302        self.base.label.as_deref()
303    }
304
305    pub fn device(&self) -> &Arc<Device> {
306        &self.device
307    }
308
309    /// Convert this encoder's commands into a [`RenderBundle`].
310    ///
311    /// We want executing a [`RenderBundle`] to be quick, so we take
312    /// this opportunity to clean up the [`RenderBundleEncoder`]'s
313    /// command stream and gather metadata about it that will help
314    /// keep [`ExecuteBundle`] simple and fast. We remove redundant
315    /// commands (along with their side data), note resource usage,
316    /// and accumulate buffer and texture initialization actions.
317    ///
318    /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle
319    pub fn finish(&mut self, desc: &RenderBundleDescriptor) -> Arc<RenderBundle> {
320        profiling::scope!("RenderBundleEncoder::finish");
321        #[cfg(feature = "trace")]
322        let trace_desc = crate::device::trace::new_render_bundle_encoder_descriptor(
323            desc.label.clone(),
324            &self.context,
325            self.is_depth_read_only,
326            self.is_stencil_read_only,
327        );
328
329        let render_bundle = self.finish_inner(desc).unwrap_or_else(|error| {
330            self.device
331                .handle_error(error, self.label(), "RenderBundleEncoder::finish");
332            RenderBundle::invalid(Arc::clone(&self.device), desc)
333        });
334
335        #[cfg(feature = "trace")]
336        if let Some(ref mut trace) = *self.device.trace.lock() {
337            use crate::device::trace::{Action, IntoTrace};
338            trace.add(Action::CreateRenderBundle {
339                id: render_bundle.to_trace(),
340                desc: trace_desc,
341                base: render_bundle.to_base_pass().to_trace(),
342            });
343        }
344
345        api_log!(
346            "RenderBundleEncoder::finish -> {:?}",
347            Arc::as_ptr(&render_bundle)
348        );
349
350        render_bundle
351    }
352
353    /// Convert this encoder's commands into a [`RenderBundle`].
354    ///
355    /// We want executing a [`RenderBundle`] to be quick, so we take
356    /// this opportunity to clean up the [`RenderBundleEncoder`]'s
357    /// command stream and gather metadata about it that will help
358    /// keep [`ExecuteBundle`] simple and fast. We remove redundant
359    /// commands (along with their side data), note resource usage,
360    /// and accumulate buffer and texture initialization actions.
361    ///
362    /// [`ExecuteBundle`]: RenderCommand::ExecuteBundle
363    pub(crate) fn finish_inner(
364        &mut self,
365        desc: &RenderBundleDescriptor,
366    ) -> Result<Arc<RenderBundle>, RenderBundleError> {
367        let scope = PassErrorScope::Bundle;
368
369        self.parent
370            .take()
371            .ok_or(RenderBundleErrorInner::Ended)
372            .map_pass_err(scope)?;
373
374        self.device.check_is_valid().map_pass_err(scope)?;
375
376        let mut state = State {
377            trackers: RenderBundleScope::new(),
378            pipeline: None,
379            vertex: Default::default(),
380            index: None,
381            flat_dynamic_offsets: Vec::new(),
382            device: Arc::clone(&self.device),
383            commands: Vec::new(),
384            buffer_memory_init_actions: Vec::new(),
385            texture_memory_init_actions: Vec::new(),
386            next_dynamic_offset: 0,
387            binder: Binder::new(),
388            immediate_state: ImmediateState::default(),
389        };
390
391        let indices = &state.device.tracker_indices;
392        state.trackers.buffers.set_size(indices.buffers.size());
393        state.trackers.textures.set_size(indices.textures.size());
394
395        for command in self.base.commands.drain(..) {
396            match command {
397                RenderCommand::SetBindGroup {
398                    index,
399                    num_dynamic_offsets,
400                    bind_group,
401                } => {
402                    let scope = PassErrorScope::SetBindGroup;
403                    set_bind_group(
404                        &mut state,
405                        &self.base.dynamic_offsets,
406                        index,
407                        num_dynamic_offsets,
408                        bind_group,
409                    )
410                    .map_pass_err(scope)?;
411                }
412                RenderCommand::SetPipeline(pipeline) => {
413                    let scope = PassErrorScope::SetPipelineRender;
414                    set_pipeline(
415                        &mut state,
416                        &self.context,
417                        self.is_depth_read_only,
418                        self.is_stencil_read_only,
419                        pipeline,
420                    )
421                    .map_pass_err(scope)?;
422                }
423                RenderCommand::SetIndexBuffer {
424                    buffer,
425                    index_format,
426                    offset,
427                    size,
428                } => {
429                    let scope = PassErrorScope::SetIndexBuffer;
430                    set_index_buffer(&mut state, buffer, index_format, offset, size)
431                        .map_pass_err(scope)?;
432                }
433                RenderCommand::SetVertexBuffer {
434                    slot,
435                    buffer,
436                    offset,
437                    size,
438                } => {
439                    let scope = PassErrorScope::SetVertexBuffer;
440                    set_vertex_buffer(&mut state, slot, buffer, offset, size)
441                        .map_pass_err(scope)?;
442                }
443                RenderCommand::SetImmediate { offset, ref data } => {
444                    let scope = PassErrorScope::SetImmediate;
445                    set_immediates(&mut state, offset, data).map_pass_err(scope)?;
446                }
447                RenderCommand::Draw {
448                    vertex_count,
449                    instance_count,
450                    first_vertex,
451                    first_instance,
452                } => {
453                    let scope = PassErrorScope::Draw {
454                        kind: DrawKind::Draw,
455                        family: DrawCommandFamily::Draw,
456                    };
457                    draw(
458                        &mut state,
459                        vertex_count,
460                        instance_count,
461                        first_vertex,
462                        first_instance,
463                    )
464                    .map_pass_err(scope)?;
465                }
466                RenderCommand::DrawIndexed {
467                    index_count,
468                    instance_count,
469                    first_index,
470                    base_vertex,
471                    first_instance,
472                } => {
473                    let scope = PassErrorScope::Draw {
474                        kind: DrawKind::Draw,
475                        family: DrawCommandFamily::DrawIndexed,
476                    };
477                    draw_indexed(
478                        &mut state,
479                        index_count,
480                        instance_count,
481                        first_index,
482                        base_vertex,
483                        first_instance,
484                    )
485                    .map_pass_err(scope)?;
486                }
487                RenderCommand::DrawMeshTasks {
488                    group_count_x,
489                    group_count_y,
490                    group_count_z,
491                } => {
492                    let scope = PassErrorScope::Draw {
493                        kind: DrawKind::Draw,
494                        family: DrawCommandFamily::DrawMeshTasks,
495                    };
496                    draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z)
497                        .map_pass_err(scope)?;
498                }
499                RenderCommand::DrawIndirect {
500                    buffer,
501                    offset,
502                    count: 1,
503                    family,
504                    vertex_or_index_limit: None,
505                    instance_limit: None,
506                } => {
507                    let scope = PassErrorScope::Draw {
508                        kind: DrawKind::DrawIndirect,
509                        family,
510                    };
511                    multi_draw_indirect(&mut state, buffer, offset, family).map_pass_err(scope)?;
512                }
513                RenderCommand::DrawIndirect {
514                    count,
515                    vertex_or_index_limit,
516                    instance_limit,
517                    ..
518                } => {
519                    unreachable!("unexpected (multi-)draw indirect with count {count}, vertex_or_index_limits {vertex_or_index_limit:?}, instance_limit {instance_limit:?} found in a render bundle");
520                }
521                RenderCommand::MultiDrawIndirectCount { .. }
522                | RenderCommand::PushDebugGroup { color: _, len: _ }
523                | RenderCommand::InsertDebugMarker { color: _, len: _ }
524                | RenderCommand::PopDebugGroup => {
525                    unimplemented!("not supported by a render bundle")
526                }
527                // Must check the TIMESTAMP_QUERY_INSIDE_PASSES feature
528                RenderCommand::WriteTimestamp { .. }
529                | RenderCommand::BeginOcclusionQuery { .. }
530                | RenderCommand::EndOcclusionQuery
531                | RenderCommand::BeginPipelineStatisticsQuery { .. }
532                | RenderCommand::EndPipelineStatisticsQuery => {
533                    unimplemented!("not supported by a render bundle")
534                }
535                RenderCommand::ExecuteBundle(_)
536                | RenderCommand::SetBlendConstant(_)
537                | RenderCommand::SetStencilReference(_)
538                | RenderCommand::SetViewport { .. }
539                | RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
540            }
541        }
542
543        let State {
544            trackers,
545            flat_dynamic_offsets,
546            device,
547            commands,
548            buffer_memory_init_actions,
549            texture_memory_init_actions,
550            ..
551        } = state;
552
553        let tracker_indices = device.tracker_indices.bundles.clone();
554        let discard_hal_labels = device
555            .instance_flags
556            .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
557
558        let string_data = mem::take(&mut self.base.string_data);
559        let context = mem::take(&mut self.context);
560        let render_bundle = RenderBundle {
561            state: ResourceState::Valid(RenderBundleState {
562                context,
563                used: trackers,
564            }),
565            base: BasePass {
566                label: desc.label.as_deref().map(str::to_owned),
567                error: None,
568                commands,
569                dynamic_offsets: flat_dynamic_offsets,
570                string_data,
571            },
572            is_depth_read_only: self.is_depth_read_only,
573            is_stencil_read_only: self.is_stencil_read_only,
574            device: device.clone(),
575            buffer_memory_init_actions,
576            texture_memory_init_actions,
577            label: desc.label.to_string(),
578            tracking_data: TrackingData::new(tracker_indices),
579            discard_hal_labels,
580        };
581
582        let render_bundle = Arc::new(render_bundle);
583
584        Ok(render_bundle)
585    }
586
587    fn set_index_buffer_inner(
588        &mut self,
589        buffer: Arc<Buffer>,
590        index_format: wgt::IndexFormat,
591        offset: wgt::BufferAddress,
592        size: Option<wgt::BufferSize>,
593    ) -> Result<(), PassStateError> {
594        pass_base!(self, PassErrorScope::SetIndexBuffer);
595        self.base.commands.push(RenderCommand::SetIndexBuffer {
596            buffer,
597            index_format,
598            offset,
599            size,
600        });
601        Ok(())
602    }
603
604    pub fn set_index_buffer(
605        &mut self,
606        buffer: Arc<Buffer>,
607        index_format: wgt::IndexFormat,
608        offset: wgt::BufferAddress,
609        size: Option<wgt::BufferSize>,
610    ) {
611        if let Err(err) = self.set_index_buffer_inner(buffer, index_format, offset, size) {
612            self.device
613                .handle_error(err, self.label(), "RenderBundleEncoder::set_index_buffer");
614        }
615    }
616
617    fn set_bind_group_inner(
618        &mut self,
619        index: u32,
620        bind_group: Option<Arc<BindGroup>>,
621        offsets: &[wgt::DynamicOffset],
622    ) -> Result<(), PassStateError> {
623        pass_base!(self, PassErrorScope::SetBindGroup);
624        let redundant = self.current_bind_groups.set_and_check_redundant(
625            &bind_group,
626            index,
627            &mut self.base.dynamic_offsets,
628            offsets,
629        );
630
631        if redundant {
632            return Ok(());
633        }
634
635        self.base.commands.push(RenderCommand::SetBindGroup {
636            index,
637            num_dynamic_offsets: offsets.len(),
638            bind_group,
639        });
640        Ok(())
641    }
642
643    pub fn set_bind_group(
644        &mut self,
645        index: u32,
646        bind_group: Option<Arc<BindGroup>>,
647        offsets: &[wgt::DynamicOffset],
648    ) {
649        if let Err(err) = self.set_bind_group_inner(index, bind_group, offsets) {
650            self.device
651                .handle_error(err, self.label(), "RenderBundleEncoder::set_bind_group");
652        }
653    }
654
655    fn set_pipeline_inner(&mut self, pipeline: Arc<RenderPipeline>) -> Result<(), PassStateError> {
656        pass_base!(self, PassErrorScope::SetPipelineRender);
657        if self.current_pipeline.set_and_check_redundant(&pipeline) {
658            return Ok(());
659        }
660
661        self.base
662            .commands
663            .push(RenderCommand::SetPipeline(pipeline));
664        Ok(())
665    }
666
667    pub fn set_pipeline(&mut self, pipeline: Arc<RenderPipeline>) {
668        if let Err(err) = self.set_pipeline_inner(pipeline) {
669            self.device
670                .handle_error(err, self.label(), "RenderBundleEncoder::set_pipeline");
671        }
672    }
673
674    fn set_vertex_buffer_inner(
675        &mut self,
676        slot: u32,
677        buffer: Option<Arc<Buffer>>,
678        offset: wgt::BufferAddress,
679        size: Option<wgt::BufferSize>,
680    ) -> Result<(), PassStateError> {
681        pass_base!(self, PassErrorScope::SetVertexBuffer);
682        self.base.commands.push(RenderCommand::SetVertexBuffer {
683            slot,
684            buffer,
685            offset,
686            size,
687        });
688        Ok(())
689    }
690
691    pub fn set_vertex_buffer(
692        &mut self,
693        slot: u32,
694        buffer: Option<Arc<Buffer>>,
695        offset: wgt::BufferAddress,
696        size: Option<wgt::BufferSize>,
697    ) {
698        if let Err(err) = self.set_vertex_buffer_inner(slot, buffer, offset, size) {
699            self.device
700                .handle_error(err, self.label(), "RenderBundleEncoder::set_vertex_buffer");
701        }
702    }
703
704    fn set_immediates_inner(&mut self, offset: u32, data: &[u8]) -> Result<(), PassStateError> {
705        pass_base!(self, PassErrorScope::SetImmediate);
706
707        // This should have been validated in content timeline
708        assert!(data.len().is_multiple_of(4));
709
710        self.base.commands.push(RenderCommand::SetImmediate {
711            offset,
712            data: data
713                .chunks_exact(size_of::<u32>())
714                .map(|ck| u32::from_le_bytes(ck.try_into().unwrap()))
715                .collect(),
716        });
717        Ok(())
718    }
719
720    pub fn set_immediates(&mut self, offset: u32, data: &[u8]) {
721        if let Err(err) = self.set_immediates_inner(offset, data) {
722            self.device
723                .handle_error(err, self.label(), "RenderBundleEncoder::set_immediates");
724        }
725    }
726
727    fn draw_inner(
728        &mut self,
729        vertex_count: u32,
730        instance_count: u32,
731        first_vertex: u32,
732        first_instance: u32,
733    ) -> Result<(), PassStateError> {
734        pass_base!(
735            self,
736            PassErrorScope::Draw {
737                kind: DrawKind::Draw,
738                family: DrawCommandFamily::Draw
739            }
740        );
741        self.base.commands.push(RenderCommand::Draw {
742            vertex_count,
743            instance_count,
744            first_vertex,
745            first_instance,
746        });
747        Ok(())
748    }
749
750    pub fn draw(
751        &mut self,
752        vertex_count: u32,
753        instance_count: u32,
754        first_vertex: u32,
755        first_instance: u32,
756    ) {
757        if let Err(err) =
758            self.draw_inner(vertex_count, instance_count, first_vertex, first_instance)
759        {
760            self.device
761                .handle_error(err, self.label(), "RenderBundleEncoder::draw");
762        }
763    }
764
765    fn draw_indexed_inner(
766        &mut self,
767        index_count: u32,
768        instance_count: u32,
769        first_index: u32,
770        base_vertex: i32,
771        first_instance: u32,
772    ) -> Result<(), PassStateError> {
773        pass_base!(
774            self,
775            PassErrorScope::Draw {
776                kind: DrawKind::Draw,
777                family: DrawCommandFamily::DrawIndexed
778            }
779        );
780        self.base.commands.push(RenderCommand::DrawIndexed {
781            index_count,
782            instance_count,
783            first_index,
784            base_vertex,
785            first_instance,
786        });
787        Ok(())
788    }
789
790    pub fn draw_indexed(
791        &mut self,
792        index_count: u32,
793        instance_count: u32,
794        first_index: u32,
795        base_vertex: i32,
796        first_instance: u32,
797    ) {
798        if let Err(err) = self.draw_indexed_inner(
799            index_count,
800            instance_count,
801            first_index,
802            base_vertex,
803            first_instance,
804        ) {
805            self.device
806                .handle_error(err, self.label(), "RenderBundleEncoder::draw_indexed");
807        }
808    }
809
810    fn draw_indirect_inner(
811        &mut self,
812        buffer: Arc<Buffer>,
813        offset: wgt::BufferAddress,
814    ) -> Result<(), PassStateError> {
815        pass_base!(
816            self,
817            PassErrorScope::Draw {
818                kind: DrawKind::DrawIndirect,
819                family: DrawCommandFamily::Draw
820            }
821        );
822        self.base.commands.push(RenderCommand::DrawIndirect {
823            buffer,
824            offset,
825            count: 1,
826            family: DrawCommandFamily::Draw,
827            vertex_or_index_limit: None,
828            instance_limit: None,
829        });
830        Ok(())
831    }
832
833    pub fn draw_indirect(&mut self, buffer: Arc<Buffer>, offset: wgt::BufferAddress) {
834        if let Err(err) = self.draw_indirect_inner(buffer, offset) {
835            self.device
836                .handle_error(err, self.label(), "RenderBundleEncoder::draw_indirect");
837        }
838    }
839
840    fn draw_indexed_indirect_inner(
841        &mut self,
842        buffer: Arc<Buffer>,
843        offset: wgt::BufferAddress,
844    ) -> Result<(), PassStateError> {
845        pass_base!(
846            self,
847            PassErrorScope::Draw {
848                kind: DrawKind::DrawIndirect,
849                family: DrawCommandFamily::DrawIndexed
850            }
851        );
852        self.base.commands.push(RenderCommand::DrawIndirect {
853            buffer,
854            offset,
855            count: 1,
856            family: DrawCommandFamily::DrawIndexed,
857            vertex_or_index_limit: None,
858            instance_limit: None,
859        });
860        Ok(())
861    }
862
863    pub fn draw_indexed_indirect(&mut self, buffer: Arc<Buffer>, offset: wgt::BufferAddress) {
864        if let Err(err) = self.draw_indexed_indirect_inner(buffer, offset) {
865            self.device.handle_error(
866                err,
867                self.label(),
868                "RenderBundleEncoder::draw_indexed_indirect",
869            );
870        }
871    }
872
873    fn push_debug_group_inner(&mut self, _label: &str) -> Result<(), PassStateError> {
874        pass_base!(self, PassErrorScope::PushDebugGroup);
875        //TODO
876        Ok(())
877    }
878
879    pub fn push_debug_group(&mut self, label: &str) {
880        if let Err(err) = self.push_debug_group_inner(label) {
881            self.device
882                .handle_error(err, self.label(), "RenderBundleEncoder::push_debug_group");
883        }
884    }
885
886    fn pop_debug_group_inner(&mut self) -> Result<(), PassStateError> {
887        pass_base!(self, PassErrorScope::PopDebugGroup);
888        //TODO
889        Ok(())
890    }
891
892    pub fn pop_debug_group(&mut self) {
893        if let Err(err) = self.pop_debug_group_inner() {
894            self.device
895                .handle_error(err, self.label(), "RenderBundleEncoder::pop_debug_group");
896        }
897    }
898
899    fn insert_debug_marker_inner(&mut self, _label: &str) -> Result<(), PassStateError> {
900        pass_base!(self, PassErrorScope::InsertDebugMarker);
901        //TODO
902        Ok(())
903    }
904
905    pub fn insert_debug_marker(&mut self, label: &str) {
906        if let Err(err) = self.insert_debug_marker_inner(label) {
907            self.device.handle_error(
908                err,
909                self.label(),
910                "RenderBundleEncoder::insert_debug_marker",
911            );
912        }
913    }
914}
915
916fn set_bind_group(
917    state: &mut State,
918    dynamic_offsets: &[u32],
919    index: u32,
920    num_dynamic_offsets: usize,
921    bind_group: Option<Arc<BindGroup>>,
922) -> Result<(), RenderBundleErrorInner> {
923    let max_bind_groups = state.device.limits.max_bind_groups;
924    if index >= max_bind_groups {
925        return Err(
926            RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
927                index,
928                max: max_bind_groups,
929            })
930            .into(),
931        );
932    }
933
934    // Identify the next `num_dynamic_offsets` entries from `dynamic_offsets`.
935    let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
936    state.next_dynamic_offset = offsets_range.end;
937    let offsets = &dynamic_offsets[offsets_range.clone()];
938
939    if let Some(bind_group) = bind_group {
940        bind_group.check_is_valid()?;
941        bind_group.same_device(&state.device)?;
942        bind_group.validate_dynamic_bindings(index, offsets)?;
943
944        unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
945        let bind_group = state.trackers.bind_groups.insert_single(bind_group);
946
947        state
948            .binder
949            .assign_group(index as usize, bind_group, offsets);
950    } else {
951        if !offsets.is_empty() {
952            return Err(RenderBundleErrorInner::Bind(
953                BindError::DynamicOffsetCountNotZero {
954                    group: index,
955                    actual: offsets.len(),
956                },
957            ));
958        }
959
960        state.binder.clear_group(index as usize);
961    }
962
963    Ok(())
964}
965
966fn set_pipeline(
967    state: &mut State,
968    context: &RenderPassContext,
969    is_depth_read_only: bool,
970    is_stencil_read_only: bool,
971    pipeline: Arc<RenderPipeline>,
972) -> Result<(), RenderBundleErrorInner> {
973    pipeline.same_device(&state.device)?;
974
975    context
976        .check_compatible(&pipeline.pass_context, pipeline.as_ref())
977        .map_err(RenderCommandError::IncompatiblePipelineTargets)?;
978
979    if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
980        return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
981    }
982    if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
983        return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
984    }
985
986    state
987        .commands
988        .push(ArcRenderCommand::SetPipeline(pipeline.clone()));
989
990    state.pipeline = Some(pipeline.clone());
991
992    state
993        .binder
994        .change_pipeline_layout(pipeline.layout()?, &pipeline.late_sized_buffer_groups);
995
996    state.vertex.update_limits(&pipeline.vertex_steps);
997
998    state.trackers.render_pipelines.insert_single(pipeline);
999    Ok(())
1000}
1001
1002// This function is duplicative of `render::set_index_buffer`.
1003fn set_index_buffer(
1004    state: &mut State,
1005    buffer: Arc<Buffer>,
1006    index_format: wgt::IndexFormat,
1007    offset: u64,
1008    size: Option<NonZeroU64>,
1009) -> Result<(), RenderBundleErrorInner> {
1010    buffer.check_is_valid()?;
1011
1012    state
1013        .trackers
1014        .buffers
1015        .merge_single(&buffer, wgt::BufferUses::INDEX)?;
1016
1017    buffer.same_device(&state.device)?;
1018    buffer.check_usage(wgt::BufferUsages::INDEX)?;
1019
1020    if !offset.is_multiple_of(u64::from(index_format.byte_size())) {
1021        return Err(RenderCommandError::UnalignedIndexBuffer {
1022            offset,
1023            alignment: index_format.byte_size() as usize,
1024        }
1025        .into());
1026    }
1027    let end = offset + buffer.resolve_binding_size(offset, size)?;
1028
1029    state
1030        .buffer_memory_init_actions
1031        .extend(buffer.initialization_status.read().create_action(
1032            &buffer,
1033            offset..end.get(),
1034            MemoryInitKind::NeedsInitializedMemory,
1035        ));
1036    state.set_index_buffer(buffer, index_format, offset..end.get());
1037    Ok(())
1038}
1039
1040// This function is duplicative of `render::set_vertex_buffer`.
1041fn set_vertex_buffer(
1042    state: &mut State,
1043    slot: u32,
1044    buffer: Option<Arc<Buffer>>,
1045    offset: u64,
1046    size: Option<NonZeroU64>,
1047) -> Result<(), RenderBundleErrorInner> {
1048    let max_vertex_buffers = state.device.limits.max_vertex_buffers;
1049    if slot >= max_vertex_buffers {
1050        return Err(RenderCommandError::VertexBufferIndexOutOfRange {
1051            index: slot,
1052            max: max_vertex_buffers,
1053        }
1054        .into());
1055    }
1056
1057    if let Some(buffer) = buffer {
1058        buffer.check_is_valid()?;
1059
1060        state
1061            .trackers
1062            .buffers
1063            .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
1064
1065        buffer.same_device(&state.device)?;
1066        buffer.check_usage(wgt::BufferUsages::VERTEX)?;
1067
1068        if !offset.is_multiple_of(wgt::VERTEX_ALIGNMENT) {
1069            return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
1070        }
1071        let binding_size = buffer.resolve_binding_size(offset, size)?;
1072        let buffer_range = offset..(offset + binding_size);
1073
1074        state
1075            .buffer_memory_init_actions
1076            .extend(buffer.initialization_status.read().create_action(
1077                &buffer,
1078                buffer_range.clone(),
1079                MemoryInitKind::NeedsInitializedMemory,
1080            ));
1081        state.vertex.set_buffer(slot as usize, buffer, buffer_range);
1082        if let Some(pipeline) = state.pipeline.as_deref() {
1083            state.vertex.update_limits(&pipeline.vertex_steps);
1084        }
1085    } else {
1086        if offset != 0 {
1087            return Err(RenderCommandError::from(
1088                crate::binding_model::BindingError::UnbindingVertexBufferOffsetNotZero {
1089                    slot,
1090                    offset,
1091                },
1092            )
1093            .into());
1094        }
1095        if let Some(size) = size {
1096            return Err(RenderCommandError::from(
1097                crate::binding_model::BindingError::UnbindingVertexBufferSizeNotZero {
1098                    slot,
1099                    size: size.get(),
1100                },
1101            )
1102            .into());
1103        }
1104
1105        state.vertex.clear_buffer(slot as usize);
1106        if let Some(pipeline) = state.pipeline.as_deref() {
1107            state.vertex.update_limits(&pipeline.vertex_steps);
1108        }
1109    }
1110
1111    Ok(())
1112}
1113
1114fn set_immediates(
1115    state: &mut State,
1116    offset: u32,
1117    data: &[u32],
1118) -> Result<(), ImmediateUploadError> {
1119    validate_immediates_alignment(offset, size_of_val(data))?;
1120
1121    state
1122        .immediate_state
1123        .set_immediates::<ImmediateUploadError>(&state.device.limits, offset, data)?;
1124    Ok(())
1125}
1126
1127fn draw(
1128    state: &mut State,
1129    vertex_count: u32,
1130    instance_count: u32,
1131    first_vertex: u32,
1132    first_instance: u32,
1133) -> Result<(), RenderBundleErrorInner> {
1134    state.is_ready(DrawCommandFamily::Draw)?;
1135
1136    state
1137        .vertex
1138        .limits
1139        .validate_vertex_limit(first_vertex, vertex_count)?;
1140    state
1141        .vertex
1142        .limits
1143        .validate_instance_limit(first_instance, instance_count)?;
1144
1145    if instance_count > 0 && vertex_count > 0 {
1146        state.flush_vertex_buffers();
1147        state.flush_bindings();
1148        state.flush_immediates();
1149        state.commands.push(ArcRenderCommand::Draw {
1150            vertex_count,
1151            instance_count,
1152            first_vertex,
1153            first_instance,
1154        });
1155    }
1156    Ok(())
1157}
1158
1159fn draw_indexed(
1160    state: &mut State,
1161    index_count: u32,
1162    instance_count: u32,
1163    first_index: u32,
1164    base_vertex: i32,
1165    first_instance: u32,
1166) -> Result<(), RenderBundleErrorInner> {
1167    state.is_ready(DrawCommandFamily::DrawIndexed)?;
1168
1169    let index = state.index.as_ref().unwrap();
1170
1171    let last_index = first_index as u64 + index_count as u64;
1172    let index_limit = index.limit();
1173    if last_index > index_limit {
1174        return Err(DrawError::IndexBeyondLimit {
1175            last_index,
1176            index_limit,
1177        }
1178        .into());
1179    }
1180    state
1181        .vertex
1182        .limits
1183        .validate_instance_limit(first_instance, instance_count)?;
1184
1185    if instance_count > 0 && index_count > 0 {
1186        state.flush_index();
1187        state.flush_vertex_buffers();
1188        state.flush_bindings();
1189        state.flush_immediates();
1190        state.commands.push(ArcRenderCommand::DrawIndexed {
1191            index_count,
1192            instance_count,
1193            first_index,
1194            base_vertex,
1195            first_instance,
1196        });
1197    }
1198    Ok(())
1199}
1200
1201fn draw_mesh_tasks(
1202    state: &mut State,
1203    group_count_x: u32,
1204    group_count_y: u32,
1205    group_count_z: u32,
1206) -> Result<(), RenderBundleErrorInner> {
1207    state.is_ready(DrawCommandFamily::DrawMeshTasks)?;
1208
1209    let limits = &state.device.limits;
1210    let (groups_size_limit, max_groups) = if state.pipeline.as_ref().unwrap().has_task_shader {
1211        (
1212            limits.max_task_workgroups_per_dimension,
1213            limits.max_task_workgroup_total_count,
1214        )
1215    } else {
1216        (
1217            limits.max_mesh_workgroups_per_dimension,
1218            limits.max_mesh_workgroup_total_count,
1219        )
1220    };
1221
1222    let total_count = WorkgroupSizeCheck {
1223        dimensions: &[group_count_x, group_count_y, group_count_z],
1224        per_dimension_limits: &[groups_size_limit, groups_size_limit, groups_size_limit],
1225        per_dimension_limits_desc: "max_task_mesh_workgroups_per_dimension",
1226
1227        total_limit: max_groups,
1228        total_limit_desc: "max_task_mesh_workgroup_total_count",
1229    }
1230    .check_and_compute_total_invocations()
1231    .map_err(|err| RenderBundleErrorInner::Draw(err.into()))?;
1232
1233    if total_count > 0 {
1234        state.flush_bindings();
1235        state.flush_immediates();
1236        state.commands.push(ArcRenderCommand::DrawMeshTasks {
1237            group_count_x,
1238            group_count_y,
1239            group_count_z,
1240        });
1241    }
1242    Ok(())
1243}
1244
1245fn multi_draw_indirect(
1246    state: &mut State,
1247    buffer: Arc<Buffer>,
1248    offset: u64,
1249    family: DrawCommandFamily,
1250) -> Result<(), RenderBundleErrorInner> {
1251    state.is_ready(family)?;
1252    state
1253        .device
1254        .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
1255
1256    buffer.check_is_valid()?;
1257    buffer.same_device(&state.device)?;
1258    buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
1259
1260    if !offset.is_multiple_of(4) {
1261        return Err(RenderCommandError::UnalignedIndirectBufferOffset(offset).into());
1262    }
1263
1264    let stride = super::get_src_stride_of_indirect_args(family);
1265    match offset.checked_add(stride) {
1266        Some(end_offset) if end_offset <= buffer.size => {}
1267        _ => {
1268            return Err(RenderCommandError::IndirectBufferOverrun {
1269                count: 1,
1270                offset,
1271                args_size: stride,
1272                buffer_size: buffer.size,
1273            }
1274            .into());
1275        }
1276    }
1277    state
1278        .buffer_memory_init_actions
1279        .extend(buffer.initialization_status.read().create_action(
1280            &buffer,
1281            offset..(offset + stride),
1282            MemoryInitKind::NeedsInitializedMemory,
1283        ));
1284
1285    let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
1286        let index = state.index.as_mut().unwrap();
1287        state.commands.extend(index.flush());
1288        index.limit()
1289    } else {
1290        state.vertex.limits.vertex_limit
1291    };
1292    let instance_limit = state.vertex.limits.instance_limit;
1293
1294    let buffer_uses = if state.device.indirect_validation.is_some()
1295        && family != DrawCommandFamily::DrawMeshTasks
1296    {
1297        wgt::BufferUses::STORAGE_READ_ONLY
1298    } else {
1299        wgt::BufferUses::INDIRECT
1300    };
1301
1302    state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
1303
1304    state.flush_vertex_buffers();
1305    state.flush_bindings();
1306    state.flush_immediates();
1307    state.commands.push(ArcRenderCommand::DrawIndirect {
1308        buffer,
1309        offset,
1310        count: 1,
1311        family,
1312
1313        vertex_or_index_limit: Some(vertex_or_index_limit),
1314        instance_limit: Some(instance_limit),
1315    });
1316    Ok(())
1317}
1318
1319/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
1320#[derive(Clone, Debug, Error)]
1321#[non_exhaustive]
1322pub enum CreateRenderBundleError {
1323    #[error(transparent)]
1324    ColorAttachment(#[from] ColorAttachmentError),
1325    #[error("Format {0:?} does not have a color aspect")]
1326    FormatNotColor(wgt::TextureFormat),
1327    #[error("Color attachment format {0:?} is not renderable")]
1328    FormatNotRenderable(wgt::TextureFormat),
1329    #[error("Format {0:?} is not a depth/stencil format")]
1330    FormatNotDepthOrStencil(wgt::TextureFormat),
1331    #[error("Render bundle must have at least one attachment (color or depth/stencil)")]
1332    NoAttachment,
1333    #[error("Invalid number of samples {0}")]
1334    InvalidSampleCount(u32),
1335    #[error(transparent)]
1336    MissingFeatures(#[from] MissingFeatures),
1337    #[error(transparent)]
1338    Device(#[from] DeviceError),
1339}
1340
1341impl WebGpuError for CreateRenderBundleError {
1342    fn webgpu_error_type(&self) -> ErrorType {
1343        match self {
1344            Self::ColorAttachment(e) => e.webgpu_error_type(),
1345            Self::FormatNotColor(_)
1346            | Self::FormatNotRenderable(_)
1347            | Self::FormatNotDepthOrStencil(_)
1348            | Self::NoAttachment
1349            | Self::InvalidSampleCount(_) => ErrorType::Validation,
1350            Self::MissingFeatures(e) => e.webgpu_error_type(),
1351            Self::Device(e) => e.webgpu_error_type(),
1352        }
1353    }
1354}
1355
1356/// Error type returned from `RenderBundleEncoder::new` if the sample count is invalid.
1357#[derive(Clone, Debug, Error)]
1358#[non_exhaustive]
1359pub enum ExecutionError {
1360    #[error(transparent)]
1361    Device(#[from] DeviceError),
1362    #[error(transparent)]
1363    DestroyedResource(#[from] DestroyedResourceError),
1364    #[error(transparent)]
1365    InvalidResource(#[from] InvalidResourceError),
1366    #[error("Using {0} in a render bundle is not implemented")]
1367    Unimplemented(&'static str),
1368}
1369
1370impl From<InvalidOrDestroyedResourceError> for ExecutionError {
1371    fn from(e: InvalidOrDestroyedResourceError) -> Self {
1372        match e {
1373            InvalidOrDestroyedResourceError::InvalidResource(e) => Self::InvalidResource(e),
1374            InvalidOrDestroyedResourceError::DestroyedResource(e) => Self::DestroyedResource(e),
1375        }
1376    }
1377}
1378
1379pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
1380
1381#[derive(Debug)]
1382pub(crate) struct RenderBundleState {
1383    pub(crate) used: RenderBundleScope,
1384    pub(super) context: RenderPassContext,
1385}
1386
1387//Note: here, `RenderBundle` is just wrapping a raw stream of render commands.
1388// The plan is to back it by an actual Vulkan secondary buffer, D3D12 Bundle,
1389// or Metal indirect command buffer.
1390/// cbindgen:ignore
1391#[derive(Debug)]
1392pub struct RenderBundle {
1393    pub(crate) state: ResourceState<RenderBundleState>,
1394    // Normalized command stream. It can be executed verbatim,
1395    // without re-binding anything on the pipeline change.
1396    base: BasePass<ArcRenderCommand, Infallible>,
1397    pub(super) is_depth_read_only: bool,
1398    pub(super) is_stencil_read_only: bool,
1399    pub(crate) device: Arc<Device>,
1400    pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1401    pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1402    /// The `label` from the descriptor used to create the resource.
1403    label: String,
1404    pub(crate) tracking_data: TrackingData,
1405    discard_hal_labels: bool,
1406}
1407
1408impl Drop for RenderBundle {
1409    #[expect(trivial_casts)]
1410    fn drop(&mut self) {
1411        profiling::scope!("RenderBundle::drop");
1412        api_log!("RenderBundle::drop {:?}", self as *const _);
1413        resource_log!("Drop {}", self.error_ident());
1414        #[cfg(feature = "trace")]
1415        if let Some(t) = self.device.trace.lock().as_mut() {
1416            use crate::device::trace::{to_trace, Action};
1417
1418            t.add(Action::DropRenderBundle(unsafe { to_trace(self) }));
1419        }
1420    }
1421}
1422
1423#[cfg(send_sync)]
1424unsafe impl Send for RenderBundle {}
1425#[cfg(send_sync)]
1426unsafe impl Sync for RenderBundle {}
1427
1428impl RenderBundle {
1429    pub(crate) fn state(&self) -> Result<&RenderBundleState, InvalidResourceError> {
1430        self.state
1431            .as_ref()
1432            .valid()
1433            .ok_or_else(|| InvalidResourceError(self.error_ident()))
1434    }
1435
1436    pub(crate) fn check_is_valid(&self) -> Result<(), InvalidResourceError> {
1437        self.state().map(|_| ())
1438    }
1439
1440    pub fn invalid(device: Arc<Device>, desc: &RenderBundleDescriptor) -> Arc<Self> {
1441        Arc::new(RenderBundle {
1442            state: ResourceState::Invalid,
1443            base: BasePass {
1444                label: desc.label.as_ref().map(|l| l.to_string()),
1445                error: None,
1446                commands: Vec::new(),
1447                dynamic_offsets: Vec::new(),
1448                string_data: Vec::new(),
1449            },
1450            is_depth_read_only: false,
1451            is_stencil_read_only: false,
1452            buffer_memory_init_actions: Vec::new(),
1453            texture_memory_init_actions: Vec::new(),
1454            label: desc.label.to_string(),
1455            tracking_data: TrackingData::new(device.tracker_indices.bundles.clone()),
1456            discard_hal_labels: false,
1457            device,
1458        })
1459    }
1460
1461    #[cfg(feature = "trace")]
1462    pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
1463        self.base.clone()
1464    }
1465
1466    /// Actually encode the contents into a native command buffer.
1467    ///
1468    /// This is partially duplicating the logic of `render_pass_end`.
1469    /// However the point of this function is to be lighter, since we already had
1470    /// a chance to go through the commands in `render_bundle_encoder_finish`.
1471    ///
1472    /// Note that the function isn't expected to fail, generally.
1473    /// All the validation has already been done by this point.
1474    /// The only failure condition is if some of the used buffers are destroyed.
1475    pub(super) unsafe fn execute(
1476        &self,
1477        raw: &mut dyn hal::DynCommandEncoder,
1478        indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
1479        indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
1480        snatch_guard: &SnatchGuard,
1481    ) -> Result<(), ExecutionError> {
1482        let mut offsets = self.base.dynamic_offsets.as_slice();
1483        let mut pipeline_layout = None::<Arc<PipelineLayout>>;
1484        if !self.discard_hal_labels {
1485            if let Some(ref label) = self.base.label {
1486                unsafe { raw.begin_debug_marker(label) };
1487            }
1488        }
1489
1490        use ArcRenderCommand as Cmd;
1491        for command in self.base.commands.iter() {
1492            match command {
1493                Cmd::SetBindGroup {
1494                    index,
1495                    num_dynamic_offsets,
1496                    bind_group,
1497                } => {
1498                    let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
1499                    unsafe {
1500                        raw.set_bind_group(
1501                            pipeline_layout
1502                                .as_ref()
1503                                .unwrap()
1504                                .raw()
1505                                .expect("PipelineLayout should be valid at this point"),
1506                            *index,
1507                            raw_bg,
1508                            &offsets[..*num_dynamic_offsets],
1509                        )
1510                    };
1511                    offsets = &offsets[*num_dynamic_offsets..];
1512                }
1513                Cmd::SetPipeline(pipeline) => {
1514                    unsafe {
1515                        raw.set_render_pipeline(
1516                            pipeline
1517                                .raw()
1518                                .expect("RenderPipeline should be valid when executing bundle"),
1519                        )
1520                    };
1521
1522                    pipeline_layout = Some(
1523                        pipeline
1524                            .layout()
1525                            .expect("PipelineLayout should be valid when executing bundle")
1526                            .clone(),
1527                    );
1528                }
1529                Cmd::SetIndexBuffer {
1530                    buffer,
1531                    index_format,
1532                    offset,
1533                    size,
1534                } => {
1535                    let buffer = buffer.try_raw(snatch_guard)?;
1536                    // SAFETY: The binding size was checked against the buffer size
1537                    // in `set_index_buffer` and again in `IndexState::flush`.
1538                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1539                    unsafe { raw.set_index_buffer(bb, *index_format) };
1540                }
1541                Cmd::SetVertexBuffer {
1542                    slot,
1543                    buffer,
1544                    offset,
1545                    size,
1546                } => {
1547                    let buffer = buffer.as_ref().unwrap().try_raw(snatch_guard)?;
1548                    // SAFETY: The binding size was checked against the buffer size
1549                    // in `set_vertex_buffer` and again in `VertexState::flush`.
1550                    let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1551                    unsafe { raw.set_vertex_buffer(*slot, bb) };
1552                }
1553                Cmd::SetImmediate { offset, data } => {
1554                    let pipeline_layout = pipeline_layout.as_ref().unwrap();
1555
1556                    // SAFETY: The range of immediates written was validated in `is_ready` before each `flush_immediates`.
1557                    unsafe { raw.set_immediates(pipeline_layout.raw().unwrap(), *offset, data) }
1558                }
1559                Cmd::Draw {
1560                    vertex_count,
1561                    instance_count,
1562                    first_vertex,
1563                    first_instance,
1564                } => {
1565                    unsafe {
1566                        raw.draw(
1567                            *first_vertex,
1568                            *vertex_count,
1569                            *first_instance,
1570                            *instance_count,
1571                        )
1572                    };
1573                }
1574                Cmd::DrawIndexed {
1575                    index_count,
1576                    instance_count,
1577                    first_index,
1578                    base_vertex,
1579                    first_instance,
1580                } => {
1581                    unsafe {
1582                        raw.draw_indexed(
1583                            *first_index,
1584                            *index_count,
1585                            *base_vertex,
1586                            *first_instance,
1587                            *instance_count,
1588                        )
1589                    };
1590                }
1591                Cmd::DrawMeshTasks {
1592                    group_count_x,
1593                    group_count_y,
1594                    group_count_z,
1595                } => unsafe {
1596                    raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1597                },
1598                Cmd::DrawIndirect {
1599                    buffer,
1600                    offset,
1601                    count: 1,
1602                    family,
1603
1604                    vertex_or_index_limit,
1605                    instance_limit,
1606                } => {
1607                    let (buffer, offset) = if self.device.indirect_validation.is_some()
1608                        && *family != DrawCommandFamily::DrawMeshTasks
1609                    {
1610                        let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1611                            indirect_draw_validation_resources,
1612                            &self.device,
1613                            buffer,
1614                            *offset,
1615                            *family,
1616                            vertex_or_index_limit
1617                                .expect("finalized render bundle missing vertex_or_index_limit"),
1618                            instance_limit.expect("finalized render bundle missing instance_limit"),
1619                        )?;
1620
1621                        let dst_buffer =
1622                            indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1623                        (dst_buffer, offset)
1624                    } else {
1625                        (buffer.try_raw(snatch_guard)?, *offset)
1626                    };
1627                    match family {
1628                        DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1629                        DrawCommandFamily::DrawIndexed => unsafe {
1630                            raw.draw_indexed_indirect(buffer, offset, 1)
1631                        },
1632                        DrawCommandFamily::DrawMeshTasks => unsafe {
1633                            raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1634                        },
1635                    }
1636                }
1637                Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1638                    return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1639                }
1640                Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1641                    return Err(ExecutionError::Unimplemented("debug-markers"))
1642                }
1643                Cmd::WriteTimestamp { .. }
1644                | Cmd::BeginOcclusionQuery { .. }
1645                | Cmd::EndOcclusionQuery
1646                | Cmd::BeginPipelineStatisticsQuery { .. }
1647                | Cmd::EndPipelineStatisticsQuery => {
1648                    return Err(ExecutionError::Unimplemented("queries"))
1649                }
1650                Cmd::ExecuteBundle(_)
1651                | Cmd::SetBlendConstant(_)
1652                | Cmd::SetStencilReference(_)
1653                | Cmd::SetViewport { .. }
1654                | Cmd::SetScissor(_) => unreachable!(),
1655            }
1656        }
1657
1658        if !self.discard_hal_labels {
1659            if let Some(_) = self.base.label {
1660                unsafe { raw.end_debug_marker() };
1661            }
1662        }
1663
1664        Ok(())
1665    }
1666}
1667
1668crate::impl_resource_type!(RenderBundle);
1669crate::impl_labeled!(RenderBundle);
1670crate::impl_parent_device!(RenderBundle);
1671crate::impl_storage_item!(RenderBundle);
1672crate::impl_trackable!(RenderBundle);
1673
1674/// A render bundle's current index buffer state.
1675///
1676/// [`RenderBundleEncoder::finish`] records the currently set index buffer here,
1677/// and calls [`State::flush_index`] before any indexed draw command to produce
1678/// a `SetIndexBuffer` command if one is necessary.
1679///
1680/// Binding ranges must be validated against the size of the buffer before
1681/// being stored in `IndexState`.
1682#[derive(Debug)]
1683struct IndexState {
1684    buffer: Arc<Buffer>,
1685    format: wgt::IndexFormat,
1686    range: Range<wgt::BufferAddress>,
1687    is_dirty: bool,
1688}
1689
1690impl IndexState {
1691    /// Return the number of entries in the current index buffer.
1692    ///
1693    /// Panic if no index buffer has been set.
1694    fn limit(&self) -> u64 {
1695        let bytes_per_index = self.format.byte_size() as u64;
1696
1697        (self.range.end - self.range.start) / bytes_per_index
1698    }
1699
1700    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1701    /// command, if needed.
1702    fn flush(&mut self) -> Option<ArcRenderCommand> {
1703        // This was all checked before, but let's check again just in case.
1704        let binding_size = self
1705            .range
1706            .end
1707            .checked_sub(self.range.start)
1708            .filter(|_| self.range.end <= self.buffer.size)
1709            .expect("index range must be contained in buffer");
1710
1711        if self.is_dirty {
1712            self.is_dirty = false;
1713            Some(ArcRenderCommand::SetIndexBuffer {
1714                buffer: self.buffer.clone(),
1715                index_format: self.format,
1716                offset: self.range.start,
1717                size: NonZeroU64::new(binding_size),
1718            })
1719        } else {
1720            None
1721        }
1722    }
1723}
1724
1725/// The state of a single vertex buffer slot during render bundle encoding.
1726///
1727/// [`RenderBundleEncoder::finish`] uses this to drop redundant
1728/// `SetVertexBuffer` commands from the final [`RenderBundle`]. It
1729/// records one vertex buffer slot's state changes here, and then
1730/// calls this type's [`flush`] method just before any draw command to
1731/// produce a `SetVertexBuffer` commands if one is necessary.
1732///
1733/// Binding ranges must be validated against the size of the buffer before
1734/// being stored in `VertexState`.
1735///
1736/// [`flush`]: IndexState::flush
1737#[derive(Debug)]
1738/// State for analyzing and cleaning up bundle command streams.
1739///
1740/// To minimize state updates, [`RenderBundleEncoder::finish`]
1741/// actually just applies commands like [`SetBindGroup`] and
1742/// [`SetIndexBuffer`] to the simulated state stored here, and then
1743/// calls the `flush_foo` methods before draw calls to produce the
1744/// update commands we actually need.
1745///
1746/// [`SetBindGroup`]: RenderCommand::SetBindGroup
1747/// [`SetIndexBuffer`]: RenderCommand::SetIndexBuffer
1748struct State {
1749    /// Resources used by this bundle. This will become [`RenderBundleState::used`].
1750    trackers: RenderBundleScope,
1751
1752    /// The currently set pipeline, if any.
1753    pipeline: Option<Arc<RenderPipeline>>,
1754
1755    /// The state of each vertex buffer slot.
1756    vertex: super::VertexState,
1757
1758    /// The current index buffer, if one has been set. We flush this state
1759    /// before indexed draw commands.
1760    index: Option<IndexState>,
1761
1762    /// Dynamic offset values used by the cleaned-up command sequence.
1763    ///
1764    /// This becomes the final [`RenderBundle`]'s [`BasePass`]'s
1765    /// [`dynamic_offsets`] list.
1766    ///
1767    /// [`dynamic_offsets`]: BasePass::dynamic_offsets
1768    flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1769
1770    device: Arc<Device>,
1771    commands: Vec<ArcRenderCommand>,
1772    buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1773    texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1774    next_dynamic_offset: usize,
1775    binder: Binder,
1776    immediate_state: ImmediateState,
1777}
1778
1779impl State {
1780    /// Set the bundle's current index buffer and its associated parameters.
1781    fn set_index_buffer(
1782        &mut self,
1783        buffer: Arc<Buffer>,
1784        format: wgt::IndexFormat,
1785        range: Range<wgt::BufferAddress>,
1786    ) {
1787        match self.index {
1788            Some(ref current)
1789                if current.buffer.is_equal(&buffer)
1790                    && current.format == format
1791                    && current.range == range =>
1792            {
1793                return
1794            }
1795            _ => (),
1796        }
1797
1798        self.index = Some(IndexState {
1799            buffer,
1800            format,
1801            range,
1802            is_dirty: true,
1803        });
1804    }
1805
1806    fn flush_immediates(&mut self) {
1807        if !self.immediate_state.immediates.is_empty() && self.immediate_state.immediates_dirty {
1808            self.commands.push(ArcRenderCommand::SetImmediate {
1809                offset: 0,
1810                data: self.immediate_state.immediates.clone(),
1811            });
1812            self.immediate_state.immediates_dirty = false;
1813        }
1814    }
1815
1816    /// Generate a `SetIndexBuffer` command to prepare for an indexed draw
1817    /// command, if needed.
1818    fn flush_index(&mut self) {
1819        let commands = self.index.as_mut().and_then(|index| index.flush());
1820        self.commands.extend(commands);
1821    }
1822
1823    fn flush_vertex_buffers(&mut self) {
1824        let vertex = &mut self.vertex;
1825        let commands = &mut self.commands;
1826        vertex.flush(|slot, buffer, offset, size| {
1827            commands.push(ArcRenderCommand::SetVertexBuffer {
1828                slot,
1829                buffer: Some(buffer.clone()),
1830                offset,
1831                size,
1832            });
1833        });
1834    }
1835
1836    /// Validation for a draw command.
1837    ///
1838    /// This should be further deduplicated with similar validation on render/compute passes.
1839    fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1840        if let Some(pipeline) = self.pipeline.as_ref() {
1841            self.binder.check_compatibility(pipeline.as_ref())?;
1842            self.binder.check_late_buffer_bindings()?;
1843
1844            self.vertex.validate(pipeline.as_ref(), &self.binder)?;
1845
1846            if family == DrawCommandFamily::DrawIndexed {
1847                let index_format = match &self.index {
1848                    Some(index) => index.format,
1849                    None => return Err(DrawError::MissingIndexBuffer),
1850                };
1851
1852                if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1853                {
1854                    return Err(DrawError::UnmatchedStripIndexFormat {
1855                        pipeline: pipeline.error_ident(),
1856                        strip_index_format: pipeline.strip_index_format,
1857                        buffer_format: index_format,
1858                    });
1859                }
1860            }
1861
1862            if !self
1863                .immediate_state
1864                .immediate_slots_set
1865                .contains(pipeline.immediate_slots_required)
1866            {
1867                return Err(DrawError::MissingImmediateData {
1868                    missing: pipeline
1869                        .immediate_slots_required
1870                        .difference(self.immediate_state.immediate_slots_set),
1871                });
1872            }
1873
1874            Ok(())
1875        } else {
1876            Err(DrawError::MissingPipeline(pass::MissingPipeline))
1877        }
1878    }
1879
1880    /// Generate `SetBindGroup` commands for any bind groups that need to be updated.
1881    ///
1882    /// This should be further deduplicated with similar code on render/compute passes.
1883    fn flush_bindings(&mut self) {
1884        let start = self.binder.take_rebind_start_index();
1885        let entries = self.binder.list_valid_with_start(start);
1886
1887        self.commands
1888            .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1889                self.buffer_memory_init_actions
1890                    .extend_from_slice(&bind_group.buffer_init_actions);
1891                self.texture_memory_init_actions
1892                    .extend_from_slice(&bind_group.texture_init_actions);
1893
1894                self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1895
1896                ArcRenderCommand::SetBindGroup {
1897                    index: i.try_into().unwrap(),
1898                    bind_group: Some(bind_group.clone()),
1899                    num_dynamic_offsets: dynamic_offsets.len(),
1900                }
1901            }));
1902    }
1903}
1904
1905/// Error encountered when finishing recording a render bundle.
1906#[derive(Clone, Debug, Error)]
1907pub enum RenderBundleErrorInner {
1908    #[error(transparent)]
1909    Create(#[from] CreateRenderBundleError),
1910    #[error(transparent)]
1911    Device(#[from] DeviceError),
1912    #[error(transparent)]
1913    RenderCommand(RenderCommandError),
1914    #[error(transparent)]
1915    Draw(#[from] DrawError),
1916    #[error(transparent)]
1917    MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1918    #[error(transparent)]
1919    Bind(#[from] BindError),
1920    #[error("Render bundle encoder has already ended")]
1921    Ended,
1922}
1923
1924impl<T> From<T> for RenderBundleErrorInner
1925where
1926    T: Into<RenderCommandError>,
1927{
1928    fn from(t: T) -> Self {
1929        Self::RenderCommand(t.into())
1930    }
1931}
1932
1933/// Error encountered when finishing recording a render bundle.
1934#[derive(Clone, Debug, Error)]
1935#[error("{scope}")]
1936pub struct RenderBundleError {
1937    pub scope: PassErrorScope,
1938    #[source]
1939    inner: Box<RenderBundleErrorInner>,
1940}
1941
1942impl WebGpuError for RenderBundleError {
1943    fn webgpu_error_type(&self) -> ErrorType {
1944        match self.inner.as_ref() {
1945            RenderBundleErrorInner::Create(e) => e.webgpu_error_type(),
1946            RenderBundleErrorInner::Device(e) => e.webgpu_error_type(),
1947            RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(),
1948            RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(),
1949            RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
1950            RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(),
1951            RenderBundleErrorInner::Ended => ErrorType::Validation,
1952        }
1953    }
1954}
1955
1956impl RenderBundleError {
1957    pub fn from_device_error(e: DeviceError) -> Self {
1958        Self {
1959            scope: PassErrorScope::Bundle,
1960            inner: Box::new(e.into()),
1961        }
1962    }
1963}
1964
1965impl<E> MapPassErr<RenderBundleError> for E
1966where
1967    E: Into<RenderBundleErrorInner>,
1968{
1969    fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1970        RenderBundleError {
1971            scope,
1972            inner: Box::new(self.into()),
1973        }
1974    }
1975}