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 pipeline_state = state.pipeline()?;
704
705 pipeline_state
706 .pipeline
707 .layout
708 .validate_immediates_ranges(offset, size_bytes)?;
709
710 state.commands.push(ArcRenderCommand::SetImmediate {
711 offset,
712 size_bytes,
713 values_offset,
714 });
715 Ok(())
716}
717
718fn draw(
719 state: &mut State,
720 vertex_count: u32,
721 instance_count: u32,
722 first_vertex: u32,
723 first_instance: u32,
724) -> Result<(), RenderBundleErrorInner> {
725 state.is_ready(DrawCommandFamily::Draw)?;
726 let pipeline = state.pipeline()?;
727
728 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
729 vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?;
730 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
731
732 if instance_count > 0 && vertex_count > 0 {
733 state.flush_vertices();
734 state.flush_bindings();
735 state.commands.push(ArcRenderCommand::Draw {
736 vertex_count,
737 instance_count,
738 first_vertex,
739 first_instance,
740 });
741 }
742 Ok(())
743}
744
745fn draw_indexed(
746 state: &mut State,
747 index_count: u32,
748 instance_count: u32,
749 first_index: u32,
750 base_vertex: i32,
751 first_instance: u32,
752) -> Result<(), RenderBundleErrorInner> {
753 state.is_ready(DrawCommandFamily::DrawIndexed)?;
754 let pipeline = state.pipeline()?;
755
756 let index = state.index.as_ref().unwrap();
757
758 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
759
760 let last_index = first_index as u64 + index_count as u64;
761 let index_limit = index.limit();
762 if last_index > index_limit {
763 return Err(DrawError::IndexBeyondLimit {
764 last_index,
765 index_limit,
766 }
767 .into());
768 }
769 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
770
771 if instance_count > 0 && index_count > 0 {
772 state.flush_index();
773 state.flush_vertices();
774 state.flush_bindings();
775 state.commands.push(ArcRenderCommand::DrawIndexed {
776 index_count,
777 instance_count,
778 first_index,
779 base_vertex,
780 first_instance,
781 });
782 }
783 Ok(())
784}
785
786fn draw_mesh_tasks(
787 state: &mut State,
788 group_count_x: u32,
789 group_count_y: u32,
790 group_count_z: u32,
791) -> Result<(), RenderBundleErrorInner> {
792 state.is_ready(DrawCommandFamily::DrawMeshTasks)?;
793
794 let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension;
795 let max_groups = state.device.limits.max_task_mesh_workgroup_total_count;
796 if group_count_x > groups_size_limit
797 || group_count_y > groups_size_limit
798 || group_count_z > groups_size_limit
799 || group_count_x * group_count_y * group_count_z > max_groups
800 {
801 return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize {
802 current: [group_count_x, group_count_y, group_count_z],
803 limit: groups_size_limit,
804 max_total: max_groups,
805 }));
806 }
807
808 if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 {
809 state.flush_bindings();
810 state.commands.push(ArcRenderCommand::DrawMeshTasks {
811 group_count_x,
812 group_count_y,
813 group_count_z,
814 });
815 }
816 Ok(())
817}
818
819fn multi_draw_indirect(
820 state: &mut State,
821 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
822 buffer_id: id::Id<id::markers::Buffer>,
823 offset: u64,
824 family: DrawCommandFamily,
825) -> Result<(), RenderBundleErrorInner> {
826 state.is_ready(family)?;
827 state
828 .device
829 .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
830
831 let pipeline = state.pipeline()?;
832
833 let buffer = buffer_guard.get(buffer_id).get()?;
834
835 buffer.same_device(&state.device)?;
836 buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
837
838 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
839
840 let stride = super::get_stride_of_indirect_args(family);
841 state
842 .buffer_memory_init_actions
843 .extend(buffer.initialization_status.read().create_action(
844 &buffer,
845 offset..(offset + stride),
846 MemoryInitKind::NeedsInitializedMemory,
847 ));
848
849 let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
850 let index = state.index.as_mut().unwrap();
851 state.commands.extend(index.flush());
852 index.limit()
853 } else {
854 vertex_limits.vertex_limit
855 };
856 let instance_limit = vertex_limits.instance_limit;
857
858 let buffer_uses = if state.device.indirect_validation.is_some() {
859 wgt::BufferUses::STORAGE_READ_ONLY
860 } else {
861 wgt::BufferUses::INDIRECT
862 };
863
864 state.trackers.buffers.merge_single(&buffer, buffer_uses)?;
865
866 state.flush_vertices();
867 state.flush_bindings();
868 state.commands.push(ArcRenderCommand::DrawIndirect {
869 buffer,
870 offset,
871 count: 1,
872 family,
873
874 vertex_or_index_limit: Some(vertex_or_index_limit),
875 instance_limit: Some(instance_limit),
876 });
877 Ok(())
878}
879
880#[derive(Clone, Debug, Error)]
882#[non_exhaustive]
883pub enum CreateRenderBundleError {
884 #[error(transparent)]
885 ColorAttachment(#[from] ColorAttachmentError),
886 #[error("Invalid number of samples {0}")]
887 InvalidSampleCount(u32),
888}
889
890impl WebGpuError for CreateRenderBundleError {
891 fn webgpu_error_type(&self) -> ErrorType {
892 match self {
893 Self::ColorAttachment(e) => e.webgpu_error_type(),
894 Self::InvalidSampleCount(_) => ErrorType::Validation,
895 }
896 }
897}
898
899#[derive(Clone, Debug, Error)]
901#[non_exhaustive]
902pub enum ExecutionError {
903 #[error(transparent)]
904 Device(#[from] DeviceError),
905 #[error(transparent)]
906 DestroyedResource(#[from] DestroyedResourceError),
907 #[error("Using {0} in a render bundle is not implemented")]
908 Unimplemented(&'static str),
909}
910
911pub type RenderBundleDescriptor<'a> = wgt::RenderBundleDescriptor<Label<'a>>;
912
913#[derive(Debug)]
918pub struct RenderBundle {
919 base: BasePass<ArcRenderCommand, Infallible>,
922 pub(super) is_depth_read_only: bool,
923 pub(super) is_stencil_read_only: bool,
924 pub(crate) device: Arc<Device>,
925 pub(crate) used: RenderBundleScope,
926 pub(super) buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
927 pub(super) texture_memory_init_actions: Vec<TextureInitTrackerAction>,
928 pub(super) context: RenderPassContext,
929 label: String,
931 pub(crate) tracking_data: TrackingData,
932 discard_hal_labels: bool,
933}
934
935impl Drop for RenderBundle {
936 fn drop(&mut self) {
937 resource_log!("Drop {}", self.error_ident());
938 }
939}
940
941#[cfg(send_sync)]
942unsafe impl Send for RenderBundle {}
943#[cfg(send_sync)]
944unsafe impl Sync for RenderBundle {}
945
946impl RenderBundle {
947 #[cfg(feature = "trace")]
948 pub(crate) fn to_base_pass(&self) -> BasePass<RenderCommand<ArcReferences>, Infallible> {
949 self.base.clone()
950 }
951
952 pub(super) unsafe fn execute(
962 &self,
963 raw: &mut dyn hal::DynCommandEncoder,
964 indirect_draw_validation_resources: &mut crate::indirect_validation::DrawResources,
965 indirect_draw_validation_batcher: &mut crate::indirect_validation::DrawBatcher,
966 snatch_guard: &SnatchGuard,
967 ) -> Result<(), ExecutionError> {
968 let mut offsets = self.base.dynamic_offsets.as_slice();
969 let mut pipeline_layout = None::<Arc<PipelineLayout>>;
970 if !self.discard_hal_labels {
971 if let Some(ref label) = self.base.label {
972 unsafe { raw.begin_debug_marker(label) };
973 }
974 }
975
976 use ArcRenderCommand as Cmd;
977 for command in self.base.commands.iter() {
978 match command {
979 Cmd::SetBindGroup {
980 index,
981 num_dynamic_offsets,
982 bind_group,
983 } => {
984 let raw_bg = bind_group.as_ref().unwrap().try_raw(snatch_guard)?;
985 unsafe {
986 raw.set_bind_group(
987 pipeline_layout.as_ref().unwrap().raw(),
988 *index,
989 raw_bg,
990 &offsets[..*num_dynamic_offsets],
991 )
992 };
993 offsets = &offsets[*num_dynamic_offsets..];
994 }
995 Cmd::SetPipeline(pipeline) => {
996 unsafe { raw.set_render_pipeline(pipeline.raw()) };
997
998 pipeline_layout = Some(pipeline.layout.clone());
999 }
1000 Cmd::SetIndexBuffer {
1001 buffer,
1002 index_format,
1003 offset,
1004 size,
1005 } => {
1006 let buffer = buffer.try_raw(snatch_guard)?;
1007 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1010 unsafe { raw.set_index_buffer(bb, *index_format) };
1011 }
1012 Cmd::SetVertexBuffer {
1013 slot,
1014 buffer,
1015 offset,
1016 size,
1017 } => {
1018 let buffer = buffer.try_raw(snatch_guard)?;
1019 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1022 unsafe { raw.set_vertex_buffer(*slot, bb) };
1023 }
1024 Cmd::SetImmediate {
1025 offset,
1026 size_bytes,
1027 values_offset,
1028 } => {
1029 let pipeline_layout = pipeline_layout.as_ref().unwrap();
1030
1031 if let Some(values_offset) = *values_offset {
1032 let values_end_offset =
1033 (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1034 let data_slice =
1035 &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1036
1037 unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1038 } else {
1039 super::immediates_clear(
1040 *offset,
1041 *size_bytes,
1042 |clear_offset, clear_data| {
1043 unsafe {
1044 raw.set_immediates(
1045 pipeline_layout.raw(),
1046 clear_offset,
1047 clear_data,
1048 )
1049 };
1050 },
1051 );
1052 }
1053 }
1054 Cmd::Draw {
1055 vertex_count,
1056 instance_count,
1057 first_vertex,
1058 first_instance,
1059 } => {
1060 unsafe {
1061 raw.draw(
1062 *first_vertex,
1063 *vertex_count,
1064 *first_instance,
1065 *instance_count,
1066 )
1067 };
1068 }
1069 Cmd::DrawIndexed {
1070 index_count,
1071 instance_count,
1072 first_index,
1073 base_vertex,
1074 first_instance,
1075 } => {
1076 unsafe {
1077 raw.draw_indexed(
1078 *first_index,
1079 *index_count,
1080 *base_vertex,
1081 *first_instance,
1082 *instance_count,
1083 )
1084 };
1085 }
1086 Cmd::DrawMeshTasks {
1087 group_count_x,
1088 group_count_y,
1089 group_count_z,
1090 } => unsafe {
1091 raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1092 },
1093 Cmd::DrawIndirect {
1094 buffer,
1095 offset,
1096 count: 1,
1097 family,
1098
1099 vertex_or_index_limit,
1100 instance_limit,
1101 } => {
1102 let (buffer, offset) = if self.device.indirect_validation.is_some() {
1103 let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1104 indirect_draw_validation_resources,
1105 &self.device,
1106 buffer,
1107 *offset,
1108 *family,
1109 vertex_or_index_limit
1110 .expect("finalized render bundle missing vertex_or_index_limit"),
1111 instance_limit.expect("finalized render bundle missing instance_limit"),
1112 )?;
1113
1114 let dst_buffer =
1115 indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1116 (dst_buffer, offset)
1117 } else {
1118 (buffer.try_raw(snatch_guard)?, *offset)
1119 };
1120 match family {
1121 DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1122 DrawCommandFamily::DrawIndexed => unsafe {
1123 raw.draw_indexed_indirect(buffer, offset, 1)
1124 },
1125 DrawCommandFamily::DrawMeshTasks => unsafe {
1126 raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1127 },
1128 }
1129 }
1130 Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1131 return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1132 }
1133 Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1134 return Err(ExecutionError::Unimplemented("debug-markers"))
1135 }
1136 Cmd::WriteTimestamp { .. }
1137 | Cmd::BeginOcclusionQuery { .. }
1138 | Cmd::EndOcclusionQuery
1139 | Cmd::BeginPipelineStatisticsQuery { .. }
1140 | Cmd::EndPipelineStatisticsQuery => {
1141 return Err(ExecutionError::Unimplemented("queries"))
1142 }
1143 Cmd::ExecuteBundle(_)
1144 | Cmd::SetBlendConstant(_)
1145 | Cmd::SetStencilReference(_)
1146 | Cmd::SetViewport { .. }
1147 | Cmd::SetScissor(_) => unreachable!(),
1148 }
1149 }
1150
1151 if !self.discard_hal_labels {
1152 if let Some(_) = self.base.label {
1153 unsafe { raw.end_debug_marker() };
1154 }
1155 }
1156
1157 Ok(())
1158 }
1159}
1160
1161crate::impl_resource_type!(RenderBundle);
1162crate::impl_labeled!(RenderBundle);
1163crate::impl_parent_device!(RenderBundle);
1164crate::impl_storage_item!(RenderBundle);
1165crate::impl_trackable!(RenderBundle);
1166
1167#[derive(Debug)]
1176struct IndexState {
1177 buffer: Arc<Buffer>,
1178 format: wgt::IndexFormat,
1179 range: Range<wgt::BufferAddress>,
1180 is_dirty: bool,
1181}
1182
1183impl IndexState {
1184 fn limit(&self) -> u64 {
1188 let bytes_per_index = self.format.byte_size() as u64;
1189
1190 (self.range.end - self.range.start) / bytes_per_index
1191 }
1192
1193 fn flush(&mut self) -> Option<ArcRenderCommand> {
1196 let binding_size = self
1198 .range
1199 .end
1200 .checked_sub(self.range.start)
1201 .filter(|_| self.range.end <= self.buffer.size)
1202 .expect("index range must be contained in buffer");
1203
1204 if self.is_dirty {
1205 self.is_dirty = false;
1206 Some(ArcRenderCommand::SetIndexBuffer {
1207 buffer: self.buffer.clone(),
1208 index_format: self.format,
1209 offset: self.range.start,
1210 size: NonZeroU64::new(binding_size),
1211 })
1212 } else {
1213 None
1214 }
1215 }
1216}
1217
1218#[derive(Debug)]
1231struct VertexState {
1232 buffer: Arc<Buffer>,
1233 range: Range<wgt::BufferAddress>,
1234 is_dirty: bool,
1235}
1236
1237impl VertexState {
1238 fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1242 Self {
1243 buffer,
1244 range,
1245 is_dirty: true,
1246 }
1247 }
1248
1249 fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1253 let binding_size = self
1254 .range
1255 .end
1256 .checked_sub(self.range.start)
1257 .filter(|_| self.range.end <= self.buffer.size)
1258 .expect("vertex range must be contained in buffer");
1259
1260 if self.is_dirty {
1261 self.is_dirty = false;
1262 Some(ArcRenderCommand::SetVertexBuffer {
1263 slot,
1264 buffer: self.buffer.clone(),
1265 offset: self.range.start,
1266 size: NonZeroU64::new(binding_size),
1267 })
1268 } else {
1269 None
1270 }
1271 }
1272}
1273
1274struct PipelineState {
1276 pipeline: Arc<RenderPipeline>,
1278
1279 steps: Vec<VertexStep>,
1282
1283 immediate_size: u32,
1285}
1286
1287impl PipelineState {
1288 fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1289 Self {
1290 pipeline: pipeline.clone(),
1291 steps: pipeline.vertex_steps.to_vec(),
1292 immediate_size: pipeline.layout.immediate_size,
1293 }
1294 }
1295
1296 fn zero_immediates(&self) -> Option<ArcRenderCommand> {
1299 if self.immediate_size == 0 {
1300 return None;
1301 }
1302
1303 Some(ArcRenderCommand::SetImmediate {
1304 offset: 0,
1305 size_bytes: self.immediate_size,
1306 values_offset: None,
1307 })
1308 }
1309}
1310
1311struct State {
1322 trackers: RenderBundleScope,
1324
1325 pipeline: Option<PipelineState>,
1327
1328 vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS],
1330
1331 index: Option<IndexState>,
1334
1335 flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1342
1343 device: Arc<Device>,
1344 commands: Vec<ArcRenderCommand>,
1345 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1346 texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1347 next_dynamic_offset: usize,
1348 binder: Binder,
1349}
1350
1351impl State {
1352 fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1354 self.pipeline
1355 .as_ref()
1356 .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into())
1357 }
1358
1359 fn set_index_buffer(
1361 &mut self,
1362 buffer: Arc<Buffer>,
1363 format: wgt::IndexFormat,
1364 range: Range<wgt::BufferAddress>,
1365 ) {
1366 match self.index {
1367 Some(ref current)
1368 if current.buffer.is_equal(&buffer)
1369 && current.format == format
1370 && current.range == range =>
1371 {
1372 return
1373 }
1374 _ => (),
1375 }
1376
1377 self.index = Some(IndexState {
1378 buffer,
1379 format,
1380 range,
1381 is_dirty: true,
1382 });
1383 }
1384
1385 fn flush_index(&mut self) {
1388 let commands = self.index.as_mut().and_then(|index| index.flush());
1389 self.commands.extend(commands);
1390 }
1391
1392 fn flush_vertices(&mut self) {
1393 let commands = self
1394 .vertex
1395 .iter_mut()
1396 .enumerate()
1397 .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1398 self.commands.extend(commands);
1399 }
1400
1401 fn is_ready(&mut self, family: DrawCommandFamily) -> Result<(), DrawError> {
1405 if let Some(pipeline) = self.pipeline.as_ref() {
1406 self.binder
1407 .check_compatibility(pipeline.pipeline.as_ref())?;
1408 self.binder.check_late_buffer_bindings()?;
1409
1410 if family == DrawCommandFamily::DrawIndexed {
1411 let pipeline = &pipeline.pipeline;
1412 let index_format = match &self.index {
1413 Some(index) => index.format,
1414 None => return Err(DrawError::MissingIndexBuffer),
1415 };
1416
1417 if pipeline.topology.is_strip() && pipeline.strip_index_format != Some(index_format)
1418 {
1419 return Err(DrawError::UnmatchedStripIndexFormat {
1420 pipeline: pipeline.error_ident(),
1421 strip_index_format: pipeline.strip_index_format,
1422 buffer_format: index_format,
1423 });
1424 }
1425 }
1426
1427 Ok(())
1428 } else {
1429 Err(DrawError::MissingPipeline(pass::MissingPipeline))
1430 }
1431 }
1432
1433 fn flush_bindings(&mut self) {
1437 let start = self.binder.take_rebind_start_index();
1438 let entries = self.binder.list_valid_with_start(start);
1439
1440 self.commands
1441 .extend(entries.map(|(i, bind_group, dynamic_offsets)| {
1442 self.buffer_memory_init_actions
1443 .extend_from_slice(&bind_group.used_buffer_ranges);
1444 self.texture_memory_init_actions
1445 .extend_from_slice(&bind_group.used_texture_ranges);
1446
1447 self.flat_dynamic_offsets.extend_from_slice(dynamic_offsets);
1448
1449 ArcRenderCommand::SetBindGroup {
1450 index: i.try_into().unwrap(),
1451 bind_group: Some(bind_group.clone()),
1452 num_dynamic_offsets: dynamic_offsets.len(),
1453 }
1454 }));
1455 }
1456
1457 fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ {
1458 self.vertex
1459 .iter()
1460 .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start))
1461 }
1462}
1463
1464#[derive(Clone, Debug, Error)]
1466pub enum RenderBundleErrorInner {
1467 #[error(transparent)]
1468 Device(#[from] DeviceError),
1469 #[error(transparent)]
1470 RenderCommand(RenderCommandError),
1471 #[error(transparent)]
1472 Draw(#[from] DrawError),
1473 #[error(transparent)]
1474 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1475 #[error(transparent)]
1476 Bind(#[from] BindError),
1477 #[error(transparent)]
1478 InvalidResource(#[from] InvalidResourceError),
1479}
1480
1481impl<T> From<T> for RenderBundleErrorInner
1482where
1483 T: Into<RenderCommandError>,
1484{
1485 fn from(t: T) -> Self {
1486 Self::RenderCommand(t.into())
1487 }
1488}
1489
1490#[derive(Clone, Debug, Error)]
1492#[error("{scope}")]
1493pub struct RenderBundleError {
1494 pub scope: PassErrorScope,
1495 #[source]
1496 inner: RenderBundleErrorInner,
1497}
1498
1499impl WebGpuError for RenderBundleError {
1500 fn webgpu_error_type(&self) -> ErrorType {
1501 let Self { scope: _, inner } = self;
1502 match inner {
1503 RenderBundleErrorInner::Device(e) => e.webgpu_error_type(),
1504 RenderBundleErrorInner::RenderCommand(e) => e.webgpu_error_type(),
1505 RenderBundleErrorInner::Draw(e) => e.webgpu_error_type(),
1506 RenderBundleErrorInner::MissingDownlevelFlags(e) => e.webgpu_error_type(),
1507 RenderBundleErrorInner::Bind(e) => e.webgpu_error_type(),
1508 RenderBundleErrorInner::InvalidResource(e) => e.webgpu_error_type(),
1509 }
1510 }
1511}
1512
1513impl RenderBundleError {
1514 pub fn from_device_error(e: DeviceError) -> Self {
1515 Self {
1516 scope: PassErrorScope::Bundle,
1517 inner: e.into(),
1518 }
1519 }
1520}
1521
1522impl<E> MapPassErr<RenderBundleError> for E
1523where
1524 E: Into<RenderBundleErrorInner>,
1525{
1526 fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1527 RenderBundleError {
1528 scope,
1529 inner: self.into(),
1530 }
1531 }
1532}
1533
1534pub mod bundle_ffi {
1535 use super::{RenderBundleEncoder, RenderCommand};
1536 use crate::{command::DrawCommandFamily, id, RawString};
1537 use core::{convert::TryInto, slice};
1538 use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1539
1540 pub unsafe fn wgpu_render_bundle_set_bind_group(
1545 bundle: &mut RenderBundleEncoder,
1546 index: u32,
1547 bind_group_id: Option<id::BindGroupId>,
1548 offsets: *const DynamicOffset,
1549 offset_length: usize,
1550 ) {
1551 let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1552
1553 let redundant = bundle.current_bind_groups.set_and_check_redundant(
1554 bind_group_id,
1555 index,
1556 &mut bundle.base.dynamic_offsets,
1557 offsets,
1558 );
1559
1560 if redundant {
1561 return;
1562 }
1563
1564 bundle.base.commands.push(RenderCommand::SetBindGroup {
1565 index,
1566 num_dynamic_offsets: offset_length,
1567 bind_group: bind_group_id,
1568 });
1569 }
1570
1571 pub fn wgpu_render_bundle_set_pipeline(
1572 bundle: &mut RenderBundleEncoder,
1573 pipeline_id: id::RenderPipelineId,
1574 ) {
1575 if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1576 return;
1577 }
1578
1579 bundle
1580 .base
1581 .commands
1582 .push(RenderCommand::SetPipeline(pipeline_id));
1583 }
1584
1585 pub fn wgpu_render_bundle_set_vertex_buffer(
1586 bundle: &mut RenderBundleEncoder,
1587 slot: u32,
1588 buffer_id: id::BufferId,
1589 offset: BufferAddress,
1590 size: Option<BufferSize>,
1591 ) {
1592 bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1593 slot,
1594 buffer: buffer_id,
1595 offset,
1596 size,
1597 });
1598 }
1599
1600 pub fn wgpu_render_bundle_set_index_buffer(
1601 encoder: &mut RenderBundleEncoder,
1602 buffer: id::BufferId,
1603 index_format: IndexFormat,
1604 offset: BufferAddress,
1605 size: Option<BufferSize>,
1606 ) {
1607 encoder.set_index_buffer(buffer, index_format, offset, size);
1608 }
1609
1610 pub unsafe fn wgpu_render_bundle_set_immediates(
1615 pass: &mut RenderBundleEncoder,
1616 offset: u32,
1617 size_bytes: u32,
1618 data: *const u8,
1619 ) {
1620 assert_eq!(
1621 offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1622 0,
1623 "Immediate data offset must be aligned to 4 bytes."
1624 );
1625 assert_eq!(
1626 size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1627 0,
1628 "Immediate data size must be aligned to 4 bytes."
1629 );
1630 let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1631 let value_offset = pass.base.immediates_data.len().try_into().expect(
1632 "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1633 );
1634
1635 pass.base.immediates_data.extend(
1636 data_slice
1637 .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1638 .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1639 );
1640
1641 pass.base.commands.push(RenderCommand::SetImmediate {
1642 offset,
1643 size_bytes,
1644 values_offset: Some(value_offset),
1645 });
1646 }
1647
1648 pub fn wgpu_render_bundle_draw(
1649 bundle: &mut RenderBundleEncoder,
1650 vertex_count: u32,
1651 instance_count: u32,
1652 first_vertex: u32,
1653 first_instance: u32,
1654 ) {
1655 bundle.base.commands.push(RenderCommand::Draw {
1656 vertex_count,
1657 instance_count,
1658 first_vertex,
1659 first_instance,
1660 });
1661 }
1662
1663 pub fn wgpu_render_bundle_draw_indexed(
1664 bundle: &mut RenderBundleEncoder,
1665 index_count: u32,
1666 instance_count: u32,
1667 first_index: u32,
1668 base_vertex: i32,
1669 first_instance: u32,
1670 ) {
1671 bundle.base.commands.push(RenderCommand::DrawIndexed {
1672 index_count,
1673 instance_count,
1674 first_index,
1675 base_vertex,
1676 first_instance,
1677 });
1678 }
1679
1680 pub fn wgpu_render_bundle_draw_indirect(
1681 bundle: &mut RenderBundleEncoder,
1682 buffer_id: id::BufferId,
1683 offset: BufferAddress,
1684 ) {
1685 bundle.base.commands.push(RenderCommand::DrawIndirect {
1686 buffer: buffer_id,
1687 offset,
1688 count: 1,
1689 family: DrawCommandFamily::Draw,
1690 vertex_or_index_limit: None,
1691 instance_limit: None,
1692 });
1693 }
1694
1695 pub fn wgpu_render_bundle_draw_indexed_indirect(
1696 bundle: &mut RenderBundleEncoder,
1697 buffer_id: id::BufferId,
1698 offset: BufferAddress,
1699 ) {
1700 bundle.base.commands.push(RenderCommand::DrawIndirect {
1701 buffer: buffer_id,
1702 offset,
1703 count: 1,
1704 family: DrawCommandFamily::DrawIndexed,
1705 vertex_or_index_limit: None,
1706 instance_limit: None,
1707 });
1708 }
1709
1710 pub unsafe fn wgpu_render_bundle_push_debug_group(
1715 _bundle: &mut RenderBundleEncoder,
1716 _label: RawString,
1717 ) {
1718 }
1720
1721 pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1722 }
1724
1725 pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1730 _bundle: &mut RenderBundleEncoder,
1731 _label: RawString,
1732 ) {
1733 }
1735}