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 if bind_group_id.is_none() {
526 return Ok(());
528 }
529
530 let bind_group_id = bind_group_id.unwrap();
531
532 let bind_group = bind_group_guard.get(bind_group_id).get()?;
533
534 bind_group.same_device(&state.device)?;
535
536 let max_bind_groups = state.device.limits.max_bind_groups;
537 if index >= max_bind_groups {
538 return Err(
539 RenderCommandError::BindGroupIndexOutOfRange(pass::BindGroupIndexOutOfRange {
540 index,
541 max: max_bind_groups,
542 })
543 .into(),
544 );
545 }
546
547 let offsets_range = state.next_dynamic_offset..state.next_dynamic_offset + num_dynamic_offsets;
549 state.next_dynamic_offset = offsets_range.end;
550 let offsets = &dynamic_offsets[offsets_range.clone()];
551
552 bind_group.validate_dynamic_bindings(index, offsets)?;
553
554 unsafe { state.trackers.merge_bind_group(&bind_group.used)? };
555 let bind_group = state.trackers.bind_groups.insert_single(bind_group);
556
557 state
558 .binder
559 .assign_group(index as usize, bind_group, offsets);
560
561 Ok(())
562}
563
564fn set_pipeline(
565 state: &mut State,
566 pipeline_guard: &crate::storage::Storage<Fallible<RenderPipeline>>,
567 context: &RenderPassContext,
568 is_depth_read_only: bool,
569 is_stencil_read_only: bool,
570 pipeline_id: id::Id<id::markers::RenderPipeline>,
571) -> Result<(), RenderBundleErrorInner> {
572 let pipeline = pipeline_guard.get(pipeline_id).get()?;
573
574 pipeline.same_device(&state.device)?;
575
576 context
577 .check_compatible(&pipeline.pass_context, pipeline.as_ref())
578 .map_err(RenderCommandError::IncompatiblePipelineTargets)?;
579
580 if pipeline.flags.contains(PipelineFlags::WRITES_DEPTH) && is_depth_read_only {
581 return Err(RenderCommandError::IncompatibleDepthAccess(pipeline.error_ident()).into());
582 }
583 if pipeline.flags.contains(PipelineFlags::WRITES_STENCIL) && is_stencil_read_only {
584 return Err(RenderCommandError::IncompatibleStencilAccess(pipeline.error_ident()).into());
585 }
586
587 let pipeline_state = PipelineState::new(&pipeline);
588
589 state
590 .commands
591 .push(ArcRenderCommand::SetPipeline(pipeline.clone()));
592
593 if let Some(cmd) = pipeline_state.zero_immediates() {
595 state.commands.push(cmd);
596 }
597
598 state.pipeline = Some(pipeline_state);
599
600 state
601 .binder
602 .change_pipeline_layout(&pipeline.layout, &pipeline.late_sized_buffer_groups);
603
604 state.trackers.render_pipelines.insert_single(pipeline);
605 Ok(())
606}
607
608fn set_index_buffer(
610 state: &mut State,
611 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
612 buffer_id: id::Id<id::markers::Buffer>,
613 index_format: wgt::IndexFormat,
614 offset: u64,
615 size: Option<NonZeroU64>,
616) -> Result<(), RenderBundleErrorInner> {
617 let buffer = buffer_guard.get(buffer_id).get()?;
618
619 state
620 .trackers
621 .buffers
622 .merge_single(&buffer, wgt::BufferUses::INDEX)?;
623
624 buffer.same_device(&state.device)?;
625 buffer.check_usage(wgt::BufferUsages::INDEX)?;
626
627 if offset % u64::try_from(index_format.byte_size()).unwrap() != 0 {
628 return Err(RenderCommandError::UnalignedIndexBuffer {
629 offset,
630 alignment: index_format.byte_size(),
631 }
632 .into());
633 }
634 let end = offset + buffer.resolve_binding_size(offset, size)?;
635
636 state
637 .buffer_memory_init_actions
638 .extend(buffer.initialization_status.read().create_action(
639 &buffer,
640 offset..end.get(),
641 MemoryInitKind::NeedsInitializedMemory,
642 ));
643 state.set_index_buffer(buffer, index_format, offset..end.get());
644 Ok(())
645}
646
647fn set_vertex_buffer(
649 state: &mut State,
650 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
651 slot: u32,
652 buffer_id: id::Id<id::markers::Buffer>,
653 offset: u64,
654 size: Option<NonZeroU64>,
655) -> Result<(), RenderBundleErrorInner> {
656 let max_vertex_buffers = state.device.limits.max_vertex_buffers;
657 if slot >= max_vertex_buffers {
658 return Err(RenderCommandError::VertexBufferIndexOutOfRange {
659 index: slot,
660 max: max_vertex_buffers,
661 }
662 .into());
663 }
664
665 let buffer = buffer_guard.get(buffer_id).get()?;
666
667 state
668 .trackers
669 .buffers
670 .merge_single(&buffer, wgt::BufferUses::VERTEX)?;
671
672 buffer.same_device(&state.device)?;
673 buffer.check_usage(wgt::BufferUsages::VERTEX)?;
674
675 if offset % wgt::VERTEX_ALIGNMENT != 0 {
676 return Err(RenderCommandError::UnalignedVertexBuffer { slot, offset }.into());
677 }
678 let end = offset + buffer.resolve_binding_size(offset, size)?;
679
680 state
681 .buffer_memory_init_actions
682 .extend(buffer.initialization_status.read().create_action(
683 &buffer,
684 offset..end.get(),
685 MemoryInitKind::NeedsInitializedMemory,
686 ));
687 state.vertex[slot as usize] = Some(VertexState::new(buffer, offset..end.get()));
688 Ok(())
689}
690
691fn set_immediates(
692 state: &mut State,
693 offset: u32,
694 size_bytes: u32,
695 values_offset: Option<u32>,
696) -> Result<(), RenderBundleErrorInner> {
697 let end_offset = offset + size_bytes;
698
699 let pipeline_state = state.pipeline()?;
700
701 pipeline_state
702 .pipeline
703 .layout
704 .validate_immediates_ranges(offset, end_offset)?;
705
706 state.commands.push(ArcRenderCommand::SetImmediate {
707 offset,
708 size_bytes,
709 values_offset,
710 });
711 Ok(())
712}
713
714fn draw(
715 state: &mut State,
716 vertex_count: u32,
717 instance_count: u32,
718 first_vertex: u32,
719 first_instance: u32,
720) -> Result<(), RenderBundleErrorInner> {
721 state.is_ready()?;
722 let pipeline = state.pipeline()?;
723
724 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
725 vertex_limits.validate_vertex_limit(first_vertex, vertex_count)?;
726 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
727
728 if instance_count > 0 && vertex_count > 0 {
729 state.flush_vertices();
730 state.flush_bindings();
731 state.commands.push(ArcRenderCommand::Draw {
732 vertex_count,
733 instance_count,
734 first_vertex,
735 first_instance,
736 });
737 }
738 Ok(())
739}
740
741fn draw_indexed(
742 state: &mut State,
743 index_count: u32,
744 instance_count: u32,
745 first_index: u32,
746 base_vertex: i32,
747 first_instance: u32,
748) -> Result<(), RenderBundleErrorInner> {
749 state.is_ready()?;
750 let pipeline = state.pipeline()?;
751
752 let index = match state.index {
753 Some(ref index) => index,
754 None => return Err(DrawError::MissingIndexBuffer.into()),
755 };
756
757 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
758
759 let last_index = first_index as u64 + index_count as u64;
760 let index_limit = index.limit();
761 if last_index > index_limit {
762 return Err(DrawError::IndexBeyondLimit {
763 last_index,
764 index_limit,
765 }
766 .into());
767 }
768 vertex_limits.validate_instance_limit(first_instance, instance_count)?;
769
770 if instance_count > 0 && index_count > 0 {
771 state.flush_index();
772 state.flush_vertices();
773 state.flush_bindings();
774 state.commands.push(ArcRenderCommand::DrawIndexed {
775 index_count,
776 instance_count,
777 first_index,
778 base_vertex,
779 first_instance,
780 });
781 }
782 Ok(())
783}
784
785fn draw_mesh_tasks(
786 state: &mut State,
787 group_count_x: u32,
788 group_count_y: u32,
789 group_count_z: u32,
790) -> Result<(), RenderBundleErrorInner> {
791 state.is_ready()?;
792
793 let groups_size_limit = state.device.limits.max_task_mesh_workgroups_per_dimension;
794 let max_groups = state.device.limits.max_task_mesh_workgroup_total_count;
795 if group_count_x > groups_size_limit
796 || group_count_y > groups_size_limit
797 || group_count_z > groups_size_limit
798 || group_count_x * group_count_y * group_count_z > max_groups
799 {
800 return Err(RenderBundleErrorInner::Draw(DrawError::InvalidGroupSize {
801 current: [group_count_x, group_count_y, group_count_z],
802 limit: groups_size_limit,
803 max_total: max_groups,
804 }));
805 }
806
807 if group_count_x > 0 && group_count_y > 0 && group_count_z > 0 {
808 state.flush_bindings();
809 state.commands.push(ArcRenderCommand::DrawMeshTasks {
810 group_count_x,
811 group_count_y,
812 group_count_z,
813 });
814 }
815 Ok(())
816}
817
818fn multi_draw_indirect(
819 state: &mut State,
820 buffer_guard: &crate::storage::Storage<Fallible<Buffer>>,
821 buffer_id: id::Id<id::markers::Buffer>,
822 offset: u64,
823 family: DrawCommandFamily,
824) -> Result<(), RenderBundleErrorInner> {
825 state.is_ready()?;
826 state
827 .device
828 .require_downlevel_flags(wgt::DownlevelFlags::INDIRECT_EXECUTION)?;
829
830 let pipeline = state.pipeline()?;
831
832 let buffer = buffer_guard.get(buffer_id).get()?;
833
834 buffer.same_device(&state.device)?;
835 buffer.check_usage(wgt::BufferUsages::INDIRECT)?;
836
837 let vertex_limits = super::VertexLimits::new(state.vertex_buffer_sizes(), &pipeline.steps);
838
839 let stride = super::get_stride_of_indirect_args(family);
840 state
841 .buffer_memory_init_actions
842 .extend(buffer.initialization_status.read().create_action(
843 &buffer,
844 offset..(offset + stride),
845 MemoryInitKind::NeedsInitializedMemory,
846 ));
847
848 let vertex_or_index_limit = if family == DrawCommandFamily::DrawIndexed {
849 let index = match state.index {
850 Some(ref mut index) => index,
851 None => return Err(DrawError::MissingIndexBuffer.into()),
852 };
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 mut bg = None;
987 if bind_group.is_some() {
988 let bind_group = bind_group.as_ref().unwrap();
989 let raw_bg = bind_group.try_raw(snatch_guard)?;
990 bg = Some(raw_bg);
991 }
992 unsafe {
993 raw.set_bind_group(
994 pipeline_layout.as_ref().unwrap().raw(),
995 *index,
996 bg,
997 &offsets[..*num_dynamic_offsets],
998 )
999 };
1000 offsets = &offsets[*num_dynamic_offsets..];
1001 }
1002 Cmd::SetPipeline(pipeline) => {
1003 unsafe { raw.set_render_pipeline(pipeline.raw()) };
1004
1005 pipeline_layout = Some(pipeline.layout.clone());
1006 }
1007 Cmd::SetIndexBuffer {
1008 buffer,
1009 index_format,
1010 offset,
1011 size,
1012 } => {
1013 let buffer = buffer.try_raw(snatch_guard)?;
1014 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1017 unsafe { raw.set_index_buffer(bb, *index_format) };
1018 }
1019 Cmd::SetVertexBuffer {
1020 slot,
1021 buffer,
1022 offset,
1023 size,
1024 } => {
1025 let buffer = buffer.try_raw(snatch_guard)?;
1026 let bb = hal::BufferBinding::new_unchecked(buffer, *offset, *size);
1029 unsafe { raw.set_vertex_buffer(*slot, bb) };
1030 }
1031 Cmd::SetImmediate {
1032 offset,
1033 size_bytes,
1034 values_offset,
1035 } => {
1036 let pipeline_layout = pipeline_layout.as_ref().unwrap();
1037
1038 if let Some(values_offset) = *values_offset {
1039 let values_end_offset =
1040 (values_offset + size_bytes / wgt::IMMEDIATE_DATA_ALIGNMENT) as usize;
1041 let data_slice =
1042 &self.base.immediates_data[(values_offset as usize)..values_end_offset];
1043
1044 unsafe { raw.set_immediates(pipeline_layout.raw(), *offset, data_slice) }
1045 } else {
1046 super::immediates_clear(
1047 *offset,
1048 *size_bytes,
1049 |clear_offset, clear_data| {
1050 unsafe {
1051 raw.set_immediates(
1052 pipeline_layout.raw(),
1053 clear_offset,
1054 clear_data,
1055 )
1056 };
1057 },
1058 );
1059 }
1060 }
1061 Cmd::Draw {
1062 vertex_count,
1063 instance_count,
1064 first_vertex,
1065 first_instance,
1066 } => {
1067 unsafe {
1068 raw.draw(
1069 *first_vertex,
1070 *vertex_count,
1071 *first_instance,
1072 *instance_count,
1073 )
1074 };
1075 }
1076 Cmd::DrawIndexed {
1077 index_count,
1078 instance_count,
1079 first_index,
1080 base_vertex,
1081 first_instance,
1082 } => {
1083 unsafe {
1084 raw.draw_indexed(
1085 *first_index,
1086 *index_count,
1087 *base_vertex,
1088 *first_instance,
1089 *instance_count,
1090 )
1091 };
1092 }
1093 Cmd::DrawMeshTasks {
1094 group_count_x,
1095 group_count_y,
1096 group_count_z,
1097 } => unsafe {
1098 raw.draw_mesh_tasks(*group_count_x, *group_count_y, *group_count_z);
1099 },
1100 Cmd::DrawIndirect {
1101 buffer,
1102 offset,
1103 count: 1,
1104 family,
1105
1106 vertex_or_index_limit,
1107 instance_limit,
1108 } => {
1109 let (buffer, offset) = if self.device.indirect_validation.is_some() {
1110 let (dst_resource_index, offset) = indirect_draw_validation_batcher.add(
1111 indirect_draw_validation_resources,
1112 &self.device,
1113 buffer,
1114 *offset,
1115 *family,
1116 vertex_or_index_limit
1117 .expect("finalized render bundle missing vertex_or_index_limit"),
1118 instance_limit.expect("finalized render bundle missing instance_limit"),
1119 )?;
1120
1121 let dst_buffer =
1122 indirect_draw_validation_resources.get_dst_buffer(dst_resource_index);
1123 (dst_buffer, offset)
1124 } else {
1125 (buffer.try_raw(snatch_guard)?, *offset)
1126 };
1127 match family {
1128 DrawCommandFamily::Draw => unsafe { raw.draw_indirect(buffer, offset, 1) },
1129 DrawCommandFamily::DrawIndexed => unsafe {
1130 raw.draw_indexed_indirect(buffer, offset, 1)
1131 },
1132 DrawCommandFamily::DrawMeshTasks => unsafe {
1133 raw.draw_mesh_tasks_indirect(buffer, offset, 1);
1134 },
1135 }
1136 }
1137 Cmd::DrawIndirect { .. } | Cmd::MultiDrawIndirectCount { .. } => {
1138 return Err(ExecutionError::Unimplemented("multi-draw-indirect"))
1139 }
1140 Cmd::PushDebugGroup { .. } | Cmd::InsertDebugMarker { .. } | Cmd::PopDebugGroup => {
1141 return Err(ExecutionError::Unimplemented("debug-markers"))
1142 }
1143 Cmd::WriteTimestamp { .. }
1144 | Cmd::BeginOcclusionQuery { .. }
1145 | Cmd::EndOcclusionQuery
1146 | Cmd::BeginPipelineStatisticsQuery { .. }
1147 | Cmd::EndPipelineStatisticsQuery => {
1148 return Err(ExecutionError::Unimplemented("queries"))
1149 }
1150 Cmd::ExecuteBundle(_)
1151 | Cmd::SetBlendConstant(_)
1152 | Cmd::SetStencilReference(_)
1153 | Cmd::SetViewport { .. }
1154 | Cmd::SetScissor(_) => unreachable!(),
1155 }
1156 }
1157
1158 if !self.discard_hal_labels {
1159 if let Some(_) = self.base.label {
1160 unsafe { raw.end_debug_marker() };
1161 }
1162 }
1163
1164 Ok(())
1165 }
1166}
1167
1168crate::impl_resource_type!(RenderBundle);
1169crate::impl_labeled!(RenderBundle);
1170crate::impl_parent_device!(RenderBundle);
1171crate::impl_storage_item!(RenderBundle);
1172crate::impl_trackable!(RenderBundle);
1173
1174#[derive(Debug)]
1183struct IndexState {
1184 buffer: Arc<Buffer>,
1185 format: wgt::IndexFormat,
1186 range: Range<wgt::BufferAddress>,
1187 is_dirty: bool,
1188}
1189
1190impl IndexState {
1191 fn limit(&self) -> u64 {
1195 let bytes_per_index = self.format.byte_size() as u64;
1196
1197 (self.range.end - self.range.start) / bytes_per_index
1198 }
1199
1200 fn flush(&mut self) -> Option<ArcRenderCommand> {
1203 let binding_size = self
1205 .range
1206 .end
1207 .checked_sub(self.range.start)
1208 .filter(|_| self.range.end <= self.buffer.size)
1209 .expect("index range must be contained in buffer");
1210
1211 if self.is_dirty {
1212 self.is_dirty = false;
1213 Some(ArcRenderCommand::SetIndexBuffer {
1214 buffer: self.buffer.clone(),
1215 index_format: self.format,
1216 offset: self.range.start,
1217 size: NonZeroU64::new(binding_size),
1218 })
1219 } else {
1220 None
1221 }
1222 }
1223}
1224
1225#[derive(Debug)]
1238struct VertexState {
1239 buffer: Arc<Buffer>,
1240 range: Range<wgt::BufferAddress>,
1241 is_dirty: bool,
1242}
1243
1244impl VertexState {
1245 fn new(buffer: Arc<Buffer>, range: Range<wgt::BufferAddress>) -> Self {
1249 Self {
1250 buffer,
1251 range,
1252 is_dirty: true,
1253 }
1254 }
1255
1256 fn flush(&mut self, slot: u32) -> Option<ArcRenderCommand> {
1260 let binding_size = self
1261 .range
1262 .end
1263 .checked_sub(self.range.start)
1264 .filter(|_| self.range.end <= self.buffer.size)
1265 .expect("vertex range must be contained in buffer");
1266
1267 if self.is_dirty {
1268 self.is_dirty = false;
1269 Some(ArcRenderCommand::SetVertexBuffer {
1270 slot,
1271 buffer: self.buffer.clone(),
1272 offset: self.range.start,
1273 size: NonZeroU64::new(binding_size),
1274 })
1275 } else {
1276 None
1277 }
1278 }
1279}
1280
1281struct PipelineState {
1283 pipeline: Arc<RenderPipeline>,
1285
1286 steps: Vec<VertexStep>,
1289
1290 immediate_size: u32,
1292}
1293
1294impl PipelineState {
1295 fn new(pipeline: &Arc<RenderPipeline>) -> Self {
1296 Self {
1297 pipeline: pipeline.clone(),
1298 steps: pipeline.vertex_steps.to_vec(),
1299 immediate_size: pipeline.layout.immediate_size,
1300 }
1301 }
1302
1303 fn zero_immediates(&self) -> Option<ArcRenderCommand> {
1306 if self.immediate_size == 0 {
1307 return None;
1308 }
1309
1310 Some(ArcRenderCommand::SetImmediate {
1311 offset: 0,
1312 size_bytes: self.immediate_size,
1313 values_offset: None,
1314 })
1315 }
1316}
1317
1318struct State {
1329 trackers: RenderBundleScope,
1331
1332 pipeline: Option<PipelineState>,
1334
1335 vertex: [Option<VertexState>; hal::MAX_VERTEX_BUFFERS],
1337
1338 index: Option<IndexState>,
1341
1342 flat_dynamic_offsets: Vec<wgt::DynamicOffset>,
1349
1350 device: Arc<Device>,
1351 commands: Vec<ArcRenderCommand>,
1352 buffer_memory_init_actions: Vec<BufferInitTrackerAction>,
1353 texture_memory_init_actions: Vec<TextureInitTrackerAction>,
1354 next_dynamic_offset: usize,
1355 binder: Binder,
1356}
1357
1358impl State {
1359 fn pipeline(&self) -> Result<&PipelineState, RenderBundleErrorInner> {
1361 self.pipeline
1362 .as_ref()
1363 .ok_or(DrawError::MissingPipeline(pass::MissingPipeline).into())
1364 }
1365
1366 fn set_index_buffer(
1368 &mut self,
1369 buffer: Arc<Buffer>,
1370 format: wgt::IndexFormat,
1371 range: Range<wgt::BufferAddress>,
1372 ) {
1373 match self.index {
1374 Some(ref current)
1375 if current.buffer.is_equal(&buffer)
1376 && current.format == format
1377 && current.range == range =>
1378 {
1379 return
1380 }
1381 _ => (),
1382 }
1383
1384 self.index = Some(IndexState {
1385 buffer,
1386 format,
1387 range,
1388 is_dirty: true,
1389 });
1390 }
1391
1392 fn flush_index(&mut self) {
1395 let commands = self.index.as_mut().and_then(|index| index.flush());
1396 self.commands.extend(commands);
1397 }
1398
1399 fn flush_vertices(&mut self) {
1400 let commands = self
1401 .vertex
1402 .iter_mut()
1403 .enumerate()
1404 .flat_map(|(i, vs)| vs.as_mut().and_then(|vs| vs.flush(i as u32)));
1405 self.commands.extend(commands);
1406 }
1407
1408 fn is_ready(&mut self) -> Result<(), DrawError> {
1412 if let Some(pipeline) = self.pipeline.as_ref() {
1413 self.binder
1414 .check_compatibility(pipeline.pipeline.as_ref())?;
1415 self.binder.check_late_buffer_bindings()?;
1416 Ok(())
1417 } else {
1418 Err(DrawError::MissingPipeline(pass::MissingPipeline))
1419 }
1420 }
1421
1422 fn flush_bindings(&mut self) {
1426 let range = self.binder.take_rebind_range();
1427 let entries = self.binder.entries(range);
1428
1429 self.commands.extend(entries.map(|(i, entry)| {
1430 let bind_group = entry.group.as_ref().unwrap();
1431
1432 self.buffer_memory_init_actions
1433 .extend_from_slice(&bind_group.used_buffer_ranges);
1434 self.texture_memory_init_actions
1435 .extend_from_slice(&bind_group.used_texture_ranges);
1436
1437 self.flat_dynamic_offsets
1438 .extend_from_slice(&entry.dynamic_offsets);
1439
1440 ArcRenderCommand::SetBindGroup {
1441 index: i.try_into().unwrap(),
1442 bind_group: Some(bind_group.clone()),
1443 num_dynamic_offsets: entry.dynamic_offsets.len(),
1444 }
1445 }));
1446 }
1447
1448 fn vertex_buffer_sizes(&self) -> impl Iterator<Item = Option<wgt::BufferAddress>> + '_ {
1449 self.vertex
1450 .iter()
1451 .map(|vbs| vbs.as_ref().map(|vbs| vbs.range.end - vbs.range.start))
1452 }
1453}
1454
1455#[derive(Clone, Debug, Error)]
1457pub enum RenderBundleErrorInner {
1458 #[error(transparent)]
1459 Device(#[from] DeviceError),
1460 #[error(transparent)]
1461 RenderCommand(RenderCommandError),
1462 #[error(transparent)]
1463 Draw(#[from] DrawError),
1464 #[error(transparent)]
1465 MissingDownlevelFlags(#[from] MissingDownlevelFlags),
1466 #[error(transparent)]
1467 Bind(#[from] BindError),
1468 #[error(transparent)]
1469 InvalidResource(#[from] InvalidResourceError),
1470}
1471
1472impl<T> From<T> for RenderBundleErrorInner
1473where
1474 T: Into<RenderCommandError>,
1475{
1476 fn from(t: T) -> Self {
1477 Self::RenderCommand(t.into())
1478 }
1479}
1480
1481#[derive(Clone, Debug, Error)]
1483#[error("{scope}")]
1484pub struct RenderBundleError {
1485 pub scope: PassErrorScope,
1486 #[source]
1487 inner: RenderBundleErrorInner,
1488}
1489
1490impl WebGpuError for RenderBundleError {
1491 fn webgpu_error_type(&self) -> ErrorType {
1492 let Self { scope: _, inner } = self;
1493 let e: &dyn WebGpuError = match inner {
1494 RenderBundleErrorInner::Device(e) => e,
1495 RenderBundleErrorInner::RenderCommand(e) => e,
1496 RenderBundleErrorInner::Draw(e) => e,
1497 RenderBundleErrorInner::MissingDownlevelFlags(e) => e,
1498 RenderBundleErrorInner::Bind(e) => e,
1499 RenderBundleErrorInner::InvalidResource(e) => e,
1500 };
1501 e.webgpu_error_type()
1502 }
1503}
1504
1505impl RenderBundleError {
1506 pub fn from_device_error(e: DeviceError) -> Self {
1507 Self {
1508 scope: PassErrorScope::Bundle,
1509 inner: e.into(),
1510 }
1511 }
1512}
1513
1514impl<E> MapPassErr<RenderBundleError> for E
1515where
1516 E: Into<RenderBundleErrorInner>,
1517{
1518 fn map_pass_err(self, scope: PassErrorScope) -> RenderBundleError {
1519 RenderBundleError {
1520 scope,
1521 inner: self.into(),
1522 }
1523 }
1524}
1525
1526pub mod bundle_ffi {
1527 use super::{RenderBundleEncoder, RenderCommand};
1528 use crate::{command::DrawCommandFamily, id, RawString};
1529 use core::{convert::TryInto, slice};
1530 use wgt::{BufferAddress, BufferSize, DynamicOffset, IndexFormat};
1531
1532 pub unsafe fn wgpu_render_bundle_set_bind_group(
1537 bundle: &mut RenderBundleEncoder,
1538 index: u32,
1539 bind_group_id: Option<id::BindGroupId>,
1540 offsets: *const DynamicOffset,
1541 offset_length: usize,
1542 ) {
1543 let offsets = unsafe { slice::from_raw_parts(offsets, offset_length) };
1544
1545 let redundant = bundle.current_bind_groups.set_and_check_redundant(
1546 bind_group_id,
1547 index,
1548 &mut bundle.base.dynamic_offsets,
1549 offsets,
1550 );
1551
1552 if redundant {
1553 return;
1554 }
1555
1556 bundle.base.commands.push(RenderCommand::SetBindGroup {
1557 index,
1558 num_dynamic_offsets: offset_length,
1559 bind_group: bind_group_id,
1560 });
1561 }
1562
1563 pub fn wgpu_render_bundle_set_pipeline(
1564 bundle: &mut RenderBundleEncoder,
1565 pipeline_id: id::RenderPipelineId,
1566 ) {
1567 if bundle.current_pipeline.set_and_check_redundant(pipeline_id) {
1568 return;
1569 }
1570
1571 bundle
1572 .base
1573 .commands
1574 .push(RenderCommand::SetPipeline(pipeline_id));
1575 }
1576
1577 pub fn wgpu_render_bundle_set_vertex_buffer(
1578 bundle: &mut RenderBundleEncoder,
1579 slot: u32,
1580 buffer_id: id::BufferId,
1581 offset: BufferAddress,
1582 size: Option<BufferSize>,
1583 ) {
1584 bundle.base.commands.push(RenderCommand::SetVertexBuffer {
1585 slot,
1586 buffer: buffer_id,
1587 offset,
1588 size,
1589 });
1590 }
1591
1592 pub fn wgpu_render_bundle_set_index_buffer(
1593 encoder: &mut RenderBundleEncoder,
1594 buffer: id::BufferId,
1595 index_format: IndexFormat,
1596 offset: BufferAddress,
1597 size: Option<BufferSize>,
1598 ) {
1599 encoder.set_index_buffer(buffer, index_format, offset, size);
1600 }
1601
1602 pub unsafe fn wgpu_render_bundle_set_immediates(
1607 pass: &mut RenderBundleEncoder,
1608 offset: u32,
1609 size_bytes: u32,
1610 data: *const u8,
1611 ) {
1612 assert_eq!(
1613 offset & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1614 0,
1615 "Immediate data offset must be aligned to 4 bytes."
1616 );
1617 assert_eq!(
1618 size_bytes & (wgt::IMMEDIATE_DATA_ALIGNMENT - 1),
1619 0,
1620 "Immediate data size must be aligned to 4 bytes."
1621 );
1622 let data_slice = unsafe { slice::from_raw_parts(data, size_bytes as usize) };
1623 let value_offset = pass.base.immediates_data.len().try_into().expect(
1624 "Ran out of immediate data space. Don't set 4gb of immediates per RenderBundle.",
1625 );
1626
1627 pass.base.immediates_data.extend(
1628 data_slice
1629 .chunks_exact(wgt::IMMEDIATE_DATA_ALIGNMENT as usize)
1630 .map(|arr| u32::from_ne_bytes([arr[0], arr[1], arr[2], arr[3]])),
1631 );
1632
1633 pass.base.commands.push(RenderCommand::SetImmediate {
1634 offset,
1635 size_bytes,
1636 values_offset: Some(value_offset),
1637 });
1638 }
1639
1640 pub fn wgpu_render_bundle_draw(
1641 bundle: &mut RenderBundleEncoder,
1642 vertex_count: u32,
1643 instance_count: u32,
1644 first_vertex: u32,
1645 first_instance: u32,
1646 ) {
1647 bundle.base.commands.push(RenderCommand::Draw {
1648 vertex_count,
1649 instance_count,
1650 first_vertex,
1651 first_instance,
1652 });
1653 }
1654
1655 pub fn wgpu_render_bundle_draw_indexed(
1656 bundle: &mut RenderBundleEncoder,
1657 index_count: u32,
1658 instance_count: u32,
1659 first_index: u32,
1660 base_vertex: i32,
1661 first_instance: u32,
1662 ) {
1663 bundle.base.commands.push(RenderCommand::DrawIndexed {
1664 index_count,
1665 instance_count,
1666 first_index,
1667 base_vertex,
1668 first_instance,
1669 });
1670 }
1671
1672 pub fn wgpu_render_bundle_draw_indirect(
1673 bundle: &mut RenderBundleEncoder,
1674 buffer_id: id::BufferId,
1675 offset: BufferAddress,
1676 ) {
1677 bundle.base.commands.push(RenderCommand::DrawIndirect {
1678 buffer: buffer_id,
1679 offset,
1680 count: 1,
1681 family: DrawCommandFamily::Draw,
1682 vertex_or_index_limit: None,
1683 instance_limit: None,
1684 });
1685 }
1686
1687 pub fn wgpu_render_bundle_draw_indexed_indirect(
1688 bundle: &mut RenderBundleEncoder,
1689 buffer_id: id::BufferId,
1690 offset: BufferAddress,
1691 ) {
1692 bundle.base.commands.push(RenderCommand::DrawIndirect {
1693 buffer: buffer_id,
1694 offset,
1695 count: 1,
1696 family: DrawCommandFamily::DrawIndexed,
1697 vertex_or_index_limit: None,
1698 instance_limit: None,
1699 });
1700 }
1701
1702 pub unsafe fn wgpu_render_bundle_push_debug_group(
1707 _bundle: &mut RenderBundleEncoder,
1708 _label: RawString,
1709 ) {
1710 }
1712
1713 pub fn wgpu_render_bundle_pop_debug_group(_bundle: &mut RenderBundleEncoder) {
1714 }
1716
1717 pub unsafe fn wgpu_render_bundle_insert_debug_marker(
1722 _bundle: &mut RenderBundleEncoder,
1723 _label: RawString,
1724 ) {
1725 }
1727}