1#![allow(clippy::reversed_empty_ranges)]
80
81use alloc::{
82 borrow::{Cow, ToOwned as _},
83 string::String,
84 sync::Arc,
85 vec::Vec,
86};
87use core::{
88 convert::Infallible,
89 num::{NonZeroU32, NonZeroU64},
90 ops::Range,
91};
92
93use arrayvec::ArrayVec;
94use thiserror::Error;
95
96use wgpu_hal::ShouldBeNonZeroExt;
97use wgt::error::{ErrorType, WebGpuError};
98
99#[cfg(feature = "trace")]
100use crate::command::ArcReferences;
101use crate::{
102 binding_model::{BindError, BindGroup, PipelineLayout},
103 command::{
104 bind::Binder, BasePass, BindGroupStateChange, ColorAttachmentError, DrawError,
105 IdReferences, MapPassErr, PassErrorScope, RenderCommand, RenderCommandError, StateChange,
106 },
107 device::{AttachmentData, Device, DeviceError, MissingDownlevelFlags, RenderPassContext},
108 hub::Hub,
109 id,
110 init_tracker::{BufferInitTrackerAction, MemoryInitKind, TextureInitTrackerAction},
111 pipeline::{PipelineFlags, RenderPipeline, VertexStep},
112 resource::{
113 Buffer, DestroyedResourceError, Fallible, InvalidResourceError, Labeled, ParentDevice,
114 RawResourceAccess, TrackingData,
115 },
116 resource_log,
117 snatch::SnatchGuard,
118 track::RenderBundleScope,
119 Label, LabelHelpers,
120};
121
122use super::{pass, render_command::ArcRenderCommand, DrawCommandFamily, DrawKind};
123
124#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
126#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
127pub struct RenderBundleEncoderDescriptor<'a> {
128 pub label: Label<'a>,
132 pub color_formats: Cow<'a, [Option<wgt::TextureFormat>]>,
138 pub depth_stencil: Option<wgt::RenderBundleDepthStencil>,
144 pub sample_count: u32,
148 pub multiview: Option<NonZeroU32>,
151}
152
153#[derive(Debug)]
154#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
155pub struct RenderBundleEncoder {
156 base: BasePass<RenderCommand<IdReferences>, Infallible>,
157 parent_id: id::DeviceId,
158 pub(crate) context: RenderPassContext,
159 pub(crate) is_depth_read_only: bool,
160 pub(crate) is_stencil_read_only: bool,
161
162 #[cfg_attr(feature = "serde", serde(skip))]
164 current_bind_groups: BindGroupStateChange,
165 #[cfg_attr(feature = "serde", serde(skip))]
166 current_pipeline: StateChange<id::RenderPipelineId>,
167}
168
169impl RenderBundleEncoder {
170 pub fn new(
171 desc: &RenderBundleEncoderDescriptor,
172 parent_id: id::DeviceId,
173 ) -> Result<Self, CreateRenderBundleError> {
174 let (is_depth_read_only, is_stencil_read_only) = match desc.depth_stencil {
175 Some(ds) => {
176 let aspects = hal::FormatAspects::from(ds.format);
177 (
178 !aspects.contains(hal::FormatAspects::DEPTH) || ds.depth_read_only,
179 !aspects.contains(hal::FormatAspects::STENCIL) || ds.stencil_read_only,
180 )
181 }
182 None => (true, true),
186 };
187
188 let max_color_attachments = hal::MAX_COLOR_ATTACHMENTS;
190
191 Ok(Self {
194 base: BasePass::new(&desc.label),
195 parent_id,
196 context: RenderPassContext {
197 attachments: AttachmentData {
198 colors: if desc.color_formats.len() > max_color_attachments {
199 return Err(CreateRenderBundleError::ColorAttachment(
200 ColorAttachmentError::TooMany {
201 given: desc.color_formats.len(),
202 limit: max_color_attachments,
203 },
204 ));
205 } else {
206 desc.color_formats.iter().cloned().collect()
207 },
208 resolves: ArrayVec::new(),
209 depth_stencil: desc.depth_stencil.map(|ds| ds.format),
210 },
211 sample_count: {
212 let sc = desc.sample_count;
213 if sc == 0 || sc > 32 || !sc.is_power_of_two() {
214 return Err(CreateRenderBundleError::InvalidSampleCount(sc));
215 }
216 sc
217 },
218 multiview_mask: desc.multiview,
219 },
220
221 is_depth_read_only,
222 is_stencil_read_only,
223 current_bind_groups: BindGroupStateChange::new(),
224 current_pipeline: StateChange::new(),
225 })
226 }
227
228 pub fn dummy(parent_id: id::DeviceId) -> Self {
229 Self {
230 base: BasePass::new(&None),
231 parent_id,
232 context: RenderPassContext {
233 attachments: AttachmentData {
234 colors: ArrayVec::new(),
235 resolves: ArrayVec::new(),
236 depth_stencil: None,
237 },
238 sample_count: 0,
239 multiview_mask: None,
240 },
241 is_depth_read_only: false,
242 is_stencil_read_only: false,
243
244 current_bind_groups: BindGroupStateChange::new(),
245 current_pipeline: StateChange::new(),
246 }
247 }
248
249 pub fn parent(&self) -> id::DeviceId {
250 self.parent_id
251 }
252
253 pub(crate) fn finish(
264 self,
265 desc: &RenderBundleDescriptor,
266 device: &Arc<Device>,
267 hub: &Hub,
268 ) -> Result<Arc<RenderBundle>, RenderBundleError> {
269 let scope = PassErrorScope::Bundle;
270
271 device.check_is_valid().map_pass_err(scope)?;
272
273 let bind_group_guard = hub.bind_groups.read();
274 let pipeline_guard = hub.render_pipelines.read();
275 let buffer_guard = hub.buffers.read();
276
277 let mut state = State {
278 trackers: RenderBundleScope::new(),
279 pipeline: None,
280 vertex: Default::default(),
281 index: None,
282 flat_dynamic_offsets: Vec::new(),
283 device: device.clone(),
284 commands: Vec::new(),
285 buffer_memory_init_actions: Vec::new(),
286 texture_memory_init_actions: Vec::new(),
287 next_dynamic_offset: 0,
288 binder: Binder::new(),
289 };
290
291 let indices = &state.device.tracker_indices;
292 state.trackers.buffers.set_size(indices.buffers.size());
293 state.trackers.textures.set_size(indices.textures.size());
294
295 let base = &self.base;
296
297 for command in &base.commands {
298 match command {
299 &RenderCommand::SetBindGroup {
300 index,
301 num_dynamic_offsets,
302 bind_group,
303 } => {
304 let scope = PassErrorScope::SetBindGroup;
305 set_bind_group(
306 &mut state,
307 &bind_group_guard,
308 &base.dynamic_offsets,
309 index,
310 num_dynamic_offsets,
311 bind_group,
312 )
313 .map_pass_err(scope)?;
314 }
315 &RenderCommand::SetPipeline(pipeline) => {
316 let scope = PassErrorScope::SetPipelineRender;
317 set_pipeline(
318 &mut state,
319 &pipeline_guard,
320 &self.context,
321 self.is_depth_read_only,
322 self.is_stencil_read_only,
323 pipeline,
324 )
325 .map_pass_err(scope)?;
326 }
327 &RenderCommand::SetIndexBuffer {
328 buffer,
329 index_format,
330 offset,
331 size,
332 } => {
333 let scope = PassErrorScope::SetIndexBuffer;
334 set_index_buffer(
335 &mut state,
336 &buffer_guard,
337 buffer,
338 index_format,
339 offset,
340 size,
341 )
342 .map_pass_err(scope)?;
343 }
344 &RenderCommand::SetVertexBuffer {
345 slot,
346 buffer,
347 offset,
348 size,
349 } => {
350 let scope = PassErrorScope::SetVertexBuffer;
351 set_vertex_buffer(&mut state, &buffer_guard, slot, buffer, offset, size)
352 .map_pass_err(scope)?;
353 }
354 &RenderCommand::SetImmediate {
355 offset,
356 size_bytes,
357 values_offset,
358 } => {
359 let scope = PassErrorScope::SetImmediate;
360 set_immediates(&mut state, offset, size_bytes, values_offset)
361 .map_pass_err(scope)?;
362 }
363 &RenderCommand::Draw {
364 vertex_count,
365 instance_count,
366 first_vertex,
367 first_instance,
368 } => {
369 let scope = PassErrorScope::Draw {
370 kind: DrawKind::Draw,
371 family: DrawCommandFamily::Draw,
372 };
373 draw(
374 &mut state,
375 vertex_count,
376 instance_count,
377 first_vertex,
378 first_instance,
379 )
380 .map_pass_err(scope)?;
381 }
382 &RenderCommand::DrawIndexed {
383 index_count,
384 instance_count,
385 first_index,
386 base_vertex,
387 first_instance,
388 } => {
389 let scope = PassErrorScope::Draw {
390 kind: DrawKind::Draw,
391 family: DrawCommandFamily::DrawIndexed,
392 };
393 draw_indexed(
394 &mut state,
395 index_count,
396 instance_count,
397 first_index,
398 base_vertex,
399 first_instance,
400 )
401 .map_pass_err(scope)?;
402 }
403 &RenderCommand::DrawMeshTasks {
404 group_count_x,
405 group_count_y,
406 group_count_z,
407 } => {
408 let scope = PassErrorScope::Draw {
409 kind: DrawKind::Draw,
410 family: DrawCommandFamily::DrawMeshTasks,
411 };
412 draw_mesh_tasks(&mut state, group_count_x, group_count_y, group_count_z)
413 .map_pass_err(scope)?;
414 }
415 &RenderCommand::DrawIndirect {
416 buffer,
417 offset,
418 count: 1,
419 family,
420 vertex_or_index_limit: None,
421 instance_limit: None,
422 } => {
423 let scope = PassErrorScope::Draw {
424 kind: DrawKind::DrawIndirect,
425 family,
426 };
427 multi_draw_indirect(&mut state, &buffer_guard, buffer, offset, family)
428 .map_pass_err(scope)?;
429 }
430 &RenderCommand::DrawIndirect {
431 count,
432 vertex_or_index_limit,
433 instance_limit,
434 ..
435 } => {
436 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");
437 }
438 &RenderCommand::MultiDrawIndirectCount { .. }
439 | &RenderCommand::PushDebugGroup { color: _, len: _ }
440 | &RenderCommand::InsertDebugMarker { color: _, len: _ }
441 | &RenderCommand::PopDebugGroup => {
442 unimplemented!("not supported by a render bundle")
443 }
444 &RenderCommand::WriteTimestamp { .. }
446 | &RenderCommand::BeginOcclusionQuery { .. }
447 | &RenderCommand::EndOcclusionQuery
448 | &RenderCommand::BeginPipelineStatisticsQuery { .. }
449 | &RenderCommand::EndPipelineStatisticsQuery => {
450 unimplemented!("not supported by a render bundle")
451 }
452 &RenderCommand::ExecuteBundle(_)
453 | &RenderCommand::SetBlendConstant(_)
454 | &RenderCommand::SetStencilReference(_)
455 | &RenderCommand::SetViewport { .. }
456 | &RenderCommand::SetScissor(_) => unreachable!("not supported by a render bundle"),
457 }
458 }
459
460 let State {
461 trackers,
462 flat_dynamic_offsets,
463 device,
464 commands,
465 buffer_memory_init_actions,
466 texture_memory_init_actions,
467 ..
468 } = state;
469
470 let tracker_indices = device.tracker_indices.bundles.clone();
471 let discard_hal_labels = device
472 .instance_flags
473 .contains(wgt::InstanceFlags::DISCARD_HAL_LABELS);
474
475 let render_bundle = RenderBundle {
476 base: BasePass {
477 label: desc.label.as_deref().map(str::to_owned),
478 error: None,
479 commands,
480 dynamic_offsets: flat_dynamic_offsets,
481 string_data: self.base.string_data,
482 immediates_data: self.base.immediates_data,
483 },
484 is_depth_read_only: self.is_depth_read_only,
485 is_stencil_read_only: self.is_stencil_read_only,
486 device: device.clone(),
487 used: trackers,
488 buffer_memory_init_actions,
489 texture_memory_init_actions,
490 context: self.context,
491 label: desc.label.to_string(),
492 tracking_data: TrackingData::new(tracker_indices),
493 discard_hal_labels,
494 };
495
496 let render_bundle = Arc::new(render_bundle);
497
498 Ok(render_bundle)
499 }
500
501 pub fn set_index_buffer(
502 &mut self,
503 buffer: id::BufferId,
504 index_format: wgt::IndexFormat,
505 offset: wgt::BufferAddress,
506 size: Option<wgt::BufferSize>,
507 ) {
508 self.base.commands.push(RenderCommand::SetIndexBuffer {
509 buffer,
510 index_format,
511 offset,
512 size,
513 });
514 }
515}
516
517fn set_bind_group(
518 state: &mut State,
519 bind_group_guard: &crate::storage::Storage<Fallible<BindGroup>>,
520 dynamic_offsets: &[u32],
521 index: u32,
522 num_dynamic_offsets: usize,
523 bind_group_id: Option<id::Id<id::markers::BindGroup>>,
524) -> Result<(), RenderBundleErrorInner> {
525 let max_bind_groups = state.device.limits.max_bind_groups;
526 if index >= max_bind_groups {
527 return Err(
528 RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
529 index,
530 max: max_bind_groups,
531 })
532 .into(),
533 );
534 }
535
536 let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
538 state.next_dynamic_offset = offsets_range.end;
539 let offsets = &dynamic_offsets[offsets_range.clone()];
540
541 let bind_group = bind_group_id.map(|id| bind_group_guard.get(id));
542
543 if let Some(bind_group) = bind_group {
544 let bind_group = bind_group.get()?;
545 bind_group.same_device(&state.device)?;
546 bind_group.validate_dynamic_bindings(index, offsets)?;
547
548 unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
549 let bind_group = state.trackers.bind_groups.insert_single(bind_group);
550
551 state
552 .binder
553 .assign_group(index as usize, bind_group, offsets);
554 } else {
555 if !offsets.is_empty() {
556 return Err(RenderBundleErrorInner::Bind(
557 BindError::DynamicOffsetCountNotZero {
558 group: index,
559 actual: offsets.len(),
560 },
561 ));
562 }
563
564 state.binder.clear_group(index as usize);
565 }
566
567 Ok(())
568}
569
570fn set_pipeline(
571 state: &mut State,
572 pipeline_guard: &crate::storage::Storage<Fallible<RenderPipeline>>,
573 context: &RenderPassContext,
574 is_depth_read_only: bool,
575 is_stencil_read_only: bool,
576 pipeline_id: id::Id<id::markers::RenderPipeline>,
577) -> Result<(), RenderBundleErrorInner> {
578 let pipeline = pipeline_guard.get(pipeline_id).get()?;
579
580 pipeline.same_device(&state.device)?;
581
582 context
583 .check_compatible(&pipeline.pass_context, pipeline.as_ref())
584 .map_err(RenderCommandError::IncompatiblePipelineTargets)?;
585
586 if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
587 return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
588 }
589 if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
590 return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
591 }
592
593 let pipeline_state = PipelineState::new(&pipeline);
594
595 state
596 .commands
597 .push(ArcRenderCommand::SetPipeline(pipeline.clone()));
598
599 if let Some(cmd) = pipeline_state.zero_immediates() {
601 state.commands.push(cmd);
602 }
603
604 state.pipeline = Some(pipeline_state);
605
606 state
607 .binder
608 .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups);
609
610 state.trackers.render_pipelines.insert_single(pipeline);
611 Ok(())
612}
613
614fn set_index_buffer(
616 state: &mut State,
617 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
618 buffer_id: id::Id<id::markers::Buffer>,
619 index_format: wgt::IndexFormat,
620 offset: u64,
621 size: Option<NonZeroU64>,
622) -> Result<(), RenderBundleErrorInner> {
623 let buffer = buffer_guard.get(buffer_id).get()?;
624
625 state
626 .trackers
627 .buffers
628 .merge_single(&buffer, wgt::BufferUses::INDEX)?;
629
630 buffer.same_device(&state.device)?;
631 buffer.check_usage(wgt::BufferUsages::INDEX)?;
632
633 if !offset.is_multiple_of(u64::try_from(index_format.byte_size()).unwrap()) {
634 return Err(RenderCommandError::UnalignedIndexBuffer {
635 offset,
636 alignment: index_format.byte_size(),
637 }
638 .into());
639 }
640 let end = offset + buffer.resolve_binding_size(offset, size)?;
641
642 state
643 .buffer_memory_init_actions
644 .extend(buffer.initialization_status.read().create_action(
645 &buffer,
646 offset..end.get(),
647 MemoryInitKind::NeedsInitializedMemory,
648 ));
649 state.set_index_buffer(buffer, index_format, offset..end.get());
650 Ok(())
651}
652
653fn set_vertex_buffer(
655 state: &mut State,
656 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
657 slot: u32,
658 buffer_id: id::Id<id::markers::Buffer>,
659 offset: u64,
660 size: Option<NonZeroU64>,
661) -> Result<(), RenderBundleErrorInner> {
662 let max_vertex_buffers = state.device.limits.max_vertex_buffers;
663 if slot >= max_vertex_buffers {
664 return Err(RenderCommandError::VertexBufferIndexOutOfRange {
665 index: slot,
666 max: max_vertex_buffers,
667 }
668 .into());
669 }
670
671 let buffer = buffer_guard.get(buffer_id).get()?;
672
673 state
674 .trackers
675 .buffers
676 .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
677
678 buffer.same_device(&state.device)?;
679 buffer.check_usage(wgt::BufferUsages::VERTEX)?;
680
681 if !offset.is_multiple_of(wgt::VERTEX_ALIGNMENT) {
682 return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
683 }
684 let end = offset + buffer.resolve_binding_size(offset, size)?;
685
686 state
687 .buffer_memory_init_actions
688 .extend(buffer.initialization_status.read().create_action(
689 &buffer,
690 offset..end.get(),
691 MemoryInitKind::NeedsInitializedMemory,
692 ));
693 state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get()));
694 Ok(())
695}
696
697fn set_immediates(
698 state: &mut State,
699 offset: u32,
700 size_bytes: u32,
701 values_offset: Option<u32>,
702) -> Result<(), RenderBundleErrorInner> {
703 let end_offset = offset + size_bytes;
704
705 let pipeline_state = state.pipeline()?;
706
707 pipeline_state
708 .pipeline
709 .layout
710 .validate_immediates_ranges(offset, end_offset)?;
711
712 state.commands.push(ArcRenderCommand::SetImmediate {
713 offset,
714 size_bytes,
715 values_offset,
716 });
717 Ok(())
718}
719
720fn draw(
721 state: &mut State,
722 vertex_count: u32,
723 instance_count: u32,
724 first_vertex: u32,
725 first_instance: u32,
726) -> Result<(), RenderBundleErrorInner> {
727 state.is_ready(DrawCommandFamily::Draw)?;
728 let pipeline = state.pipeline()?;
729
730 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
731 vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?;
732 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
733
734 if instance_count > 0 && vertex_count > 0 {
735 state.flush_vertices();
736 state.flush_bindings();
737 state.commands.push(ArcRenderCommand::Draw {
738 vertex_count,
739 instance_count,
740 first_vertex,
741 first_instance,
742 });
743 }
744 Ok(())
745}
746
747fn draw_indexed(
748 state: &mut State,
749 index_count: u32,
750 instance_count: u32,
751 first_index: u32,
752 base_vertex: i32,
753 first_instance: u32,
754) -> Result<(), RenderBundleErrorInner> {
755 state.is_ready(DrawCommandFamily::DrawIndexed)?;
756 let pipeline = state.pipeline()?;
757
758 let index = state.index.as_ref().unwrap();
759
760 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
761
762 let last_index = first_index as u64 + index_count as u64;
763 let index_limit = index.limit();
764 if last_index > index_limit {
765 return Err(DrawError::IndexBeyondLimit {
766 last_index,
767 index_limit,
768 }
769 .into());
770 }
771 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
772
773 if instance_count > 0 && index_count > 0 {
774 state.flush_index();
775 state.flush_vertices();
776 state.flush_bindings();
777 state.commands.push(ArcRenderCommand::DrawIndexed {
778 index_count,
779 instance_count,
780 first_index,
781 base_vertex,
782 first_instance,
783 });
784 }
785 Ok(())
786}
787
788fn draw_mesh_tasks(
789 state: &mut State,
790 group_count_x: u32,
791 group_count_y: u32,
792 group_count_z: u32,
793) -> Result<(), RenderBundleErrorInner> {
794 state.is_ready(DrawCommandFamily::DrawMeshTasks)?;
795
796 let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension;
797 let max_groups = state.device.limits.max_task_mesh_workgroup_total_count;
798 if group_count_x > groups_size_limit
799 || group_count_y > groups_size_limit
800 || group_count_z > groups_size_limit
801 || group_count_x * group_count_y * group_count_z > max_groups
802 {
803 return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize {
804 current: [group_count_x, group_count_y, group_count_z],
805 limit: groups_size_limit,
806 max_total: max_groups,
807 }));
808 }
809
810 if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 {
811 state.flush_bindings();
812 state.commands.push(ArcRenderCommand::DrawMeshTasks {
813 group_count_x,
814 group_count_y,
815 group_count_z,
816 });
817 }
818 Ok(())
819}
820
821fn multi_draw_indirect(
822 state: &mut State,
823 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
824 buffer_id: id::Id<id::markers::Buffer>,
825 offset: u64,
826 family: DrawCommandFamily,
827) -> Result<(), RenderBundleErrorInner> {
828 state.is_ready(family)?;
829 state
830 .device
831 .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
832
833 let pipeline = state.pipeline()?;
834
835 let buffer = buffer_guard.get(buffer_id).get()?;
836
837 buffer.same_device(&state.device)?;
838 buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
839
840 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
841
842 let stride = super::get_stride_of_indirect_args(family);
843 state
844 .buffer_memory_init_actions
845 .extend(buffer.initialization_status.read().create_action(
846 &buffer,
847 offset..(offset + stride),
848 MemoryInitKind::NeedsInitializedMemory,
849 ));
850
851 let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
852 let index = state.index.as_mut().unwrap();
853 state.commands.extend(index.flush());
854 index.limit()
855 } else {
856 vertex_limits.vertex_limit
857 };
858 let instance_limit = vertex_limits.instance_limit;
859
860 let buffer_uses = if state.device.indirect_validation.is_some() {
861 wgt::BufferUses::STORAGE_READ_ONLY
862 } else {
863 wgt::BufferUses::INDIRECT
864 };
865
866 state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
867
868 state.flush_vertices();
869 state.flush_bindings();
870 state.commands.push(ArcRenderCommand::DrawIndirect {
871 buffer,
872 offset,
873 count: 1,
874 family,
875
876 vertex_or_index_limit: Some(vertex_or_index_limit),
877 instance_limit: Some(instance_limit),
878 });
879 Ok(())
880}
881
882#[derive(Clone, Debug, Error)]
884#[non_exhaustive]
885pub enum CreateRenderBundleError {
886 #[error(transparent)]
887 ColorAttachment(#[from] ColorAttachmentError),
888 #[error("Invalid number of samples {0}")]
889 InvalidSampleCount(u32),
890}
891
892impl WebGpuError for CreateRenderBundleError {
893 fn webgpu_error_type(&self) -> ErrorType {
894 match self {
895 Self::ColorAttachment(e) => e.webgpu_error_type(),
896 Self::InvalidSampleCount(_) => ErrorType::Validation,
897 }
898 }
899}
900
901#[derive(Clone, Debug, Error)]
903#[non_exhaustive]
904pub enum ExecutionError {
905 #[error(transparent)]
906 Device(#[from] DeviceError),
907 #[error(transparent)]
908 DestroyedResource(#[from] DestroyedResourceError),
909 #[error("Using {0} in a render bundle is not implemented")]
910 Unimplemented(&'static str),
911}
912
913pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
914
915#[derive(Debug)]
920pub struct RenderBundle {
921 base: BasePass<ArcRenderCommand, Infallible>,
924 pub(super) is_depth_read_only: bool,
925 pub(super) is_stencil_read_only: bool,
926 pub(crate) device: Arc<Device>,
927 pub(crate) used: RenderBundleScope,
928 pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
929 pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
930 pub(super) context: RenderPassContext,
931 label: String,
933 pub(crate) tracking_data: TrackingData,
934 discard_hal_labels: bool,
935}
936
937impl Drop for RenderBundle {
938 fn drop(&mut self) {
939 resource_log!("Drop {}", self.error_ident());
940 }
941}
942
943#[cfg(send_sync)]
944unsafe impl Send for RenderBundle {}
945#[cfg(send_sync)]
946unsafe impl Sync for RenderBundle {}
947
948impl RenderBundle {
949 #[cfg(feature = "trace")]
950 pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
951 self.base.clone()
952 }
953
954 pub(super) unsafe fn execute(
964 &self,
965 raw: &mut dyn hal::DynCommandEncoder,
966 indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
967 indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
968 snatch_guard: &SnatchGuard,
969 ) -> Result<(), ExecutionError> {
970 let mut offsets = self.base.dynamic_offsets.as_slice();
971 let mut pipeline_layout = None::<Arc<PipelineLayout>>;
972 if !self.discard_hal_labels {
973 if let Some(ref label) = self.base.label {
974 unsafe { raw.begin_debug_marker(label) };
975 }
976 }
977
978 use ArcRenderCommand as Cmd;
979 for command in self.base.commands.iter() {
980 match command {
981 Cmd::SetBindGroup {
982 index,
983 num_dynamic_offsets,
984 bind_group,
985 } => {
986 let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
987 unsafe {
988 raw.set_bind_group(
989 pipeline_layout.as_ref().unwrap().raw(),
990 *index,
991 raw_bg,
992 &offsets[..*num_dynamic_offsets],
993 )
994 };
995 offsets = &offsets[*num_dynamic_offsets..];
996 }
997 Cmd::SetPipeline(pipeline) => {
998 unsafe { raw.set_render_pipeline(pipeline.raw()) };
999
1000 pipeline_layout = Some(pipeline.layout.clone());
1001 }
1002 Cmd::SetIndexBuffer {
1003 buffer,
1004 index_format,
1005 offset,
1006 size,
1007 } => {
1008 let buffer = buffer.try_raw(snatch_guard)?;
1009 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1012 unsafe { raw.set_index_buffer(bb, *index_format) };
1013 }
1014 Cmd::SetVertexBuffer {
1015 slot,
1016 buffer,
1017 offset,
1018 size,
1019 } => {
1020 let buffer = buffer.try_raw(snatch_guard)?;
1021 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1024 unsafe { raw.set_vertex_buffer(*slot, bb) };
1025 }
1026 Cmd::SetImmediate {
1027 offset,
1028 size_bytes,
1029 values_offset,
1030 } => {
1031 let pipeline_layout = pipeline_layout.as_ref().unwrap();
1032
1033 if let Some(values_offset) = *values_offset {
1034 let values_end_offset =
1035 (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1036 let data_slice =
1037 &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1038
1039 unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1040 } else {
1041 super::immediates_clear(
1042 *offset,
1043 *size_bytes,
1044 |clear_offset, clear_data| {
1045 unsafe {
1046 raw.set_immediates(
1047 pipeline_layout.raw(),
1048 clear_offset,
1049 clear_data,
1050 )
1051 };
1052 },
1053 );
1054 }
1055 }
1056 Cmd::Draw {
1057 vertex_count,
1058 instance_count,
1059 first_vertex,
1060 first_instance,
1061 } => {
1062 unsafe {
1063 raw.draw(
1064 *first_vertex,
1065 *vertex_count,
1066 *first_instance,
1067 *instance_count,
1068 )
1069 };
1070 }
1071 Cmd::DrawIndexed {
1072 index_count,
1073 instance_count,
1074 first_index,
1075 base_vertex,
1076 first_instance,
1077 } => {
1078 unsafe {
1079 raw.draw_indexed(
1080 *first_index,
1081 *index_count,
1082 *base_vertex,
1083 *first_instance,
1084 *instance_count,
1085 )
1086 };
1087 }
1088 Cmd::DrawMeshTasks {
1089 group_count_x,
1090 group_count_y,
1091 group_count_z,
1092 } => unsafe {
1093 raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1094 },
1095 Cmd::DrawIndirect {
1096 buffer,
1097 offset,
1098 count: 1,
1099 family,
1100
1101 vertex_or_index_limit,
1102 instance_limit,
1103 } => {
1104 let (buffer, offset) = if self.device.indirect_validation.is_some() {
1105 let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1106 indirect_draw_validation_resources,
1107 &self.device,
1108 buffer,
1109 *offset,
1110 *family,
1111 vertex_or_index_limit
1112 .expect("finalized render bundle missing vertex_or_index_limit"),
1113 instance_limit.expect("finalized render bundle missing instance_limit"),
1114 )?;
1115
1116 let dst_buffer =
1117 indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1118 (dst_buffer, offset)
1119 } else {
1120 (buffer.try_raw(snatch_guard)?, *offset)
1121 };
1122 match family {
1123 DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1124 DrawCommandFamily::DrawIndexed => unsafe {
1125 raw.draw_indexed_indirect(buffer, offset, 1)
1126 },
1127 DrawCommandFamily::DrawMeshTasks => unsafe {
1128 raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1129 },
1130 }
1131 }
1132 Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1133 return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1134 }
1135 Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1136 return Err(ExecutionError::Unimplemented("debug-markers"))
1137 }
1138 Cmd::WriteTimestamp { .. }
1139 | Cmd::BeginOcclusionQuery { .. }
1140 | Cmd::EndOcclusionQuery
1141 | Cmd::BeginPipelineStatisticsQuery { .. }
1142 | Cmd::EndPipelineStatisticsQuery => {
1143 return Err(ExecutionError::Unimplemented("queries"))
1144 }
1145 Cmd::ExecuteBundle(_)
1146 | Cmd::SetBlendConstant(_)
1147 | Cmd::SetStencilReference(_)
1148 | Cmd::SetViewport { .. }
1149 | Cmd::SetScissor(_) => unreachable!(),
1150 }
1151 }
1152
1153 if !self.discard_hal_labels {
1154 if let Some(_) = self.base.label {
1155 unsafe { raw.end_debug_marker() };
1156 }
1157 }
1158
1159 Ok(())
1160 }
1161}
1162
1163crate::impl_resource_type!(RenderBundle);
1164crate::impl_labeled!(RenderBundle);
1165crate::impl_parent_device!(RenderBundle);
1166crate::impl_storage_item!(RenderBundle);
1167crate::impl_trackable!(RenderBundle);
1168
1169#[derive(Debug)]
1178struct IndexState {
1179 buffer: Arc<Buffer>,
1180 format: wgt::IndexFormat,
1181 range: Range<wgt::BufferAddress>,
1182 is_dirty: bool,
1183}
1184
1185impl IndexState {
1186 fn limit(&self) -> u64 {
1190 let bytes_per_index = self.format.byte_size() as u64;
1191
1192 (self.range.end - self.range.start) / bytes_per_index
1193 }
1194
1195 fn flush(&mut self) -> Option<ArcRenderCommand> {
1198 let binding_size = self
1200 .range
1201 .end
1202 .checked_sub(self.range.start)
1203 .filter(|_| self.range.end <= self.buffer.size)
1204 .expect("index range must be contained in buffer");
1205
1206 if self.is_dirty {
1207 self.is_dirty = false;
1208 Some(ArcRenderCommand::SetIndexBuffer {
1209 buffer: self.buffer.clone(),
1210 index_format: self.format,
1211 offset: self.range.start,
1212 size: NonZeroU64::new(binding_size),
1213 })
1214 } else {
1215 None
1216 }
1217 }
1218}
1219
1220#[derive(Debug)]
1233struct VertexState {
1234 buffer: Arc<Buffer>,
1235 range: Range<wgt::BufferAddress>,
1236 is_dirty: bool,
1237}
1238
1239impl VertexState {
1240 fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1244 Self {
1245 buffer,
1246 range,
1247 is_dirty: true,
1248 }
1249 }
1250
1251 fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1255 let binding_size = self
1256 .range
1257 .end
1258 .checked_sub(self.range.start)
1259 .filter(|_| self.range.end <= self.buffer.size)
1260 .expect("vertex range must be contained in buffer");
1261
1262 if self.is_dirty {
1263 self.is_dirty = false;
1264 Some(ArcRenderCommand::SetVertexBuffer {
1265 slot,
1266 buffer: self.buffer.clone(),
1267 offset: self.range.start,
1268 size: NonZeroU64::new(binding_size),
1269 })
1270 } else {
1271 None
1272 }
1273 }
1274}
1275
1276struct PipelineState {
1278 pipeline: Arc<RenderPipeline>,
1280
1281 steps: Vec<VertexStep>,
1284
1285 immediate_size: u32,
1287}
1288
1289impl PipelineState {
1290 fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1291 Self {
1292 pipeline: pipeline.clone(),
1293 steps: pipeline.vertex_steps.to_vec(),
1294 immediate_size: pipeline.layout.immediate_size,
1295 }
1296 }
1297
1298 fn zero_immediates(&self) -> Option<ArcRenderCommand> {
1301 if self.immediate_size == 0 {
1302 return None;
1303 }
1304
1305 Some(ArcRenderCommand::SetImmediate {
1306 offset: 0,
1307 size_bytes: self.immediate_size,
1308 values_offset: None,
1309 })
1310 }
1311}
1312
1313struct State {
1324 trackers: RenderBundleScope,
1326
1327 pipeline: Option<PipelineState>,
1329
1330 vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS],
1332
1333 index: Option<IndexState>,
1336
1337 flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1344
1345 device: Arc<Device>,
1346 commands: Vec<ArcRenderCommand>,
1347 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1348 texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1349 next_dynamic_offset: usize,
1350 binder: Binder,
1351}
1352
1353impl State {
1354 fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1356 self.pipeline
1357 .as_ref()
1358 .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into())
1359 }
1360
1361 fn set_index_buffer(
1363 &mut self,
1364 buffer: Arc<Buffer>,
1365 format: wgt::IndexFormat,
1366 range: Range<wgt::BufferAddress>,
1367 ) {
1368 match self.index {
1369 Some(ref current)
1370 if current.buffer.is_equal(&buffer)
1371 && current.format == format
1372 && current.range == range =>
1373 {
1374 return
1375 }
1376 _ => (),
1377 }
1378
1379 self.index = Some(IndexState {
1380 buffer,
1381 format,
1382 range,
1383 is_dirty: true,
1384 });
1385 }
1386
1387 fn flush_index(&mut self) {
1390 let commands = self.index.as_mut().and_then(|index| index.flush());
1391 self.commands.extend(commands);
1392 }
1393
1394 fn flush_vertices(&mut self) {
1395 let commands = self
1396 .vertex
1397 .iter_mut()
1398 .enumerate()
1399 .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1400 self.commands.extend(commands);
1401 }
1402
1403 fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1407 if let Some(pipeline) = self.pipeline.as_ref() {
1408 self.binder
1409 .check_compatibility(pipeline.pipeline.as_ref())?;
1410 self.binder.check_late_buffer_bindings()?;
1411
1412 if family == DrawCommandFamily::DrawIndexed {
1413 let pipeline = &pipeline.pipeline;
1414 let index_format = match &self.index {
1415 Some(index) => index.format,
1416 None => return Err(DrawError::MissingIndexBuffer),
1417 };
1418
1419 if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1420 {
1421 return Err(DrawError::UnmatchedStripIndexFormat {
1422 pipeline: pipeline.error_ident(),
1423 strip_index_format: pipeline.strip_index_format,
1424 buffer_format: index_format,
1425 });
1426 }
1427 }
1428
1429 Ok(())
1430 } else {
1431 Err(DrawError::MissingPipeline(pass::MissingPipeline))
1432 }
1433 }
1434
1435 fn flush_bindings(&mut self) {
1439 let start = self.binder.take_rebind_start_index();
1440 let entries = self.binder.list_valid_with_start(start);
1441
1442 self.commands
1443 .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1444 self.buffer_memory_init_actions
1445 .extend_from_slice(&bind_group.used_buffer_ranges);
1446 self.texture_memory_init_actions
1447 .extend_from_slice(&bind_group.used_texture_ranges);
1448
1449 self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1450
1451 ArcRenderCommand::SetBindGroup {
1452 index: i.try_into().unwrap(),
1453 bind_group: Some(bind_group.clone()),
1454 num_dynamic_offsets: dynamic_offsets.len(),
1455 }
1456 }));
1457 }
1458
1459 fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ {
1460 self.vertex
1461 .iter()
1462 .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start))
1463 }
1464}
1465
1466#[derive(Clone, Debug, Error)]
1468pub enum RenderBundleErrorInner {
1469 #[error(transparent)]
1470 Device(#[from] DeviceError),
1471 #[error(transparent)]
1472 RenderCommand(RenderCommandError),
1473 #[error(transparent)]
1474 Draw(#[from] DrawError),
1475 #[error(transparent)]
1476 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1477 #[error(transparent)]
1478 Bind(#[from] BindError),
1479 #[error(transparent)]
1480 InvalidResource(#[from] InvalidResourceError),
1481}
1482
1483impl<T> From<T> for RenderBundleErrorInner
1484where
1485 T: Into<RenderCommandError>,
1486{
1487 fn from(t: T) -> Self {
1488 Self::RenderCommand(t.into())
1489 }
1490}
1491
1492#[derive(Clone, Debug, Error)]
1494#[error("{scope}")]
1495pub struct RenderBundleError {
1496 pub scope: PassErrorScope,
1497 #[source]
1498 inner: RenderBundleErrorInner,
1499}
1500
1501impl WebGpuError for RenderBundleError {
1502 fn webgpu_error_type(&self) -> ErrorType {
1503 let Self { scope: _, inner } = self;
1504 let e: &dyn WebGpuError = match inner {
1505 RenderBundleErrorInner::Device(e) => e,
1506 RenderBundleErrorInner::RenderCommand(e) => e,
1507 RenderBundleErrorInner::Draw(e) => e,
1508 RenderBundleErrorInner::MissingDownlevelFlags(e) => e,
1509 RenderBundleErrorInner::Bind(e) => e,
1510 RenderBundleErrorInner::InvalidResource(e) => e,
1511 };
1512 e.webgpu_error_type()
1513 }
1514}
1515
1516impl RenderBundleError {
1517 pub fn from_device_error(e: DeviceError) -> Self {
1518 Self {
1519 scope: PassErrorScope::Bundle,
1520 inner: e.into(),
1521 }
1522 }
1523}
1524
1525impl<E> MapPassErr<RenderBundleError> for E
1526where
1527 E: Into<RenderBundleErrorInner>,
1528{
1529 fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1530 RenderBundleError {
1531 scope,
1532 inner: self.into(),
1533 }
1534 }
1535}
1536
1537pub mod bundle_ffi {
1538 use super::{RenderBundleEncoder, RenderCommand};
1539 use crate::{command::DrawCommandFamily, id, RawString};
1540 use core::{convert::TryInto, slice};
1541 use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1542
1543 pub unsafe fn wgpu_render_bundle_set_bind_group(
1548 bundle: &mut RenderBundleEncoder,
1549 index: u32,
1550 bind_group_id: Option<id::BindGroupId>,
1551 offsets: *const DynamicOffset,
1552 offset_length: usize,
1553 ) {
1554 let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1555
1556 let redundant = bundle.current_bind_groups.set_and_check_redundant(
1557 bind_group_id,
1558 index,
1559 &mut bundle.base.dynamic_offsets,
1560 offsets,
1561 );
1562
1563 if redundant {
1564 return;
1565 }
1566
1567 bundle.base.commands.push(RenderCommand::SetBindGroup {
1568 index,
1569 num_dynamic_offsets: offset_length,
1570 bind_group: bind_group_id,
1571 });
1572 }
1573
1574 pub fn wgpu_render_bundle_set_pipeline(
1575 bundle: &mut RenderBundleEncoder,
1576 pipeline_id: id::RenderPipelineId,
1577 ) {
1578 if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1579 return;
1580 }
1581
1582 bundle
1583 .base
1584 .commands
1585 .push(RenderCommand::SetPipeline(pipeline_id));
1586 }
1587
1588 pub fn wgpu_render_bundle_set_vertex_buffer(
1589 bundle: &mut RenderBundleEncoder,
1590 slot: u32,
1591 buffer_id: id::BufferId,
1592 offset: BufferAddress,
1593 size: Option<BufferSize>,
1594 ) {
1595 bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1596 slot,
1597 buffer: buffer_id,
1598 offset,
1599 size,
1600 });
1601 }
1602
1603 pub fn wgpu_render_bundle_set_index_buffer(
1604 encoder: &mut RenderBundleEncoder,
1605 buffer: id::BufferId,
1606 index_format: IndexFormat,
1607 offset: BufferAddress,
1608 size: Option<BufferSize>,
1609 ) {
1610 encoder.set_index_buffer(buffer, index_format, offset, size);
1611 }
1612
1613 pub unsafe fn wgpu_render_bundle_set_immediates(
1618 pass: &mut RenderBundleEncoder,
1619 offset: u32,
1620 size_bytes: u32,
1621 data: *const u8,
1622 ) {
1623 assert_eq!(
1624 offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1625 0,
1626 "Immediate data offset must be aligned to 4 bytes."
1627 );
1628 assert_eq!(
1629 size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1630 0,
1631 "Immediate data size must be aligned to 4 bytes."
1632 );
1633 let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1634 let value_offset = pass.base.immediates_data.len().try_into().expect(
1635 "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1636 );
1637
1638 pass.base.immediates_data.extend(
1639 data_slice
1640 .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1641 .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1642 );
1643
1644 pass.base.commands.push(RenderCommand::SetImmediate {
1645 offset,
1646 size_bytes,
1647 values_offset: Some(value_offset),
1648 });
1649 }
1650
1651 pub fn wgpu_render_bundle_draw(
1652 bundle: &mut RenderBundleEncoder,
1653 vertex_count: u32,
1654 instance_count: u32,
1655 first_vertex: u32,
1656 first_instance: u32,
1657 ) {
1658 bundle.base.commands.push(RenderCommand::Draw {
1659 vertex_count,
1660 instance_count,
1661 first_vertex,
1662 first_instance,
1663 });
1664 }
1665
1666 pub fn wgpu_render_bundle_draw_indexed(
1667 bundle: &mut RenderBundleEncoder,
1668 index_count: u32,
1669 instance_count: u32,
1670 first_index: u32,
1671 base_vertex: i32,
1672 first_instance: u32,
1673 ) {
1674 bundle.base.commands.push(RenderCommand::DrawIndexed {
1675 index_count,
1676 instance_count,
1677 first_index,
1678 base_vertex,
1679 first_instance,
1680 });
1681 }
1682
1683 pub fn wgpu_render_bundle_draw_indirect(
1684 bundle: &mut RenderBundleEncoder,
1685 buffer_id: id::BufferId,
1686 offset: BufferAddress,
1687 ) {
1688 bundle.base.commands.push(RenderCommand::DrawIndirect {
1689 buffer: buffer_id,
1690 offset,
1691 count: 1,
1692 family: DrawCommandFamily::Draw,
1693 vertex_or_index_limit: None,
1694 instance_limit: None,
1695 });
1696 }
1697
1698 pub fn wgpu_render_bundle_draw_indexed_indirect(
1699 bundle: &mut RenderBundleEncoder,
1700 buffer_id: id::BufferId,
1701 offset: BufferAddress,
1702 ) {
1703 bundle.base.commands.push(RenderCommand::DrawIndirect {
1704 buffer: buffer_id,
1705 offset,
1706 count: 1,
1707 family: DrawCommandFamily::DrawIndexed,
1708 vertex_or_index_limit: None,
1709 instance_limit: None,
1710 });
1711 }
1712
1713 pub unsafe fn wgpu_render_bundle_push_debug_group(
1718 _bundle: &mut RenderBundleEncoder,
1719 _label: RawString,
1720 ) {
1721 }
1723
1724 pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1725 }
1727
1728 pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1733 _bundle: &mut RenderBundleEncoder,
1734 _label: RawString,
1735 ) {
1736 }
1738}